Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 41fc46d

Browse files
author
Michal Vlasák
committed
✨ Use CLS for sentry scope edit
1 parent fa12b3f commit 41fc46d

3 files changed

Lines changed: 83 additions & 14 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"dependencies": {
3333
"@types/express": "^4.16.0",
3434
"@types/pino": "^6.3.1",
35+
"cls-hooked": "^4.2.2",
3536
"lodash.foreach": "^4.5.0",
3637
"lodash.isempty": "^4.4.0",
3738
"lodash.isobject": "^3.0.2",

src/sentry.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { captureException, captureMessage, Severity, withScope } from '@sentry/node';
1+
import { captureException, captureMessage, Scope, Severity, withScope } from '@sentry/node';
2+
import { createNamespace } from 'cls-hooked';
23
import * as pino from 'pino';
34
import { Cosmas } from '.';
45
import { levels } from './levels';
56

7+
const clsNamespace = createNamespace('cosmas.sentryExtend');
8+
9+
type SentryCallback = (scope: Scope) => void;
10+
611
const reportToSentry = (obj: any) => {
712
if (!obj.stack) {
813
return captureMessage(obj.message || obj);
@@ -36,10 +41,27 @@ export const extendSentry = (logger: Cosmas, options: { sentry: string | true; s
3641
originalWrite.call(this, s);
3742
const obj = JSON.parse(s);
3843
if (obj.level < (options.sentryLevel || levels.warn)) return;
44+
const sentryCallback: SentryCallback | undefined = clsNamespace.get('sentryCallback');
3945
withScope((scope) => {
4046
scope.setLevel(PINO_TO_SENTRY[obj.level]);
4147
scope.setExtras(obj);
48+
if (sentryCallback) sentryCallback(scope);
4249
reportToSentry(obj);
4350
});
4451
};
52+
53+
logger.realHooks.logMethod = function (inputArgs, method) {
54+
// 2nd argument is Sentry options
55+
const sentryCallback: SentryCallback | undefined = inputArgs[1];
56+
// TODO: automatic type for 2nd log method arg
57+
58+
if (!sentryCallback) {
59+
return method.apply(this, inputArgs);
60+
}
61+
62+
clsNamespace.runAndReturn(() => {
63+
clsNamespace.set('sentryCallback', sentryCallback);
64+
return method.apply(this, inputArgs);
65+
});
66+
};
4567
};

src/tests/sentry-mocked.test.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Scope } from '@sentry/node';
12
import omit = require('omit-deep');
23
import { CosmasFactory } from '../index';
34
import { levels } from '../levels';
@@ -16,6 +17,9 @@ const withScope = jest.fn((fn) =>
1617
setLevel: (level: any) => {
1718
scope.level = level;
1819
},
20+
setTags: (val: any) => {
21+
scope.tags = val;
22+
},
1923
})
2024
);
2125

@@ -93,19 +97,19 @@ describe('sentry mocked', () => {
9397
`);
9498
expect(captureMessage.mock.results[0].value.scope.extras['cosmas.pkgVersion']).toBeDefined();
9599
expect(omit(captureMessage.mock.results[0].value, 'cosmas.pkgVersion')).toMatchInlineSnapshot(`
96-
Object {
97-
"data": "fatal",
98-
"scope": Object {
99-
"extras": Object {
100-
"level": 60,
101-
"message": "fatal",
102-
"severity": "CRITICAL",
103-
"time": "2018-03-06T13:30:36.000Z",
104-
},
105-
"level": "critical",
106-
},
107-
}
108-
`);
100+
Object {
101+
"data": "fatal",
102+
"scope": Object {
103+
"extras": Object {
104+
"level": 60,
105+
"message": "fatal",
106+
"severity": "CRITICAL",
107+
"time": "2018-03-06T13:30:36.000Z",
108+
},
109+
"level": "critical",
110+
},
111+
}
112+
`);
109113
Date.now = dateNow;
110114
});
111115

@@ -126,4 +130,46 @@ Object {
126130
},
127131
});
128132
});
133+
134+
test('can pass sentry tags', async () => {
135+
const dateNow = Date.now;
136+
Date.now = jest.fn(() => 1520343036000);
137+
await new Promise((resolve, reject) => {
138+
const logger = loggerFactory();
139+
extendSentry(logger, { sentry: 'DSN' });
140+
captureMessage.mockImplementation(createCapture(resolve));
141+
logger.fatal('sentryfatal', (scope: Scope) => {
142+
scope.setContext('dummyContext', { foo: 'bar' });
143+
scope.setTags({ first: 'firstTag' });
144+
scope.setExtras({ extra: 'extraValue' });
145+
});
146+
});
147+
expect(captureMessage).toHaveBeenCalledTimes(1);
148+
expect(captureException).not.toHaveBeenCalled();
149+
expect(captureMessage.mock.calls[0]).toMatchInlineSnapshot(`
150+
Array [
151+
"sentryfatal",
152+
]
153+
`);
154+
expect(omit(captureMessage.mock.results[0].value, 'cosmas.pkgVersion')).toMatchInlineSnapshot(`
155+
Object {
156+
"data": "sentryfatal",
157+
"scope": Object {
158+
"context": Object {
159+
"dummyContext": Object {
160+
"foo": "bar",
161+
},
162+
},
163+
"extras": Object {
164+
"extra": "extraValue",
165+
},
166+
"level": "critical",
167+
"tags": Object {
168+
"first": "firstTag",
169+
},
170+
},
171+
}
172+
`);
173+
Date.now = dateNow;
174+
});
129175
});

0 commit comments

Comments
 (0)