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 = `${LogLevelEnum}`; /** * 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); /** * 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; /** * 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; /** * 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; /** * 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; }