@devmoods/sentry 0.2.0 → 0.4.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 +172 -4
- package/dist/index.d.ts +18 -1
- package/dist/index.js +44 -0
- package/package.json +10 -9
- 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="1777360796652" clover="3.2.0">
|
|
3
|
+
<project timestamp="1777360796652" 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="
|
|
7
|
+
<line num="14" 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":
|
|
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":14,"column":13},"end":{"line":19,"column":2}}},"fnMap":{},"branchMap":{},"s":{"0":10},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":1,"seen":{"s:14:13:19: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-28T07:19:56.646Z
|
|
105
105
|
</div>
|
|
106
106
|
<script src="prettify.js"></script>
|
|
107
107
|
<script>
|
package/coverage/index.ts.html
CHANGED
|
@@ -185,7 +185,120 @@
|
|
|
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>
|
|
234
|
+
<a name='L169'></a><a href='#L169'>169</a>
|
|
235
|
+
<a name='L170'></a><a href='#L170'>170</a>
|
|
236
|
+
<a name='L171'></a><a href='#L171'>171</a>
|
|
237
|
+
<a name='L172'></a><a href='#L172'>172</a>
|
|
238
|
+
<a name='L173'></a><a href='#L173'>173</a>
|
|
239
|
+
<a name='L174'></a><a href='#L174'>174</a>
|
|
240
|
+
<a name='L175'></a><a href='#L175'>175</a>
|
|
241
|
+
<a name='L176'></a><a href='#L176'>176</a>
|
|
242
|
+
<a name='L177'></a><a href='#L177'>177</a>
|
|
243
|
+
<a name='L178'></a><a href='#L178'>178</a>
|
|
244
|
+
<a name='L179'></a><a href='#L179'>179</a></td><td class="line-coverage quiet"><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-yes">10x</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>
|
|
280
|
+
<span class="cline-any cline-neutral"> </span>
|
|
281
|
+
<span class="cline-any cline-neutral"> </span>
|
|
282
|
+
<span class="cline-any cline-neutral"> </span>
|
|
283
|
+
<span class="cline-any cline-neutral"> </span>
|
|
284
|
+
<span class="cline-any cline-neutral"> </span>
|
|
285
|
+
<span class="cline-any cline-neutral"> </span>
|
|
286
|
+
<span class="cline-any cline-neutral"> </span>
|
|
287
|
+
<span class="cline-any cline-neutral"> </span>
|
|
288
|
+
<span class="cline-any cline-neutral"> </span>
|
|
289
|
+
<span class="cline-any cline-neutral"> </span>
|
|
290
|
+
<span class="cline-any cline-neutral"> </span>
|
|
291
|
+
<span class="cline-any cline-neutral"> </span>
|
|
292
|
+
<span class="cline-any cline-neutral"> </span>
|
|
293
|
+
<span class="cline-any cline-neutral"> </span>
|
|
294
|
+
<span class="cline-any cline-neutral"> </span>
|
|
295
|
+
<span class="cline-any cline-neutral"> </span>
|
|
296
|
+
<span class="cline-any cline-neutral"> </span>
|
|
297
|
+
<span class="cline-any cline-neutral"> </span>
|
|
298
|
+
<span class="cline-any cline-neutral"> </span>
|
|
299
|
+
<span class="cline-any cline-neutral"> </span>
|
|
300
|
+
<span class="cline-any cline-neutral"> </span>
|
|
301
|
+
<span class="cline-any cline-neutral"> </span>
|
|
189
302
|
<span class="cline-any cline-neutral"> </span>
|
|
190
303
|
<span class="cline-any cline-neutral"> </span>
|
|
191
304
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -194,7 +307,6 @@
|
|
|
194
307
|
<span class="cline-any cline-neutral"> </span>
|
|
195
308
|
<span class="cline-any cline-neutral"> </span>
|
|
196
309
|
<span class="cline-any cline-neutral"> </span>
|
|
197
|
-
<span class="cline-any cline-yes">1x</span>
|
|
198
310
|
<span class="cline-any cline-neutral"> </span>
|
|
199
311
|
<span class="cline-any cline-neutral"> </span>
|
|
200
312
|
<span class="cline-any cline-neutral"> </span>
|
|
@@ -308,7 +420,11 @@
|
|
|
308
420
|
<span class="cline-any cline-neutral"> </span>
|
|
309
421
|
<span class="cline-any cline-neutral"> </span>
|
|
310
422
|
<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 {
|
|
423
|
+
import {
|
|
424
|
+
type LoggerAdapter,
|
|
425
|
+
type MetricAttributes,
|
|
426
|
+
type MetricsAdapter,
|
|
427
|
+
} from '@devmoods/observability';
|
|
312
428
|
import * as Sentry from '@sentry/node';
|
|
313
429
|
import { type ErrorRequestHandler } from 'express';
|
|
314
430
|
|
|
@@ -393,6 +509,58 @@ export function sentryErrorHandler(
|
|
|
393
509
|
}) as unknown as ErrorRequestHandler;
|
|
394
510
|
}
|
|
395
511
|
|
|
512
|
+
/**
|
|
513
|
+
* MetricsAdapter backed by `Sentry.metrics` (available in @sentry/node >=10.25.0).
|
|
514
|
+
* Requires `Sentry.init()` to have been called; calls are silently dropped when
|
|
515
|
+
* Sentry is not initialised, matching the behaviour of SentryLoggerAdapter.
|
|
516
|
+
*
|
|
517
|
+
* Instrument mapping:
|
|
518
|
+
* increment → Sentry.metrics.count (monotonically increasing counter)
|
|
519
|
+
* gauge → Sentry.metrics.gauge (current absolute value)
|
|
520
|
+
* distribution → Sentry.metrics.distribution (arbitrary value distribution)
|
|
521
|
+
* timing → Sentry.metrics.distribution (distribution with unit "millisecond")
|
|
522
|
+
*/
|
|
523
|
+
export class SentryMetricsAdapter implements MetricsAdapter {
|
|
524
|
+
increment(name: string, value = 1, attributes?: MetricAttributes): void {
|
|
525
|
+
/* v8 ignore next -- @preserve */
|
|
526
|
+
if (!Sentry.isInitialized()) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
Sentry.metrics.count(name, value, { attributes });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
gauge(name: string, value: number, attributes?: MetricAttributes): void {
|
|
533
|
+
/* v8 ignore next -- @preserve */
|
|
534
|
+
if (!Sentry.isInitialized()) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
Sentry.metrics.gauge(name, value, { attributes });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
distribution(
|
|
541
|
+
name: string,
|
|
542
|
+
value: number,
|
|
543
|
+
attributes?: MetricAttributes,
|
|
544
|
+
): void {
|
|
545
|
+
/* v8 ignore next -- @preserve */
|
|
546
|
+
if (!Sentry.isInitialized()) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
Sentry.metrics.distribution(name, value, { attributes });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
timing(name: string, value: number, attributes?: MetricAttributes): void {
|
|
553
|
+
/* v8 ignore next -- @preserve */
|
|
554
|
+
if (!Sentry.isInitialized()) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
Sentry.metrics.distribution(name, value, {
|
|
558
|
+
unit: 'millisecond',
|
|
559
|
+
attributes,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
396
564
|
export class SentryLoggerAdapter implements LoggerAdapter {
|
|
397
565
|
constructor() {}
|
|
398
566
|
|
|
@@ -436,7 +604,7 @@ export class SentryLoggerAdapter implements LoggerAdapter {
|
|
|
436
604
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
437
605
|
Code coverage generated by
|
|
438
606
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
439
|
-
at 2026-04-
|
|
607
|
+
at 2026-04-28T07:19:56.646Z
|
|
440
608
|
</div>
|
|
441
609
|
<script src="prettify.js"></script>
|
|
442
610
|
<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.4.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
|
-
"build": "pnpm run clean &&
|
|
21
|
+
"build": "pnpm run clean && tsc",
|
|
21
22
|
"prepublishOnly": "pnpm run clean && pnpm run build"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@devmoods/config": "0.
|
|
25
|
-
"@devmoods/observability": "0.
|
|
26
|
-
"@sentry/node": "^10.
|
|
27
|
-
"@sentry/node-native": "^10.
|
|
28
|
-
"@sentry/profiling-node": "^10.
|
|
25
|
+
"@devmoods/config": "0.3.0",
|
|
26
|
+
"@devmoods/observability": "0.4.0",
|
|
27
|
+
"@sentry/node": "^10.50.0",
|
|
28
|
+
"@sentry/node-native": "^10.50.0",
|
|
29
|
+
"@sentry/profiling-node": "^10.50.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": "97c5d5f84397815a726bf44ae3f401f24c852fc6"
|
|
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
|
|