rollbar 2.26.3 → 3.0.0-alpha.1

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.
Files changed (186) hide show
  1. package/.cursor/rules/guidelines.mdc +154 -0
  2. package/.github/workflows/ci.yml +32 -12
  3. package/.lgtm.yml +7 -7
  4. package/.prettierignore +18 -0
  5. package/.vscode/settings.json +39 -0
  6. package/CHANGELOG.md +121 -35
  7. package/CLAUDE.md +201 -0
  8. package/Gruntfile.js +101 -48
  9. package/Makefile +3 -3
  10. package/README.md +2 -4
  11. package/SECURITY.md +5 -0
  12. package/babel.config.json +9 -0
  13. package/bower.json +1 -3
  14. package/codex.md +148 -0
  15. package/defaults.js +17 -5
  16. package/dist/plugins/jquery.min.js +1 -1
  17. package/dist/rollbar.js +18748 -5375
  18. package/dist/rollbar.js.map +1 -1
  19. package/dist/rollbar.min.js +2 -1
  20. package/dist/rollbar.min.js.LICENSE.txt +1 -0
  21. package/dist/rollbar.min.js.map +1 -1
  22. package/dist/rollbar.named-amd.js +19368 -6000
  23. package/dist/rollbar.named-amd.js.map +1 -1
  24. package/dist/rollbar.named-amd.min.js +3 -1
  25. package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
  26. package/dist/rollbar.named-amd.min.js.map +1 -1
  27. package/dist/rollbar.noconflict.umd.js +18749 -5380
  28. package/dist/rollbar.noconflict.umd.js.map +1 -1
  29. package/dist/rollbar.noconflict.umd.min.js +3 -1
  30. package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
  31. package/dist/rollbar.noconflict.umd.min.js.map +1 -1
  32. package/dist/rollbar.snippet.js +1 -1
  33. package/dist/rollbar.umd.js +19367 -6000
  34. package/dist/rollbar.umd.js.map +1 -1
  35. package/dist/rollbar.umd.min.js +3 -1
  36. package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
  37. package/dist/rollbar.umd.min.js.map +1 -1
  38. package/docs/extension-exceptions.md +35 -30
  39. package/docs/migration_v0_to_v1.md +41 -38
  40. package/eslint.config.mjs +33 -0
  41. package/get_versions.js +33 -0
  42. package/index.d.ts +270 -231
  43. package/karma.conf.js +18 -27
  44. package/package.json +21 -21
  45. package/prettier.config.js +7 -0
  46. package/src/api.js +78 -14
  47. package/src/apiUtility.js +14 -11
  48. package/src/browser/core.js +138 -72
  49. package/src/browser/defaults/scrubFields.js +3 -3
  50. package/src/browser/detection.js +7 -8
  51. package/src/browser/domUtility.js +18 -8
  52. package/src/browser/globalSetup.js +12 -6
  53. package/src/browser/logger.js +1 -1
  54. package/src/browser/plugins/jquery.js +35 -35
  55. package/src/browser/predicates.js +1 -1
  56. package/src/browser/replay/defaults.js +71 -0
  57. package/src/browser/replay/recorder.js +193 -0
  58. package/src/browser/replay/replayMap.js +195 -0
  59. package/src/browser/rollbar.js +12 -8
  60. package/src/browser/rollbarWrapper.js +8 -5
  61. package/src/browser/shim.js +43 -19
  62. package/src/browser/snippet_callback.js +6 -4
  63. package/src/browser/telemetry.js +573 -361
  64. package/src/browser/transforms.js +46 -27
  65. package/src/browser/transport/fetch.js +26 -14
  66. package/src/browser/transport/xhr.js +41 -14
  67. package/src/browser/transport.js +93 -33
  68. package/src/browser/url.js +16 -8
  69. package/src/browser/wrapGlobals.js +27 -8
  70. package/src/defaults.js +3 -3
  71. package/src/errorParser.js +14 -11
  72. package/src/merge.js +32 -23
  73. package/src/notifier.js +16 -13
  74. package/src/predicates.js +43 -23
  75. package/src/queue.js +133 -40
  76. package/src/rateLimiter.js +59 -18
  77. package/src/react-native/logger.js +1 -1
  78. package/src/react-native/rollbar.js +59 -55
  79. package/src/react-native/transforms.js +13 -9
  80. package/src/react-native/transport.js +44 -34
  81. package/src/rollbar.js +72 -21
  82. package/src/scrub.js +0 -1
  83. package/src/server/locals.js +69 -39
  84. package/src/server/logger.js +4 -4
  85. package/src/server/parser.js +72 -47
  86. package/src/server/rollbar.js +135 -56
  87. package/src/server/sourceMap/stackTrace.js +33 -18
  88. package/src/server/telemetry/urlHelpers.js +9 -11
  89. package/src/server/telemetry.js +68 -45
  90. package/src/server/transforms.js +37 -21
  91. package/src/server/transport.js +62 -32
  92. package/src/telemetry.js +162 -33
  93. package/src/tracing/context.js +24 -0
  94. package/src/tracing/contextManager.js +37 -0
  95. package/src/tracing/defaults.js +7 -0
  96. package/src/tracing/exporter.js +188 -0
  97. package/src/tracing/hrtime.js +98 -0
  98. package/src/tracing/id.js +24 -0
  99. package/src/tracing/session.js +55 -0
  100. package/src/tracing/span.js +92 -0
  101. package/src/tracing/spanProcessor.js +15 -0
  102. package/src/tracing/tracer.js +46 -0
  103. package/src/tracing/tracing.js +89 -0
  104. package/src/transforms.js +33 -21
  105. package/src/truncation.js +8 -5
  106. package/src/utility/headers.js +43 -43
  107. package/src/utility/replace.js +9 -0
  108. package/src/utility/traverse.js +1 -1
  109. package/src/utility.js +123 -52
  110. package/test/api.test.js +88 -41
  111. package/test/apiUtility.test.js +48 -50
  112. package/test/browser.core.test.js +142 -141
  113. package/test/browser.domUtility.test.js +53 -36
  114. package/test/browser.predicates.test.js +14 -14
  115. package/test/browser.replay.recorder.test.js +416 -0
  116. package/test/browser.rollbar.test.js +655 -515
  117. package/test/browser.telemetry.test.js +46 -39
  118. package/test/browser.transforms.test.js +164 -139
  119. package/test/browser.transport.test.js +59 -50
  120. package/test/browser.url.test.js +13 -12
  121. package/test/fixtures/locals.fixtures.js +245 -126
  122. package/test/fixtures/replay/index.js +20 -0
  123. package/test/fixtures/replay/payloads.fixtures.js +229 -0
  124. package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
  125. package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
  126. package/test/notifier.test.js +91 -79
  127. package/test/predicates.test.js +261 -215
  128. package/test/queue.test.js +231 -215
  129. package/test/rateLimiter.test.js +51 -43
  130. package/test/react-native.rollbar.test.js +150 -116
  131. package/test/react-native.transforms.test.js +23 -25
  132. package/test/react-native.transport.test.js +26 -14
  133. package/test/replay/index.js +2 -0
  134. package/test/replay/integration/api.spans.test.js +136 -0
  135. package/test/replay/integration/e2e.test.js +228 -0
  136. package/test/replay/integration/index.js +9 -0
  137. package/test/replay/integration/queue.replayMap.test.js +332 -0
  138. package/test/replay/integration/replayMap.test.js +163 -0
  139. package/test/replay/integration/sessionRecording.test.js +390 -0
  140. package/test/replay/unit/api.postSpans.test.js +150 -0
  141. package/test/replay/unit/index.js +7 -0
  142. package/test/replay/unit/queue.replayMap.test.js +225 -0
  143. package/test/replay/unit/replayMap.test.js +348 -0
  144. package/test/replay/util/index.js +5 -0
  145. package/test/replay/util/mockRecordFn.js +80 -0
  146. package/test/server.lambda.mocha.test.mjs +172 -0
  147. package/test/server.locals.constructor.mocha.test.mjs +80 -0
  148. package/test/server.locals.error-handling.mocha.test.mjs +387 -0
  149. package/test/server.locals.merge.mocha.test.mjs +267 -0
  150. package/test/server.locals.test-utils.mjs +114 -0
  151. package/test/server.parser.mocha.test.mjs +87 -0
  152. package/test/server.predicates.mocha.test.mjs +63 -0
  153. package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
  154. package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
  155. package/test/server.rollbar.logging.mocha.test.mjs +326 -0
  156. package/test/server.rollbar.misc.mocha.test.mjs +44 -0
  157. package/test/server.rollbar.test-utils.mjs +57 -0
  158. package/test/server.telemetry.mocha.test.mjs +377 -0
  159. package/test/server.transforms.data.mocha.test.mjs +163 -0
  160. package/test/server.transforms.error.mocha.test.mjs +199 -0
  161. package/test/server.transforms.request.mocha.test.mjs +208 -0
  162. package/test/server.transforms.scrub.mocha.test.mjs +140 -0
  163. package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
  164. package/test/server.transforms.test-utils.mjs +62 -0
  165. package/test/server.transport.mocha.test.mjs +269 -0
  166. package/test/telemetry.test.js +178 -38
  167. package/test/tracing/contextManager.test.js +28 -0
  168. package/test/tracing/exporter.toPayload.test.js +400 -0
  169. package/test/tracing/id.test.js +24 -0
  170. package/test/tracing/span.test.js +183 -0
  171. package/test/tracing/spanProcessor.test.js +73 -0
  172. package/test/tracing/tracing.test.js +105 -0
  173. package/test/transforms.test.js +70 -68
  174. package/test/truncation.test.js +57 -55
  175. package/test/utility.test.js +310 -228
  176. package/webpack.config.js +36 -70
  177. package/.eslintignore +0 -7
  178. package/.gitmodules +0 -3
  179. package/test/server.lambda.test.js +0 -177
  180. package/test/server.locals.test.js +0 -841
  181. package/test/server.parser.test.js +0 -72
  182. package/test/server.predicates.test.js +0 -89
  183. package/test/server.rollbar.test.js +0 -676
  184. package/test/server.telemetry.test.js +0 -318
  185. package/test/server.transforms.test.js +0 -1099
  186. package/test/server.transport.test.js +0 -201
