rollbar 3.0.0 → 3.1.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.
Files changed (40) hide show
  1. package/dist/rollbar.cjs +260 -3
  2. package/dist/rollbar.js +88 -2
  3. package/dist/rollbar.js.map +1 -1
  4. package/dist/rollbar.min.cjs +1 -1
  5. package/dist/rollbar.min.js +1 -1
  6. package/dist/rollbar.min.js.map +1 -1
  7. package/dist/rollbar.named-amd.js +88 -2
  8. package/dist/rollbar.named-amd.js.map +1 -1
  9. package/dist/rollbar.named-amd.min.js +1 -1
  10. package/dist/rollbar.named-amd.min.js.map +1 -1
  11. package/dist/rollbar.noconflict.umd.js +88 -2
  12. package/dist/rollbar.noconflict.umd.js.map +1 -1
  13. package/dist/rollbar.noconflict.umd.min.js +1 -1
  14. package/dist/rollbar.noconflict.umd.min.js.map +1 -1
  15. package/dist/rollbar.replay.js +88 -2
  16. package/dist/rollbar.replay.js.map +1 -1
  17. package/dist/rollbar.replay.min.js +1 -1
  18. package/dist/rollbar.replay.min.js.map +1 -1
  19. package/dist/rollbar.replay.noconflict.umd.js +88 -2
  20. package/dist/rollbar.replay.noconflict.umd.js.map +1 -1
  21. package/dist/rollbar.replay.noconflict.umd.min.js +1 -1
  22. package/dist/rollbar.replay.noconflict.umd.min.js.map +1 -1
  23. package/dist/rollbar.replay.umd.js +88 -2
  24. package/dist/rollbar.replay.umd.js.map +1 -1
  25. package/dist/rollbar.replay.umd.min.js +1 -1
  26. package/dist/rollbar.replay.umd.min.js.map +1 -1
  27. package/dist/rollbar.snippet.js +1 -1
  28. package/dist/rollbar.umd.js +88 -2
  29. package/dist/rollbar.umd.js.map +1 -1
  30. package/dist/rollbar.umd.min.js +1 -1
  31. package/dist/rollbar.umd.min.js.map +1 -1
  32. package/index.d.ts +15 -0
  33. package/package.json +2 -1
  34. package/src/browser/telemetry.js +25 -0
  35. package/src/defaults.js +1 -1
  36. package/src/rollbar.js +3 -1
  37. package/src/server/middleware/rollbarExpressMiddleware.js +63 -0
  38. package/src/server/rollbar.js +13 -0
  39. package/src/server/telemetry.js +120 -1
  40. package/src/utility.js +70 -0
package/index.d.ts CHANGED
@@ -33,6 +33,7 @@ declare class Rollbar implements Rollbar.Components {
33
33
  ): Rollbar.LambdaHandler<T>;
34
34
 
35
35
  public errorHandler(): Rollbar.ExpressErrorHandler;
36
+ public expressMiddleware(): Rollbar.ExpressMiddleware | undefined;
36
37
 
37
38
  // Components
38
39
 
@@ -219,6 +220,11 @@ declare namespace Rollbar {
219
220
  response: any,
220
221
  next: ExpressNextFunction,
221
222
  ) => any;
223
+ export type ExpressMiddleware = (
224
+ request: any,
225
+ response: any,
226
+ next: ExpressNextFunction,
227
+ ) => any;
222
228
  export type ExpressNextFunction = (err?: any) => void;
223
229
  class Locals {}
224
230
  export type LocalsType = typeof Locals;
