@logtape/sentry 2.2.0-dev.619 → 2.2.0-dev.620

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,19 @@ deno add npm:@sentry/deno npm:@sentry/core
47
47
  bun add @sentry/bun
48
48
  ~~~~
49
49
 
50
+ If your application initializes Sentry through a framework SDK such as
51
+ `@sentry/nextjs` or `@sentry/react-native`, pass the same namespace object to
52
+ the sink:
53
+
54
+ ~~~~ typescript
55
+ import * as Sentry from "@sentry/nextjs";
56
+ import { getSentrySink } from "@logtape/sentry";
57
+
58
+ Sentry.init({ dsn: process.env.SENTRY_DSN });
59
+
60
+ getSentrySink({ sentry: Sentry });
61
+ ~~~~
62
+
50
63
 
51
64
  Docs
52
65
  ----
package/dist/mod.cjs CHANGED
@@ -59,13 +59,15 @@ function mapLevelForLogs(level) {
59
59
  /**
60
60
  * Gets a LogTape sink that sends logs to Sentry.
61
61
  *
62
- * This sink uses Sentry's global capture functions from `@sentry/core`,
63
- * following Sentry v8+ best practices. Simply call `Sentry.init()` before
64
- * creating the sink, and it will automatically use your initialized client.
62
+ * This sink uses Sentry's global capture functions from `@sentry/core` by
63
+ * default, following Sentry v8+ best practices. Simply call `Sentry.init()`
64
+ * before creating the sink, and it will automatically use your initialized
65
+ * client when both packages resolve the same Sentry module instance.
65
66
  *
66
67
  * @param optionsOrClient Optional configuration. Can be:
67
68
  * - Omitted: Uses global Sentry functions (recommended)
68
69
  * - Object with options: Configure sink behavior
70
+ * - Object with `sentry`: Use an application-provided Sentry SDK namespace
69
71
  * - Sentry client instance: Backward compatibility (deprecated)
70
72
  * @returns A LogTape sink that sends logs to Sentry.
71
73
  *
@@ -87,6 +89,24 @@ function mapLevelForLogs(level) {
87
89
  * });
88
90
  * ```
89
91
  *
92
+ * @example With an application-provided Sentry namespace
93
+ * ```typescript
94
+ * import { configure } from "@logtape/logtape";
95
+ * import { getSentrySink } from "@logtape/sentry";
96
+ * import * as Sentry from "@sentry/nextjs";
97
+ *
98
+ * Sentry.init({ dsn: process.env.SENTRY_DSN });
99
+ *
100
+ * await configure({
101
+ * sinks: {
102
+ * sentry: getSentrySink({ sentry: Sentry }),
103
+ * },
104
+ * loggers: [
105
+ * { category: [], sinks: ["sentry"], lowestLevel: "error" },
106
+ * ],
107
+ * });
108
+ * ```
109
+ *
90
110
  * @example With options
91
111
  * ```typescript
92
112
  * import * as Sentry from "@sentry/node";
@@ -126,23 +146,25 @@ function mapLevelForLogs(level) {
126
146
  * @since 1.0.0
127
147
  */
128
148
  function getSentrySink(optionsOrClient) {
129
- let sentry;
149
+ let legacyClient;
130
150
  let options = {};
131
151
  if (optionsOrClient == null) {} else if (typeof optionsOrClient === "object" && "captureMessage" in optionsOrClient && typeof optionsOrClient.captureMessage === "function") {
132
152
  (0, __logtape_logtape.getLogger)([
133
153
  "logtape",
134
154
  "meta",
135
155
  "sentry"
136
- ]).warn("Passing a client directly is deprecated and will be removed in v2.0.0. Use getSentrySink() instead - simpler and recommended!");
137
- sentry = optionsOrClient;
156
+ ]).warn("Passing a client directly is deprecated. Use getSentrySink({ sentry: Sentry }) instead.");
157
+ legacyClient = optionsOrClient;
138
158
  } else if (typeof optionsOrClient === "object") options = optionsOrClient;
139
159
  else throw new Error(`[@logtape/sentry] Invalid parameter (type: ${typeof optionsOrClient}).\n\nExpected one of:
140
160
  getSentrySink() // Recommended
141
161
  getSentrySink({ options }) // With options
162
+ getSentrySink({ sentry }) // With a Sentry SDK namespace
142
163
  getSentrySink(client) // Deprecated (v1.1.x compat)
143
164
  `);
