Files
logger/src/types.ts
T
mifi 64b63e1c52
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
fix:prettier and some type updates
2026-08-21 12:33:03 -03:00

304 lines
12 KiB
TypeScript

import { LogLevel as LogLevelEnum, LoggerEnvironment as LoggerEnvironmentEnum } from './constants';
/**
* Severity threshold for log filtering, ordered from most to least verbose.
*
* | Level | Meaning |
* |-----------|----------------------------------------------|
* | `trace` | Extremely detailed diagnostics |
* | `debug` | Development diagnostics |
* | `info` | Routine operational messages |
* | `warn` | Unexpected but recoverable conditions |
* | `error` | Failures that need attention |
* | `silent` | Suppresses all console-bound output |
*
* A logger emits events at or above its configured threshold (e.g. `warn`
* allows `warn` and `error`).
*/
export type LogLevel =
`${Exclude<LogLevelEnum, LogLevelEnum.FATAL | LogLevelEnum.LOG | LogLevelEnum.WARNING>}`;
/**
* Deployment stage used to pick a default log level when none is configured
* explicitly or via runtime overrides.
*
* Defaults:
* - `development` → `trace`
* - `staging` → `warn`
* - `production` → `error`
*/
export type LoggerEnvironment = `${LoggerEnvironmentEnum}`;
/**
* A value passed as a log argument after the message.
*
* Prefer a zero-argument function for expensive payloads: it is only invoked
* when the event is actually emitted, so suppressed logs avoid the work.
* The factory may be `async` or otherwise return a `Promise`; top-level
* thenables are settled before sinks see the event (fire-and-forget).
*
* @example
* ```ts
* logger.debug("state", () => expensiveSnapshot());
* logger.debug("response", async () => ({ data: await res.json() }));
* ```
*/
export type LogData = unknown | (() => unknown | Promise<unknown>);
/**
* Per-call options for `trace` / `debug` / `log` / `info` / `warn` / `error`
* (and `assert`). Not supported on console utilities such as `group`.
*
* When the last argument is a plain object whose keys are only these option
* names, it is treated as options rather than log data.
*/
export interface LogCallOptions {
/**
* Force this event to Sentry Logs, ignoring the Sentry Logs threshold.
* No-op for `error` (errors become Issues, not Logs) and in development.
*/
sentry?: boolean;
/**
* Skip creating a Sentry Issue for an `error`-level event.
* No-op at lower levels.
*/
suppressSentry?: boolean;
}
/**
* A structured log event after level and namespace policy have allowed it
* (or after it was selected for Sentry delivery).
*/
export interface LogEvent {
/** Severity of this event. Never `silent`. */
level: Exclude<LogLevel, LogLevelEnum.SILENT>;
/**
* Colon-joined namespace for this logger (e.g. `"WIDGET:Button"`).
* Omitted when the logger was created without a namespace.
*/
namespace?: string;
/**
* Evaluated arguments for the event (call options already stripped).
* Lazy factories have been invoked and top-level thenables settled;
* rejection reasons appear in place of rejected thenables.
*/
arguments: readonly unknown[];
/**
* Wall-clock time when the log call was made (before awaiting thenables).
*/
timestamp: Date;
/** Deployment stage from {@link LoggerOptions.environment}. */
environment: LoggerEnvironment;
/**
* `true` when the event should be written to Sentry Logs
* (`sentry.logger.*`), not as an Issue.
*/
sendToSentryLogs: boolean;
/**
* `true` when the event should be captured as a Sentry Issue
* (`captureException` / `captureMessage`).
*/
sendToSentryIssue: boolean;
/**
* `true` when the event passed the logger's level/namespace policy and
* should be rendered by console-oriented sinks.
*/
sendToConsole: boolean;
/**
* `true` when emit was deferred to settle top-level thenables.
* Omitted for fully synchronous emits. Console sinks render `[async]`.
*/
async?: boolean;
}
/**
* Destination that receives enabled {@link LogEvent}s.
*
* Provide custom sinks via {@link LoggerOptions.sinks}, or use
* {@link createConsoleSink} / `createSentrySink` from `@mifi/logger/sentry`.
*
* @example
* ```ts
* const sink: LogSink = {
* emit(event) {
* if (event.sendToConsole) analytics.track("log", event);
* },
* };
* ```
*/
export interface LogSink {
/**
* Handle a single log event.
* Implementations should respect `sendToConsole` / `sendToSentryLogs` /
* `sendToSentryIssue` as needed.
*/
emit(event: LogEvent): void;
}
/**
* Options for {@link createLogger}.
*
* Precedence for the effective console level (highest wins):
* 1. Explicit {@link LoggerOptions.level}
* 2. Browser `sessionStorage.showLoggingFor` (or {@link LoggerOptions.sessionStorage})
* 3. `MIFI_LOG_LEVEL` / `MIFI_LOG_NAMESPACES` from {@link LoggerOptions.env} or `process.env`
* 4. Default for {@link LoggerOptions.environment}
*/
export interface LoggerOptions {
/**
* Deployment stage. Selects the default log level when no override applies.
* @see LoggerEnvironment
*/
environment: LoggerEnvironment;
/**
* Optional namespace label(s). A string is used as-is; an array is joined
* with `:` (empty segments dropped). Shown in console output as
* `[A][B]` for `"A:B"`.
*
* @example
* ```ts
* createLogger({ environment: "development", namespace: "API" });
* createLogger({ environment: "development", namespace: ["WIDGET", "Button"] });
* ```
*/
namespace?: string | readonly string[];
/**
* Runtime used for the default destination policy (console vs Sentry-only
* in production browsers). Inferred from `globalThis.window` when omitted.
*/
runtime?: 'browser' | 'node';
/**
* Hard level override. Takes precedence over session storage, env vars,
* and environment defaults.
*/
level?: LogLevel;
/**
* Browser-only source for the `showLoggingFor` override key.
* Defaults to `globalThis.sessionStorage` when available.
*
* Expected value format matches {@link parseLoggingOverride}:
* `debug` or `debug:WIDGET,API`.
*/
sessionStorage?: Pick<Storage, 'getItem'>;
/**
* Node/container environment variable map. Defaults to `process.env`
* when available.
*
* Recognized keys:
* - `MIFI_LOG_LEVEL` — one of the {@link LogLevel} values
* - `MIFI_LOG_NAMESPACES` — optional comma-separated namespace filters
*/
env?: Record<string, string | undefined>;
/**
* Staging/production Sentry destination (typically from `createSentrySink`).
* Ignored in development (nothing is sent to Sentry).
*
* - Production **browser**: default sinks are Sentry only (no console).
* - Production **Node**: console (stderr for errors) and Sentry.
* - Staging: console and Sentry.
*
* Ignored when {@link LoggerOptions.sinks} is provided.
*/
sentry?: LogSink | false;
/**
* Complete destination list. When set, replaces the default console/Sentry
* policy entirely — {@link LoggerOptions.sentry} is not used.
*/
sinks?: readonly LogSink[];
}
/**
* Console-shaped logger with namespaces, lazy arguments, child loggers,
* and optional Sentry Issues / Logs delivery.
*
* Standard methods (`trace` … `error`) accept an optional trailing
* {@link LogCallOptions} object. Console utility methods (`group`, `time`,
* `table`, …) are gated by the same console policy and call through to
* `globalThis.console` when enabled — they do not accept {@link LogCallOptions}.
*
* @example
* ```ts
* const logger = createLogger({ environment: "development", namespace: "WIDGET" });
* logger.info("ready");
* logger.child("Button").debug("clicked", { id: 1 });
* logger.warn("investigate", { orderId }, { sentry: true });
* logger.error("expected", err, { suppressSentry: true });
* ```
*/
export interface Logger {
/**
* Colon-joined namespace for this instance, if any.
* Immutable; use {@link Logger.child} to nest further segments.
*/
readonly namespace?: string;
/**
* Returns a new logger that appends `namespace` to this logger's namespace.
*
* @param namespace - Segment to append (e.g. `"Button"` → `"WIDGET:Button"`).
* @returns A new {@link Logger} sharing the same options and sinks.
*
* @example
* ```ts
* const widget = createLogger({ environment: "development", namespace: "WIDGET" });
* const button = widget.child("Button"); // namespace === "WIDGET:Button"
* ```
*/
child(namespace: string): Logger;
/** Emit a `trace` event when the level policy allows it. */
trace(message?: unknown, data?: LogData, options?: LogCallOptions): void;
trace(...arguments_: readonly unknown[]): void;
/** Emit a `debug` event when the level policy allows it. */
debug(message?: unknown, data?: LogData, options?: LogCallOptions): void;
debug(...arguments_: readonly unknown[]): void;
/** Alias of {@link Logger.info}. */
log(message?: unknown, data?: LogData, options?: LogCallOptions): void;
log(...arguments_: readonly unknown[]): void;
/** Emit an `info` event when the level policy allows it. */
info(message?: unknown, data?: LogData, options?: LogCallOptions): void;
info(...arguments_: readonly unknown[]): void;
/** Emit a `warn` event when the level policy allows it. */
warn(message?: unknown, data?: LogData, options?: LogCallOptions): void;
warn(...arguments_: readonly unknown[]): void;
/** Emit an `error` event when the level policy allows it. */
error(message?: unknown, data?: LogData, options?: LogCallOptions): void;
error(...arguments_: readonly unknown[]): void;
/**
* When `condition` is falsy, emits an `error` with `"Assertion failed"`
* followed by any extra arguments (optional trailing {@link LogCallOptions}).
*
* @param condition - Truthy values pass silently.
* @param arguments_ - Extra context included with the failure event.
*/
assert(condition: unknown, ...arguments_: readonly unknown[]): void;
/** Starts a console group when the level policy allows `info`. */
group(...arguments_: readonly unknown[]): void;
/** Starts a collapsed console group when the level policy allows `info`. */
groupCollapsed(...arguments_: readonly unknown[]): void;
/** Ends the current console group when the level policy allows `info`. */
groupEnd(): void;
/** Logs an object with interactive inspection when the level policy allows `info`. */
dir(item?: unknown, options?: unknown): void;
/** Logs XML/HTML as an interactive tree when the level policy allows `info`. */
dirxml(...arguments_: readonly unknown[]): void;
/** Renders tabular data when the level policy allows `info`. */
table(tabularData?: unknown, properties?: readonly string[]): void;
/** Clears the console when the level policy allows `info`. */
clear(): void;
/** Increments a named counter when the level policy allows `info`. */
count(label?: string): void;
/** Resets a named counter when the level policy allows `info`. */
countReset(label?: string): void;
/** Starts a named timer when the level policy allows `info`. */
time(label?: string): void;
/** Logs elapsed time for a named timer when the level policy allows `info`. */
timeLog(label?: string, ...arguments_: readonly unknown[]): void;
/** Stops a named timer and logs elapsed time when the level policy allows `info`. */
timeEnd(label?: string): void;
/** Adds a timestamp marker to the performance timeline when allowed. */
timeStamp(label?: string): void;
/** Starts a CPU profile (where supported) when the level policy allows `info`. */
profile(label?: string): void;
/** Ends a CPU profile (where supported) when the level policy allows `info`. */
profileEnd(label?: string): void;
}