fix(sentry): widen SentryLike so official SDKs assign without adapters
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish unknown status

This commit is contained in:
2026-08-07 16:05:09 -03:00
parent 19de8db119
commit 3c468dbf44
6 changed files with 507 additions and 10 deletions
+37 -6
View File
@@ -1,5 +1,11 @@
import type { LogEvent, LogLevel, LogSink } from "../types";
/**
* Sentry Issue severity levels (`@sentry/core` `SeverityLevel`).
* Note: Issues use `"warning"`; Logs use `"warn"`.
*/
export type SentrySeverityLevel = "fatal" | "error" | "warning" | "log" | "info" | "debug";
/**
* Options for {@link createSentrySink}.
*
@@ -23,13 +29,16 @@ export interface SentrySinkOptions {
/**
* Minimal Sentry scope surface required by {@link createSentrySink}.
* Satisfied by `@sentry/browser`, `@sentry/node`, `@sentry/nextjs`, etc.
*
* Method parameter types are intentionally aligned with Sentry's public API
* (contravariance) so `typeof Sentry` assigns to {@link SentryLike} without adapters.
*/
export interface SentryScopeLike {
/**
* Sets the severity for the event about to be captured.
* @param level - Logger level string (e.g. `"error"`, `"info"`).
* @param level - Sentry Issue severity (`"warning"`, not logger `"warn"`).
*/
setLevel(level: string): void;
setLevel(level: SentrySeverityLevel): void;
/**
* Attaches a string tag to the event.
* @param key - Tag name (this package uses `"logger.namespace"`).
@@ -59,6 +68,8 @@ export interface SentryLoggerApiLike {
* Minimal Sentry client surface required by {@link createSentrySink}.
* Pass your app's Sentry SDK module; this package does not depend on Sentry.
*
* Structural typing matches official `@sentry/*` exports — no consumer adapter needed.
*
* @example
* ```ts
* import * as Sentry from "@sentry/nextjs";
@@ -80,10 +91,11 @@ export interface SentryLike {
captureException(error: Error): void;
/**
* Captures a string message when no `Error` is present in the arguments.
* Second argument matches Sentry's optional severity / capture-context slot.
* @param message - Joined string arguments, or a fallback.
* @param level - Event severity.
* @param captureContext - Issue severity (use {@link toSentrySeverity} mapping).
*/
captureMessage(message: string, level: string): void;
captureMessage(message: string, captureContext?: SentrySeverityLevel): void | string;
/** Structured Logs API. Required when using `{ logs: true }` or `{ sentry: true }`. */
logger?: SentryLoggerApiLike;
}
@@ -103,6 +115,21 @@ export function isSentrySink(sink: LogSink): sink is SentrySink {
return "kind" in sink && (sink as { kind?: unknown }).kind === "sentry";
}
/**
* Maps a logger level to a Sentry **Issue** {@link SentrySeverityLevel}.
*
* - `"warn"` → `"warning"` (Issues API; Logs keep `"warn"`)
* - `"trace"` → `"debug"` (closest Issue severity)
* - other levels map by name (`error`, `info`, `debug`)
*
* @param level - Logger emit level (never `silent`).
*/
export function toSentrySeverity(level: Exclude<LogLevel, "silent">): SentrySeverityLevel {
if (level === "warn") return "warning";
if (level === "trace") return "debug";
return level;
}
function isAttributeValue(value: unknown): value is string | number | boolean {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
@@ -143,6 +170,9 @@ export function toSentryLogPayload(event: LogEvent): {
* event with `{ sentry: true }` on the log call. Suppress an Issue with
* `{ suppressSentry: true }`.
*
* Official `@sentry/node`, `@sentry/browser`, and `@sentry/nextjs` modules are
* assignable to {@link SentryLike} directly — no adapter object required.
*
* @param sentry - Any object implementing {@link SentryLike} (typically the Sentry SDK).
* @param options - Optional Logs configuration.
* @returns A sink to pass as `sentry` or inside `sinks` on {@link createLogger}.
@@ -174,12 +204,13 @@ export function createSentrySink(sentry: SentryLike, options: SentrySinkOptions
if (event.sendToSentryIssue) {
const error = event.arguments.find((item): item is Error => item instanceof Error);
const { message, attributes } = toSentryLogPayload(event);
const severity = toSentrySeverity(event.level);
sentry.withScope((scope) => {
scope.setLevel(event.level);
scope.setLevel(severity);
if (event.namespace) scope.setTag("logger.namespace", event.namespace);
scope.setExtras(attributes);
if (error) sentry.captureException(error);
else sentry.captureMessage(message, event.level);
else sentry.captureMessage(message, severity);
});
return;
}