feat(logger): add explicit Sentry event channel
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful

This commit is contained in:
2026-08-05 13:05:06 -03:00
parent 0ccb7f911d
commit 44f471653e
7 changed files with 66 additions and 10 deletions
+18 -2
View File
@@ -97,13 +97,20 @@ export function createLogger(options: LoggerOptions): Logger {
levelRank(level) >= levelRank(threshold) &&
(!runtimeOverride?.namespaces.length ||
namespaceMatches(namespace, runtimeOverride.namespaces));
const emit = (level: Exclude<LogLevel, "silent">, arguments_: readonly unknown[]) => {
if (!enabled(level)) return;
const emit = (
level: Exclude<LogLevel, "silent">,
arguments_: readonly unknown[],
sendToSentry = false,
) => {
const sendToConsole = enabled(level);
if (!sendToConsole && !sendToSentry) return;
const event: LogEvent = {
level,
namespace,
arguments: evaluate(arguments_),
timestamp: new Date(),
sendToSentry,
sendToConsole,
};
sinks.forEach((sink) => sink.emit(event));
};
@@ -123,6 +130,14 @@ export function createLogger(options: LoggerOptions): Logger {
};
const api: Logger = {
namespace,
sentry: {
trace: (...items) => emit("trace", items, true),
debug: (...items) => emit("debug", items, true),
log: (...items) => emit("info", items, true),
info: (...items) => emit("info", items, true),
warn: (...items) => emit("warn", items, true),
error: (...items) => emit("error", items, true),
},
child: (child) =>
createLogger({ ...options, namespace: [namespace, child].filter(Boolean) as string[] }),
trace: (...items) => emit("trace", items),
@@ -187,6 +202,7 @@ function supportsAnsi(): boolean {
export function createConsoleSink(): LogSink {
return {
emit: (event) => {
if (!event.sendToConsole) return;
const method = event.level === "trace" ? "debug" : event.level;
const console_ = globalThis.console as Console & Record<string, unknown>;
const fn = console_[method];
+3 -3
View File
@@ -15,7 +15,7 @@ export interface SentryLike {
export function createSentrySink(sentry: SentryLike): LogSink {
return {
emit(event: LogEvent) {
if (event.level !== "error") return;
if (event.level !== "error" && !event.sendToSentry) return;
const error = event.arguments.find((item): item is Error => item instanceof Error);
const extras = Object.fromEntries(
event.arguments
@@ -26,11 +26,11 @@ export function createSentrySink(sentry: SentryLike): LogSink {
event.arguments.filter((item) => typeof item === "string").join(" ") ||
"Logger error";
sentry.withScope((scope) => {
scope.setLevel("error");
scope.setLevel(event.level);
if (event.namespace) scope.setTag("logger.namespace", event.namespace);
scope.setExtras(extras);
if (error) sentry.captureException(error);
else sentry.captureMessage(message, "error");
else sentry.captureMessage(message, event.level);
});
},
};
+16
View File
@@ -9,6 +9,10 @@ export interface LogEvent {
namespace?: string;
arguments: readonly unknown[];
timestamp: Date;
/** Whether this event was explicitly selected for Sentry capture. */
sendToSentry: boolean;
/** Whether the default console sink should render this event. */
sendToConsole: boolean;
}
/** A destination for enabled log events. */
@@ -33,8 +37,20 @@ export interface LoggerOptions {
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;
}
export interface Logger {
readonly namespace?: string;
/** Sends selected events to a configured Sentry sink without changing the default policy for other logs. */
readonly sentry: SentryLogger;
child(namespace: string): Logger;
trace(...arguments_: readonly unknown[]): void;
debug(...arguments_: readonly unknown[]): void;