Skip to content

Commit bd59033

Browse files
sergicalclaude
andauthored
feat(deno): Export logs API from Deno SDK (#19313)
## Summary - Re-export `logger` and `consoleLoggingIntegration` from `@sentry/core` in the Deno SDK - Add integration test verifying `logger.info()` produces a log envelope item with correct `level` and `body` ## Test plan - [x] `yarn build:dev:filter @sentry/deno` — builds successfully - [x] `cd packages/deno && yarn test` — all 13 tests pass - [x] `eslint packages/deno/src/index.ts` — no lint errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Closes #19314 (added automatically) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 51a0cf0 commit bd59033

3 files changed

Lines changed: 135 additions & 4 deletions

File tree

packages/deno/src/client.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ServerRuntimeClientOptions } from '@sentry/core';
2-
import { SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
2+
import { _INTERNAL_flushLogsBuffer, SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
33
import type { DenoClientOptions } from './types';
44

55
function getHostName(): string | undefined {
@@ -19,6 +19,8 @@ function getHostName(): string | undefined {
1919
* @see SentryClient for usage documentation.
2020
*/
2121
export class DenoClient extends ServerRuntimeClient<DenoClientOptions> {
22+
private _logOnExitFlushListener: (() => void) | undefined;
23+
2224
/**
2325
* Creates a new Deno SDK instance.
2426
* @param options Configuration options for this SDK.
@@ -36,13 +38,42 @@ export class DenoClient extends ServerRuntimeClient<DenoClientOptions> {
3638
version: SDK_VERSION,
3739
};
3840

41+
const serverName = options.serverName || getHostName();
42+
3943
const clientOptions: ServerRuntimeClientOptions = {
4044
...options,
4145
platform: 'javascript',
4246
runtime: { name: 'deno', version: Deno.version.deno },
43-
serverName: options.serverName || getHostName(),
47+
serverName,
4448
};
4549

4650
super(clientOptions);
51+
52+
if (this.getOptions().enableLogs) {
53+
this._logOnExitFlushListener = () => {
54+
_INTERNAL_flushLogsBuffer(this);
55+
};
56+
57+
if (serverName) {
58+
this.on('beforeCaptureLog', log => {
59+
log.attributes = {
60+
...log.attributes,
61+
'server.address': serverName,
62+
};
63+
});
64+
}
65+
66+
globalThis.addEventListener('unload', this._logOnExitFlushListener);
67+
}
68+
}
69+
70+
/** @inheritDoc */
71+
// @ts-expect-error - PromiseLike is a subset of Promise
72+
public async close(timeout?: number | undefined): PromiseLike<boolean> {
73+
if (this._logOnExitFlushListener) {
74+
globalThis.removeEventListener('unload', this._logOnExitFlushListener);
75+
}
76+
77+
return super.close(timeout);
4778
}
4879
}

packages/deno/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export type {
22
Breadcrumb,
33
BreadcrumbHint,
4+
Log,
5+
LogSeverityLevel,
46
Metric,
57
PolymorphicRequest,
68
RequestEventData,
@@ -91,6 +93,8 @@ export {
9193
wrapMcpServerWithSentry,
9294
featureFlagsIntegration,
9395
metrics,
96+
logger,
97+
consoleLoggingIntegration,
9498
} from '@sentry/core';
9599

96100
export { DenoClient } from './client';

packages/deno/test/mod.test.ts

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { Envelope, Event } from '@sentry/core';
1+
import type { Envelope, Event, Log } from '@sentry/core';
22
import { createStackParser, forEachEnvelopeItem, nodeStackLineParser } from '@sentry/core';
33
import { assertEquals } from 'https://deno.land/std@0.202.0/assert/assert_equals.ts';
44
import { assertSnapshot } from 'https://deno.land/std@0.202.0/testing/snapshot.ts';
5-
import { DenoClient, getCurrentScope, getDefaultIntegrations, metrics, Scope } from '../build/esm/index.js';
5+
import { DenoClient, getCurrentScope, getDefaultIntegrations, logger, metrics, Scope } from '../build/esm/index.js';
66
import { getNormalizedEvent } from './normalize.ts';
77
import { makeTestTransport } from './transport.ts';
88

@@ -111,6 +111,102 @@ Deno.test('metrics.count captures a counter metric', async () => {
111111
assertEquals(metricItem.items[0].value, 5);
112112
});
113113

114+
Deno.test('logger.info captures a log envelope item', async () => {
115+
const envelopes: Array<Envelope> = [];
116+
const client = new DenoClient({
117+
dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507',
118+
enableLogs: true,
119+
integrations: getDefaultIntegrations({}),
120+
stackParser: createStackParser(nodeStackLineParser()),
121+
transport: makeTestTransport(envelope => {
122+
envelopes.push(envelope);
123+
}),
124+
});
125+
126+
client.init();
127+
const scope = new Scope();
128+
scope.setClient(client);
129+
130+
logger.info('test log message', { key: 'value' }, { scope });
131+
132+
await client.flush(2000);
133+
134+
// deno-lint-ignore no-explicit-any
135+
let logItem: any = undefined;
136+
for (const envelope of envelopes) {
137+
forEachEnvelopeItem(envelope, item => {
138+
const [headers, body] = item;
139+
if (headers.type === 'log') {
140+
logItem = body;
141+
}
142+
});
143+
}
144+
145+
assertEquals(logItem !== undefined, true);
146+
assertEquals(logItem.items.length, 1);
147+
assertEquals(logItem.items[0].level, 'info');
148+
assertEquals(logItem.items[0].body, 'test log message');
149+
});
150+
151+
Deno.test('adds server.address to log attributes', () => {
152+
const client = new DenoClient({
153+
dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507',
154+
enableLogs: true,
155+
serverName: 'test-server',
156+
integrations: getDefaultIntegrations({}),
157+
stackParser: createStackParser(nodeStackLineParser()),
158+
transport: makeTestTransport(() => {}),
159+
});
160+
161+
const log: Log = { level: 'info', message: 'test message', attributes: {} };
162+
client.emit('beforeCaptureLog', log);
163+
164+
assertEquals(log.attributes?.['server.address'], 'test-server');
165+
});
166+
167+
Deno.test('preserves existing log attributes when adding server.address', () => {
168+
const client = new DenoClient({
169+
dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507',
170+
enableLogs: true,
171+
serverName: 'test-server',
172+
integrations: getDefaultIntegrations({}),
173+
stackParser: createStackParser(nodeStackLineParser()),
174+
transport: makeTestTransport(() => {}),
175+
});
176+
177+
const log: Log = { level: 'info', message: 'test message', attributes: { 'existing.attr': 'value' } };
178+
client.emit('beforeCaptureLog', log);
179+
180+
assertEquals(log.attributes?.['existing.attr'], 'value');
181+
assertEquals(log.attributes?.['server.address'], 'test-server');
182+
});
183+
184+
Deno.test('close() removes unload listener when enableLogs is true', async () => {
185+
const removeEventListenerCalls: Array<string> = [];
186+
const originalRemoveEventListener = globalThis.removeEventListener;
187+
globalThis.removeEventListener = ((event: string, ...args: unknown[]) => {
188+
removeEventListenerCalls.push(event);
189+
// deno-lint-ignore no-explicit-any
190+
return originalRemoveEventListener.call(globalThis, event, ...(args as [any]));
191+
}) as typeof globalThis.removeEventListener;
192+
193+
try {
194+
const client = new DenoClient({
195+
dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507',
196+
enableLogs: true,
197+
integrations: getDefaultIntegrations({}),
198+
stackParser: createStackParser(nodeStackLineParser()),
199+
transport: makeTestTransport(() => {}),
200+
});
201+
202+
await client.close();
203+
204+
assertEquals(removeEventListenerCalls.includes('unload'), true);
205+
} finally {
206+
globalThis.removeEventListener = originalRemoveEventListener;
207+
}
208+
});
209+
114210
Deno.test('App runs without errors', async _ => {
115211
const cmd = new Deno.Command('deno', {
116212
args: ['run', '--allow-net=some-domain.com', './test/example.ts'],

0 commit comments

Comments
 (0)