forked from polarsignals/custom-labels
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
154 lines (140 loc) · 6.03 KB
/
Copy pathindex.js
File metadata and controls
154 lines (140 loc) · 6.03 KB
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
152
153
154
'use strict';
const SCHEMA_VERSION = 'nodejs_v1_dev';
// V8 layout constants the addon captured from the V8 headers Node bundles.
// On non-Linux these fall back to the values matching Node's standard
// build (no V8 pointer compression, no sandbox) — the reader is Linux-only
// per the OTEP anyway, so non-Linux callers republishing process context
// see consistent values.
let WRAPPED_OBJECT_OFFSET = 24;
let TAGGED_SIZE = 8;
let NATIVE_WRAP_FIELDS_OFFSET = 24;
let JS_MAP_TABLE_OFFSET = 0x18;
let ORDERED_HASH_MAP_HEADER_SIZE = 0x10;
// Public surface, populated by the Linux branch below. On other
// platforms these stay as no-op stubs / a sham class.
let ThreadContext;
let getContext;
let clearContext;
if (process.platform === 'linux') {
const bindings = require('bindings');
const addon = bindings('customlabels');
WRAPPED_OBJECT_OFFSET = addon.wrappedObjectOffset;
TAGGED_SIZE = addon.taggedSize;
NATIVE_WRAP_FIELDS_OFFSET = addon.nativeWrapFieldsOffset;
JS_MAP_TABLE_OFFSET = addon.jsMapTableOffset;
ORDERED_HASH_MAP_HEADER_SIZE = addon.orderedHashMapHeaderSize;
ThreadContext = addon.ThreadContext;
const { AsyncLocalStorage } = require('node:async_hooks');
let als;
function asyncContextFrameError() {
const [major] = process.versions.node.split('.').map(Number);
// Explicit opt-out: it's not in use.
if (process.execArgv.includes('--no-async-context-frame')) return 'Node explicitly launched with --no-async-context-frame';
// Since Node 24, AsyncContextFrame is the default unless disabled.
if (major >= 24) return undefined;
// In Node 22/23, it existed behind an experimental flag.
if (process.execArgv.includes('--experimental-async-context-frame')) return undefined;
if (major >= 22) return 'Node versions prior to v24 must be launched with --experimental-async-context-frame';
// Older versions: not available.
return 'Node major versions prior to v22 do not support the feature at all';
}
function ensureHook() {
if (als) return;
const err = asyncContextFrameError();
if (err) {
throw new Error(`otel thread-ctx writer requires async_context_frame support, which is unavailable: ${err}.`);
}
als = new AsyncLocalStorage();
addon.storeAls(als);
}
getContext = function () {
return als ? als.getStore() : undefined;
};
// Idempotent: clearing when the hook hasn't been installed (no prior
// enter / run on a ThreadContext) is a no-op.
clearContext = function () {
if (!als) return;
als.enterWith(undefined);
};
// Install the active-context channel on the ThreadContext prototype so
// the only way to push a ThreadContext into our AsyncLocalStorage is
// via the context itself — callers can't poison the ALS with an
// arbitrary object.
ThreadContext.prototype.enter = function () {
ensureHook();
als.enterWith(this);
};
ThreadContext.prototype.run = function (fn) {
ensureHook();
return als.run(this, fn);
};
// Debug accessor (not part of the stable API; for tests / reader dev):
// returns a Uint8Array view of the currently attached record, or undefined.
exports._currentRecordBytes = function () {
const c = getContext();
return c ? c.debugBytes() : undefined;
};
} else {
// Non-Linux degradation. The writer's reader contract is ELF-TLSDESC,
// meaningful only on Linux; on other platforms we still want the API
// to be callable so consumers don't have to gate every call site —
// construction succeeds but produces an inert context, and the
// enter/run/clearContext entry points don't wire anything into
// AsyncLocalStorage.
class NoopThreadContext {
appendAttributes() {}
invalidate() {}
isTruncated() { return false; }
debugBytes() { return new Uint8Array(0); }
enter() {}
run(fn) { return fn(); }
}
ThreadContext = NoopThreadContext;
getContext = function () { return undefined; };
clearContext = function () {};
exports._currentRecordBytes = function () { return undefined; };
}
/**
* Snapshot of the OTEP-4719 process-context attributes the caller should
* publish so an out-of-process reader can (a) decode the on-the-wire
* uint8 key indexes back to names and (b) walk V8's wrapper / hashmap
* layout without doing its own V8-internal symbol lookups. The `keys`
* argument is the same string list the caller writes into ThreadContext's
* positional attributes array: index N in this array is the uint8 key
* index N in the on-the-wire record.
*
* The returned object is frozen and defensively copied; safe to spread
* into the caller's process-context attribute map.
*/
function getProcessContextAttributes(keys) {
if (!Array.isArray(keys)) {
throw new TypeError('keys must be an array of attribute names');
}
if (keys.length > 256) {
throw new RangeError('keys array exceeds 256 entries');
}
const seen = new Set();
for (let i = 0; i < keys.length; ++i) {
const name = keys[i];
if (typeof name !== 'string') {
throw new TypeError('every key must be a string');
}
if (seen.has(name)) {
throw new Error(`duplicate key name at index ${i}: ${name}`);
}
seen.add(name);
}
return Object.freeze({
'threadlocal.schema_version': SCHEMA_VERSION,
'threadlocal.attribute_key_map': Object.freeze(keys.slice()),
'threadlocal.wrapped_object_offset': WRAPPED_OBJECT_OFFSET,
'threadlocal.tagged_size': TAGGED_SIZE,
'threadlocal.native_wrap_fields_offset': NATIVE_WRAP_FIELDS_OFFSET,
'threadlocal.js_map_table_offset': JS_MAP_TABLE_OFFSET,
'threadlocal.ordered_hash_map_header_size': ORDERED_HASH_MAP_HEADER_SIZE,
});
}
exports.ThreadContext = ThreadContext;
exports.getContext = getContext;
exports.clearContext = clearContext;
exports.getProcessContextAttributes = getProcessContextAttributes;