144
- const captureMessage = sentry ? (msg, ctx) => sentry.captureMessage(String(msg), ctx) : __sentry_core.captureMessage;
145
- const captureException = sentry ? (exception, hint) => sentry.captureException(exception, hint) : __sentry_core.captureException;
165
+ const sentry = options.sentry ?? __sentry_core;
166
+ const captureMessage = legacyClient ? (msg, ctx) => legacyClient.captureMessage(String(msg), ctx) : sentry.captureMessage;
167
+ const captureException = legacyClient ? (exception, hint) => legacyClient.captureException(exception, hint) : sentry.captureException;
146
168
  return (record) => {
147
169
  try {
148
170
  const { category } = record;
@@ -158,21 +180,21 @@ function getSentrySink(optionsOrClient) {
158
180
  category: transformed.category.join("."),
159
181
  timestamp: transformed.timestamp
160
182
  };
161
- const activeSpan = (0, __sentry_core.getActiveSpan)();
183
+ const activeSpan = sentry.getActiveSpan();
162
184
  if (activeSpan) {
163
185
  const spanCtx = activeSpan.spanContext();
164
186
  attributes.trace_id = spanCtx.traceId;
165
187
  attributes.span_id = spanCtx.spanId;
166
188
  if ("parentSpanId" in spanCtx) attributes.parent_span_id = spanCtx.parentSpanId;
167
189
  }
168
- const client = (0, __sentry_core.getClient)();
190
+ const client = sentry.getClient();
169
191
  if (client) {
170
192
  const { enableLogs, _experiments } = client.getOptions();
171
193
  const loggingEnabled = enableLogs ?? _experiments?.enableLogs;
172
- if (loggingEnabled && "logger" in __sentry_core) {
194
+ const sentryLogger = sentry.logger;
195
+ if (loggingEnabled && sentryLogger != null) {
173
196
  const logLevel = mapLevelForLogs(transformed.level);
174
- const sentryLogger = __sentry_core.logger;
175
- const logFn = sentryLogger?.[logLevel];
197
+ const logFn = sentryLogger[logLevel];
176
198
  if (typeof logFn === "function") logFn(paramMessage, attributes);
177
199
  }
178
200
  }
@@ -191,7 +213,7 @@ function getSentrySink(optionsOrClient) {
191
213
  extra: attributes
192
214
  });
193
215
  else if (options.enableBreadcrumbs) {
194
- const isolationScope = (0, __sentry_core.getIsolationScope)();
216
+ const isolationScope = sentry.getIsolationScope();
195
217
  isolationScope?.addBreadcrumb({
196
218
  category: transformed.category.join("."),
197
219
  level: eventLevel,
package/dist/mod.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LogRecord, Sink } from "@logtape/logtape";
2
- import { SeverityLevel } from "@sentry/core";
2
+ import { LogSeverityLevel, ParameterizedString, SeverityLevel } from "@sentry/core";
3
3
 
4
4
  //#region src/mod.d.ts
5
5
 
@@ -20,11 +20,86 @@ interface SentryInstance {
20
20
  captureMessage: (message: string, captureContext?: SeverityLevel | unknown) => string;
21
21
  captureException: (exception: unknown, hint?: unknown) => string;
22
22
  }
23
+ /**
24
+ * A Sentry SDK namespace object.
25
+ *
26
+ * Pass the namespace imported by your application when *@logtape/sentry* should
27
+ * use the same Sentry module instance that initialized your app, for example
28
+ * `import * as Sentry from "@sentry/node"`.
29
+ *
30
+ * @since 2.2.0
31
+ */
32
+ interface SentryNamespace {
33
+ /**
34
+ * Captures a message event and sends it to Sentry.
35
+ */
36
+ captureMessage(message: ParameterizedString, captureContext?: SeverityLevel | unknown): string;
37
+ /**
38
+ * Captures an exception event and sends it to Sentry.
39
+ */
40
+ captureException(exception: unknown, hint?: unknown): string;
41
+ /**
42
+ * Gets the currently active span, if any.
43
+ */
44
+ getActiveSpan(): {
45
+ spanContext: () => {
46
+ traceId: string;
47
+ spanId: string;
48
+ parentSpanId?: string;
49
+ };
50
+ } | undefined;
51
+ /**
52
+ * Gets the currently active Sentry client, if any.
53
+ */
54
+ getClient(): {
55
+ getOptions: () => {
56
+ enableLogs?: boolean;
57
+ _experiments?: {
58
+ enableLogs?: boolean;
59
+ };
60
+ };
61
+ } | undefined;
62
+ /**
63
+ * Gets the current isolation scope.
64
+ */
65
+ getIsolationScope(): {
66
+ addBreadcrumb: (breadcrumb: {
67
+ category: string;
68
+ level: SeverityLevel;
69
+ message: string;
70
+ timestamp: number;
71
+ data: Record<string, unknown>;
72
+ }) => void;
73
+ } | undefined;
74
+ /**
75
+ * Sentry's structured logging API, available in Sentry SDK 9.41.0+.
76
+ */
77
+ logger?: Partial<Record<LogSeverityLevel, (message: ParameterizedString, attributes: Record<string, unknown>) => void>>;
78
+ }
23
79
  /**
24
80
  * Options for configuring the Sentry sink.
25
81
  * @since 1.3.0
26
82
  */
27
83
  interface SentrySinkOptions {
84
+ /**
85
+ * Sentry SDK namespace to use for capture, scope, span, and structured log
86
+ * APIs.
87
+ *
88
+ * This is useful when your application initializes Sentry through a framework
89
+ * SDK such as `@sentry/nextjs` or `@sentry/react-native`, and
90
+ * *@logtape/sentry* resolves a different `@sentry/core` module instance.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * import * as Sentry from "@sentry/node";
95
+ *
96
+ * getSentrySink({ sentry: Sentry });
97
+ * ```
98
+ *
99
+ * @default `@sentry/core`
100
+ * @since 2.2.0
101
+ */
102
+ sentry?: SentryNamespace;
28
103
  /**
29
104
  * Enable automatic breadcrumb creation for log events.
30
105
  *
@@ -47,13 +122,15 @@ interface SentrySinkOptions {
47
122
  /**
48
123
  * Gets a LogTape sink that sends logs to Sentry.
49
124
  *
50
- * This sink uses Sentry's global capture functions from `@sentry/core`,
51
- * following Sentry v8+ best practices. Simply call `Sentry.init()` before
52
- * creating the sink, and it will automatically use your initialized client.
125
+ * This sink uses Sentry's global capture functions from `@sentry/core` by
126
+ * default, following Sentry v8+ best practices. Simply call `Sentry.init()`
127
+ * before creating the sink, and it will automatically use your initialized
128
+ * client when both packages resolve the same Sentry module instance.
53
129
  *
54
130
  * @param optionsOrClient Optional configuration. Can be:
55
131
  * - Omitted: Uses global Sentry functions (recommended)
56
132
  * - Object with options: Configure sink behavior
133
+ * - Object with `sentry`: Use an application-provided Sentry SDK namespace
57
134
  * - Sentry client instance: Backward compatibility (deprecated)
58
135
  * @returns A LogTape sink that sends logs to Sentry.
59
136
  *
@@ -75,6 +152,24 @@ interface SentrySinkOptions {
75
152
  * });
76
153
  * ```
77
154
  *
155
+ * @example With an application-provided Sentry namespace
156
+ * ```typescript
157
+ * import { configure } from "@logtape/logtape";
158
+ * import { getSentrySink } from "@logtape/sentry";
159
+ * import * as Sentry from "@sentry/nextjs";
160
+ *
161
+ * Sentry.init({ dsn: process.env.SENTRY_DSN });
162
+ *
163
+ * await configure({
164
+ * sinks: {
165
+ * sentry: getSentrySink({ sentry: Sentry }),
166
+ * },
167
+ * loggers: [
168
+ * { category: [], sinks: ["sentry"], lowestLevel: "error" },
169
+ * ],
170
+ * });
171
+ * ```
172
+ *
78
173
  * @example With options
79
174
  * ```typescript
80
175
  * import * as Sentry from "@sentry/node";
@@ -116,5 +211,5 @@ interface SentrySinkOptions {
116
211
  declare function getSentrySink(optionsOrClient?: SentrySinkOptions | SentryInstance): Sink;
117
212
  //# sourceMappingURL=mod.d.ts.map
118
213
  //#endregion
119
- export { SentryInstance, SentrySinkOptions, getSentrySink };
214
+ export { SentryInstance, SentryNamespace, SentrySinkOptions, getSentrySink };
120
215
  //# sourceMappingURL=mod.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.cts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AAmHA;AAYA;;;;AAmB+C;AAwE/C;;;;;AAEO,UAzGU,cAAA,CAyGV;qDAtGc;;;;;;;UASJ,iBAAA;;;;;;;;;;;;;;;;;;wBAmBO,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwEtB,aAAA,mBACI,oBAAoB,iBACrC"}
1
+ {"version":3,"file":"mod.d.cts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4GA;AAiBA;;;;;;;;;;AA2DW,UA5EM,cAAA,CA4EN;EAAO,cAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,cAAA,CAAA,EAzEG,aAyEH,GAAA,OAAA,EAAA,GAAA,MAAA;EAeD,gBAAA,EAAA,CAAA,SAAiB,EAAA,OAAA,EAAA,IAAA,CAAA,EAAA,OAAA,EAAA,GAAA,MAAA;;;;;AAuCa;AA4F/C;;;;;AAEO,UA/MU,eAAA,CA+MV;;;;0BA1MM,sCACQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA0CN;;;YAGD;;;;;;WAQH,QACP,OACE,4BAEW,iCACG;;;;;;UAUH,iBAAA;;;;;;;;;;;;;;;;;;;WAmBN;;;;;;;;;;;;;;;;;;wBAoBa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4FtB,aAAA,mBACI,oBAAoB,iBACrC"}
package/dist/mod.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LogRecord, Sink } from "@logtape/logtape";
2
- import { SeverityLevel } from "@sentry/core";
2
+ import { LogSeverityLevel, ParameterizedString, SeverityLevel } from "@sentry/core";
3
3
 
4
4
  //#region src/mod.d.ts
5
5
 
@@ -20,11 +20,86 @@ interface SentryInstance {
20
20
  captureMessage: (message: string, captureContext?: SeverityLevel | unknown) => string;
21
21
  captureException: (exception: unknown, hint?: unknown) => string;
22
22
  }
23
+ /**
24
+ * A Sentry SDK namespace object.
25
+ *
26
+ * Pass the namespace imported by your application when *@logtape/sentry* should
27
+ * use the same Sentry module instance that initialized your app, for example
28
+ * `import * as Sentry from "@sentry/node"`.
29
+ *
30
+ * @since 2.2.0
31
+ */
32
+ interface SentryNamespace {
33
+ /**
34
+ * Captures a message event and sends it to Sentry.
35
+ */
36
+ captureMessage(message: ParameterizedString, captureContext?: SeverityLevel | unknown): string;
37
+ /**
38
+ * Captures an exception event and sends it to Sentry.
39
+ */
40
+ captureException(exception: unknown, hint?: unknown): string;
41
+ /**
42
+ * Gets the currently active span, if any.
43
+ */
44
+ getActiveSpan(): {
45
+ spanContext: () => {
46
+ traceId: string;
47
+ spanId: string;
48
+ parentSpanId?: string;
49
+ };
50
+ } | undefined;
51
+ /**
52
+ * Gets the currently active Sentry client, if any.
53
+ */
54
+ getClient(): {
55
+ getOptions: () => {
56
+ enableLogs?: boolean;
57
+ _experiments?: {
58
+ enableLogs?: boolean;
59
+ };
60
+ };
61
+ } | undefined;
62
+ /**
63
+ * Gets the current isolation scope.
64
+ */
65
+ getIsolationScope(): {
66
+ addBreadcrumb: (breadcrumb: {
67
+ category: string;
68
+ level: SeverityLevel;
69
+ message: string;
70
+ timestamp: number;
71
+ data: Record<string, unknown>;
72
+ }) => void;
73
+ } | undefined;
74
+ /**
75
+ * Sentry's structured logging API, available in Sentry SDK 9.41.0+.
76
+ */
77
+ logger?: Partial<Record<LogSeverityLevel, (message: ParameterizedString, attributes: Record<string, unknown>) => void>>;
78
+ }
23
79
  /**
24
80
  * Options for configuring the Sentry sink.
25
81
  * @since 1.3.0
26
82
  */
27
83
  interface SentrySinkOptions {
84
+ /**
85
+ * Sentry SDK namespace to use for capture, scope, span, and structured log
86
+ * APIs.
87
+ *
88
+ * This is useful when your application initializes Sentry through a framework
89
+ * SDK such as `@sentry/nextjs` or `@sentry/react-native`, and
90
+ * *@logtape/sentry* resolves a different `@sentry/core` module instance.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * import * as Sentry from "@sentry/node";
95
+ *
96
+ * getSentrySink({ sentry: Sentry });
97
+ * ```
98
+ *
99
+ * @default `@sentry/core`
100
+ * @since 2.2.0
101
+ */
102
+ sentry?: SentryNamespace;
28
103
  /**
29
104
  * Enable automatic breadcrumb creation for log events.
30
105
  *
@@ -47,13 +122,15 @@ interface SentrySinkOptions {
47
122
  /**
48
123
  * Gets a LogTape sink that sends logs to Sentry.
49
124
  *
50
- * This sink uses Sentry's global capture functions from `@sentry/core`,
51
- * following Sentry v8+ best practices. Simply call `Sentry.init()` before
52
- * creating the sink, and it will automatically use your initialized client.
125
+ * This sink uses Sentry's global capture functions from `@sentry/core` by
126
+ * default, following Sentry v8+ best practices. Simply call `Sentry.init()`
127
+ * before creating the sink, and it will automatically use your initialized
128
+ * client when both packages resolve the same Sentry module instance.
53
129
  *
54
130
  * @param optionsOrClient Optional configuration. Can be:
55
131
  * - Omitted: Uses global Sentry functions (recommended)
56
132
  * - Object with options: Configure sink behavior
133
+ * - Object with `sentry`: Use an application-provided Sentry SDK namespace
57
134
  * - Sentry client instance: Backward compatibility (deprecated)
58
135
  * @returns A LogTape sink that sends logs to Sentry.
59
136
  *
@@ -75,6 +152,24 @@ interface SentrySinkOptions {
75
152
  * });
76
153
  * ```
77
154
  *
155
+ * @example With an application-provided Sentry namespace
156
+ * ```typescript
157
+ * import { configure } from "@logtape/logtape";
158
+ * import { getSentrySink } from "@logtape/sentry";
159
+ * import * as Sentry from "@sentry/nextjs";
160
+ *
161
+ * Sentry.init({ dsn: process.env.SENTRY_DSN });
162
+ *
163
+ * await configure({
164
+ * sinks: {
165
+ * sentry: getSentrySink({ sentry: Sentry }),
166
+ * },
167
+ * loggers: [
168
+ * { category: [], sinks: ["sentry"], lowestLevel: "error" },
169
+ * ],
170
+ * });
171
+ * ```
172
+ *
78
173
  * @example With options
79
174
  * ```typescript
80
175
  * import * as Sentry from "@sentry/node";
@@ -116,5 +211,5 @@ interface SentrySinkOptions {
116
211
  declare function getSentrySink(optionsOrClient?: SentrySinkOptions | SentryInstance): Sink;
117
212
  //# sourceMappingURL=mod.d.ts.map
118
213
  //#endregion
119
- export { SentryInstance, SentrySinkOptions, getSentrySink };
214
+ export { SentryInstance, SentryNamespace, SentrySinkOptions, getSentrySink };
120
215
  //# sourceMappingURL=mod.d.ts.map
package/dist/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AAmHA;AAYA;;;;AAmB+C;AAwE/C;;;;;AAEO,UAzGU,cAAA,CAyGV;qDAtGc;;;;;;;UASJ,iBAAA;;;;;;;;;;;;;;;;;;wBAmBO,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwEtB,aAAA,mBACI,oBAAoB,iBACrC"}
1
+ {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AA4GA;AAiBA;;;;;;;;;;AA2DW,UA5EM,cAAA,CA4EN;EAAO,cAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,cAAA,CAAA,EAzEG,aAyEH,GAAA,OAAA,EAAA,GAAA,MAAA;EAeD,gBAAA,EAAA,CAAA,SAAiB,EAAA,OAAA,EAAA,IAAA,CAAA,EAAA,OAAA,EAAA,GAAA,MAAA;;;;;AAuCa;AA4F/C;;;;;AAEO,UA/MU,eAAA,CA+MV;;;;0BA1MM,sCACQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA0CN;;;YAGD;;;;;;WAQH,QACP,OACE,4BAEW,iCACG;;;;;;UAUH,iBAAA;;;;;;;;;;;;;;;;;;;WAmBN;;;;;;;;;;;;;;;;;;wBAoBa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4FtB,aAAA,mBACI,oBAAoB,iBACrC"}
package/dist/mod.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { compareLogLevel, getLogger } from "@logtape/logtape";
2
2
  import * as SentryCore from "@sentry/core";
3
- import { captureException, captureMessage, getActiveSpan, getClient, getIsolationScope } from "@sentry/core";
4
3
 
5
4
  //#region src/mod.ts
6
5
  /**
@@ -59,13 +58,15 @@ function mapLevelForLogs(level) {
59
58
  /**
60
59
  * Gets a LogTape sink that sends logs to Sentry.
61
60
  *
62
- * This sink uses Sentry's global capture functions from `@sentry/core`,
63
- * following Sentry v8+ best practices. Simply call `Sentry.init()` before
64
- * creating the sink, and it will automatically use your initialized client.
61
+ * This sink uses Sentry's global capture functions from `@sentry/core` by
62
+ * default, following Sentry v8+ best practices. Simply call `Sentry.init()`
63
+ * before creating the sink, and it will automatically use your initialized
64
+ * client when both packages resolve the same Sentry module instance.
65
65
  *
66
66
  * @param optionsOrClient Optional configuration. Can be:
67
67
  * - Omitted: Uses global Sentry functions (recommended)
68
68
  * - Object with options: Configure sink behavior
69
+ * - Object with `sentry`: Use an application-provided Sentry SDK namespace
69
70
  * - Sentry client instance: Backward compatibility (deprecated)
70
71
  * @returns A LogTape sink that sends logs to Sentry.
71
72
  *
@@ -87,6 +88,24 @@ function mapLevelForLogs(level) {
87
88
  * });
88
89
  * ```
89
90
  *
91
+ * @example With an application-provided Sentry namespace
92
+ * ```typescript
93
+ * import { configure } from "@logtape/logtape";
94
+ * import { getSentrySink } from "@logtape/sentry";
95
+ * import * as Sentry from "@sentry/nextjs";
96
+ *
97
+ * Sentry.init({ dsn: process.env.SENTRY_DSN });
98
+ *
99
+ * await configure({
100
+ * sinks: {
101
+ * sentry: getSentrySink({ sentry: Sentry }),
102
+ * },
103
+ * loggers: [
104
+ * { category: [], sinks: ["sentry"], lowestLevel: "error" },
105
+ * ],
106
+ * });
107
+ * ```
108
+ *
90
109
  * @example With options
91
110
  * ```typescript
92
111
  * import * as Sentry from "@sentry/node";
@@ -126,23 +145,25 @@ function mapLevelForLogs(level) {
126
145
  * @since 1.0.0
127
146
  */
128
147
  function getSentrySink(optionsOrClient) {
129
- let sentry;
148
+ let legacyClient;
130
149
  let options = {};
131
150
  if (optionsOrClient == null) {} else if (typeof optionsOrClient === "object" && "captureMessage" in optionsOrClient && typeof optionsOrClient.captureMessage === "function") {
132
151
  getLogger([
133
152
  "logtape",
134
153
  "meta",
135
154
  "sentry"
136
- ]).warn("Passing a client directly is deprecated and will be removed in v2.0.0. Use getSentrySink() instead - simpler and recommended!");
137
- sentry = optionsOrClient;
155
+ ]).warn("Passing a client directly is deprecated. Use getSentrySink({ sentry: Sentry }) instead.");
156
+ legacyClient = optionsOrClient;
138
157
  } else if (typeof optionsOrClient === "object") options = optionsOrClient;
139
158
  else throw new Error(`[@logtape/sentry] Invalid parameter (type: ${typeof optionsOrClient}).\n\nExpected one of:
140
159
  getSentrySink() // Recommended
141
160
  getSentrySink({ options }) // With options
161
+ getSentrySink({ sentry }) // With a Sentry SDK namespace
142
162
  getSentrySink(client) // Deprecated (v1.1.x compat)
143
163
  `);
144
- const captureMessage$1 = sentry ? (msg, ctx) => sentry.captureMessage(String(msg), ctx) : captureMessage;
145
- const captureException$1 = sentry ? (exception, hint) => sentry.captureException(exception, hint) : captureException;
164
+ const sentry = options.sentry ?? SentryCore;
165
+ const captureMessage = legacyClient ? (msg, ctx) => legacyClient.captureMessage(String(msg), ctx) : sentry.captureMessage;
166
+ const captureException = legacyClient ? (exception, hint) => legacyClient.captureException(exception, hint) : sentry.captureException;
146
167
  return (record) => {
147
168
  try {
148
169
  const { category } = record;
@@ -158,40 +179,40 @@ function getSentrySink(optionsOrClient) {
158
179
  category: transformed.category.join("."),
159
180
  timestamp: transformed.timestamp
160
181
  };
161
- const activeSpan = getActiveSpan();
182
+ const activeSpan = sentry.getActiveSpan();
162
183
  if (activeSpan) {
163
184
  const spanCtx = activeSpan.spanContext();
164
185
  attributes.trace_id = spanCtx.traceId;
165
186
  attributes.span_id = spanCtx.spanId;
166
187
  if ("parentSpanId" in spanCtx) attributes.parent_span_id = spanCtx.parentSpanId;
167
188
  }
168
- const client = getClient();
189
+ const client = sentry.getClient();
169
190
  if (client) {
170
191
  const { enableLogs, _experiments } = client.getOptions();
171
192
  const loggingEnabled = enableLogs ?? _experiments?.enableLogs;
172
- if (loggingEnabled && "logger" in SentryCore) {
193
+ const sentryLogger = sentry.logger;
194
+ if (loggingEnabled && sentryLogger != null) {
173
195
  const logLevel = mapLevelForLogs(transformed.level);
174
- const sentryLogger = SentryCore.logger;
175
- const logFn = sentryLogger?.[logLevel];
196
+ const logFn = sentryLogger[logLevel];
176
197
  if (typeof logFn === "function") logFn(paramMessage, attributes);
177
198
  }
178
199
  }
179
200
  const isErrorLevel = compareLogLevel(transformed.level, "error") >= 0;
180
201
  if (isErrorLevel && transformed.properties.error instanceof Error) {
181
202
  const { error,...rest } = attributes;
182
- captureException$1(error, {
203
+ captureException(error, {
183
204
  level: eventLevel,
184
205
  extra: {
185
206
  message,
186
207
  ...rest
187
208
  }
188
209
  });
189
- } else if (isErrorLevel) captureMessage$1(paramMessage, {
210
+ } else if (isErrorLevel) captureMessage(paramMessage, {
190
211
  level: eventLevel,
191
212
  extra: attributes
192
213
  });
193
214
  else if (options.enableBreadcrumbs) {
194
- const isolationScope = getIsolationScope();
215
+ const isolationScope = sentry.getIsolationScope();
195
216
  isolationScope?.addBreadcrumb({
196
217
  category: transformed.category.join("."),
197
218
  level: eventLevel,
package/dist/mod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","names":["record: LogRecord","tplValues: string[]","inspect: (value: unknown) => string","level: LogLevel","optionsOrClient?: SentrySinkOptions | SentryInstance","sentry: SentryInstance | undefined","options: SentrySinkOptions","captureMessage","msg: ParameterizedString","ctx?: unknown","globalCaptureMessage","captureException","exception: unknown","hint?: unknown","globalCaptureException"],"sources":["../src/mod.ts"],"sourcesContent":["import {\n compareLogLevel,\n getLogger,\n type LogLevel,\n type LogRecord,\n type Sink,\n} from \"@logtape/logtape\";\nimport type {\n LogSeverityLevel,\n ParameterizedString,\n SeverityLevel,\n} from \"@sentry/core\";\n// Import namespace to safely check for public logger API (added in v9.41.0)\nimport * as SentryCore from \"@sentry/core\";\nimport {\n captureException as globalCaptureException,\n captureMessage as globalCaptureMessage,\n getActiveSpan as globalGetActiveSpan,\n getClient as globalGetClient,\n getIsolationScope as globalGetIsolationScope,\n} from \"@sentry/core\";\n\n/**\n * Converts a LogTape {@link LogRecord} into a Sentry {@link ParameterizedString}.\n *\n * This preserves the template structure for better message grouping in Sentry,\n * allowing similar messages with different values to be grouped together.\n *\n * @param record The log record to convert.\n * @returns A parameterized string with template and values.\n */\nfunction getParameterizedString(record: LogRecord): ParameterizedString {\n let result = \"\";\n let tplString = \"\";\n const tplValues: string[] = [];\n for (let i = 0; i < record.message.length; i++) {\n if (i % 2 === 0) {\n result += record.message[i];\n tplString += String(record.message[i]).replaceAll(\"%\", \"%%\");\n } else {\n const value = inspect(record.message[i]);\n result += value;\n tplString += `%s`;\n tplValues.push(value);\n }\n }\n const paramStr = new String(result) as ParameterizedString;\n paramStr.__sentry_template_string__ = tplString;\n paramStr.__sentry_template_values__ = tplValues;\n return paramStr;\n}\n\n/**\n * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},\n * and in Node.js/Bun it is {@link util.inspect}. If neither is available, it\n * falls back to {@link JSON.stringify}.\n *\n * @param value The value to inspect.\n * @returns The string representation of the value.\n */\nconst inspect: (value: unknown) => string =\n // @ts-ignore: Deno global\n \"Deno\" in globalThis && \"inspect\" in globalThis.Deno &&\n // @ts-ignore: Deno global\n typeof globalThis.Deno.inspect === \"function\"\n // @ts-ignore: Deno global\n ? globalThis.Deno.inspect\n // @ts-ignore: Node.js global\n : \"util\" in globalThis && \"inspect\" in globalThis.util &&\n // @ts-ignore: Node.js global\n typeof globalThis.util.inspect === \"function\"\n // @ts-ignore: Node.js global\n ? globalThis.util.inspect\n : JSON.stringify;\n\n// Level normalization helpers\n\nfunction mapLevelForEvents(level: LogLevel): SeverityLevel {\n switch (level) {\n case \"trace\":\n return \"debug\";\n default:\n return level as SeverityLevel; // debug | info | error | fatal\n }\n}\n\nfunction mapLevelForLogs(level: LogLevel): LogSeverityLevel {\n switch (level) {\n case \"trace\":\n return \"debug\";\n case \"warning\":\n return \"warn\";\n case \"debug\":\n case \"info\":\n case \"error\":\n case \"fatal\":\n return level;\n default:\n return \"info\"; // fallback\n }\n}\n\n/**\n * A Sentry client instance type (used for v1.1.x backward compatibility).\n *\n * Client instances only support `captureMessage` and `captureException`.\n * For scope operations (breadcrumbs, user context, traces), the sink always\n * uses global functions from `@sentry/core`.\n *\n * @deprecated This is only used for backward compatibility with v1.1.x.\n * New code should use `getSentrySink()` without parameters, which automatically\n * uses Sentry's global functions.\n *\n * @since 1.3.0\n */\nexport interface SentryInstance {\n captureMessage: (\n message: string,\n captureContext?: SeverityLevel | unknown,\n ) => string;\n captureException: (exception: unknown, hint?: unknown) => string;\n}\n\n/**\n * Options for configuring the Sentry sink.\n * @since 1.3.0\n */\nexport interface SentrySinkOptions {\n /**\n * Enable automatic breadcrumb creation for log events.\n *\n * When enabled, all logs become breadcrumbs in Sentry's isolation scope,\n * providing a complete context trail when errors occur. Breadcrumbs are\n * lightweight and only appear in error reports for debugging.\n *\n * @default false\n * @since 1.3.0\n */\n enableBreadcrumbs?: boolean;\n\n /**\n * Optional hook to transform or filter records before sending to Sentry.\n * Return `null` to drop the record.\n *\n * @since 1.3.0\n */\n beforeSend?: (record: LogRecord) => LogRecord | null;\n}\n\n/**\n * Gets a LogTape sink that sends logs to Sentry.\n *\n * This sink uses Sentry's global capture functions from `@sentry/core`,\n * following Sentry v8+ best practices. Simply call `Sentry.init()` before\n * creating the sink, and it will automatically use your initialized client.\n *\n * @param optionsOrClient Optional configuration. Can be:\n * - Omitted: Uses global Sentry functions (recommended)\n * - Object with options: Configure sink behavior\n * - Sentry client instance: Backward compatibility (deprecated)\n * @returns A LogTape sink that sends logs to Sentry.\n *\n * @example Recommended usage - no parameters\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getSentrySink } from \"@logtape/sentry\";\n * import * as Sentry from \"@sentry/node\";\n *\n * Sentry.init({ dsn: process.env.SENTRY_DSN });\n *\n * await configure({\n * sinks: {\n * sentry: getSentrySink(), // That's it!\n * },\n * loggers: [\n * { category: [], sinks: [\"sentry\"], lowestLevel: \"error\" },\n * ],\n * });\n * ```\n *\n * @example With options\n * ```typescript\n * import * as Sentry from \"@sentry/node\";\n * Sentry.init({ dsn: process.env.SENTRY_DSN });\n *\n * await configure({\n * sinks: {\n * sentry: getSentrySink({\n * enableBreadcrumbs: true,\n * }),\n * },\n * loggers: [\n * { category: [], sinks: [\"sentry\"], lowestLevel: \"info\" },\n * ],\n * });\n * ```\n *\n * @example Edge functions - must flush before termination\n * ```typescript\n * // Cloudflare Workers\n * export default {\n * async fetch(request, env, ctx) {\n * logger.error(\"Something happened\");\n * ctx.waitUntil(Sentry.flush(2000)); // Don't block response\n * return new Response(\"OK\");\n * }\n * };\n * ```\n *\n * @example Legacy usage (v1.1.x - deprecated)\n * ```typescript\n * import { getClient } from \"@sentry/node\";\n * const client = getClient();\n * getSentrySink(client); // Still works but shows deprecation warning\n * ```\n *\n * @since 1.0.0\n */\nexport function getSentrySink(\n optionsOrClient?: SentrySinkOptions | SentryInstance,\n): Sink {\n let sentry: SentryInstance | undefined;\n let options: SentrySinkOptions = {};\n\n // Detect which API pattern is being used\n if (optionsOrClient == null) {\n // Pattern: getSentrySink() - no params (RECOMMENDED)\n // Use global functions\n } else if (\n typeof optionsOrClient === \"object\" &&\n \"captureMessage\" in optionsOrClient &&\n typeof optionsOrClient.captureMessage === \"function\"\n ) {\n // Pattern: getSentrySink(client) - DEPRECATED (v1.1.x backward compatibility)\n getLogger([\"logtape\", \"meta\", \"sentry\"]).warn(\n \"Passing a client directly is deprecated and will be removed in v2.0.0. \" +\n \"Use getSentrySink() instead - simpler and recommended!\",\n );\n sentry = optionsOrClient as SentryInstance;\n } else if (typeof optionsOrClient === \"object\") {\n // Pattern: getSentrySink({ options }) - options object\n options = optionsOrClient as SentrySinkOptions;\n } else {\n throw new Error(\n `[@logtape/sentry] Invalid parameter (type: ${typeof optionsOrClient}).\\n\\n` +\n \"Expected one of:\\n\" +\n \" getSentrySink() // Recommended\\n\" +\n \" getSentrySink({ options }) // With options\\n\" +\n \" getSentrySink(client) // Deprecated (v1.1.x compat)\\n\",\n );\n }\n\n // Choose which Sentry functions to use:\n // - For capture functions: use client if provided (v1.1.x compat), otherwise globals\n // - For scope operations: ALWAYS use globals (client doesn't have these methods)\n const captureMessage = sentry\n ? (msg: ParameterizedString, ctx?: unknown) =>\n sentry.captureMessage(String(msg), ctx)\n : globalCaptureMessage;\n const captureException = sentry\n ? (exception: unknown, hint?: unknown) =>\n sentry.captureException(exception, hint)\n : globalCaptureException;\n\n return (record: LogRecord) => {\n try {\n // Skip meta logger records to prevent infinite recursion\n const { category } = record;\n if (\n category[0] === \"logtape\" && category[1] === \"meta\" &&\n category[2] === \"sentry\"\n ) {\n return;\n }\n\n // Optional transformation/filtering\n const transformed = options.beforeSend\n ? options.beforeSend(record)\n : record;\n if (transformed == null) return;\n\n // Parameterized message for structured logging and events\n const paramMessage = getParameterizedString(transformed);\n const message = paramMessage.toString();\n\n // Level mapping\n const eventLevel = mapLevelForEvents(transformed.level);\n\n // Enriched structured attributes\n const attributes = {\n ...transformed.properties,\n \"sentry.origin\": \"auto.logging.logtape\",\n category: transformed.category.join(\".\"),\n timestamp: transformed.timestamp,\n } as Record<string, unknown>;\n\n // After enriched attributes\n const activeSpan = globalGetActiveSpan();\n if (activeSpan) {\n const spanCtx = activeSpan.spanContext();\n attributes.trace_id = spanCtx.traceId;\n attributes.span_id = spanCtx.spanId;\n if (\"parentSpanId\" in spanCtx) {\n attributes.parent_span_id = spanCtx.parentSpanId; // Optional\n }\n }\n\n // Send structured log if Sentry logging is enabled (v9.41.0+)\n // Uses public logger API when available (SDK 9.41.0+)\n const client = globalGetClient();\n if (client) {\n const { enableLogs, _experiments } = client.getOptions();\n const loggingEnabled = enableLogs ?? _experiments?.enableLogs;\n\n if (loggingEnabled && \"logger\" in SentryCore) {\n const logLevel = mapLevelForLogs(transformed.level);\n const sentryLogger = SentryCore.logger as unknown as\n | Record<\n string,\n ((msg: ParameterizedString, attrs: unknown) => void)\n >\n | undefined;\n const logFn = sentryLogger?.[logLevel];\n if (typeof logFn === \"function\") {\n logFn(paramMessage, attributes);\n }\n }\n }\n\n // Capture as Sentry event (Issue) based on level and error presence\n // Use compareLogLevel() to handle future severity level additions\n const isErrorLevel = compareLogLevel(transformed.level, \"error\") >= 0;\n\n if (isErrorLevel && transformed.properties.error instanceof Error) {\n // Error instance at error/fatal level -> captureException for stack trace\n const { error, ...rest } = attributes;\n captureException(error as Error, {\n level: eventLevel,\n extra: { message, ...rest },\n });\n } else if (isErrorLevel) {\n // Error/fatal level without Error instance -> captureMessage as Issue\n captureMessage(paramMessage, {\n level: eventLevel,\n extra: attributes,\n });\n } else if (options.enableBreadcrumbs) {\n // Non-error levels -> breadcrumbs only (if enabled)\n const isolationScope = globalGetIsolationScope();\n isolationScope?.addBreadcrumb({\n category: transformed.category.join(\".\"),\n level: eventLevel,\n message,\n timestamp: transformed.timestamp / 1000,\n data: attributes,\n });\n }\n } catch (err) {\n // Never throw from a sink; keep failures silent but visible in debug\n try {\n console.debug(\"[@logtape/sentry] sink error\", err);\n } catch { /* ignore console errors */ }\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mod.js","names":["record: LogRecord","tplValues: string[]","inspect: (value: unknown) => string","level: LogLevel","optionsOrClient?: SentrySinkOptions | SentryInstance","legacyClient: SentryInstance | undefined","options: SentrySinkOptions","msg: ParameterizedString","ctx?: unknown","exception: unknown","hint?: unknown"],"sources":["../src/mod.ts"],"sourcesContent":["import {\n compareLogLevel,\n getLogger,\n type LogLevel,\n type LogRecord,\n type Sink,\n} from \"@logtape/logtape\";\nimport type {\n LogSeverityLevel,\n ParameterizedString,\n SeverityLevel,\n} from \"@sentry/core\";\n// Import namespace to safely check for public logger API (added in v9.41.0)\nimport * as SentryCore from \"@sentry/core\";\n\n/**\n * Converts a LogTape {@link LogRecord} into a Sentry {@link ParameterizedString}.\n *\n * This preserves the template structure for better message grouping in Sentry,\n * allowing similar messages with different values to be grouped together.\n *\n * @param record The log record to convert.\n * @returns A parameterized string with template and values.\n */\nfunction getParameterizedString(record: LogRecord): ParameterizedString {\n let result = \"\";\n let tplString = \"\";\n const tplValues: string[] = [];\n for (let i = 0; i < record.message.length; i++) {\n if (i % 2 === 0) {\n result += record.message[i];\n tplString += String(record.message[i]).replaceAll(\"%\", \"%%\");\n } else {\n const value = inspect(record.message[i]);\n result += value;\n tplString += `%s`;\n tplValues.push(value);\n }\n }\n const paramStr = new String(result) as ParameterizedString;\n paramStr.__sentry_template_string__ = tplString;\n paramStr.__sentry_template_values__ = tplValues;\n return paramStr;\n}\n\n/**\n * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},\n * and in Node.js/Bun it is {@link util.inspect}. If neither is available, it\n * falls back to {@link JSON.stringify}.\n *\n * @param value The value to inspect.\n * @returns The string representation of the value.\n */\nconst inspect: (value: unknown) => string =\n // @ts-ignore: Deno global\n \"Deno\" in globalThis && \"inspect\" in globalThis.Deno &&\n // @ts-ignore: Deno global\n typeof globalThis.Deno.inspect === \"function\"\n // @ts-ignore: Deno global\n ? globalThis.Deno.inspect\n // @ts-ignore: Node.js global\n : \"util\" in globalThis && \"inspect\" in globalThis.util &&\n // @ts-ignore: Node.js global\n typeof globalThis.util.inspect === \"function\"\n // @ts-ignore: Node.js global\n ? globalThis.util.inspect\n : JSON.stringify;\n\n// Level normalization helpers\n\nfunction mapLevelForEvents(level: LogLevel): SeverityLevel {\n switch (level) {\n case \"trace\":\n return \"debug\";\n default:\n return level as SeverityLevel; // debug | info | error | fatal\n }\n}\n\nfunction mapLevelForLogs(level: LogLevel): LogSeverityLevel {\n switch (level) {\n case \"trace\":\n return \"debug\";\n case \"warning\":\n return \"warn\";\n case \"debug\":\n case \"info\":\n case \"error\":\n case \"fatal\":\n return level;\n default:\n return \"info\"; // fallback\n }\n}\n\n/**\n * A Sentry client instance type (used for v1.1.x backward compatibility).\n *\n * Client instances only support `captureMessage` and `captureException`.\n * For scope operations (breadcrumbs, user context, traces), the sink always\n * uses global functions from `@sentry/core`.\n *\n * @deprecated This is only used for backward compatibility with v1.1.x.\n * New code should use `getSentrySink()` without parameters, which automatically\n * uses Sentry's global functions.\n *\n * @since 1.3.0\n */\nexport interface SentryInstance {\n captureMessage: (\n message: string,\n captureContext?: SeverityLevel | unknown,\n ) => string;\n captureException: (exception: unknown, hint?: unknown) => string;\n}\n\n/**\n * A Sentry SDK namespace object.\n *\n * Pass the namespace imported by your application when *@logtape/sentry* should\n * use the same Sentry module instance that initialized your app, for example\n * `import * as Sentry from \"@sentry/node\"`.\n *\n * @since 2.2.0\n */\nexport interface SentryNamespace {\n /**\n * Captures a message event and sends it to Sentry.\n */\n captureMessage(\n message: ParameterizedString,\n captureContext?: SeverityLevel | unknown,\n ): string;\n\n /**\n * Captures an exception event and sends it to Sentry.\n */\n captureException(exception: unknown, hint?: unknown): string;\n\n /**\n * Gets the currently active span, if any.\n */\n getActiveSpan():\n | {\n spanContext: () => {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n };\n }\n | undefined;\n\n /**\n * Gets the currently active Sentry client, if any.\n */\n getClient():\n | {\n getOptions: () => {\n enableLogs?: boolean;\n _experiments?: {\n enableLogs?: boolean;\n };\n };\n }\n | undefined;\n\n /**\n * Gets the current isolation scope.\n */\n getIsolationScope():\n | {\n addBreadcrumb: (breadcrumb: {\n category: string;\n level: SeverityLevel;\n message: string;\n timestamp: number;\n data: Record<string, unknown>;\n }) => void;\n }\n | undefined;\n\n /**\n * Sentry's structured logging API, available in Sentry SDK 9.41.0+.\n */\n logger?: Partial<\n Record<\n LogSeverityLevel,\n (\n message: ParameterizedString,\n attributes: Record<string, unknown>,\n ) => void\n >\n >;\n}\n\n/**\n * Options for configuring the Sentry sink.\n * @since 1.3.0\n */\nexport interface SentrySinkOptions {\n /**\n * Sentry SDK namespace to use for capture, scope, span, and structured log\n * APIs.\n *\n * This is useful when your application initializes Sentry through a framework\n * SDK such as `@sentry/nextjs` or `@sentry/react-native`, and\n * *@logtape/sentry* resolves a different `@sentry/core` module instance.\n *\n * @example\n * ```typescript\n * import * as Sentry from \"@sentry/node\";\n *\n * getSentrySink({ sentry: Sentry });\n * ```\n *\n * @default `@sentry/core`\n * @since 2.2.0\n */\n sentry?: SentryNamespace;\n\n /**\n * Enable automatic breadcrumb creation for log events.\n *\n * When enabled, all logs become breadcrumbs in Sentry's isolation scope,\n * providing a complete context trail when errors occur. Breadcrumbs are\n * lightweight and only appear in error reports for debugging.\n *\n * @default false\n * @since 1.3.0\n */\n enableBreadcrumbs?: boolean;\n\n /**\n * Optional hook to transform or filter records before sending to Sentry.\n * Return `null` to drop the record.\n *\n * @since 1.3.0\n */\n beforeSend?: (record: LogRecord) => LogRecord | null;\n}\n\n/**\n * Gets a LogTape sink that sends logs to Sentry.\n *\n * This sink uses Sentry's global capture functions from `@sentry/core` by\n * default, following Sentry v8+ best practices. Simply call `Sentry.init()`\n * before creating the sink, and it will automatically use your initialized\n * client when both packages resolve the same Sentry module instance.\n *\n * @param optionsOrClient Optional configuration. Can be:\n * - Omitted: Uses global Sentry functions (recommended)\n * - Object with options: Configure sink behavior\n * - Object with `sentry`: Use an application-provided Sentry SDK namespace\n * - Sentry client instance: Backward compatibility (deprecated)\n * @returns A LogTape sink that sends logs to Sentry.\n *\n * @example Recommended usage - no parameters\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getSentrySink } from \"@logtape/sentry\";\n * import * as Sentry from \"@sentry/node\";\n *\n * Sentry.init({ dsn: process.env.SENTRY_DSN });\n *\n * await configure({\n * sinks: {\n * sentry: getSentrySink(), // That's it!\n * },\n * loggers: [\n * { category: [], sinks: [\"sentry\"], lowestLevel: \"error\" },\n * ],\n * });\n * ```\n *\n * @example With an application-provided Sentry namespace\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getSentrySink } from \"@logtape/sentry\";\n * import * as Sentry from \"@sentry/nextjs\";\n *\n * Sentry.init({ dsn: process.env.SENTRY_DSN });\n *\n * await configure({\n * sinks: {\n * sentry: getSentrySink({ sentry: Sentry }),\n * },\n * loggers: [\n * { category: [], sinks: [\"sentry\"], lowestLevel: \"error\" },\n * ],\n * });\n * ```\n *\n * @example With options\n * ```typescript\n * import * as Sentry from \"@sentry/node\";\n * Sentry.init({ dsn: process.env.SENTRY_DSN });\n *\n * await configure({\n * sinks: {\n * sentry: getSentrySink({\n * enableBreadcrumbs: true,\n * }),\n * },\n * loggers: [\n * { category: [], sinks: [\"sentry\"], lowestLevel: \"info\" },\n * ],\n * });\n * ```\n *\n * @example Edge functions - must flush before termination\n * ```typescript\n * // Cloudflare Workers\n * export default {\n * async fetch(request, env, ctx) {\n * logger.error(\"Something happened\");\n * ctx.waitUntil(Sentry.flush(2000)); // Don't block response\n * return new Response(\"OK\");\n * }\n * };\n * ```\n *\n * @example Legacy usage (v1.1.x - deprecated)\n * ```typescript\n * import { getClient } from \"@sentry/node\";\n * const client = getClient();\n * getSentrySink(client); // Still works but shows deprecation warning\n * ```\n *\n * @since 1.0.0\n */\nexport function getSentrySink(\n optionsOrClient?: SentrySinkOptions | SentryInstance,\n): Sink {\n let legacyClient: SentryInstance | undefined;\n let options: SentrySinkOptions = {};\n\n // Detect which API pattern is being used\n if (optionsOrClient == null) {\n // Pattern: getSentrySink() - no params (RECOMMENDED)\n // Use global functions\n } else if (\n typeof optionsOrClient === \"object\" &&\n \"captureMessage\" in optionsOrClient &&\n typeof optionsOrClient.captureMessage === \"function\"\n ) {\n // Pattern: getSentrySink(client) - DEPRECATED (v1.1.x backward compatibility)\n getLogger([\"logtape\", \"meta\", \"sentry\"]).warn(\n \"Passing a client directly is deprecated. \" +\n \"Use getSentrySink({ sentry: Sentry }) instead.\",\n );\n legacyClient = optionsOrClient as SentryInstance;\n } else if (typeof optionsOrClient === \"object\") {\n // Pattern: getSentrySink({ options }) - options object\n options = optionsOrClient as SentrySinkOptions;\n } else {\n throw new Error(\n `[@logtape/sentry] Invalid parameter (type: ${typeof optionsOrClient}).\\n\\n` +\n \"Expected one of:\\n\" +\n \" getSentrySink() // Recommended\\n\" +\n \" getSentrySink({ options }) // With options\\n\" +\n \" getSentrySink({ sentry }) // With a Sentry SDK namespace\\n\" +\n \" getSentrySink(client) // Deprecated (v1.1.x compat)\\n\",\n );\n }\n\n const sentry = options.sentry ?? SentryCore;\n\n // Choose which Sentry functions to use:\n // - For capture functions: use client if provided (v1.1.x compat),\n // otherwise the configured SDK namespace.\n // - For scope operations: use the configured SDK namespace because clients\n // don't expose current scope/span APIs.\n const captureMessage = legacyClient\n ? (msg: ParameterizedString, ctx?: unknown) =>\n legacyClient.captureMessage(String(msg), ctx)\n : sentry.captureMessage;\n const captureException = legacyClient\n ? (exception: unknown, hint?: unknown) =>\n legacyClient.captureException(exception, hint)\n : sentry.captureException;\n\n return (record: LogRecord) => {\n try {\n // Skip meta logger records to prevent infinite recursion\n const { category } = record;\n if (\n category[0] === \"logtape\" && category[1] === \"meta\" &&\n category[2] === \"sentry\"\n ) {\n return;\n }\n\n // Optional transformation/filtering\n const transformed = options.beforeSend\n ? options.beforeSend(record)\n : record;\n if (transformed == null) return;\n\n // Parameterized message for structured logging and events\n const paramMessage = getParameterizedString(transformed);\n const message = paramMessage.toString();\n\n // Level mapping\n const eventLevel = mapLevelForEvents(transformed.level);\n\n // Enriched structured attributes\n const attributes = {\n ...transformed.properties,\n \"sentry.origin\": \"auto.logging.logtape\",\n category: transformed.category.join(\".\"),\n timestamp: transformed.timestamp,\n } as Record<string, unknown>;\n\n // After enriched attributes\n const activeSpan = sentry.getActiveSpan();\n if (activeSpan) {\n const spanCtx = activeSpan.spanContext();\n attributes.trace_id = spanCtx.traceId;\n attributes.span_id = spanCtx.spanId;\n if (\"parentSpanId\" in spanCtx) {\n attributes.parent_span_id = spanCtx.parentSpanId; // Optional\n }\n }\n\n // Send structured log if Sentry logging is enabled (v9.41.0+)\n // Uses public logger API when available (SDK 9.41.0+)\n const client = sentry.getClient();\n if (client) {\n const { enableLogs, _experiments } = client.getOptions();\n const loggingEnabled = enableLogs ?? _experiments?.enableLogs;\n\n const sentryLogger = sentry.logger as SentryNamespace[\"logger\"];\n if (loggingEnabled && sentryLogger != null) {\n const logLevel = mapLevelForLogs(transformed.level);\n const logFn = sentryLogger[logLevel];\n if (typeof logFn === \"function\") {\n logFn(paramMessage, attributes);\n }\n }\n }\n\n // Capture as Sentry event (Issue) based on level and error presence\n // Use compareLogLevel() to handle future severity level additions\n const isErrorLevel = compareLogLevel(transformed.level, \"error\") >= 0;\n\n if (isErrorLevel && transformed.properties.error instanceof Error) {\n // Error instance at error/fatal level -> captureException for stack trace\n const { error, ...rest } = attributes;\n captureException(error as Error, {\n level: eventLevel,\n extra: { message, ...rest },\n });\n } else if (isErrorLevel) {\n // Error/fatal level without Error instance -> captureMessage as Issue\n captureMessage(paramMessage, {\n level: eventLevel,\n extra: attributes,\n });\n } else if (options.enableBreadcrumbs) {\n // Non-error levels -> breadcrumbs only (if enabled)\n const isolationScope = sentry.getIsolationScope();\n isolationScope?.addBreadcrumb({\n category: transformed.category.join(\".\"),\n level: eventLevel,\n message,\n timestamp: transformed.timestamp / 1000,\n data: attributes,\n });\n }\n } catch (err) {\n // Never throw from a sink; keep failures silent but visible in debug\n try {\n console.debug(\"[@logtape/sentry] sink error\", err);\n } catch { /* ignore console errors */ }\n }\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/sentry",
3
- "version": "2.2.0-dev.619+83dde3d4",
3
+ "version": "2.2.0-dev.620+455a47e2",
4
4
  "description": "LogTape Sentry sink",
5
5
  "keywords": [
6
6
  "LogTape",
@@ -45,7 +45,7 @@
45
45
  ],
46
46
  "peerDependencies": {
47
47
  "@sentry/core": ">=8.0.0",
48
- "@logtape/logtape": "^2.2.0-dev.619+83dde3d4"
48
+ "@logtape/logtape": "^2.2.0-dev.620+455a47e2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@sentry/core": "^9.41.0",