Skip to content

Commit 9e243c6

Browse files
authored
feat: Capture logs for Electron integrations (#1199)
1 parent 9b1443d commit 9e243c6

5 files changed

Lines changed: 89 additions & 21 deletions

File tree

src/main/integrations/child-process.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { addBreadcrumb, captureMessage, defineIntegration, SeverityLevel } from '@sentry/core';
2-
import { childProcessIntegration as nodeChildProcessIntegration, NodeClient } from '@sentry/node';
1+
import {
2+
addBreadcrumb,
3+
captureMessage,
4+
defineIntegration,
5+
Log,
6+
ParameterizedString,
7+
SeverityLevel,
8+
} from '@sentry/core';
9+
import { childProcessIntegration as nodeChildProcessIntegration, logger, NodeClient } from '@sentry/node';
310
import { app } from 'electron';
411
import { EXIT_REASONS, ExitReason } from '../electron-normalize';
512
import { ElectronMainOptions } from '../sdk';
@@ -22,21 +29,27 @@ const DEFAULT_OPTIONS: ChildProcessOptions = {
2229
events: ['abnormal-exit', 'launch-failed', 'integrity-failure'],
2330
};
2431

32+
type LogFn = (msg: ParameterizedString, attributes: Log['attributes']) => void;
33+
2534
/** Gets message and severity */
26-
function getMessageAndSeverity(reason: ExitReason, proc?: string): { message: string; level: SeverityLevel } {
27-
const message = `'${proc}' process exited with '${reason}'`;
35+
function getMessageAndSeverity(
36+
reason: ExitReason,
37+
process: string,
38+
): { message: string; messageFmt: ParameterizedString; level: SeverityLevel; log: LogFn } {
39+
const message = `'${process}' process exited with '${reason}'`;
40+
const messageFmt = logger.fmt`'${process}' process exited with '${reason}'`;
2841

2942
switch (reason) {
3043
case 'abnormal-exit':
3144
case 'killed':
32-
return { message, level: 'warning' };
45+
return { message, level: 'warning', log: logger.warn, messageFmt };
3346
case 'crashed':
3447
case 'oom':
3548
case 'launch-failed':
3649
case 'integrity-failure':
37-
return { message, level: 'fatal' };
50+
return { message, level: 'fatal', log: logger.error, messageFmt };
3851
default:
39-
return { message, level: 'debug' };
52+
return { message, level: 'debug', log: logger.info, messageFmt };
4053
}
4154
}
4255

@@ -70,10 +83,10 @@ export const childProcessIntegration = defineIntegration((userOptions: Partial<O
7083

7184
app.on('child-process-gone', (_, details) => {
7285
const { reason } = details;
86+
const { message, level, log, messageFmt } = getMessageAndSeverity(details.reason, details.type);
7387

7488
// Capture message first
7589
if (events.includes(reason)) {
76-
const { message, level } = getMessageAndSeverity(details.reason, details.type);
7790
captureMessage(message, { level, tags: { 'event.process': details.type } });
7891
}
7992

@@ -82,19 +95,26 @@ export const childProcessIntegration = defineIntegration((userOptions: Partial<O
8295
addBreadcrumb({
8396
type: 'process',
8497
category: 'child-process',
85-
...getMessageAndSeverity(details.reason, details.type),
98+
message,
99+
level,
86100
data: details,
87101
});
102+
103+
log(messageFmt, {
104+
exitCode: details.exitCode,
105+
name: details.name,
106+
serviceName: details.serviceName,
107+
});
88108
}
89109
});
90110

91111
app.on('render-process-gone', (_, contents, details) => {
92112
const { reason } = details;
93113
const name = clientOptions?.getRendererName?.(contents) || 'renderer';
114+
const { message, level, log, messageFmt } = getMessageAndSeverity(details.reason, name);
94115

95116
// Capture message first
96117
if (events.includes(reason)) {
97-
const { message, level } = getMessageAndSeverity(details.reason, name);
98118
captureMessage(message, level);
99119
}
100120

@@ -103,9 +123,14 @@ export const childProcessIntegration = defineIntegration((userOptions: Partial<O
103123
addBreadcrumb({
104124
type: 'process',
105125
category: 'child-process',
106-
...getMessageAndSeverity(details.reason, name),
126+
message,
127+
level,
107128
data: details,
108129
});
130+
131+
log(messageFmt, {
132+
exitCode: details.exitCode,
133+
});
109134
}
110135
});
111136
}

src/main/integrations/electron-breadcrumbs.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { addBreadcrumb, Breadcrumb, defineIntegration } from '@sentry/core';
2-
import { NodeClient } from '@sentry/node';
2+
import { logger, NodeClient } from '@sentry/node';
33
import { app, autoUpdater, BrowserWindow, powerMonitor, screen, WebContents } from 'electron';
44
import { getRendererProperties, trackRendererProperties } from '../renderers';
55
import { ElectronMainOptions } from '../sdk';
@@ -137,6 +137,18 @@ export const electronBreadcrumbsIntegration = defineIntegration(
137137
}
138138

139139
addBreadcrumb(breadcrumb);
140+
141+
const attributes: Record<string, unknown> = {};
142+
143+
if (breadcrumb.data?.id) {
144+
attributes.id = breadcrumb.data.id;
145+
}
146+
147+
if (breadcrumb.data?.url) {
148+
attributes.url = breadcrumb.data.url;
149+
}
150+
151+
logger.info(logger.fmt`electron.${category}.${event}`, attributes);
140152
}
141153

142154
return emit(event, ...args);

src/main/integrations/net-breadcrumbs.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
stringMatchesSomePattern,
1515
TracePropagationTargets,
1616
} from '@sentry/core';
17+
import { logger } from '@sentry/node';
1718
import { ClientRequest, ClientRequestConstructorOptions, IncomingMessage, net as electronNet } from 'electron';
1819
import * as urlModule from 'url';
1920

@@ -200,6 +201,7 @@ function addRequestBreadcrumb(
200201
req: ClientRequest,
201202
res?: IncomingMessage,
202203
): void {
204+
const level = getBreadcrumbLogLevelFromHttpStatusCode(res?.statusCode);
203205
addBreadcrumb(
204206
{
205207
type: 'http',
@@ -209,14 +211,27 @@ function addRequestBreadcrumb(
209211
method: method,
210212
status_code: res?.statusCode,
211213
},
212-
level: getBreadcrumbLogLevelFromHttpStatusCode(res?.statusCode),
214+
level,
213215
},
214216
{
215217
event,
216218
request: req,
217219
response: res,
218220
},
219221
);
222+
223+
const attributes = { statusCode: res?.statusCode };
224+
225+
switch (level) {
226+
case 'error':
227+
logger.error(logger.fmt`Electron.net request failed: ${method} ${url}`, attributes);
228+
break;
229+
case 'warning':
230+
logger.warn(logger.fmt`Electron.net request warning: ${method} ${url}`, attributes);
231+
break;
232+
default:
233+
logger.info(logger.fmt`Electron.net request succeeded: ${method} ${url}`, attributes);
234+
}
220235
}
221236

222237
/**

test/e2e/test-apps/other/renderer-error/src/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
_experiments: { enableLogs: true },
1313
});
1414

15-
logger.trace('User clicked submit button', {
16-
buttonId: 'submit-form',
17-
formId: 'user-profile',
18-
timestamp: Date.now()
19-
});
15+
logger.trace('User clicked submit button', {
16+
buttonId: 'submit-form',
17+
formId: 'user-profile',
18+
timestamp: Date.now()
19+
});
2020

2121
// setTimeout(() => {
2222
// throw new Error('Some renderer error');

test/e2e/test-apps/other/renderer-error/test.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,27 @@ electronTestRunner(__dirname, async (ctx) => {
1111
[
1212
{
1313
type: 'log',
14-
item_count: 2,
14+
item_count: expect.any(Number),
1515
content_type: 'application/vnd.sentry.items.log+json',
1616
},
1717
{
18-
items: [
18+
items: expect.arrayContaining([
19+
{
20+
timestamp: expect.any(Number),
21+
level: 'info',
22+
body: 'electron.app.ready',
23+
trace_id: UUID_MATCHER,
24+
severity_number: 9,
25+
attributes: {
26+
'sentry.release': { value: 'javascript-logs@1.0.0', type: 'string' },
27+
'sentry.environment': { value: 'development', type: 'string' },
28+
'sentry.sdk.name': { value: 'sentry.javascript.electron', type: 'string' },
29+
'sentry.sdk.version': { value: SDK_VERSION, type: 'string' },
30+
'sentry.message.template': { value: 'electron.%s.%s', type: 'string' },
31+
'sentry.message.parameter.0': { value: 'app', type: 'string' },
32+
'sentry.message.parameter.1': { value: 'ready', type: 'string' },
33+
},
34+
},
1935
{
2036
timestamp: expect.any(Number),
2137
level: 'info',
@@ -47,7 +63,7 @@ electronTestRunner(__dirname, async (ctx) => {
4763
'sentry.sdk.version': { value: SDK_VERSION, type: 'string' },
4864
},
4965
},
50-
],
66+
]),
5167
},
5268
],
5369
],

0 commit comments

Comments
 (0)