web: Packagify Logger (#20541)

* Prep logger for use outside web workspace.

* Bump. Prep.

* Add to publish list.

* Update deps.

* Add package directory.
This commit is contained in:
Teffen Ellis
2026-02-25 03:03:25 +01:00
committed by GitHub
parent 61c594301f
commit d30a18e0a5
15 changed files with 2626 additions and 0 deletions
@@ -29,6 +29,7 @@ jobs:
- packages/eslint-config
- packages/prettier-config
- packages/docusaurus-config
- packages/logger-js
- packages/esbuild-plugin-live-reload
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
+1
View File
@@ -34,6 +34,7 @@ packages/docusaurus-config @goauthentik/frontend
packages/esbuild-plugin-live-reload @goauthentik/frontend
packages/eslint-config @goauthentik/frontend
packages/prettier-config @goauthentik/frontend
packages/logger-js @goauthentik/frontend
packages/tsconfig @goauthentik/frontend
# Web
web/ @goauthentik/frontend
+4
View File
@@ -0,0 +1,4 @@
README.md
node_modules
_media
!.github/README.md
+13
View File
@@ -0,0 +1,13 @@
# Prettier Ignorefile
## Node
node_modules
## Static Files
**/LICENSE
./README.md
## Build asset directories
coverage
dist
out
+18
View File
@@ -0,0 +1,18 @@
The MIT License (MIT)
Copyright (c) 2025 Authentik Security, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+30
View File
@@ -0,0 +1,30 @@
/**
* @file ESLint Configuration
*
* @import { Config } from "eslint/config";
*/
import { createESLintPackageConfig } from "@goauthentik/eslint-config";
import { defineConfig } from "eslint/config";
// @ts-check
/**
* @type {Config[]}
*/
const eslintConfig = defineConfig(
createESLintPackageConfig({
parserOptions: {
tsconfigRootDir: import.meta.dirname,
},
}),
{
rules: {
"no-console": "off",
},
files: ["shared/**"],
},
);
export default eslintConfig;
+1
View File
@@ -0,0 +1 @@
export * from "./lib/browser.js";
+122
View File
@@ -0,0 +1,122 @@
/**
* @file Console logger for browser environments.
*
* @remarks
* The repetition of log levels, typedefs, and method signatures is intentional
* to give IDEs and type checkers a mapping of log methods to the TypeScript
* provided JSDoc comments.
*
* Additionally, no wrapper functions are used to avoid the browser's console
* reported call site being the wrapper instead of the actual caller.
*
* @import { IConsoleLogger } from "./shared.js"
* @import { Logger } from "pino"
*/
import { LogLevelLabel, LogLevels } from "./shared.js";
/* eslint-disable no-console */
//#region Constants
/**
* Colors for log levels in the browser console.
*
* @remarks
*
* The colors are derived from Carbon Design System's palette to ensure
* sufficient contrast and accessibility across light and dark themes.
*/
const LogLevelColors = /** @type {const} */ ({
info: `light-dark(#0043CE, #4589FF)`,
warn: `light-dark(#F1C21B, #F1C21B)`,
error: `light-dark(#DA1E28, #FA4D56)`,
debug: `light-dark(#8A3FFC, #A56EFF)`,
trace: `light-dark(#8A3FFC, #A56EFF)`,
fatal: `light-dark(#DA1E28, #FA4D56)`,
});
//#endregion
//#region Functions
/**
* Creates a logger with the given prefix.
*
* @param {string} [prefix]
* @param {...string} args
* @returns {Logger}
*
*/
export function createLogger(prefix, ...args) {
const msgPrefix = prefix ? `(${prefix}):` : ":";
/**
* @type {Partial<Logger>}
*/
const logger = {
msgPrefix,
};
for (const level of LogLevels) {
const label = LogLevelLabel[level];
const color = LogLevelColors[level];
// @ts-expect-error Alias the log method to the appropriate console method,
// defaulting to console.log if the level is not supported.
const method = level in console ? console[level] : console.log;
logger[level] = method.bind(
console,
`%c${label}%c ${msgPrefix}%c`,
`font-weight: 700; color: ${color};`,
`font-weight: 600; color: CanvasText;`,
"",
...args,
);
}
return /** @type {Logger} */ (logger);
}
//#endregion
//#region Console Logger
/**
* A singleton logger instance for the browser.
*
* ```js
* import { ConsoleLogger } from "#logger/browser";
*
* ConsoleLogger.info("Hello, world!");
* ```
*
* @implements {IConsoleLogger}
* @runtime browser
*/
// @ts-expect-error Logging properties are dynamically assigned.
export class ConsoleLogger {
/** @type {typeof console.info} */
static info;
/** @type {typeof console.warn} */
static warn;
/** @type {typeof console.error} */
static error;
/** @type {typeof console.debug} */
static debug;
/** @type {typeof console.trace} */
static trace;
/**
* Creates a logger with the given prefix.
* @param {string} logPrefix
*/
static prefix(logPrefix) {
return createLogger(logPrefix);
}
}
Object.assign(ConsoleLogger, createLogger());
//#endregion
+79
View File
@@ -0,0 +1,79 @@
/**
* Application logger.
*
* @import { LoggerOptions, Level } from "pino"
* @import { PrettyOptions } from "pino-pretty"
* @import { IConsoleLogger } from "./shared.js"
*/
/// <reference types="../types/node.js" />
import { fixture, prefix } from "./shared.js";
let warnedAboutPino = false;
const { pino } = await import("pino").catch(() => {
if (!warnedAboutPino) {
console.warn(
`Pino is not available. Falling back to a lightweight console logger.
Please install Pino to get the full logging experience: npm install pino`,
);
warnedAboutPino = true;
}
return import("./shared.js").then((module) => ({ pino: module.pinoLight }));
});
//#region Constants
/**
* Default options for creating a Pino logger.
*
* @category Logger
* @satisfies {LoggerOptions<never, false>}
*/
export const DEFAULT_PINO_LOGGER_OPTIONS = {
enabled: true,
level: "info",
transport: {
target: "./transport.js",
options: /** @satisfies {PrettyOptions} */ ({
colorize: true,
}),
},
};
//#endregion
//#region Functions
/**
* Read the log level from the environment.
* @return {Level}
*/
export function readLogLevel() {
return process.env.AK_LOG_LEVEL || DEFAULT_PINO_LOGGER_OPTIONS.level;
}
/**
* A singleton logger instance for Node.js.
*
* ```js
* import { ConsoleLogger } from "#logger/node";
*
* ConsoleLogger.info("Hello, world!");
* ```
*
* @runtime node
* @type {IConsoleLogger}
*/
export const ConsoleLogger = Object.assign(
pino({
...DEFAULT_PINO_LOGGER_OPTIONS,
level: readLogLevel(),
}),
{
fixture,
prefix,
},
);
+146
View File
@@ -0,0 +1,146 @@
/**
* @file Lightweight logging when Pino is not available
* @import { ChildLoggerOptions, LoggerOptions, Logger, BaseLogger, LoggerExtras, LogFnFields, Level, LogFn } from "pino"
* @import { PrettyOptions } from "pino-pretty"
*/
/* eslint-disable no-console */
//#region Constants
/**
* Labels log levels in the browser console.
* @satisfies {Record<Level, string>}
*/
export const LogLevelLabel = /** @type {const} */ ({
info: "[INFO]",
warn: "[WARN]",
error: "[ERROR]",
debug: "[DEBUG]",
trace: "[TRACE]",
fatal: "[FATAL]",
});
/**
* Predefined log levels.
*/
export const LogLevels = /** @type {Level[]} */ (Object.keys(LogLevelLabel));
//#region Functions
/**
* Creates a logger with the given prefix.
*
* @param {string | null} [prefix]
* @param {...string} args
* @returns {Logger}
*/
function createConsoleLogger(prefix, ...args) {
const msgPrefix = prefix ? `(${prefix}):` : ":";
/**
* @type {Partial<Logger>}
*/
const logger = {
msgPrefix,
};
for (const level of LogLevels) {
const label = LogLevelLabel[level];
// @ts-expect-error Alias the log method to the appropriate console method,
// defaulting to console.log if the level is not supported.
const method = level in console ? console[level] : console.log;
logger[level] = method.bind(console, `${label} ${msgPrefix}`, ...args);
}
return /** @type {Logger} */ (logger);
}
/**
* @typedef {Logger} FixtureLogger
*/
/**
* @this {Logger}
* @param {string} fixtureName
* @param {string} [testName]
* @param {ChildLoggerOptions} [options]
* @returns {FixtureLogger}
*/
export function fixture(fixtureName, testName, options) {
return this.child(
{ name: fixtureName },
{
msgPrefix: `[${testName}] `,
...options,
},
);
}
/**
* @this {Logger}
* @param {Record<string, unknown>} bindings
* @param {ChildLoggerOptions} [_options]
* @returns {Logger}
*/
export function child(bindings, _options) {
const prefix = typeof bindings.name === "string" ? bindings.name : null;
return Object.assign(createConsoleLogger(prefix), { ...bindings });
}
/**
* @this {{ child: typeof child }}
* @param {string} label
* @returns {IConsoleLogger}
*/
export function prefix(label) {
// @ts-expect-error Create a child logger with the given prefix.
return this.child({ name: label });
}
/**
* @typedef {object} CustomLoggerMethods
* @property {typeof fixture} fixture
* @property {typeof prefix} prefix
* @property {typeof child} child
*/
/**
* @typedef {Record<Level, LogFn>} BaseConsoleLogger
*/
/**
* @typedef {BaseConsoleLogger & CustomLoggerMethods} IConsoleLogger
*/
/**
* @type {CustomLoggerMethods}
*/
export const customLoggerMethods = {
fixture,
prefix,
child,
};
/**
* Creates a lightweight logger that mimics the Pino API but falls back to
* console methods when Pino is not available.
* @param {LoggerOptions<never, false>} options
* @return {IConsoleLogger}
*/
export function pinoLight(options) {
const baseLogger = createConsoleLogger(options.name);
/**
* @type {IConsoleLogger}
*/
const logger = {
...baseLogger,
fixture,
prefix,
child,
};
return logger;
}
+22
View File
@@ -0,0 +1,22 @@
/**
* @file Pretty transport for Pino
*
* @import { PrettyOptions } from "pino-pretty"
*/
import PinoPretty from "pino-pretty";
/**
* @param {PrettyOptions} options
*/
function prettyTransporter(options) {
const pretty = PinoPretty({
...options,
ignore: "pid,hostname",
translateTime: "SYS:HH:MM:ss",
});
return pretty;
}
export default prettyTransporter;
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
{
"name": "@goauthentik/logger-js",
"version": "1.0.0",
"description": "Pino-based logger configuration for Authentik",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/goauthentik/authentik.git",
"directory": "packages/logger-js"
},
"scripts": {
"build": "tsc -p .",
"lint": "eslint --fix .",
"lint-check": "eslint --max-warnings 0 .",
"prettier": "prettier --write .",
"prettier-check": "prettier --check ."
},
"type": "module",
"types": "./out/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./out/index.d.ts",
"node": "./lib/node.js",
"import": "./index.js",
"browser": "./lib/browser.js"
},
"./browser": {
"types": "./out/lib/browser.d.ts",
"import": "./lib/browser.js"
},
"./node": {
"types": "./out/lib/node.d.ts",
"import": "./lib/node.js"
}
},
"devDependencies": {
"@eslint/js": "^9.39.3",
"@goauthentik/prettier-config": "../prettier-config",
"@goauthentik/tsconfig": "../tsconfig",
"@types/node": "^25.3.0",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^9.39.3",
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1"
},
"peerDependencies": {
"pino": "^10.3.1",
"pino-pretty": "^13.1.2"
},
"files": [
"./index.js",
"lib/**/*",
"out/**/*"
],
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
"name": "node",
"version": ">=24",
"onFail": "ignore"
},
"packageManager": {
"name": "npm",
"version": ">=11.10.1",
"onFail": "ignore"
}
},
"prettier": "@goauthentik/prettier-config",
"overrides": {
"format-imports": {
"eslint": "$eslint"
}
},
"peerDependenciesMeta": {
"pino": {
"optional": true
},
"pino-pretty": {
"optional": true
}
},
"publishConfig": {
"access": "public"
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "@goauthentik/tsconfig",
"compilerOptions": {
"lib": ["ESNext"],
"resolveJsonModule": true,
"baseUrl": ".",
"checkJs": true,
"emitDeclarationOnly": true
},
"exclude": [
// ---
"**/out/**/*"
]
}
+27
View File
@@ -0,0 +1,27 @@
declare module "process" {
import { Level } from "pino";
global {
namespace NodeJS {
interface ProcessEnv {
/**
* An environment variable used to determine
* whether Node.js is running in production mode.
*
* @see {@link https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production | The difference between development and production}
*/
readonly NODE_ENV?: "development" | "production";
/**
* Whether or not we are running on a CI server.
*/
readonly CI?: string;
/**
* The application log level.
*/
readonly AK_LOG_LEVEL?: Level;
}
}
}
}