-
Notifications
You must be signed in to change notification settings - Fork 230
/
Copy pathRunContext.js
151 lines (137 loc) · 5.54 KB
/
RunContext.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/
'use strict';
// A RunContext is the immutable structure that holds which transaction and span
// are currently active, if any, for the running JavaScript code.
//
// Module instrumentation code interacts with run contexts via a number of
// methods on the `Instrumentation` instance at `agent._instrumentation`.
//
// User code using the agent's API (the Agent API, Transaction API, and Span API)
// are not exposes to RunContext instances. However users of the OpenTelemetry
// API, provided by the OpenTelemetry Bridge, *are* exposed to OpenTelemetry
// `Context` instances -- which RunContext implements.
//
// A RunContext holds:
// - a current Transaction, which can be null; and
// - a *stack* of Spans, where the top-of-stack span is the "current" one.
// A stack is necessary to support the semantics of multiple started and ended
// spans in the same async task. E.g.:
// apm.startTransaction('t')
// var s1 = apm.startSpan('s1')
// var s2 = apm.startSpan('s2')
// s2.end()
// assert(apm.currentSpan === s1, 's1 is now the current span')
// - a mapping of "values". This is an arbitrary key-value mapping, but exists
// primarily to implement OpenTelemetry `interface Context`
// https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.1.0/src/context/types.ts#L17-L41
//
// A RunContext is immutable. This means that `runContext.enterSpan(span)` and
// other similar methods return a new/separate RunContext instance. This is
// done so that a run-context change in the current code does not change
// anything for other code bound to the original RunContext (e.g. via
// `ins.bindFunction` or `ins.bindEmitter`).
//
// Warning: Agent code should never use the `RunContext` class directly
// because a subclass can be provided for the `Instrumentation` to use.
// Instead new instances should be built from an existing one, typically the
// active one (`_runCtxMgr.active()`) or the root one (`_runCtxMgr.root()`).
class RunContext {
constructor(trans, spans, parentValues) {
this._trans = trans || null;
this._spans = spans || [];
this._values = parentValues ? new Map(parentValues) : new Map();
}
currTransaction() {
return this._trans;
}
// Returns the currently active span, if any, otherwise null.
currSpan() {
if (this._spans.length > 0) {
return this._spans[this._spans.length - 1];
} else {
return null;
}
}
// Return a new RunContext for a newly active/current Transaction.
enterTrans(trans) {
return new this.constructor(trans, null, this._values);
}
// Return a new RunContext with the given span added to the top of the spans
// stack.
enterSpan(span) {
const newSpans = this._spans.slice();
newSpans.push(span);
return new this.constructor(this._trans, newSpans, this._values);
}
// Return a new RunContext with the given transaction (and hence all of its
// spans) removed.
leaveTrans() {
return new this.constructor(null, null, this._values);
}
// Return a new RunContext with the given span removed, or null if there is
// no change (the given span isn't part of the run context).
//
// Typically this span is the top of stack (i.e. it is the current span).
// However, it is possible to have out-of-order span.end() or even end a span
// that isn't part of the current run context stack at all. (See
// test/instrumentation/run-context/fixtures/end-non-current-spans.js for
// examples.)
leaveSpan(span) {
let newRc = null;
let newSpans;
const lastSpan = this._spans[this._spans.length - 1];
if (lastSpan && lastSpan.id === span.id) {
// Fast path for common case: `span` is top of stack.
newSpans = this._spans.slice(0, this._spans.length - 1);
newRc = new this.constructor(this._trans, newSpans, this._values);
} else {
const stackIdx = this._spans.findIndex((s) => s.id === span.id);
if (stackIdx !== -1) {
newSpans = this._spans
.slice(0, stackIdx)
.concat(this._spans.slice(stackIdx + 1));
newRc = new this.constructor(this._trans, newSpans, this._values);
}
}
return newRc;
}
// A string representation useful for debug logging.
// For example:
// RunContext(Transaction(abc123, 'trans name'), [Span(def456, 'span name', ended)])
// ^^^^^^^-- if the span has ended
// ^^^^^^ ^^^^^^-- id
// ^^^^^^^^^^-- the class name, typically "RunContext", but can be overriden
toString() {
const bits = [];
if (this._trans) {
bits.push(this._trans.toString());
}
if (this._spans.length > 0) {
const spanStrs = this._spans.map((s) => s.toString());
bits.push('[' + spanStrs + ']');
}
return `${this.constructor.name}(${bits.join(', ')})`;
}
// ---- The following implements the OTel Context interface.
// https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.0.4/src/context/types.ts#L17
getValue(key) {
return this._values.get(key);
}
setValue(key, value) {
const rc = new this.constructor(this._trans, this._spans, this._values);
rc._values.set(key, value);
return rc;
}
deleteValue(key) {
const rc = new this.constructor(this._trans, this._spans, this._values);
rc._values.delete(key);
return rc;
}
}
module.exports = {
RunContext,
};