package/src/telemetry.js CHANGED
@@ -1,15 +1,22 @@
1
1
  var _ = require('./utility');
2
2
 
3
- var MAX_EVENTS = 100;
3
+ const MAX_EVENTS = 100;
4
4
 
5
- function Telemeter(options) {
5
+ // Temporary workaround while solving commonjs -> esm issues in Node 18 - 20.
6
+ function fromMillis(millis) {
7
+ return [Math.trunc(millis / 1000), Math.round((millis % 1000) * 1e6)];
8
+ }
9
+
10
+ function Telemeter(options, tracing) {
6
11
  this.queue = [];
7
12
  this.options = _.merge(options);
8
13
  var maxTelemetryEvents = this.options.maxTelemetryEvents || MAX_EVENTS;
9
14
  this.maxQueueSize = Math.max(0, Math.min(maxTelemetryEvents, MAX_EVENTS));
15
+ this.tracing = tracing;
16
+ this.telemetrySpan = this.tracing?.startSpan('rollbar-telemetry', {});
10
17
  }
11
18
 
12
- Telemeter.prototype.configure = function(options) {
19
+ Telemeter.prototype.configure = function (options) {
13
20
  var oldOptions = this.options;
14
21
  this.options = _.merge(oldOptions, options);
15
22
  var maxTelemetryEvents = this.options.maxTelemetryEvents || MAX_EVENTS;
@@ -22,7 +29,7 @@ Telemeter.prototype.configure = function(options) {
22
29
  this.queue.splice(0, deleteCount);
23
30
  };
24
31
 
25
- Telemeter.prototype.copyEvents = function() {
32
+ Telemeter.prototype.copyEvents = function () {
26
33
  var events = Array.prototype.slice.call(this.queue, 0);
27
34
  if (_.isFunction(this.options.filterTelemetry)) {
28
35
  try {
@@ -39,20 +46,29 @@ Telemeter.prototype.copyEvents = function() {
39
46
  return events;
40
47
  };
41
48
 
42
- Telemeter.prototype.capture = function(type, metadata, level, rollbarUUID, timestamp) {
49
+ Telemeter.prototype.capture = function (
50
+ type,
51
+ metadata,
52
+ level,
53
+ rollbarUUID,
54
+ timestamp,
55
+ ) {
43
56
  var e = {
44
57
  level: getLevel(type, level),
45
58
  type: type,
46
59
  timestamp_ms: timestamp || _.now(),
47
60
  body: metadata,
48
- source: 'client'
61
+ source: 'client',
49
62
  };
50
63
  if (rollbarUUID) {
51
64
  e.uuid = rollbarUUID;
52
65
  }
53
66
 
54
67
  try {
55
- if (_.isFunction(this.options.filterTelemetry) && this.options.filterTelemetry(e)) {
68
+ if (
69
+ _.isFunction(this.options.filterTelemetry) &&
70
+ this.options.filterTelemetry(e)
71
+ ) {
56
72
  return false;
57
73
  }
58
74
  } catch (exc) {
@@ -63,37 +79,114 @@ Telemeter.prototype.capture = function(type, metadata, level, rollbarUUID, times
63
79
  return e;
64
80
  };
65
81
 
66
- Telemeter.prototype.captureEvent = function(type, metadata, level, rollbarUUID) {
82
+ Telemeter.prototype.captureEvent = function (
83
+ type,
84
+ metadata,
85
+ level,
86
+ rollbarUUID,
87
+ ) {
67
88
  return this.capture(type, metadata, level, rollbarUUID);
68
89
  };
69
90
 
70
- Telemeter.prototype.captureError = function(err, level, rollbarUUID, timestamp) {
71
- var metadata = {
72
- message: err.message || String(err)
73
- };
91
+ Telemeter.prototype.captureError = function (
92
+ err,
93
+ level,
94
+ rollbarUUID,
95
+ timestamp,
96
+ ) {
97
+ const message = err.message || String(err);
98
+ var metadata = {message};
74
99
  if (err.stack) {
75
100
  metadata.stack = err.stack;
76
101
  }
102
+ this.telemetrySpan?.addEvent(
103
+ 'rollbar-occurrence-event',
104
+ {
105
+ message,
106
+ level,
107
+ type: 'error',
108
+ uuid: rollbarUUID,
109
+ 'occurrence.type': 'error', // deprecated
110
+ 'occurrence.uuid': rollbarUUID, // deprecated
111
+ },
112
+
113
+ fromMillis(timestamp),
114
+ );
115
+
77
116
  return this.capture('error', metadata, level, rollbarUUID, timestamp);
78
117
  };
79
118
 
80
- Telemeter.prototype.captureLog = function(message, level, rollbarUUID, timestamp) {
81
- return this.capture('log', {
82
- message: message
83
- }, level, rollbarUUID, timestamp);
119
+ Telemeter.prototype.captureLog = function (
120
+ message,
121
+ level,
122
+ rollbarUUID,
123
+ timestamp,
124
+ ) {
125
+ // If the uuid is present, this is a message occurrence.
126
+ if (rollbarUUID) {
127
+ this.telemetrySpan?.addEvent(
128
+ 'rollbar-occurrence-event',
129
+ {
130
+ message,
131
+ level,
132
+ type: 'message',
133
+ uuid: rollbarUUID,
134
+ 'occurrence.type': 'message', // deprecated
135
+ 'occurrence.uuid': rollbarUUID, // deprecated
136
+ },
137
+ fromMillis(timestamp),
138
+ );
139
+ } else {
140
+ this.telemetrySpan?.addEvent(
141
+ 'rollbar-log-event',
142
+ {message, level},
143
+ fromMillis(timestamp),
144
+ );
145
+ }
146
+
147
+ return this.capture(
148
+ 'log',
149
+ {message},
150
+ level,
151
+ rollbarUUID,
152
+ timestamp,
153
+ );
84
154
  };
85
155
 
86
- Telemeter.prototype.captureNetwork = function(metadata, subtype, rollbarUUID, requestData) {
156
+ Telemeter.prototype.captureNetwork = function (
157
+ metadata,
158
+ subtype,
159
+ rollbarUUID,
160
+ requestData,
161
+ ) {
87
162
  subtype = subtype || 'xhr';
88
163
  metadata.subtype = metadata.subtype || subtype;
89
164
  if (requestData) {
90
165
  metadata.request = requestData;
91
166
  }
92
167
  var level = this.levelFromStatus(metadata.status_code);
93
- return this.capture('network', metadata, level, rollbarUUID);
168
+
169
+ const endTimeNano = (metadata.end_time_ms || 0) * 1e6;
170
+
171
+ this.telemetrySpan?.addEvent(
172
+ 'rollbar-network-event',
173
+ {
174
+ type: metadata.subtype,
175
+ method: metadata.method,
176
+ url : metadata.url,
177
+ statusCode : metadata.status_code,
178
+ 'request.headers': JSON.stringify(metadata.request_headers || {}),
179
+ 'response.headers': JSON.stringify(metadata.response?.headers || {}),
180
+ 'response.timeUnixNano': endTimeNano.toString(),
181
+ },
182
+
183
+ fromMillis(metadata.start_time_ms)
184
+ );
185
+
186
+ return this.capture('network', metadata, level, rollbarUUID, metadata.start_time_ms);
94
187
  };
95
188
 
96
- Telemeter.prototype.levelFromStatus = function(statusCode) {
189
+ Telemeter.prototype.levelFromStatus = function (statusCode) {
97
190
  if (statusCode >= 200 && statusCode < 400) {
98
191
  return 'info';
99
192
  }
@@ -103,10 +196,16 @@ Telemeter.prototype.levelFromStatus = function(statusCode) {
103
196
  return 'info';
104
197
  };
105
198
 
106
- Telemeter.prototype.captureDom = function(subtype, element, value, checked, rollbarUUID) {
199
+ Telemeter.prototype.captureDom = function (
200
+ subtype,
201
+ element,
202
+ value,
203
+ checked,
204
+ rollbarUUID,
205
+ ) {
107
206
  var metadata = {
108
207
  subtype: subtype,
109
- element: element
208
+ element: element,
110
209
  };
111
210
  if (value !== undefined) {
112
211
  metadata.value = value;
@@ -117,31 +216,55 @@ Telemeter.prototype.captureDom = function(subtype, element, value, checked, roll
117
216
  return this.capture('dom', metadata, 'info', rollbarUUID);
118
217
  };
119
218
 
120
- Telemeter.prototype.captureNavigation = function(from, to, rollbarUUID) {
121
- return this.capture('navigation', {from: from, to: to}, 'info', rollbarUUID);
219
+ Telemeter.prototype.captureNavigation = function (from, to, rollbarUUID, timestamp) {
220
+ this.telemetrySpan?.addEvent(
221
+ 'rollbar-navigation-event',
222
+ {'previous.url.full': from, 'url.full': to},
223
+ fromMillis(timestamp),
224
+ );
225
+
226
+ return this.capture(
227
+ 'navigation',
228
+ {from, to},
229
+ 'info',
230
+ rollbarUUID,
231
+ timestamp,
232
+ );
122
233
  };
123
234
 
124
- Telemeter.prototype.captureDomContentLoaded = function(ts) {
125
- return this.capture('navigation', {subtype: 'DOMContentLoaded'}, 'info', undefined, ts && ts.getTime());
235
+ Telemeter.prototype.captureDomContentLoaded = function (ts) {
236
+ return this.capture(
237
+ 'navigation',
238
+ { subtype: 'DOMContentLoaded' },
239
+ 'info',
240
+ undefined,
241
+ ts && ts.getTime(),
242
+ );
126
243
  /**
127
244
  * If we decide to make this a dom event instead, then use the line below:
128
245
  return this.capture('dom', {subtype: 'DOMContentLoaded'}, 'info', undefined, ts && ts.getTime());
129
246
  */
130
247
  };
131
- Telemeter.prototype.captureLoad = function(ts) {
132
- return this.capture('navigation', {subtype: 'load'}, 'info', undefined, ts && ts.getTime());
248
+ Telemeter.prototype.captureLoad = function (ts) {
249
+ return this.capture(
250
+ 'navigation',
251
+ { subtype: 'load' },
252
+ 'info',
253
+ undefined,
254
+ ts && ts.getTime(),
255
+ );
133
256
  /**
134
257
  * If we decide to make this a dom event instead, then use the line below:
135
258
  return this.capture('dom', {subtype: 'load'}, 'info', undefined, ts && ts.getTime());
136
259
  */
137
260
  };
138
261
 
139
- Telemeter.prototype.captureConnectivityChange = function(type, rollbarUUID) {
140
- return this.captureNetwork({change: type}, 'connectivity', rollbarUUID);
262
+ Telemeter.prototype.captureConnectivityChange = function (type, rollbarUUID) {
263
+ return this.captureNetwork({ change: type }, 'connectivity', rollbarUUID);
141
264
  };
142
265
 
143
266
  // Only intended to be used internally by the notifier
144
- Telemeter.prototype._captureRollbarItem = function(item) {
267
+ Telemeter.prototype._captureRollbarItem = function (item) {
145
268
  if (!this.options.includeItemsInTelemetry) {
146
269
  return;
147
270
  }
@@ -152,11 +275,17 @@ Telemeter.prototype._captureRollbarItem = function(item) {
152
275
  return this.captureLog(item.message, item.level, item.uuid, item.timestamp);
153
276
  }
154
277
  if (item.custom) {
155
- return this.capture('log', item.custom, item.level, item.uuid, item.timestamp);
278
+ return this.capture(
279
+ 'log',
280
+ item.custom,
281
+ item.level,
282
+ item.uuid,
283
+ item.timestamp,
284
+ );
156
285
  }
157
286
  };
158
287
 
159
- Telemeter.prototype.push = function(e) {
288
+ Telemeter.prototype.push = function (e) {
160
289
  this.queue.push(e);
161
290
  if (this.queue.length > this.maxQueueSize) {
162
291
  this.queue.shift();
@@ -169,7 +298,7 @@ function getLevel(type, level) {
169
298
  }
170
299
  var defaultLevel = {
171
300
  error: 'error',
172
- manual: 'info'
301
+ manual: 'info',
173
302
  };
174
303
  return defaultLevel[type] || 'info';
175
304
  }
@@ -0,0 +1,24 @@
1
+ export class Context {
2
+ constructor(parentContext) {
3
+ this._currentContext = parentContext ? new Map(parentContext) : new Map();
4
+ }
5
+
6
+ getValue(key) {
7
+ return this._currentContext.get(key);
8
+ }
9
+
10
+ setValue (key, value) {
11
+ const context = new Context(this._currentContext);
12
+ context._currentContext.set(key, value);
13
+ return context;
14
+ }
15
+
16
+ deleteValue(key) {
17
+ const context = new Context(self._currentContext);
18
+ context._currentContext.delete(key);
19
+ return context;
20
+ }
21
+ }
22
+
23
+ export const ROOT_CONTEXT = new Context();
24
+
@@ -0,0 +1,37 @@
1
+ import { ROOT_CONTEXT } from './context.js';
2
+
3
+ export class ContextManager {
4
+ constructor() {
5
+ this.currentContext = ROOT_CONTEXT;
6
+ }
7
+
8
+ active() {
9
+ return this.currentContext;
10
+ }
11
+
12
+ enterContext(context) {
13
+ const previousContext = this.currentContext;
14
+ this.currentContext = context || ROOT_CONTEXT;
15
+ return previousContext;
16
+ }
17
+
18
+ exitContext(context) {
19
+ this.currentContext = context;
20
+ return this.currentContext;
21
+ }
22
+
23
+ with(context, fn, thisArg, ...args) {
24
+ const previousContext = this.enterContext(context);
25
+ try {
26
+ return fn.call(thisArg, ...args);
27
+ } finally {
28
+ this.exitContext(previousContext);
29
+ }
30
+ }
31
+ }
32
+
33
+ export function createContextKey(key) {
34
+ // Use Symbol for OpenTelemetry compatibility.
35
+ return Symbol.for(key);
36
+ }
37
+
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Default tracing options
3
+ */
4
+ export default {
5
+ enabled: false,
6
+ endpoint: 'api.rollbar.com/api/1/session/',
7
+ }
@@ -0,0 +1,188 @@
1
+ import hrtime from './hrtime';
2
+
3
+ /**
4
+ * SpanExporter is responsible for exporting ReadableSpan objects
5
+ * and transforming them into the OTLP-compatible format.
6
+ */
7
+ export class SpanExporter {
8
+ /**
9
+ * Export spans to the span export queue
10
+ *
11
+ * @param {Array} spans - Array of ReadableSpan objects to export
12
+ * @param {Function} _resultCallback - Optional callback (not used)
13
+ */
14
+ export(spans, _resultCallback) {
15
+ console.log(spans); // console exporter, TODO: make optional
16
+ spanExportQueue.push(...spans);
17
+ }
18
+
19
+ /**
20
+ * Transforms an array of ReadableSpan objects into the OTLP format payload
21
+ * compatible with the Rollbar API. This follows the OpenTelemetry protocol
22
+ * specification for traces.
23
+ *
24
+ * @returns {Object} OTLP format payload for API transmission
25
+ */
26
+ toPayload() {
27
+ const spans = spanExportQueue.slice();
28
+ spanExportQueue.length = 0;
29
+
30
+ if (!spans || !spans.length) {
31
+ return { resourceSpans: [] };
32
+ }
33
+
34
+ const resource = (spans[0] && spans[0].resource) || {};
35
+
36
+ const scopeMap = new Map();
37
+
38
+ for (const span of spans) {
39
+ const scopeKey = span.instrumentationScope
40
+ ? `${span.instrumentationScope.name}:${span.instrumentationScope.version}`
41
+ : 'default:1.0.0';
42
+
43
+ if (!scopeMap.has(scopeKey)) {
44
+ scopeMap.set(scopeKey, {
45
+ scope: span.instrumentationScope || {
46
+ name: 'default',
47
+ version: '1.0.0',
48
+ attributes: [],
49
+ },
50
+ spans: [],
51
+ });
52
+ }
53
+
54
+ scopeMap.get(scopeKey).spans.push(this._transformSpan(span));
55
+ }
56
+
57
+ return {
58
+ resourceSpans: [
59
+ {
60
+ resource: this._transformResource(resource),
61
+ scopeSpans: Array.from(scopeMap.values()).map((scopeData) => ({
62
+ scope: this._transformInstrumentationScope(scopeData.scope),
63
+ spans: scopeData.spans,
64
+ })),
65
+ },
66
+ ],
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Transforms a ReadableSpan into the OTLP Span format
72
+ *
73
+ * @private
74
+ * @param {Object} span - ReadableSpan object to transform
75
+ * @returns {Object} OTLP Span format
76
+ */
77
+ _transformSpan(span) {
78
+ const transformAttributes = (attributes) => {
79
+ return Object.entries(attributes || {}).map(([key, value]) => ({
80
+ key,
81
+ value: this._transformAnyValue(value),
82
+ }));
83
+ };
84
+
85
+ const transformEvents = (events) => {
86
+ return (events || []).map((event) => ({
87
+ timeUnixNano: hrtime.toNanos(event.time),
88
+ name: event.name,
89
+ attributes: transformAttributes(event.attributes),
90
+ }));
91
+ };
92
+
93
+ return {
94
+ traceId: span.spanContext.traceId,
95
+ spanId: span.spanContext.spanId,
96
+ parentSpanId: span.parentSpanId || '',
97
+ name: span.name,
98
+ kind: span.kind || 1, // INTERNAL by default
99
+ startTimeUnixNano: hrtime.toNanos(span.startTime),
100
+ endTimeUnixNano: hrtime.toNanos(span.endTime),
101
+ attributes: transformAttributes(span.attributes),
102
+ events: transformEvents(span.events),
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Transforms a resource object into OTLP Resource format
108
+ *
109
+ * @private
110
+ * @param {Object} resource - Resource information
111
+ * @returns {Object} OTLP Resource format
112
+ */
113
+ _transformResource(resource) {
114
+ const attributes = resource.attributes || {};
115
+ const keyValues = Object.entries(attributes).map(([key, value]) => ({
116
+ key,
117
+ value: this._transformAnyValue(value),
118
+ }));
119
+
120
+ return {
121
+ attributes: keyValues,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Transforms an instrumentation scope into OTLP InstrumentationScope format
127
+ *
128
+ * @private
129
+ * @param {Object} scope - Instrumentation scope information
130
+ * @returns {Object} OTLP InstrumentationScope format
131
+ */
132
+ _transformInstrumentationScope(scope) {
133
+ return {
134
+ name: scope.name || '',
135
+ version: scope.version || '',
136
+ attributes: (scope.attributes || []).map((attr) => ({
137
+ key: attr.key,
138
+ value: this._transformAnyValue(attr.value),
139
+ })),
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Transforms a JavaScript value into an OTLP AnyValue
145
+ *
146
+ * @private
147
+ * @param {any} value - Value to transform
148
+ * @returns {Object} OTLP AnyValue format
149
+ */
150
+ _transformAnyValue(value) {
151
+ if (value === null || value === undefined) {
152
+ return { stringValue: '' };
153
+ }
154
+
155
+ const type = typeof value;
156
+
157
+ if (type === 'string') {
158
+ return { stringValue: value };
159
+ } else if (type === 'number') {
160
+ if (Number.isInteger(value)) {
161
+ return { intValue: value.toString() };
162
+ } else {
163
+ return { doubleValue: value };
164
+ }
165
+ } else if (type === 'boolean') {
166
+ return { boolValue: value };
167
+ } else if (Array.isArray(value)) {
168
+ return {
169
+ arrayValue: {
170
+ values: value.map((v) => this._transformAnyValue(v)),
171
+ },
172
+ };
173
+ } else if (type === 'object') {
174
+ return {
175
+ kvlistValue: {
176
+ values: Object.entries(value).map(([k, v]) => ({
177
+ key: k,
178
+ value: this._transformAnyValue(v),
179
+ })),
180
+ },
181
+ };
182
+ }
183
+
184
+ return { stringValue: String(value) };
185
+ }
186
+ }
187
+
188
+ export const spanExportQueue = [];
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @module hrtime
3
+ *
4
+ * @description Methods for handling OpenTelemetry hrtime.
5
+ */
6
+
7
+ /**
8
+ * Convert a duration in milliseconds to an OpenTelemetry hrtime tuple.
9
+ *
10
+ * @param {number} millis - The duration in milliseconds.
11
+ * @returns {[number, number]} An array where the first element is seconds
12
+ * and the second is nanoseconds.
13
+ */
14
+ function fromMillis(millis) {
15
+ return [Math.trunc(millis / 1000), Math.round((millis % 1000) * 1e6)];
16
+ }
17
+
18
+ /**
19
+ * Convert an OpenTelemetry hrtime tuple back to a duration in milliseconds.
20
+ *
21
+ * @param {[number, number]} hrtime - The hrtime tuple [seconds, nanoseconds].
22
+ * @returns {number} The total duration in milliseconds.
23
+ */
24
+ function toMillis(hrtime) {
25
+ return hrtime[0] * 1e3 + Math.round(hrtime[1] / 1e6);
26
+ }
27
+
28
+ /**
29
+ * Convert an OpenTelemetry hrtime tuple back to a duration in nanoseconds.
30
+ *
31
+ * @param {[number, number]} hrtime - The hrtime tuple [seconds, nanoseconds].
32
+ * @returns {number} The total duration in nanoseconds.
33
+ */
34
+ function toNanos(hrtime) {
35
+ return hrtime[0] * 1e9 + hrtime[1];
36
+ }
37
+
38
+ /**
39
+ * Adds two OpenTelemetry hrtime tuples.
40
+ *
41
+ * @param {[number, number]} a - The first hrtime tuple [s, ns].
42
+ * @param {[number, number]} b - The second hrtime tuple [s, ns].
43
+ * @returns {[number, number]} Summed hrtime tuple, normalized.
44
+ *
45
+ */
46
+ function add(a, b) {
47
+ return [a[0] + b[0] + Math.trunc((a[1] + b[1]) / 1e9), (a[1] + b[1]) % 1e9];
48
+ }
49
+
50
+ /**
51
+ * Get the current high-resolution time as an OpenTelemetry hrtime tuple.
52
+ *
53
+ * Uses the Performance API (timeOrigin + now()).
54
+ *
55
+ * @returns {[number, number]} The current hrtime tuple [s, ns].
56
+ */
57
+ function now() {
58
+ return add(fromMillis(performance.timeOrigin), fromMillis(performance.now()));
59
+ }
60
+
61
+ /**
62
+ * Check if a value is a valid OpenTelemetry hrtime tuple.
63
+ *
64
+ * An hrtime tuple is an Array of exactly two numbers:
65
+ * [seconds, nanoseconds]
66
+ *
67
+ * @param {*} value – anything to test
68
+ * @returns {boolean} true if `value` is a [number, number] array of length 2
69
+ *
70
+ * @example
71
+ * isHrTime([ 1, 500 ]); // true
72
+ * isHrTime([ 0, 1e9 ]); // true
73
+ * isHrTime([ '1', 500 ]); // false
74
+ * isHrTime({ 0: 1, 1: 500 }); // false
75
+ */
76
+ function isHrTime(value) {
77
+ return (
78
+ Array.isArray(value) &&
79
+ value.length === 2 &&
80
+ typeof value[0] === 'number' &&
81
+ typeof value[1] === 'number'
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Methods for handling hrtime. OpenTelemetry uses the [seconds, nanoseconds]
87
+ * format for hrtime in the `ReadableSpan` interface.
88
+ *
89
+ * @example
90
+ * import hrtime from '@tracing/hrtime.js';
91
+ *
92
+ * hrtime.fromMillis(1000);
93
+ * hrtime.toMillis([0, 1000]);
94
+ * hrtime.add([0, 0], [0, 1000]);
95
+ * hrtime.now();
96
+ * hrtime.isHrTime([0, 1000]);
97
+ */
98
+ export default { fromMillis, toMillis, toNanos, add, now, isHrTime };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Generate a random hexadecimal ID of specified byte length
3
+ *
4
+ * @param {number} bytes - Number of bytes for the ID (default: 16)
5
+ * @returns {string} - Hexadecimal string representation
6
+ */
7
+ function gen(bytes = 16) {
8
+ let randomBytes = new Uint8Array(bytes);
9
+ crypto.getRandomValues(randomBytes);
10
+ let randHex = Array.from(randomBytes, (byte) =>
11
+ byte.toString(16).padStart(2, '0'),
12
+ ).join('');
13
+ return randHex;
14
+ }
15
+
16
+ /**
17
+ * Tracing id generation utils
18
+ *
19
+ * @example
20
+ * import id from './id.js';
21
+ *
22
+ * const spanId = id.gen(8); // => "a1b2c3d4e5f6..."
23
+ */
24
+ export default { gen };