@@ -333,9 +339,18 @@ declare namespace Rollbar {
333
339
  export interface TransformSpanParams {
334
340
  span: any;
335
341
  }
342
+ export type TracingPropagationHeader =
343
+ | 'baggage'
344
+ | 'traceparent'
345
+ | 'tracestate';
346
+ export interface TracingPropagationOptions {
347
+ enabledHeaders?: TracingPropagationHeader[];
348
+ enabledCorsUrls?: (string | RegExp)[];
349
+ }
336
350
  export interface TracingOptions {
337
351
  enabled?: boolean;
338
352
  endpoint?: string;
353
+ propagation?: TracingPropagationOptions;
339
354
  transformSpan?: (params: TransformSpanParams) => void;
340
355
  }
341
356
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollbar",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -125,6 +125,7 @@
125
125
  "ts-node": "^10.9.2",
126
126
  "typescript": "^5.9.3",
127
127
  "typescript-eslint": "^8.46.4",
128
+ "undici": "^6.23.0",
128
129
  "webpack": "^5.98.0",
129
130
  "webpack-cli": "^6.0.1",
130
131
  "webpack-node-externals": "^3.0.0"
@@ -284,6 +284,23 @@ class Instrumenter {
284
284
  function (orig) {
285
285
  return function (data) {
286
286
  const xhr = this;
287
+ const tracing = self.rollbar?.tracing;
288
+ if (
289
+ _.shouldAddBaggageHeader(
290
+ self.options,
291
+ tracing,
292
+ xhr.__rollbar_xhr?.url,
293
+ )
294
+ ) {
295
+ try {
296
+ xhr.setRequestHeader(
297
+ 'baggage',
298
+ `rollbar.session.id=${tracing.sessionId}`,
299
+ );
300
+ } catch (_e) {
301
+ /* ignore errors from adding baggage header */
302
+ }
303
+ }
287
304
 
288
305
  function onreadystatechangeHandler() {
289
306
  if (xhr.__rollbar_xhr) {
@@ -435,6 +452,14 @@ class Instrumenter {
435
452
  if (args[1] && args[1].method) {
436
453
  method = args[1].method;
437
454
  }
455
+ const tracing = self.rollbar?.tracing;
456
+ if (_.shouldAddBaggageHeader(self.options, tracing, url)) {
457
+ const headers = {
458
+ baggage: `rollbar.session.id=${tracing.sessionId}`,
459
+ };
460
+
461
+ _.addHeadersToFetch(args, headers);
462
+ }
438
463
  const metadata = {
439
464
  method: method,
440
465
  url: url,
package/src/defaults.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Default options shared across platforms
3
3
  */
4
- export const version = '3.0.0';
4
+ export const version = '3.1.0';
5
5
  export const endpoint = 'api.rollbar.com/api/1/item/';
6
6
  export const logLevel = 'debug';
7
7
  export const reportLevel = 'debug';
package/src/rollbar.js CHANGED
@@ -184,9 +184,11 @@ Rollbar.prototype._log = function (defaultLevel, item) {
184
184
 
185
185
  Rollbar.prototype._addItemAttributes = function (item) {
186
186
  const span = this.tracing?.getSpan();
187
+ const asyncLocalSessionId = _.getSessionIdFromAsyncLocalStorage(this);
188
+ const sessionId = asyncLocalSessionId || this.tracing?.sessionId;
187
189
 
188
190
  const attributes = [
189
- { key: 'session_id', value: this.tracing?.sessionId },
191
+ { key: 'session_id', value: sessionId },
190
192
  { key: 'span_id', value: span?.spanId },
191
193
  { key: 'trace_id', value: span?.traceId },
192
194
  ];
@@ -0,0 +1,63 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
3
+ export function extractSessionId(headerValue) {
4
+ if (!headerValue) {
5
+ return null;
6
+ }
7
+ const rawValue = Array.isArray(headerValue)
8
+ ? headerValue.join(',')
9
+ : headerValue;
10
+ if (typeof rawValue !== 'string') {
11
+ return null;
12
+ }
13
+ const entries = rawValue.split(',');
14
+ for (const entry of entries) {
15
+ const trimmed = entry.trim();
16
+ if (!trimmed) {
17
+ continue;
18
+ }
19
+ const equalsIndex = trimmed.indexOf('=');
20
+ if (equalsIndex === -1) {
21
+ continue;
22
+ }
23
+ const key = trimmed.slice(0, equalsIndex).trim();
24
+ if (key !== 'rollbar.session.id') {
25
+ continue;
26
+ }
27
+ const value = trimmed.slice(equalsIndex + 1).trim();
28
+ if (!value) {
29
+ return null;
30
+ }
31
+ try {
32
+ return decodeURIComponent(value);
33
+ } catch (_e) {
34
+ return value;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function getBaggageHeader(req) {
41
+ if (!req) {
42
+ return null;
43
+ }
44
+ if (typeof req.get === 'function') {
45
+ return req.get('baggage');
46
+ }
47
+ return req.headers?.baggage || null;
48
+ }
49
+
50
+ export default function rollbarExpressMiddleware(rollbar) {
51
+ const storage = rollbar?.client.asyncLocalStorage || new AsyncLocalStorage();
52
+ if (rollbar) {
53
+ rollbar.client.asyncLocalStorage = storage;
54
+ }
55
+
56
+ return function rollbarExpressMiddlewareHandler(req, _res, next) {
57
+ const sessionId = extractSessionId(getBaggageHeader(req));
58
+ if (!sessionId) {
59
+ return next();
60
+ }
61
+ return storage.run({ sessionId }, () => next());
62
+ };
63
+ }
@@ -15,6 +15,7 @@ import truncation from '../truncation.js';
15
15
  import * as _ from '../utility.js';
16
16
 
17
17
  import * as serverDefaults from './defaults.js';
18
+ import rollbarExpressMiddleware from './middleware/rollbarExpressMiddleware.js';
18
19
  import Instrumenter from './telemetry.js';
19
20
  import * as transforms from './transforms.js';
20
21
  import Transport from './transport.js';
@@ -460,6 +461,18 @@ Rollbar.wrapCallback = function (f) {
460
461
  }
461
462
  };
462
463
 
464
+ Rollbar.prototype.expressMiddleware = function () {
465
+ return rollbarExpressMiddleware(this);
466
+ };
467
+
468
+ Rollbar.expressMiddleware = function () {
469
+ if (_instance) {
470
+ return rollbarExpressMiddleware(_instance);
471
+ }
472
+ handleUninitialized();
473
+ return undefined;
474
+ };
475
+
463
476
  Rollbar.prototype.captureEvent = function () {
464
477
  var event = _.createTelemetryEvent(arguments);
465
478
  return this.client.captureEvent(event.type, event.metadata, event.level);
@@ -85,6 +85,15 @@ Instrumenter.prototype.instrumentNetwork = function () {
85
85
  this.replacements,
86
86
  'network',
87
87
  );
88
+ if (typeof globalThis.fetch === 'function') {
89
+ replace(
90
+ globalThis,
91
+ 'fetch',
92
+ fetchRequestWrapper.bind(this),
93
+ this.replacements,
94
+ 'network',
95
+ );
96
+ }
88
97
  };
89
98
 
90
99
  function networkRequestWrapper(orig) {
@@ -93,10 +102,22 @@ function networkRequestWrapper(orig) {
93
102
  return (...args) => {
94
103
  const [url, options, cb] = args;
95
104
  var mergedOptions = urlHelpers.mergeOptions(url, options, cb);
105
+ const requestUrl = urlHelpers.constructUrl(mergedOptions.options);
106
+ const sessionId = _.getSessionIdFromAsyncLocalStorage(this.rollbar.client);
107
+
108
+ if (
109
+ sessionId &&
110
+ _.shouldAddBaggageHeader(this.options, { sessionId }, requestUrl)
111
+ ) {
112
+ if (!mergedOptions.options.headers) {
113
+ mergedOptions.options.headers = {};
114
+ }
115
+ mergedOptions.options.headers.baggage = `rollbar.session.id=${sessionId}`;
116
+ }
96
117
 
97
118
  var metadata = {
98
119
  method: mergedOptions.options.method || 'GET',
99
- url: urlHelpers.constructUrl(mergedOptions.options),
120
+ url: requestUrl,
100
121
  status_code: null,
101
122
  start_time_ms: _.now(),
102
123
  end_time_ms: null,
@@ -142,6 +163,104 @@ function responseCallbackWrapper(options, metadata, callback) {
142
163
  };
143
164
  }
144
165
 
166
+ function fetchRequestWrapper(orig) {
167
+ var telemeter = this.telemeter;
168
+
169
+ return (...args) => {
170
+ const input = args[0];
171
+ const init = args[1];
172
+ let method = 'GET';
173
+ let url;
174
+ const sessionId = _.getSessionIdFromAsyncLocalStorage(this.rollbar.client);
175
+
176
+ if (_.isType(input, 'string') || input instanceof URL) {
177
+ url = input.toString();
178
+ } else if (input) {
179
+ url = input.url;
180
+ if (input.method) {
181
+ method = input.method;
182
+ }
183
+ }
184
+
185
+ if (init && init.method) {
186
+ method = init.method;
187
+ }
188
+
189
+ if (
190
+ sessionId &&
191
+ _.shouldAddBaggageHeader(this.options, { sessionId }, url)
192
+ ) {
193
+ const headers = { baggage: `rollbar.session.id=${sessionId}` };
194
+
195
+ _.addHeadersToFetch(args, headers);
196
+ }
197
+
198
+ const metadata = {
199
+ method: method,
200
+ url: url,
201
+ status_code: null,
202
+ start_time_ms: _.now(),
203
+ end_time_ms: null,
204
+ };
205
+
206
+ if (this.autoInstrument.networkRequestHeaders) {
207
+ const requestHeaders = normalizeFetchHeaders(
208
+ init && init.headers ? init.headers : input && input.headers,
209
+ );
210
+ if (requestHeaders) {
211
+ metadata.request_headers = requestHeaders;
212
+ }
213
+ }
214
+
215
+ telemeter.captureNetwork(metadata, 'fetch');
216
+
217
+ return orig.apply(globalThis, args).then(
218
+ (res) => {
219
+ metadata.end_time_ms = _.now();
220
+ metadata.status_code = res.status;
221
+ if (this.autoInstrument.networkResponseHeaders) {
222
+ const responseHeaders = normalizeFetchHeaders(res.headers);
223
+ if (responseHeaders) {
224
+ metadata.response = metadata.response || {};
225
+ metadata.response.headers = responseHeaders;
226
+ }
227
+ }
228
+ return res;
229
+ },
230
+ (err) => {
231
+ metadata.end_time_ms = _.now();
232
+ metadata.status_code = 0;
233
+ metadata.error = [err.name, err.message].join(': ');
234
+ throw err;
235
+ },
236
+ );
237
+ };
238
+ }
239
+
240
+ function normalizeFetchHeaders(headers) {
241
+ if (!headers) return null;
242
+ if (typeof headers.forEach === 'function') {
243
+ const normalized = {};
244
+ headers.forEach((value, key) => {
245
+ normalized[key] = value;
246
+ });
247
+ return normalized;
248
+ }
249
+ if (Array.isArray(headers)) {
250
+ const normalized = {};
251
+ headers.forEach((pair) => {
252
+ if (pair && pair.length > 1) {
253
+ normalized[pair[0]] = pair[1];
254
+ }
255
+ });
256
+ return normalized;
257
+ }
258
+ if (_.isType(headers, 'object')) {
259
+ return headers;
260
+ }
261
+ return null;
262
+ }
263
+
145
264
  Instrumenter.prototype.captureNetwork = function (
146
265
  metadata,
147
266
  subtype,
package/src/utility.js CHANGED
@@ -146,6 +146,10 @@ function isBrowser() {
146
146
  return typeof window !== 'undefined';
147
147
  }
148
148
 
149
+ function isRequestObject(input) {
150
+ return typeof Request !== 'undefined' && input instanceof Request;
151
+ }
152
+
149
153
  function redact() {
150
154
  return '********';
151
155
  }
@@ -867,6 +871,69 @@ function merge() {
867
871
  return result;
868
872
  }
869
873
 
874
+ function shouldAddBaggageHeader(options, tracing, url) {
875
+ if (!tracing?.sessionId || !url) {
876
+ return false;
877
+ }
878
+ const propagation = options?.tracing?.propagation;
879
+ const enabledHeaders = propagation?.enabledHeaders;
880
+ if (!Array.isArray(enabledHeaders) || !enabledHeaders.includes('baggage')) {
881
+ return false;
882
+ }
883
+ const enabledCorsUrls = propagation?.enabledCorsUrls;
884
+ if (!Array.isArray(enabledCorsUrls) || enabledCorsUrls.length === 0) {
885
+ return false;
886
+ }
887
+ return enabledCorsUrls.some((pattern) => {
888
+ if (isType(pattern, 'string')) {
889
+ return url === pattern;
890
+ }
891
+ if (isType(pattern, 'regexp')) {
892
+ return pattern.test(url);
893
+ }
894
+ return false;
895
+ });
896
+ }
897
+
898
+ function addHeadersToFetch(args, newHeaders) {
899
+ // Headers may be in the request object or the init object.
900
+ // If present in both places, the init object must be used.
901
+ //
902
+ let init = args[1];
903
+ const initHeaders = init?.headers;
904
+ const reqHeaders = isRequestObject(args[0]) && args[0].headers;
905
+ let headers = initHeaders || reqHeaders;
906
+
907
+ // If headers are not present in either place, they are added to the init object.
908
+ // If there is no init object, one must be created and added to args.
909
+ if (!headers) {
910
+ if (!init) {
911
+ args[1] = init = {};
912
+ }
913
+ headers = init.headers = {};
914
+ }
915
+
916
+ // `headers` may be a Headers object or a plain object.
917
+ if (headers instanceof Headers) {
918
+ for (const key of Object.keys(newHeaders)) {
919
+ headers.append(key, newHeaders[key]);
920
+ }
921
+ } else if (isObject(headers)) {
922
+ for (const key of Object.keys(newHeaders)) {
923
+ headers[key] = newHeaders[key];
924
+ }
925
+ }
926
+ }
927
+
928
+ function getSessionIdFromAsyncLocalStorage(client) {
929
+ const storage = client.asyncLocalStorage;
930
+ if (!storage || typeof storage.getStore !== 'function') {
931
+ return null;
932
+ }
933
+ const store = storage.getStore();
934
+ return store?.sessionId || null;
935
+ }
936
+
870
937
  export {
871
938
  addParamsAndAccessTokenToPath,
872
939
  createItem,
@@ -902,4 +969,7 @@ export {
902
969
  maxByteSize,
903
970
  typeName,
904
971
  uuid4,
972
+ shouldAddBaggageHeader,
973
+ addHeadersToFetch,
974
+ getSessionIdFromAsyncLocalStorage,
905
975
  };