- `logger.debug('message', async () => ({ asyncReturn: await asyncFn() }))` is now supported
- NOTE: this may result in logging occurring out of band for these calls if other events fire befor they settle
Update CI labels to direct jobs to correct servers
303 lines
12 KiB
TypeScript
303 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 = `${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<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, "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;
|
|
}
|