304 lines
12 KiB
TypeScript
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;
|
|
}
|