@multiplayer-app/session-recorder-common 1.3.32 → 1.3.34

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.captureException = void 0;
3
+ exports.shouldCaptureException = exports.captureException = void 0;
4
4
  const api_1 = require("@opentelemetry/api");
5
5
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
6
6
  const set_resource_attributes_1 = require("./set-resource-attributes");
@@ -10,7 +10,7 @@ const set_resource_attributes_1 = require("./set-resource-attributes");
10
10
  * @returns {void}
11
11
  */
12
12
  const captureException = (error, errorInfo) => {
13
- if (!error) {
13
+ if (!error || !(0, exports.shouldCaptureException)(error)) {
14
14
  return;
15
15
  }
16
16
  const activeContext = api_1.context.active();
@@ -18,7 +18,7 @@ const captureException = (error, errorInfo) => {
18
18
  let isNewSpan = false;
19
19
  if (!span || !span.isRecording()) {
20
20
  span = api_1.trace.getTracer('exception').startSpan(error.name || 'Error', {
21
- attributes: Object.assign({ [semantic_conventions_1.ATTR_EXCEPTION_MESSAGE]: error.message, [semantic_conventions_1.ATTR_EXCEPTION_STACKTRACE]: error.stack, [semantic_conventions_1.ATTR_EXCEPTION_TYPE]: error.name }, (0, set_resource_attributes_1.getResourceAttributes)()),
21
+ attributes: Object.assign({ [semantic_conventions_1.ATTR_EXCEPTION_MESSAGE]: error.message, [semantic_conventions_1.ATTR_EXCEPTION_STACKTRACE]: error.stack, [semantic_conventions_1.ATTR_EXCEPTION_TYPE]: error.name }, (0, set_resource_attributes_1.getResourceAttributes)())
22
22
  });
23
23
  api_1.trace.setSpan(activeContext, span);
24
24
  isNewSpan = true;
@@ -27,7 +27,7 @@ const captureException = (error, errorInfo) => {
27
27
  span.setAttributes({
28
28
  [semantic_conventions_1.ATTR_EXCEPTION_MESSAGE]: error.message,
29
29
  [semantic_conventions_1.ATTR_EXCEPTION_STACKTRACE]: error.stack,
30
- [semantic_conventions_1.ATTR_EXCEPTION_TYPE]: error.name,
30
+ [semantic_conventions_1.ATTR_EXCEPTION_TYPE]: error.name
31
31
  });
32
32
  }
33
33
  if (errorInfo) {
@@ -38,11 +38,46 @@ const captureException = (error, errorInfo) => {
38
38
  span.recordException(error);
39
39
  span.setStatus({
40
40
  code: api_1.SpanStatusCode.ERROR,
41
- message: error.message,
41
+ message: error.message
42
42
  });
43
43
  if (isNewSpan) {
44
44
  span.end();
45
45
  }
46
46
  };
47
47
  exports.captureException = captureException;
48
+ /**
49
+ * Best-effort deduplication of exceptions that fire multiple times
50
+ * (e.g. framework handler + global handlers) within a short time window.
51
+ */
52
+ const exceptionDedupeWindowMs = 2000;
53
+ const recentExceptionFingerprints = new Map();
54
+ const shouldCaptureException = (error, _errorInfo) => {
55
+ if (!error)
56
+ return false;
57
+ const now = Date.now();
58
+ // Build a fingerprint that is stable enough across repeated emissions
59
+ // but not so broad that different errors collapse into one.
60
+ const keyParts = [];
61
+ keyParts.push(error.name || 'Error');
62
+ keyParts.push(error.message || '');
63
+ // First stack line tends to include file/line where it originated.
64
+ if (typeof error.stack === 'string') {
65
+ const firstFrame = error.stack.split('\n')[1] || '';
66
+ keyParts.push(firstFrame.trim());
67
+ }
68
+ const fingerprint = keyParts.join('|').slice(0, 500);
69
+ const lastSeen = recentExceptionFingerprints.get(fingerprint);
70
+ if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {
71
+ return false;
72
+ }
73
+ recentExceptionFingerprints.set(fingerprint, now);
74
+ // Cheap cleanup of old entries to avoid unbounded growth.
75
+ for (const [key, ts] of recentExceptionFingerprints) {
76
+ if (now - ts > exceptionDedupeWindowMs * 5) {
77
+ recentExceptionFingerprints.delete(key);
78
+ }
79
+ }
80
+ return true;
81
+ };
82
+ exports.shouldCaptureException = shouldCaptureException;
48
83
  //# sourceMappingURL=capture-exception.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capture-exception.js","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":";;;AAAA,4CAAmE;AACnE,8EAI4C;AAC5C,uEAAiE;AAEjE;;;;GAIG;AACI,MAAM,gBAAgB,GAAG,CAC9B,KAAY,EACZ,SAA+B,EAC/B,EAAE;IACF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAM;IACR,CAAC;IAED,MAAM,aAAa,GAAG,aAAO,CAAC,MAAM,EAAE,CAAA;IAEtC,IAAI,IAAI,GAAG,WAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IACvC,IAAI,SAAS,GAAG,KAAK,CAAA;IAErB,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACjC,IAAI,GAAG,WAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,SAAS,CAC3C,KAAK,CAAC,IAAI,IAAI,OAAO,EACrB;YACE,UAAU,kBACR,CAAC,6CAAsB,CAAC,EAAE,KAAK,CAAC,OAAO,EACvC,CAAC,gDAAyB,CAAC,EAAE,KAAK,CAAC,KAAK,EACxC,CAAC,0CAAmB,CAAC,EAAE,KAAK,CAAC,IAAI,IAC9B,IAAA,+CAAqB,GAAE,CAC3B;SACF,CACF,CAAA;QACD,WAAK,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;QAClC,SAAS,GAAG,IAAI,CAAA;IAClB,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,aAAa,CAAC;YACjB,CAAC,6CAAsB,CAAC,EAAE,KAAK,CAAC,OAAO;YACvC,CAAC,gDAAyB,CAAC,EAAE,KAAK,CAAC,KAAK;YACxC,CAAC,0CAAmB,CAAC,EAAE,KAAK,CAAC,IAAI;SAClC,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;YACjD,IAAI,CAAC,YAAY,CAAC,cAAc,GAAG,EAAE,EAAE,KAAK,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC3B,IAAI,CAAC,SAAS,CAAC;QACb,IAAI,EAAE,oBAAc,CAAC,KAAK;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAA;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC,GAAG,EAAE,CAAA;IACZ,CAAC;AACH,CAAC,CAAA;AAlDY,QAAA,gBAAgB,oBAkD5B","sourcesContent":["import { context, trace, SpanStatusCode } from '@opentelemetry/api'\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions'\nimport { getResourceAttributes } from './set-resource-attributes'\n\n/**\n * @description Add error to current span\n * @param {Error} error\n * @returns {void}\n */\nexport const captureException = (\n error: Error,\n errorInfo?: Record<string, any>,\n) => {\n if (!error) {\n return\n }\n\n const activeContext = context.active()\n\n let span = trace.getSpan(activeContext)\n let isNewSpan = false\n\n if (!span || !span.isRecording()) {\n span = trace.getTracer('exception').startSpan(\n error.name || 'Error',\n {\n attributes: {\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name,\n ...getResourceAttributes(),\n },\n },\n )\n trace.setSpan(activeContext, span)\n isNewSpan = true\n } else {\n span.setAttributes({\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name,\n })\n }\n\n if (errorInfo) {\n Object.entries(errorInfo).forEach(([key, value]) => {\n span.setAttribute(`error_info.${key}`, value)\n })\n }\n\n span.recordException(error)\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n })\n\n if (isNewSpan) {\n span.end()\n }\n}\n"]}
1
+ {"version":3,"file":"capture-exception.js","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, trace, SpanStatusCode } from '@opentelemetry/api'\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE\n} from '@opentelemetry/semantic-conventions'\nimport { getResourceAttributes } from './set-resource-attributes'\n\n/**\n * @description Add error to current span\n * @param {Error} error\n * @returns {void}\n */\nexport const captureException = (error: Error, errorInfo?: Record<string, any>) => {\n if (!error || !shouldCaptureException(error)) {\n return\n }\n\n const activeContext = context.active()\n\n let span = trace.getSpan(activeContext)\n let isNewSpan = false\n\n if (!span || !span.isRecording()) {\n span = trace.getTracer('exception').startSpan(error.name || 'Error', {\n attributes: {\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name,\n ...getResourceAttributes()\n }\n })\n trace.setSpan(activeContext, span)\n isNewSpan = true\n } else {\n span.setAttributes({\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name\n })\n }\n\n if (errorInfo) {\n Object.entries(errorInfo).forEach(([key, value]) => {\n span.setAttribute(`error_info.${key}`, value)\n })\n }\n\n span.recordException(error)\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message\n })\n\n if (isNewSpan) {\n span.end()\n }\n}\n\n/**\n * Best-effort deduplication of exceptions that fire multiple times\n * (e.g. framework handler + global handlers) within a short time window.\n */\n\nconst exceptionDedupeWindowMs = 2000\nconst recentExceptionFingerprints = new Map<string, number>()\n\nexport const shouldCaptureException = (error: Error, _errorInfo?: Record<string, any>): boolean => {\n if (!error) return false\n\n const now = Date.now()\n\n // Build a fingerprint that is stable enough across repeated emissions\n // but not so broad that different errors collapse into one.\n const keyParts: string[] = []\n keyParts.push(error.name || 'Error')\n keyParts.push(error.message || '')\n\n // First stack line tends to include file/line where it originated.\n if (typeof error.stack === 'string') {\n const firstFrame = error.stack.split('\\n')[1] || ''\n keyParts.push(firstFrame.trim())\n }\n\n const fingerprint = keyParts.join('|').slice(0, 500)\n\n const lastSeen = recentExceptionFingerprints.get(fingerprint)\n if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {\n return false\n }\n\n recentExceptionFingerprints.set(fingerprint, now)\n\n // Cheap cleanup of old entries to avoid unbounded growth.\n for (const [key, ts] of recentExceptionFingerprints) {\n if (now - ts > exceptionDedupeWindowMs * 5) {\n recentExceptionFingerprints.delete(key)\n }\n }\n\n return true\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/session-recorder-common",
3
- "version": "1.3.32",
3
+ "version": "1.3.34",
4
4
  "description": "Multiplayer Fullstack Session Recorder - opentelemetry",
5
5
  "author": {
6
6
  "name": "Multiplayer Software, Inc.",
@@ -2,7 +2,7 @@ import { context, trace, SpanStatusCode } from '@opentelemetry/api'
2
2
  import {
3
3
  ATTR_EXCEPTION_MESSAGE,
4
4
  ATTR_EXCEPTION_STACKTRACE,
5
- ATTR_EXCEPTION_TYPE,
5
+ ATTR_EXCEPTION_TYPE
6
6
  } from '@opentelemetry/semantic-conventions'
7
7
  import { getResourceAttributes } from './set-resource-attributes'
8
8
 
@@ -11,11 +11,8 @@ import { getResourceAttributes } from './set-resource-attributes'
11
11
  * @param {Error} error
12
12
  * @returns {void}
13
13
  */
14
- export const captureException = (
15
- error: Error,
16
- errorInfo?: Record<string, any>,
17
- ) => {
18
- if (!error) {
14
+ export const captureException = (error: Error, errorInfo?: Record<string, any>) => {
15
+ if (!error || !shouldCaptureException(error)) {
19
16
  return
20
17
  }
21
18
 
@@ -25,24 +22,21 @@ export const captureException = (
25
22
  let isNewSpan = false
26
23
 
27
24
  if (!span || !span.isRecording()) {
28
- span = trace.getTracer('exception').startSpan(
29
- error.name || 'Error',
30
- {
31
- attributes: {
32
- [ATTR_EXCEPTION_MESSAGE]: error.message,
33
- [ATTR_EXCEPTION_STACKTRACE]: error.stack,
34
- [ATTR_EXCEPTION_TYPE]: error.name,
35
- ...getResourceAttributes(),
36
- },
37
- },
38
- )
25
+ span = trace.getTracer('exception').startSpan(error.name || 'Error', {
26
+ attributes: {
27
+ [ATTR_EXCEPTION_MESSAGE]: error.message,
28
+ [ATTR_EXCEPTION_STACKTRACE]: error.stack,
29
+ [ATTR_EXCEPTION_TYPE]: error.name,
30
+ ...getResourceAttributes()
31
+ }
32
+ })
39
33
  trace.setSpan(activeContext, span)
40
34
  isNewSpan = true
41
35
  } else {
42
36
  span.setAttributes({
43
37
  [ATTR_EXCEPTION_MESSAGE]: error.message,
44
38
  [ATTR_EXCEPTION_STACKTRACE]: error.stack,
45
- [ATTR_EXCEPTION_TYPE]: error.name,
39
+ [ATTR_EXCEPTION_TYPE]: error.name
46
40
  })
47
41
  }
48
42
 
@@ -55,10 +49,54 @@ export const captureException = (
55
49
  span.recordException(error)
56
50
  span.setStatus({
57
51
  code: SpanStatusCode.ERROR,
58
- message: error.message,
52
+ message: error.message
59
53
  })
60
54
 
61
55
  if (isNewSpan) {
62
56
  span.end()
63
57
  }
64
58
  }
59
+
60
+ /**
61
+ * Best-effort deduplication of exceptions that fire multiple times
62
+ * (e.g. framework handler + global handlers) within a short time window.
63
+ */
64
+
65
+ const exceptionDedupeWindowMs = 2000
66
+ const recentExceptionFingerprints = new Map<string, number>()
67
+
68
+ export const shouldCaptureException = (error: Error, _errorInfo?: Record<string, any>): boolean => {
69
+ if (!error) return false
70
+
71
+ const now = Date.now()
72
+
73
+ // Build a fingerprint that is stable enough across repeated emissions
74
+ // but not so broad that different errors collapse into one.
75
+ const keyParts: string[] = []
76
+ keyParts.push(error.name || 'Error')
77
+ keyParts.push(error.message || '')
78
+
79
+ // First stack line tends to include file/line where it originated.
80
+ if (typeof error.stack === 'string') {
81
+ const firstFrame = error.stack.split('\n')[1] || ''
82
+ keyParts.push(firstFrame.trim())
83
+ }
84
+
85
+ const fingerprint = keyParts.join('|').slice(0, 500)
86
+
87
+ const lastSeen = recentExceptionFingerprints.get(fingerprint)
88
+ if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {
89
+ return false
90
+ }
91
+
92
+ recentExceptionFingerprints.set(fingerprint, now)
93
+
94
+ // Cheap cleanup of old entries to avoid unbounded growth.
95
+ for (const [key, ts] of recentExceptionFingerprints) {
96
+ if (now - ts > exceptionDedupeWindowMs * 5) {
97
+ recentExceptionFingerprints.delete(key)
98
+ }
99
+ }
100
+
101
+ return true
102
+ }