feat!: API options object support to control sentry handling; removal of logger.sentry.
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish unknown status

This commit is contained in:
2026-08-07 12:07:29 -03:00
parent 9f18e0855e
commit 6bee3a801d
11 changed files with 1170 additions and 161 deletions
+236 -24
View File
@@ -1,77 +1,289 @@
/** A severity threshold, ordered from most to least verbose. */
/**
* 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 = "trace" | "debug" | "info" | "warn" | "error" | "silent";
/**
* 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 = "development" | "staging" | "production";
/**
* 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.
*
* @example
* ```ts
* logger.debug("state", () => expensiveSnapshot());
* ```
*/
export type LogData = unknown | (() => unknown);
/** A structured event after its level policy has allowed it to be emitted. */
/**
* 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 `() => unknown` functions have already been invoked.
*/
arguments: readonly unknown[];
/** Wall-clock time when the event was created. */
timestamp: Date;
/** Whether this event was explicitly selected for Sentry capture. */
sendToSentry: boolean;
/** Whether the default console sink should render this event. */
/** 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;
}
/** A destination for enabled log events. */
/**
* 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. It is inferred when omitted. */
/**
* Runtime used for the default destination policy (console vs Sentry-only
* in production browsers). Inferred from `globalThis.window` when omitted.
*/
runtime?: "browser" | "node";
/** Hard override. It takes precedence over runtime environment defaults. */
/**
* Hard level override. Takes precedence over session storage, env vars,
* and environment defaults.
*/
level?: LogLevel;
/** Browser-only session override source; defaults to global sessionStorage when available. */
/**
* 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 source; defaults to process.env when available. */
/**
* 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>;
/** Production error destination. Browser errors go only here; Node errors also go to stderr. */
/**
* 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 override. When provided, `sentry` and default console behavior are not used. */
/**
* Complete destination list. When set, replaces the default console/Sentry
* policy entirely — {@link LoggerOptions.sentry} is not used.
*/
sinks?: readonly LogSink[];
}
/** Explicit Sentry reporting methods. These bypass the logger's normal level threshold. */
export interface SentryLogger {
trace(...arguments_: readonly unknown[]): void;
debug(...arguments_: readonly unknown[]): void;
log(...arguments_: readonly unknown[]): void;
info(...arguments_: readonly unknown[]): void;
warn(...arguments_: readonly unknown[]): void;
error(...arguments_: readonly unknown[]): void;
}
/**
* 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;
/** Sends selected events to a configured Sentry sink without changing the default policy for other logs. */
readonly sentry: SentryLogger;
/**
* 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;
}