rollbar 2.26.4 → 3.0.0-alpha.2
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/.claude/settings.local.json +3 -0
- package/.cursor/rules/guidelines.mdc +154 -0
- package/.github/workflows/ci.yml +4 -6
- package/CLAUDE.local.md +297 -0
- package/CLAUDE.md +201 -0
- package/CLAUDE.testrunner.md +470 -0
- package/Gruntfile.js +59 -16
- package/Makefile +3 -3
- package/SECURITY.md +5 -0
- package/babel.config.json +9 -0
- package/codex.md +148 -0
- package/dist/plugins/jquery.min.js +1 -1
- package/dist/rollbar.js +19332 -6596
- package/dist/rollbar.js.map +1 -1
- package/dist/rollbar.min.js +2 -1
- package/dist/rollbar.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.min.js.map +1 -1
- package/dist/rollbar.named-amd.js +19332 -6596
- package/dist/rollbar.named-amd.js.map +1 -1
- package/dist/rollbar.named-amd.min.js +2 -1
- package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.named-amd.min.js.map +1 -1
- package/dist/rollbar.noconflict.umd.js +19319 -6581
- package/dist/rollbar.noconflict.umd.js.map +1 -1
- package/dist/rollbar.noconflict.umd.min.js +2 -1
- package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.noconflict.umd.min.js.map +1 -1
- package/dist/rollbar.snippet.js +1 -1
- package/dist/rollbar.umd.js +19333 -6597
- package/dist/rollbar.umd.js.map +1 -1
- package/dist/rollbar.umd.min.js +2 -1
- package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.umd.min.js.map +1 -1
- package/eslint.config.mjs +33 -0
- package/karma.conf.js +5 -14
- package/package.json +19 -20
- package/src/api.js +57 -4
- package/src/apiUtility.js +2 -3
- package/src/browser/core.js +37 -9
- package/src/browser/replay/defaults.js +70 -0
- package/src/browser/replay/recorder.js +194 -0
- package/src/browser/replay/replayMap.js +195 -0
- package/src/browser/rollbar.js +11 -7
- package/src/browser/telemetry.js +3 -3
- package/src/browser/transport/fetch.js +17 -4
- package/src/browser/transport/xhr.js +17 -1
- package/src/browser/transport.js +11 -8
- package/src/defaults.js +1 -1
- package/src/queue.js +65 -4
- package/src/react-native/rollbar.js +1 -1
- package/src/rollbar.js +52 -10
- package/src/server/rollbar.js +3 -2
- package/src/telemetry.js +76 -11
- package/src/tracing/context.js +24 -0
- package/src/tracing/contextManager.js +37 -0
- package/src/tracing/defaults.js +7 -0
- package/src/tracing/exporter.js +188 -0
- package/src/tracing/hrtime.js +98 -0
- package/src/tracing/id.js +24 -0
- package/src/tracing/session.js +55 -0
- package/src/tracing/span.js +92 -0
- package/src/tracing/spanProcessor.js +15 -0
- package/src/tracing/tracer.js +46 -0
- package/src/tracing/tracing.js +89 -0
- package/src/utility.js +34 -0
- package/test/api.test.js +57 -12
- package/test/apiUtility.test.js +5 -6
- package/test/browser.core.test.js +1 -10
- package/test/browser.domUtility.test.js +1 -1
- package/test/browser.predicates.test.js +1 -1
- package/test/browser.replay.recorder.test.js +430 -0
- package/test/browser.rollbar.test.js +58 -12
- package/test/browser.telemetry.test.js +1 -1
- package/test/browser.transforms.test.js +20 -13
- package/test/browser.transport.test.js +5 -4
- package/test/browser.url.test.js +1 -1
- package/test/fixtures/replay/index.js +20 -0
- package/test/fixtures/replay/payloads.fixtures.js +229 -0
- package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
- package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
- package/test/notifier.test.js +1 -1
- package/test/predicates.test.js +1 -1
- package/test/queue.test.js +1 -1
- package/test/rateLimiter.test.js +1 -1
- package/test/react-native.rollbar.test.js +1 -1
- package/test/react-native.transforms.test.js +2 -2
- package/test/react-native.transport.test.js +3 -3
- package/test/replay/index.js +2 -0
- package/test/replay/integration/api.spans.test.js +136 -0
- package/test/replay/integration/e2e.test.js +228 -0
- package/test/replay/integration/index.js +9 -0
- package/test/replay/integration/queue.replayMap.test.js +332 -0
- package/test/replay/integration/replayMap.test.js +163 -0
- package/test/replay/integration/sessionRecording.test.js +390 -0
- package/test/replay/unit/api.postSpans.test.js +150 -0
- package/test/replay/unit/index.js +7 -0
- package/test/replay/unit/queue.replayMap.test.js +225 -0
- package/test/replay/unit/replayMap.test.js +348 -0
- package/test/replay/util/index.js +5 -0
- package/test/replay/util/mockRecordFn.js +80 -0
- package/test/server.lambda.mocha.test.mjs +172 -0
- package/test/server.locals.constructor.mocha.test.mjs +80 -0
- package/test/server.locals.error-handling.mocha.test.mjs +387 -0
- package/test/server.locals.merge.mocha.test.mjs +267 -0
- package/test/server.locals.test-utils.mjs +114 -0
- package/test/server.parser.mocha.test.mjs +87 -0
- package/test/server.predicates.mocha.test.mjs +63 -0
- package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
- package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
- package/test/server.rollbar.logging.mocha.test.mjs +326 -0
- package/test/server.rollbar.misc.mocha.test.mjs +44 -0
- package/test/server.rollbar.test-utils.mjs +57 -0
- package/test/server.telemetry.mocha.test.mjs +377 -0
- package/test/server.transforms.data.mocha.test.mjs +163 -0
- package/test/server.transforms.error.mocha.test.mjs +199 -0
- package/test/server.transforms.request.mocha.test.mjs +208 -0
- package/test/server.transforms.scrub.mocha.test.mjs +140 -0
- package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
- package/test/server.transforms.test-utils.mjs +62 -0
- package/test/server.transport.mocha.test.mjs +269 -0
- package/test/telemetry.test.js +132 -1
- package/test/tracing/contextManager.test.js +28 -0
- package/test/tracing/exporter.toPayload.test.js +400 -0
- package/test/tracing/id.test.js +24 -0
- package/test/tracing/span.test.js +183 -0
- package/test/tracing/spanProcessor.test.js +73 -0
- package/test/tracing/tracing.test.js +105 -0
- package/test/transforms.test.js +2 -2
- package/test/truncation.test.js +2 -2
- package/test/utility.test.js +44 -6
- package/webpack.config.js +6 -44
- package/.eslintignore +0 -7
- package/test/server.lambda.test.js +0 -194
- package/test/server.locals.test.js +0 -1068
- package/test/server.parser.test.js +0 -78
- package/test/server.predicates.test.js +0 -91
- package/test/server.rollbar.test.js +0 -728
- package/test/server.telemetry.test.js +0 -443
- package/test/server.transforms.test.js +0 -1193
- package/test/server.transport.test.js +0 -269
package/src/telemetry.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
var _ = require('./utility');
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const MAX_EVENTS = 100;
|
|
4
4
|
|
|
5
|
-
|
|
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
19
|
Telemeter.prototype.configure = function (options) {
|
|
@@ -87,12 +94,25 @@ Telemeter.prototype.captureError = function (
|
|
|
87
94
|
rollbarUUID,
|
|
88
95
|
timestamp,
|
|
89
96
|
) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
97
|
+
const message = err.message || String(err);
|
|
98
|
+
var metadata = {message};
|
|
93
99
|
if (err.stack) {
|
|
94
100
|
metadata.stack = err.stack;
|
|
95
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
|
+
|
|
96
116
|
return this.capture('error', metadata, level, rollbarUUID, timestamp);
|
|
97
117
|
};
|
|
98
118
|
|
|
@@ -102,11 +122,31 @@ Telemeter.prototype.captureLog = function (
|
|
|
102
122
|
rollbarUUID,
|
|
103
123
|
timestamp,
|
|
104
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
|
+
|
|
105
147
|
return this.capture(
|
|
106
148
|
'log',
|
|
107
|
-
{
|
|
108
|
-
message: message,
|
|
109
|
-
},
|
|
149
|
+
{message},
|
|
110
150
|
level,
|
|
111
151
|
rollbarUUID,
|
|
112
152
|
timestamp,
|
|
@@ -125,7 +165,25 @@ Telemeter.prototype.captureNetwork = function (
|
|
|
125
165
|
metadata.request = requestData;
|
|
126
166
|
}
|
|
127
167
|
var level = this.levelFromStatus(metadata.status_code);
|
|
128
|
-
|
|
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);
|
|
129
187
|
};
|
|
130
188
|
|
|
131
189
|
Telemeter.prototype.levelFromStatus = function (statusCode) {
|
|
@@ -158,12 +216,19 @@ Telemeter.prototype.captureDom = function (
|
|
|
158
216
|
return this.capture('dom', metadata, 'info', rollbarUUID);
|
|
159
217
|
};
|
|
160
218
|
|
|
161
|
-
Telemeter.prototype.captureNavigation = function (from, to, 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
|
+
|
|
162
226
|
return this.capture(
|
|
163
227
|
'navigation',
|
|
164
|
-
{
|
|
228
|
+
{from, to},
|
|
165
229
|
'info',
|
|
166
230
|
rollbarUUID,
|
|
231
|
+
timestamp,
|
|
167
232
|
);
|
|
168
233
|
};
|
|
169
234
|
|
|
@@ -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,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 };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import id from './id.js';
|
|
2
|
+
|
|
3
|
+
const SESSION_KEY = 'RollbarSession';
|
|
4
|
+
|
|
5
|
+
export class Session {
|
|
6
|
+
constructor(tracing, options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.tracing = tracing;
|
|
9
|
+
this.window = tracing.window;
|
|
10
|
+
this.session = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
if (this.session) {
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
return this.getSession() || this.createSession();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getSession() {
|
|
21
|
+
try {
|
|
22
|
+
const serializedSession = this.window.sessionStorage.getItem(SESSION_KEY);
|
|
23
|
+
|
|
24
|
+
if (!serializedSession) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.session = JSON.parse(serializedSession);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
createSession() {
|
|
36
|
+
this.session = {
|
|
37
|
+
id: id.gen(),
|
|
38
|
+
createdAt: Date.now(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return this.setSession(this.session);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setSession(session) {
|
|
45
|
+
const sessionString = JSON.stringify(session);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
this.window.sessionStorage.setItem(SESSION_KEY, sessionString);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import hrtime from './hrtime.js';
|
|
2
|
+
|
|
3
|
+
export class Span {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.initReadableSpan(options);
|
|
6
|
+
|
|
7
|
+
this.spanProcessor = options.spanProcessor;
|
|
8
|
+
this.spanProcessor.onStart(this, options.context);
|
|
9
|
+
|
|
10
|
+
if (options.attributes) {
|
|
11
|
+
this.setAttributes(options.attributes);
|
|
12
|
+
}
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
initReadableSpan(options) {
|
|
17
|
+
this.span = {
|
|
18
|
+
name: options.name,
|
|
19
|
+
kind: options.kind,
|
|
20
|
+
spanContext: options.spanContext,
|
|
21
|
+
parentSpanId: options.parentSpanId,
|
|
22
|
+
startTime: options.startTime || hrtime.now(),
|
|
23
|
+
endTime: [0, 0],
|
|
24
|
+
status: { code: 0, message: '' },
|
|
25
|
+
attributes: { 'session.id': options.session.id },
|
|
26
|
+
links: [],
|
|
27
|
+
events: [],
|
|
28
|
+
duration: 0,
|
|
29
|
+
ended: false,
|
|
30
|
+
resource: options.resource,
|
|
31
|
+
instrumentationScope: options.scope,
|
|
32
|
+
droppedAttributesCount: 0,
|
|
33
|
+
droppedEventsCount: 0,
|
|
34
|
+
droppedLinksCount: 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
spanContext() {
|
|
39
|
+
return this.span.spanContext;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get spanId() {
|
|
43
|
+
return this.span.spanContext.spanId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get traceId() {
|
|
47
|
+
return this.span.spanContext.traceId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setAttribute(key, value) {
|
|
51
|
+
if (value == null || this.ended) return this;
|
|
52
|
+
if (key.length === 0) return this;
|
|
53
|
+
|
|
54
|
+
this.span.attributes[key] = value;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setAttributes(attributes) {
|
|
59
|
+
for (const [k, v] of Object.entries(attributes)) {
|
|
60
|
+
this.setAttribute(k, v);
|
|
61
|
+
}
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addEvent(name, attributes = {}, time) {
|
|
66
|
+
if (this.span.ended) return this;
|
|
67
|
+
|
|
68
|
+
this.span.events.push({
|
|
69
|
+
name,
|
|
70
|
+
attributes,
|
|
71
|
+
time: time || hrtime.now(),
|
|
72
|
+
droppedAttributesCount: 0,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isRecording() {
|
|
79
|
+
return this.span.ended === false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
end(attributes, time) {
|
|
83
|
+
if (attributes) this.setAttributes(attributes);
|
|
84
|
+
this.span.endTime = time || hrtime.now();
|
|
85
|
+
this.span.ended = true;
|
|
86
|
+
this.spanProcessor.onEnd(this);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export() {
|
|
90
|
+
return this.span;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class SpanProcessor {
|
|
2
|
+
constructor(exporter) {
|
|
3
|
+
this.exporter = exporter;
|
|
4
|
+
this.pendingSpans = new Map()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
onStart(span, _parentContext) {
|
|
8
|
+
this.pendingSpans.set(span.span.spanContext.spanId, span);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onEnd(span) {
|
|
12
|
+
this.exporter.export([span.export()])
|
|
13
|
+
this.pendingSpans.delete(span.span.spanContext.spanId);
|
|
14
|
+
}
|
|
15
|
+
}
|