Skip to content

Commit 39b2ea0

Browse files
committed
feat(browser): Add console logging integration
1 parent 00a1018 commit 39b2ea0

File tree

6 files changed

+351
-78
lines changed

6 files changed

+351
-78
lines changed

packages/browser/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export * from './exports';
22

3-
import * as logger from './log';
4-
3+
import * as logger from './logs/exports';
54
export { logger };
5+
export { consoleLoggingIntegration } from './logs/console-integration';
66

77
export { reportingObserverIntegration } from './integrations/reportingobserver';
88
export { httpClientIntegration } from './integrations/httpclient';
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { Client, Log, LogSeverityLevel, ParameterizedString } from '@sentry/core';
2+
import { getClient, _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer } from '@sentry/core';
3+
import { WINDOW } from '../helpers';
4+
5+
/**
6+
* TODO: Make this configurable
7+
*/
8+
const DEFAULT_FLUSH_INTERVAL = 5000;
9+
10+
let timeout: ReturnType<typeof setTimeout> | undefined;
11+
12+
/**
13+
* This is a global timeout that is used to flush the logs buffer.
14+
* It is used to ensure that logs are flushed even if the client is not flushed.
15+
*/
16+
function startFlushTimeout(client: Client): void {
17+
if (timeout) {
18+
clearTimeout(timeout);
19+
}
20+
21+
timeout = setTimeout(() => {
22+
_INTERNAL_flushLogsBuffer(client);
23+
}, DEFAULT_FLUSH_INTERVAL);
24+
}
25+
26+
let isClientListenerAdded = false;
27+
/**
28+
* This is a function that is used to add a flush listener to the client.
29+
* It is used to ensure that the logger buffer is flushed when the client is flushed.
30+
*/
31+
function addFlushingListeners(client: Client): void {
32+
if (isClientListenerAdded || !client.getOptions()._experiments?.enableLogs) {
33+
return;
34+
}
35+
36+
isClientListenerAdded = true;
37+
38+
if (WINDOW.document) {
39+
WINDOW.document.addEventListener('visibilitychange', () => {
40+
if (WINDOW.document.visibilityState === 'hidden') {
41+
_INTERNAL_flushLogsBuffer(client);
42+
}
43+
});
44+
}
45+
46+
client.on('flush', () => {
47+
_INTERNAL_flushLogsBuffer(client);
48+
});
49+
}
50+
51+
/**
52+
* Capture a log with the given level.
53+
*
54+
* @param level - The level of the log.
55+
* @param message - The message to log.
56+
* @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100.
57+
* @param severityNumber - The severity number of the log.
58+
*/
59+
export function captureLog(
60+
level: LogSeverityLevel,
61+
message: ParameterizedString,
62+
attributes?: Log['attributes'],
63+
severityNumber?: Log['severityNumber'],
64+
): void {
65+
const client = getClient();
66+
if (client) {
67+
addFlushingListeners(client);
68+
69+
startFlushTimeout(client);
70+
}
71+
72+
_INTERNAL_captureLog({ level, message, attributes, severityNumber }, client, undefined);
73+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { ConsoleLevel, IntegrationFn } from '@sentry/core';
2+
import {
3+
addConsoleInstrumentationHandler,
4+
logger,
5+
CONSOLE_LEVELS,
6+
defineIntegration,
7+
safeJoin,
8+
getClient,
9+
} from '@sentry/core';
10+
import { captureLog } from './capture';
11+
import { DEBUG_BUILD } from '../debug-build';
12+
13+
interface CaptureConsoleOptions {
14+
levels: ConsoleLevel[];
15+
}
16+
17+
const INTEGRATION_NAME = 'ConsoleLogs';
18+
19+
const _consoleLoggingIntegration = ((options: Partial<CaptureConsoleOptions> = {}) => {
20+
const levels = options.levels || CONSOLE_LEVELS;
21+
22+
return {
23+
name: INTEGRATION_NAME,
24+
setup(client) {
25+
if (!client.getOptions()._experiments?.enableLogs) {
26+
DEBUG_BUILD && logger.warn('`_experiments.enableLogs` is not enabled, ConsoleLogs integration disabled');
27+
return;
28+
}
29+
30+
addConsoleInstrumentationHandler(({ args, level }) => {
31+
if (getClient() !== client || !levels.includes(level)) {
32+
return;
33+
}
34+
35+
if (level === 'assert') {
36+
if (!args[0]) {
37+
const message = `Assertion failed: ${safeJoin(args.slice(1), ' ') || 'console.assert'}`;
38+
captureLog('error', message);
39+
}
40+
return;
41+
}
42+
43+
const message = safeJoin(args, ' ');
44+
captureLog(level === 'log' ? 'info' : level, message);
45+
});
46+
},
47+
};
48+
}) satisfies IntegrationFn;
49+
50+
/**
51+
* Captures calls to the `console` API as logs in Sentry. Requires `_experiments.enableLogs` to be enabled.
52+
*
53+
* @experimental This feature is experimental and may be changed or removed in future versions.
54+
*
55+
* By default the integration instruments `console.debug`, `console.info`, `console.warn`, `console.error`,
56+
* `console.log`, `console.trace`, and `console.assert`. You can use the `levels` option to customize which
57+
* levels are captured.
58+
*
59+
* @example
60+
*
61+
* ```ts
62+
* import * as Sentry from '@sentry/browser';
63+
*
64+
* Sentry.init({
65+
* integrations: [Sentry.consoleLoggingIntegration({ levels: ['error', 'warn'] })],
66+
* });
67+
* ```
68+
*/
69+
export const consoleLoggingIntegration = defineIntegration(_consoleLoggingIntegration);
Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,5 @@
1-
import type { LogSeverityLevel, Log, Client, ParameterizedString } from '@sentry/core';
2-
import { getClient, _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer } from '@sentry/core';
3-
4-
import { WINDOW } from './helpers';
5-
6-
/**
7-
* TODO: Make this configurable
8-
*/
9-
const DEFAULT_FLUSH_INTERVAL = 5000;
10-
11-
let timeout: ReturnType<typeof setTimeout> | undefined;
12-
13-
/**
14-
* This is a global timeout that is used to flush the logs buffer.
15-
* It is used to ensure that logs are flushed even if the client is not flushed.
16-
*/
17-
function startFlushTimeout(client: Client): void {
18-
if (timeout) {
19-
clearTimeout(timeout);
20-
}
21-
22-
timeout = setTimeout(() => {
23-
_INTERNAL_flushLogsBuffer(client);
24-
}, DEFAULT_FLUSH_INTERVAL);
25-
}
26-
27-
let isClientListenerAdded = false;
28-
/**
29-
* This is a function that is used to add a flush listener to the client.
30-
* It is used to ensure that the logger buffer is flushed when the client is flushed.
31-
*/
32-
function addFlushingListeners(client: Client): void {
33-
if (isClientListenerAdded || !client.getOptions()._experiments?.enableLogs) {
34-
return;
35-
}
36-
37-
isClientListenerAdded = true;
38-
39-
if (WINDOW.document) {
40-
WINDOW.document.addEventListener('visibilitychange', () => {
41-
if (WINDOW.document.visibilityState === 'hidden') {
42-
_INTERNAL_flushLogsBuffer(client);
43-
}
44-
});
45-
}
46-
47-
client.on('flush', () => {
48-
_INTERNAL_flushLogsBuffer(client);
49-
});
50-
}
51-
52-
/**
53-
* Capture a log with the given level.
54-
*
55-
* @param level - The level of the log.
56-
* @param message - The message to log.
57-
* @param attributes - Arbitrary structured data that stores information about the log - e.g., userId: 100.
58-
* @param severityNumber - The severity number of the log.
59-
*/
60-
function captureLog(
61-
level: LogSeverityLevel,
62-
message: ParameterizedString,
63-
attributes?: Log['attributes'],
64-
severityNumber?: Log['severityNumber'],
65-
): void {
66-
const client = getClient();
67-
if (client) {
68-
addFlushingListeners(client);
69-
70-
startFlushTimeout(client);
71-
}
72-
73-
_INTERNAL_captureLog({ level, message, attributes, severityNumber }, client, undefined);
74-
}
1+
import type { Log, ParameterizedString } from '@sentry/core';
2+
import { captureLog } from './capture';
753

764
/**
775
* @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.

0 commit comments

Comments
 (0)