@devmoods/sentry 0.2.0 → 0.3.0
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/coverage/clover.xml +3 -3
- package/coverage/coverage-final.json +1 -1
- package/coverage/index.html +1 -1
- package/coverage/index.ts.html +139 -4
- package/dist/index.d.ts +18 -1
- package/dist/index.js +44 -0
- package/package.json +9 -8
- package/src/__tests__/index.spec.ts +138 -0
- package/src/index.ts +57 -1
package/coverage/clover.xml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<coverage generated="
|
|
3
|
-
<project timestamp="
|
|
2
|
+
<coverage generated="1776355785825" clover="3.2.0">
|
|
3
|
+
<project timestamp="1776355785825" name="All files">
|
|
4
4
|
<metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0" elements="1" coveredelements="1" complexity="0" loc="1" ncloc="1" packages="1" files="1" classes="1"/>
|
|
5
5
|
<file name="index.ts" path="/Users/hanse/dev/github.com/devmoods/express-extras/packages/@devmoods/sentry/src/index.ts">
|
|
6
6
|
<metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
|
|
7
|
-
<line num="10" count="
|
|
7
|
+
<line num="10" count="10" type="stmt"/>
|
|
8
8
|
</file>
|
|
9
9
|
</project>
|
|
10
10
|
</coverage>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"/Users/hanse/dev/github.com/devmoods/express-extras/packages/@devmoods/sentry/src/index.ts": {"path":"/Users/hanse/dev/github.com/devmoods/express-extras/packages/@devmoods/sentry/src/index.ts","statementMap":{"0":{"start":{"line":10,"column":13},"end":{"line":15,"column":2}}},"fnMap":{},"branchMap":{},"s":{"0":
|
|
1
|
+
{"/Users/hanse/dev/github.com/devmoods/express-extras/packages/@devmoods/sentry/src/index.ts": {"path":"/Users/hanse/dev/github.com/devmoods/express-extras/packages/@devmoods/sentry/src/index.ts","statementMap":{"0":{"start":{"line":10,"column":13},"end":{"line":15,"column":2}}},"fnMap":{},"branchMap":{},"s":{"0":10},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":1,"seen":{"s:10:13:15:2":0}}}
|
|
2
2
|
}
|
package/coverage/index.html
CHANGED
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
102
102
|
Code coverage generated by
|
|
103
103
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
104
|
-
at 2026-04-
|
|
104
|
+
at 2026-04-16T16:09:45.820Z
|
|
105
105
|
</div>
|
|
106
106
|
<script src="prettify.js"></script>
|
|
107
107
|
<script>
|
package/coverage/index.ts.html
CHANGED
|
@@ -185,7 +185,98 @@
|
|
|
185
185
|
<a name='L120'></a><a href='#L120'>120</a>
|
|
186
186
|
<a name='L121'></a><a href='#L121'>121</a>
|
|
187
187
|
<a name='L122'></a><a href='#L122'>122</a>
|
|
188
|
-
<a name='L123'></a><a href='#L123'>123</a
|
|
188
|
+
<a name='L123'></a><a href='#L123'>123</a>
|
|
189
|
+
<a name='L124'></a><a href='#L124'>124</a>
|
|
190
|
+
<a name='L125'></a><a href='#L125'>125</a>
|
|
191
|
+
<a name='L126'></a><a href='#L126'>126</a>
|
|
192
|
+
<a name='L127'></a><a href='#L127'>127</a>
|
|
193
|
+
<a name='L128'></a><a href='#L128'>128</a>
|
|
194
|
+
<a name='L129'></a><a href='#L129'>129</a>
|
|
195
|
+
<a name='L130'></a><a href='#L130'>130</a>
|
|
196
|
+
<a name='L131'></a><a href='#L131'>131</a>
|
|
197
|
+
<a name='L132'></a><a href='#L132'>132</a>
|
|
198
|
+
<a name='L133'></a><a href='#L133'>133</a>
|
|
199
|
+
<a name='L134'></a><a href='#L134'>134</a>
|
|
200
|
+
<a name='L135'></a><a href='#L135'>135</a>
|
|
201
|
+
<a name='L136'></a><a href='#L136'>136</a>
|
|
202
|
+
<a name='L137'></a><a href='#L137'>137</a>
|
|
203
|
+
<a name='L138'></a><a href='#L138'>138</a>
|
|
204
|
+
<a name='L139'></a><a href='#L139'>139</a>
|
|
205
|
+
<a name='L140'></a><a href='#L140'>140</a>
|
|
206
|
+
<a name='L141'></a><a href='#L141'>141</a>
|
|
207
|
+
<a name='L142'></a><a href='#L142'>142</a>
|
|
208
|
+
<a name='L143'></a><a href='#L143'>143</a>
|
|
209
|
+
<a name='L144'></a><a href='#L144'>144</a>
|
|
210
|
+
<a name='L145'></a><a href='#L145'>145</a>
|
|
211
|
+
<a name='L146'></a><a href='#L146'>146</a>
|
|
212
|
+
<a name='L147'></a><a href='#L147'>147</a>
|
|
213
|
+
<a name='L148'></a><a href='#L148'>148</a>
|
|
214
|
+
<a name='L149'></a><a href='#L149'>149</a>
|
|
215
|
+
<a name='L150'></a><a href='#L150'>150</a>
|
|
216
|
+
<a name='L151'></a><a href='#L151'>151</a>
|
|
217
|
+
<a name='L152'></a><a href='#L152'>152</a>
|
|
218
|
+
<a name='L153'></a><a href='#L153'>153</a>
|
|
219
|
+
<a name='L154'></a><a href='#L154'>154</a>
|
|
220
|
+
<a name='L155'></a><a href='#L155'>155</a>
|
|
221
|
+
<a name='L156'></a><a href='#L156'>156</a>
|
|
222
|
+
<a name='L157'></a><a href='#L157'>157</a>
|
|
223
|
+
<a name='L158'></a><a href='#L158'>158</a>
|
|
224
|
+
<a name='L159'></a><a href='#L159'>159</a>
|
|
225
|
+
<a name='L160'></a><a href='#L160'>160</a>
|
|
226
|
+
<a name='L161'></a><a href='#L161'>161</a>
|
|
227
|
+
<a name='L162'></a><a href='#L162'>162</a>
|
|
228
|
+
<a name='L163'></a><a href='#L163'>163</a>
|
|
229
|
+
<a name='L164'></a><a href='#L164'>164</a>
|
|
230
|
+
<a name='L165'></a><a href='#L165'>165</a>
|
|
231
|
+
<a name='L166'></a><a href='#L166'>166</a>
|
|
232
|
+
<a name='L167'></a><a href='#L167'>167</a>
|
|
233
|
+
<a name='L168'></a><a href='#L168'>168</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
|
234
|
+
<span class="cline-any cline-neutral"> </span>
|
|
235
|
+
<span class="cline-any cline-neutral"> </span>
|
|
236
|
+
<span class="cline-any cline-neutral"> </span>
|
|
237
|
+
<span class="cline-any cline-neutral"> </span>
|
|
238
|
+
<span class="cline-any cline-neutral"> </span>
|
|
239
|
+
<span class="cline-any cline-neutral"> </span>
|
|
240
|
+
<span class="cline-any cline-neutral"> </span>
|
|
241
|
+
<span class="cline-any cline-neutral"> </span>
|
|
242
|
+
<span class="cline-any cline-yes">10x</span>
|
|
243
|
+
<span class="cline-any cline-neutral"> </span>
|
|
244
|
+
<span class="cline-any cline-neutral"> </span>
|
|
245
|
+
<span class="cline-any cline-neutral"> </span>
|
|
246
|
+
<span class="cline-any cline-neutral"> </span>
|
|
247
|
+
<span class="cline-any cline-neutral"> </span>
|
|
248
|
+
<span class="cline-any cline-neutral"> </span>
|
|
249
|
+
<span class="cline-any cline-neutral"> </span>
|
|
250
|
+
<span class="cline-any cline-neutral"> </span>
|
|
251
|
+
<span class="cline-any cline-neutral"> </span>
|
|
252
|
+
<span class="cline-any cline-neutral"> </span>
|
|
253
|
+
<span class="cline-any cline-neutral"> </span>
|
|
254
|
+
<span class="cline-any cline-neutral"> </span>
|
|
255
|
+
<span class="cline-any cline-neutral"> </span>
|
|
256
|
+
<span class="cline-any cline-neutral"> </span>
|
|
257
|
+
<span class="cline-any cline-neutral"> </span>
|
|
258
|
+
<span class="cline-any cline-neutral"> </span>
|
|
259
|
+
<span class="cline-any cline-neutral"> </span>
|
|
260
|
+
<span class="cline-any cline-neutral"> </span>
|
|
261
|
+
<span class="cline-any cline-neutral"> </span>
|
|
262
|
+
<span class="cline-any cline-neutral"> </span>
|
|
263
|
+
<span class="cline-any cline-neutral"> </span>
|
|
264
|
+
<span class="cline-any cline-neutral"> </span>
|
|
265
|
+
<span class="cline-any cline-neutral"> </span>
|
|
266
|
+
<span class="cline-any cline-neutral"> </span>
|
|
267
|
+
<span class="cline-any cline-neutral"> </span>
|
|
268
|
+
<span class="cline-any cline-neutral"> </span>
|
|
269
|
+
<span class="cline-any cline-neutral"> </span>
|
|
270
|
+
<span class="cline-any cline-neutral"> </span>
|
|
271
|
+
<span class="cline-any cline-neutral"> </span>
|
|
272
|
+
<span class="cline-any cline-neutral"> </span>
|
|
273
|
+
<span class="cline-any cline-neutral"> </span>
|
|
274
|
+
<span class="cline-any cline-neutral"> </span>
|
|
275
|
+
<span class="cline-any cline-neutral"> </span>
|
|
276
|
+
<span class="cline-any cline-neutral"> </span>
|
|
277
|
+
<span class="cline-any cline-neutral"> </span>
|
|
278
|
+
<span class="cline-any cline-neutral"> </span>
|
|
279
|
+
<span class="cline-any cline-neutral"> </span>
|
|
189
280
|
<span class="cline-any cline-neutral"> </span>
|
|
190
281
|
<span class="cline-any cline-neutral"> </span>
|
|
191
282
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -194,7 +285,6 @@
|
|
|
194
285
|
<span class="cline-any cline-neutral"> </span>
|
|
195
286
|
<span class="cline-any cline-neutral"> </span>
|
|
196
287
|
<span class="cline-any cline-neutral"> </span>
|
|
197
|
-
<span class="cline-any cline-yes">1x</span>
|
|
198
288
|
<span class="cline-any cline-neutral"> </span>
|
|
199
289
|
<span class="cline-any cline-neutral"> </span>
|
|
200
290
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -308,7 +398,7 @@
|
|
|
308
398
|
<span class="cline-any cline-neutral"> </span>
|
|
309
399
|
<span class="cline-any cline-neutral"> </span>
|
|
310
400
|
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { config, define, t, type ConfigFromDefs } from '@devmoods/config';
|
|
311
|
-
import { type LoggerAdapter } from '@devmoods/observability';
|
|
401
|
+
import { type LoggerAdapter, type MetricAttributes, type MetricsAdapter } from '@devmoods/observability';
|
|
312
402
|
import * as Sentry from '@sentry/node';
|
|
313
403
|
import { type ErrorRequestHandler } from 'express';
|
|
314
404
|
|
|
@@ -393,6 +483,51 @@ export function sentryErrorHandler(
|
|
|
393
483
|
}) as unknown as ErrorRequestHandler;
|
|
394
484
|
}
|
|
395
485
|
|
|
486
|
+
/**
|
|
487
|
+
* MetricsAdapter backed by `Sentry.metrics` (available in @sentry/node >=10.25.0).
|
|
488
|
+
* Requires `Sentry.init()` to have been called; calls are silently dropped when
|
|
489
|
+
* Sentry is not initialised, matching the behaviour of SentryLoggerAdapter.
|
|
490
|
+
*
|
|
491
|
+
* Instrument mapping:
|
|
492
|
+
* increment → Sentry.metrics.count (monotonically increasing counter)
|
|
493
|
+
* gauge → Sentry.metrics.gauge (current absolute value)
|
|
494
|
+
* distribution → Sentry.metrics.distribution (arbitrary value distribution)
|
|
495
|
+
* timing → Sentry.metrics.distribution (distribution with unit "millisecond")
|
|
496
|
+
*/
|
|
497
|
+
export class SentryMetricsAdapter implements MetricsAdapter {
|
|
498
|
+
increment(name: string, value = 1, attributes?: MetricAttributes): void {
|
|
499
|
+
/* v8 ignore next -- @preserve */
|
|
500
|
+
if (!Sentry.isInitialized()) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
Sentry.metrics.count(name, value, { attributes });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
gauge(name: string, value: number, attributes?: MetricAttributes): void {
|
|
507
|
+
/* v8 ignore next -- @preserve */
|
|
508
|
+
if (!Sentry.isInitialized()) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
Sentry.metrics.gauge(name, value, { attributes });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
distribution(name: string, value: number, attributes?: MetricAttributes): void {
|
|
515
|
+
/* v8 ignore next -- @preserve */
|
|
516
|
+
if (!Sentry.isInitialized()) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
Sentry.metrics.distribution(name, value, { attributes });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
timing(name: string, value: number, attributes?: MetricAttributes): void {
|
|
523
|
+
/* v8 ignore next -- @preserve */
|
|
524
|
+
if (!Sentry.isInitialized()) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
Sentry.metrics.distribution(name, value, { unit: 'millisecond', attributes });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
396
531
|
export class SentryLoggerAdapter implements LoggerAdapter {
|
|
397
532
|
constructor() {}
|
|
398
533
|
|
|
@@ -436,7 +571,7 @@ export class SentryLoggerAdapter implements LoggerAdapter {
|
|
|
436
571
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
437
572
|
Code coverage generated by
|
|
438
573
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
439
|
-
at 2026-04-
|
|
574
|
+
at 2026-04-16T16:09:45.820Z
|
|
440
575
|
</div>
|
|
441
576
|
<script src="prettify.js"></script>
|
|
442
577
|
<script>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type LoggerAdapter } from '@devmoods/observability';
|
|
1
|
+
import { type LoggerAdapter, type MetricAttributes, type MetricsAdapter } from '@devmoods/observability';
|
|
2
2
|
import * as Sentry from '@sentry/node';
|
|
3
3
|
import { type ErrorRequestHandler } from 'express';
|
|
4
4
|
declare module '@devmoods/config' {
|
|
@@ -18,6 +18,23 @@ export declare function initSentry(options?: SentryOptions): Promise<void>;
|
|
|
18
18
|
export declare function closeSentry(): Promise<void>;
|
|
19
19
|
export declare function sentryOptions(options?: SentryOptions): Promise<SentryOptions>;
|
|
20
20
|
export declare function sentryErrorHandler(options?: Sentry.NodeOptions): ErrorRequestHandler;
|
|
21
|
+
/**
|
|
22
|
+
* MetricsAdapter backed by `Sentry.metrics` (available in @sentry/node >=10.25.0).
|
|
23
|
+
* Requires `Sentry.init()` to have been called; calls are silently dropped when
|
|
24
|
+
* Sentry is not initialised, matching the behaviour of SentryLoggerAdapter.
|
|
25
|
+
*
|
|
26
|
+
* Instrument mapping:
|
|
27
|
+
* increment → Sentry.metrics.count (monotonically increasing counter)
|
|
28
|
+
* gauge → Sentry.metrics.gauge (current absolute value)
|
|
29
|
+
* distribution → Sentry.metrics.distribution (arbitrary value distribution)
|
|
30
|
+
* timing → Sentry.metrics.distribution (distribution with unit "millisecond")
|
|
31
|
+
*/
|
|
32
|
+
export declare class SentryMetricsAdapter implements MetricsAdapter {
|
|
33
|
+
increment(name: string, value?: number, attributes?: MetricAttributes): void;
|
|
34
|
+
gauge(name: string, value: number, attributes?: MetricAttributes): void;
|
|
35
|
+
distribution(name: string, value: number, attributes?: MetricAttributes): void;
|
|
36
|
+
timing(name: string, value: number, attributes?: MetricAttributes): void;
|
|
37
|
+
}
|
|
21
38
|
export declare class SentryLoggerAdapter implements LoggerAdapter {
|
|
22
39
|
constructor();
|
|
23
40
|
captureError(error: Error | null, attributes?: Record<string, any>): void;
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,50 @@ export function sentryErrorHandler(options = {}) {
|
|
|
61
61
|
...options,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* MetricsAdapter backed by `Sentry.metrics` (available in @sentry/node >=10.25.0).
|
|
66
|
+
* Requires `Sentry.init()` to have been called; calls are silently dropped when
|
|
67
|
+
* Sentry is not initialised, matching the behaviour of SentryLoggerAdapter.
|
|
68
|
+
*
|
|
69
|
+
* Instrument mapping:
|
|
70
|
+
* increment → Sentry.metrics.count (monotonically increasing counter)
|
|
71
|
+
* gauge → Sentry.metrics.gauge (current absolute value)
|
|
72
|
+
* distribution → Sentry.metrics.distribution (arbitrary value distribution)
|
|
73
|
+
* timing → Sentry.metrics.distribution (distribution with unit "millisecond")
|
|
74
|
+
*/
|
|
75
|
+
export class SentryMetricsAdapter {
|
|
76
|
+
increment(name, value = 1, attributes) {
|
|
77
|
+
/* v8 ignore next -- @preserve */
|
|
78
|
+
if (!Sentry.isInitialized()) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
Sentry.metrics.count(name, value, { attributes });
|
|
82
|
+
}
|
|
83
|
+
gauge(name, value, attributes) {
|
|
84
|
+
/* v8 ignore next -- @preserve */
|
|
85
|
+
if (!Sentry.isInitialized()) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
Sentry.metrics.gauge(name, value, { attributes });
|
|
89
|
+
}
|
|
90
|
+
distribution(name, value, attributes) {
|
|
91
|
+
/* v8 ignore next -- @preserve */
|
|
92
|
+
if (!Sentry.isInitialized()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
Sentry.metrics.distribution(name, value, { attributes });
|
|
96
|
+
}
|
|
97
|
+
timing(name, value, attributes) {
|
|
98
|
+
/* v8 ignore next -- @preserve */
|
|
99
|
+
if (!Sentry.isInitialized()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
Sentry.metrics.distribution(name, value, {
|
|
103
|
+
unit: 'millisecond',
|
|
104
|
+
attributes,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
64
108
|
export class SentryLoggerAdapter {
|
|
65
109
|
constructor() { }
|
|
66
110
|
captureError(error, attributes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/sentry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,22 +14,23 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"clean": "rm -rf dist",
|
|
17
|
-
"lint": "
|
|
17
|
+
"lint": "oxlint .",
|
|
18
|
+
"format": "oxfmt .",
|
|
18
19
|
"test": "vitest",
|
|
19
20
|
"test:coverage": "pnpm run test --coverage",
|
|
20
21
|
"build": "pnpm run clean && tsgo",
|
|
21
22
|
"prepublishOnly": "pnpm run clean && pnpm run build"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@devmoods/config": "0.2.
|
|
25
|
-
"@devmoods/observability": "0.
|
|
26
|
-
"@sentry/node": "^10.
|
|
27
|
-
"@sentry/node-native": "^10.
|
|
28
|
-
"@sentry/profiling-node": "^10.
|
|
25
|
+
"@devmoods/config": "0.2.4",
|
|
26
|
+
"@devmoods/observability": "0.3.0",
|
|
27
|
+
"@sentry/node": "^10.49.0",
|
|
28
|
+
"@sentry/node-native": "^10.49.0",
|
|
29
|
+
"@sentry/profiling-node": "^10.49.0",
|
|
29
30
|
"express": "^4.21.2"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"express": ">=4.21.2 <5.0.0"
|
|
33
34
|
},
|
|
34
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "2c4100d80823bcf16bd962b4b8a26bd078522dfc"
|
|
35
36
|
}
|
|
@@ -2,6 +2,38 @@ import { overrideConfig } from '@devmoods/config';
|
|
|
2
2
|
|
|
3
3
|
import { sentryOptions } from '../index.js';
|
|
4
4
|
|
|
5
|
+
async function loadModuleWithSentryMock(initialized = true) {
|
|
6
|
+
vi.resetModules();
|
|
7
|
+
|
|
8
|
+
const sentryMock = {
|
|
9
|
+
init: vi.fn(),
|
|
10
|
+
close: vi.fn(),
|
|
11
|
+
isInitialized: vi.fn(() => initialized),
|
|
12
|
+
expressErrorHandler: vi.fn(() => vi.fn()),
|
|
13
|
+
captureException: vi.fn(),
|
|
14
|
+
logger: {
|
|
15
|
+
trace: vi.fn(),
|
|
16
|
+
debug: vi.fn(),
|
|
17
|
+
info: vi.fn(),
|
|
18
|
+
warn: vi.fn(),
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
fatal: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
metrics: {
|
|
23
|
+
count: vi.fn(),
|
|
24
|
+
gauge: vi.fn(),
|
|
25
|
+
distribution: vi.fn(),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
vi.doMock('@sentry/node', () => sentryMock);
|
|
30
|
+
|
|
31
|
+
await import('../setupTests.js');
|
|
32
|
+
const module = await import('../index.js');
|
|
33
|
+
|
|
34
|
+
return { ...module, Sentry: sentryMock };
|
|
35
|
+
}
|
|
36
|
+
|
|
5
37
|
test('sentryOptions', async () => {
|
|
6
38
|
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
7
39
|
|
|
@@ -26,3 +58,109 @@ test('sentryOptions', async () => {
|
|
|
26
58
|
|
|
27
59
|
reset();
|
|
28
60
|
});
|
|
61
|
+
|
|
62
|
+
describe('SentryMetricsAdapter', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.restoreAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('increment calls Sentry.metrics.count with default value of 1', async () => {
|
|
68
|
+
const { Sentry, SentryMetricsAdapter } = await loadModuleWithSentryMock();
|
|
69
|
+
const adapter = new SentryMetricsAdapter();
|
|
70
|
+
|
|
71
|
+
adapter.increment('req.count');
|
|
72
|
+
|
|
73
|
+
expect(Sentry.metrics.count).toHaveBeenCalledWith('req.count', 1, {
|
|
74
|
+
attributes: undefined,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('increment passes explicit value and attributes', async () => {
|
|
79
|
+
const { Sentry, SentryMetricsAdapter } = await loadModuleWithSentryMock();
|
|
80
|
+
const adapter = new SentryMetricsAdapter();
|
|
81
|
+
|
|
82
|
+
adapter.increment('req.count', 5, { status: '200' });
|
|
83
|
+
|
|
84
|
+
expect(Sentry.metrics.count).toHaveBeenCalledWith('req.count', 5, {
|
|
85
|
+
attributes: { status: '200' },
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('increment is a no-op when Sentry is not initialised', async () => {
|
|
90
|
+
const { Sentry, SentryMetricsAdapter } =
|
|
91
|
+
await loadModuleWithSentryMock(false);
|
|
92
|
+
const adapter = new SentryMetricsAdapter();
|
|
93
|
+
|
|
94
|
+
adapter.increment('req.count');
|
|
95
|
+
|
|
96
|
+
expect(Sentry.metrics.count).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('gauge calls Sentry.metrics.gauge', async () => {
|
|
100
|
+
const { Sentry, SentryMetricsAdapter } = await loadModuleWithSentryMock();
|
|
101
|
+
const adapter = new SentryMetricsAdapter();
|
|
102
|
+
|
|
103
|
+
adapter.gauge('mem.usage', 512, { host: 'web-1' });
|
|
104
|
+
|
|
105
|
+
expect(Sentry.metrics.gauge).toHaveBeenCalledWith('mem.usage', 512, {
|
|
106
|
+
attributes: { host: 'web-1' },
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('gauge is a no-op when Sentry is not initialised', async () => {
|
|
111
|
+
const { Sentry, SentryMetricsAdapter } =
|
|
112
|
+
await loadModuleWithSentryMock(false);
|
|
113
|
+
const adapter = new SentryMetricsAdapter();
|
|
114
|
+
|
|
115
|
+
adapter.gauge('mem.usage', 512);
|
|
116
|
+
|
|
117
|
+
expect(Sentry.metrics.gauge).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('distribution calls Sentry.metrics.distribution', async () => {
|
|
121
|
+
const { Sentry, SentryMetricsAdapter } = await loadModuleWithSentryMock();
|
|
122
|
+
const adapter = new SentryMetricsAdapter();
|
|
123
|
+
|
|
124
|
+
adapter.distribution('payload.size', 2048, { route: '/upload' });
|
|
125
|
+
|
|
126
|
+
expect(Sentry.metrics.distribution).toHaveBeenCalledWith(
|
|
127
|
+
'payload.size',
|
|
128
|
+
2048,
|
|
129
|
+
{
|
|
130
|
+
attributes: { route: '/upload' },
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('distribution is a no-op when Sentry is not initialised', async () => {
|
|
136
|
+
const { Sentry, SentryMetricsAdapter } =
|
|
137
|
+
await loadModuleWithSentryMock(false);
|
|
138
|
+
const adapter = new SentryMetricsAdapter();
|
|
139
|
+
|
|
140
|
+
adapter.distribution('payload.size', 2048);
|
|
141
|
+
|
|
142
|
+
expect(Sentry.metrics.distribution).not.toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('timing calls Sentry.metrics.distribution with unit millisecond', async () => {
|
|
146
|
+
const { Sentry, SentryMetricsAdapter } = await loadModuleWithSentryMock();
|
|
147
|
+
const adapter = new SentryMetricsAdapter();
|
|
148
|
+
|
|
149
|
+
adapter.timing('db.query', 37, { table: 'users' });
|
|
150
|
+
|
|
151
|
+
expect(Sentry.metrics.distribution).toHaveBeenCalledWith('db.query', 37, {
|
|
152
|
+
unit: 'millisecond',
|
|
153
|
+
attributes: { table: 'users' },
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('timing is a no-op when Sentry is not initialised', async () => {
|
|
158
|
+
const { Sentry, SentryMetricsAdapter } =
|
|
159
|
+
await loadModuleWithSentryMock(false);
|
|
160
|
+
const adapter = new SentryMetricsAdapter();
|
|
161
|
+
|
|
162
|
+
adapter.timing('db.query', 37);
|
|
163
|
+
|
|
164
|
+
expect(Sentry.metrics.distribution).not.toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { config, define, t, type ConfigFromDefs } from '@devmoods/config';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type LoggerAdapter,
|
|
4
|
+
type MetricAttributes,
|
|
5
|
+
type MetricsAdapter,
|
|
6
|
+
} from '@devmoods/observability';
|
|
3
7
|
import * as Sentry from '@sentry/node';
|
|
4
8
|
import { type ErrorRequestHandler } from 'express';
|
|
5
9
|
|
|
@@ -84,6 +88,58 @@ export function sentryErrorHandler(
|
|
|
84
88
|
}) as unknown as ErrorRequestHandler;
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/**
|
|
92
|
+
* MetricsAdapter backed by `Sentry.metrics` (available in @sentry/node >=10.25.0).
|
|
93
|
+
* Requires `Sentry.init()` to have been called; calls are silently dropped when
|
|
94
|
+
* Sentry is not initialised, matching the behaviour of SentryLoggerAdapter.
|
|
95
|
+
*
|
|
96
|
+
* Instrument mapping:
|
|
97
|
+
* increment → Sentry.metrics.count (monotonically increasing counter)
|
|
98
|
+
* gauge → Sentry.metrics.gauge (current absolute value)
|
|
99
|
+
* distribution → Sentry.metrics.distribution (arbitrary value distribution)
|
|
100
|
+
* timing → Sentry.metrics.distribution (distribution with unit "millisecond")
|
|
101
|
+
*/
|
|
102
|
+
export class SentryMetricsAdapter implements MetricsAdapter {
|
|
103
|
+
increment(name: string, value = 1, attributes?: MetricAttributes): void {
|
|
104
|
+
/* v8 ignore next -- @preserve */
|
|
105
|
+
if (!Sentry.isInitialized()) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
Sentry.metrics.count(name, value, { attributes });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
gauge(name: string, value: number, attributes?: MetricAttributes): void {
|
|
112
|
+
/* v8 ignore next -- @preserve */
|
|
113
|
+
if (!Sentry.isInitialized()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
Sentry.metrics.gauge(name, value, { attributes });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
distribution(
|
|
120
|
+
name: string,
|
|
121
|
+
value: number,
|
|
122
|
+
attributes?: MetricAttributes,
|
|
123
|
+
): void {
|
|
124
|
+
/* v8 ignore next -- @preserve */
|
|
125
|
+
if (!Sentry.isInitialized()) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
Sentry.metrics.distribution(name, value, { attributes });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
timing(name: string, value: number, attributes?: MetricAttributes): void {
|
|
132
|
+
/* v8 ignore next -- @preserve */
|
|
133
|
+
if (!Sentry.isInitialized()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
Sentry.metrics.distribution(name, value, {
|
|
137
|
+
unit: 'millisecond',
|
|
138
|
+
attributes,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
87
143
|
export class SentryLoggerAdapter implements LoggerAdapter {
|
|
88
144
|
constructor() {}
|
|
89
145
|
|