autotel-edge 3.0.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.
- package/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/chunk-F32WSLNX.js +309 -0
- package/dist/chunk-F32WSLNX.js.map +1 -0
- package/dist/events.d.ts +86 -0
- package/dist/events.js +157 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +921 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +89 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +166 -0
- package/dist/sampling.js +108 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Dj85cPUj.d.ts +182 -0
- package/package.json +88 -0
- package/src/api/logger.test.ts +367 -0
- package/src/api/logger.ts +197 -0
- package/src/compose.ts +243 -0
- package/src/core/buffer.ts +16 -0
- package/src/core/config.test.ts +388 -0
- package/src/core/config.ts +167 -0
- package/src/core/context.ts +224 -0
- package/src/core/exporter.ts +99 -0
- package/src/core/provider.ts +45 -0
- package/src/core/span.ts +222 -0
- package/src/core/spanprocessor.test.ts +521 -0
- package/src/core/spanprocessor.ts +232 -0
- package/src/core/trace-context.ts +66 -0
- package/src/core/tracer.test.ts +123 -0
- package/src/core/tracer.ts +216 -0
- package/src/events/index.test.ts +242 -0
- package/src/events/index.ts +338 -0
- package/src/events.ts +6 -0
- package/src/functional.test.ts +702 -0
- package/src/functional.ts +846 -0
- package/src/index.ts +81 -0
- package/src/logger.ts +13 -0
- package/src/sampling/index.test.ts +297 -0
- package/src/sampling/index.ts +276 -0
- package/src/sampling.ts +6 -0
- package/src/testing/index.ts +9 -0
- package/src/testing.ts +6 -0
- package/src/types.ts +267 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncLocalStorage-based context manager for edge environments
|
|
3
|
+
*
|
|
4
|
+
* Copyright The OpenTelemetry Authors
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ContextManager, Context } from '@opentelemetry/api';
|
|
9
|
+
import { ROOT_CONTEXT } from '@opentelemetry/api';
|
|
10
|
+
|
|
11
|
+
//@ts-ignore - node:async_hooks available in CF Workers with nodejs_compat
|
|
12
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
13
|
+
//@ts-ignore
|
|
14
|
+
import { EventEmitter } from 'node:events';
|
|
15
|
+
|
|
16
|
+
type Func<T> = (...args: unknown[]) => T;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Store a map for each event of all original listeners and their "patched"
|
|
20
|
+
* version. So when a listener is removed by the user, the corresponding
|
|
21
|
+
* patched function will be also removed.
|
|
22
|
+
*/
|
|
23
|
+
interface PatchMap {
|
|
24
|
+
[name: string]: WeakMap<Func<void>, Func<void>>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ADD_LISTENER_METHODS = [
|
|
28
|
+
'addListener' as const,
|
|
29
|
+
'on' as const,
|
|
30
|
+
'once' as const,
|
|
31
|
+
'prependListener' as const,
|
|
32
|
+
'prependOnceListener' as const,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
abstract class AbstractAsyncHooksContextManager implements ContextManager {
|
|
36
|
+
abstract active(): Context;
|
|
37
|
+
|
|
38
|
+
abstract with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
|
|
39
|
+
context: Context,
|
|
40
|
+
fn: F,
|
|
41
|
+
thisArg?: ThisParameterType<F>,
|
|
42
|
+
...args: A
|
|
43
|
+
): ReturnType<F>;
|
|
44
|
+
|
|
45
|
+
abstract enable(): this;
|
|
46
|
+
|
|
47
|
+
abstract disable(): this;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Binds a context to the target function or event emitter
|
|
51
|
+
*/
|
|
52
|
+
bind<T>(context: Context, target: T): T {
|
|
53
|
+
if (target instanceof EventEmitter) {
|
|
54
|
+
return this._bindEventEmitter(context, target);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof target === 'function') {
|
|
58
|
+
return this._bindFunction(context, target);
|
|
59
|
+
}
|
|
60
|
+
return target;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _bindFunction<T extends Function>(context: Context, target: T): T {
|
|
64
|
+
const manager = this;
|
|
65
|
+
const contextWrapper = function (this: never, ...args: unknown[]) {
|
|
66
|
+
return manager.with(context, () => target.apply(this, args));
|
|
67
|
+
};
|
|
68
|
+
Object.defineProperty(contextWrapper, 'length', {
|
|
69
|
+
enumerable: false,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: false,
|
|
72
|
+
value: target.length,
|
|
73
|
+
});
|
|
74
|
+
return contextWrapper as any;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* By default, EventEmitter calls callbacks with their context, which we do
|
|
79
|
+
* not want. Instead we bind a specific context to all callbacks.
|
|
80
|
+
*/
|
|
81
|
+
private _bindEventEmitter<T extends EventEmitter>(
|
|
82
|
+
context: Context,
|
|
83
|
+
ee: T,
|
|
84
|
+
): T {
|
|
85
|
+
const map = this._getPatchMap(ee);
|
|
86
|
+
if (map !== undefined) return ee;
|
|
87
|
+
this._createPatchMap(ee);
|
|
88
|
+
|
|
89
|
+
// Patch methods that add a listener to propagate context
|
|
90
|
+
for (const methodName of ADD_LISTENER_METHODS) {
|
|
91
|
+
if (ee[methodName] === undefined) continue;
|
|
92
|
+
ee[methodName] = this._patchAddListener(ee, ee[methodName], context);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Patch methods that remove a listener
|
|
96
|
+
if (typeof ee.removeListener === 'function') {
|
|
97
|
+
ee.removeListener = this._patchRemoveListener(ee, ee.removeListener);
|
|
98
|
+
}
|
|
99
|
+
if (typeof ee.off === 'function') {
|
|
100
|
+
ee.off = this._patchRemoveListener(ee, ee.off);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Patch method that removes all listeners
|
|
104
|
+
if (typeof ee.removeAllListeners === 'function') {
|
|
105
|
+
ee.removeAllListeners = this._patchRemoveAllListeners(
|
|
106
|
+
ee,
|
|
107
|
+
ee.removeAllListeners,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return ee;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private _patchRemoveListener(ee: EventEmitter, original: Function) {
|
|
114
|
+
const contextManager = this;
|
|
115
|
+
return function (this: never, event: string, listener: Func<void>) {
|
|
116
|
+
const events = contextManager._getPatchMap(ee)?.[event];
|
|
117
|
+
if (events === undefined) {
|
|
118
|
+
return original.call(this, event, listener);
|
|
119
|
+
}
|
|
120
|
+
const patchedListener = events.get(listener);
|
|
121
|
+
return original.call(this, event, patchedListener || listener);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private _patchRemoveAllListeners(ee: EventEmitter, original: Function) {
|
|
126
|
+
const contextManager = this;
|
|
127
|
+
return function (this: never, event: string) {
|
|
128
|
+
const map = contextManager._getPatchMap(ee);
|
|
129
|
+
if (map !== undefined) {
|
|
130
|
+
if (arguments.length === 0) {
|
|
131
|
+
contextManager._createPatchMap(ee);
|
|
132
|
+
} else if (map[event] !== undefined) {
|
|
133
|
+
delete map[event];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return Reflect.apply(original, this, arguments);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private _patchAddListener(
|
|
141
|
+
ee: EventEmitter,
|
|
142
|
+
original: Function,
|
|
143
|
+
context: Context,
|
|
144
|
+
) {
|
|
145
|
+
const contextManager = this;
|
|
146
|
+
return function (this: never, event: string, listener: Func<void>) {
|
|
147
|
+
/**
|
|
148
|
+
* This check prevents double-wrapping the listener.
|
|
149
|
+
* The implementation for ee.once wraps the listener and calls ee.on.
|
|
150
|
+
* Without this check, we would wrap that wrapped listener.
|
|
151
|
+
*/
|
|
152
|
+
if (contextManager._wrapped) {
|
|
153
|
+
return original.call(this, event, listener);
|
|
154
|
+
}
|
|
155
|
+
let map = contextManager._getPatchMap(ee);
|
|
156
|
+
if (map === undefined) {
|
|
157
|
+
map = contextManager._createPatchMap(ee);
|
|
158
|
+
}
|
|
159
|
+
let listeners = map[event];
|
|
160
|
+
if (listeners === undefined) {
|
|
161
|
+
listeners = new WeakMap();
|
|
162
|
+
map[event] = listeners;
|
|
163
|
+
}
|
|
164
|
+
const patchedListener = contextManager.bind(context, listener);
|
|
165
|
+
// Store a weak reference of the user listener to ours
|
|
166
|
+
listeners.set(listener, patchedListener);
|
|
167
|
+
|
|
168
|
+
contextManager._wrapped = true;
|
|
169
|
+
try {
|
|
170
|
+
return original.call(this, event, patchedListener);
|
|
171
|
+
} finally {
|
|
172
|
+
contextManager._wrapped = false;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private _createPatchMap(ee: EventEmitter): PatchMap {
|
|
178
|
+
const map = Object.create(null);
|
|
179
|
+
(ee as any)[this._kOtListeners] = map;
|
|
180
|
+
return map;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private _getPatchMap(ee: EventEmitter): PatchMap | undefined {
|
|
184
|
+
return (ee as never)[this._kOtListeners];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private readonly _kOtListeners = Symbol('OtListeners');
|
|
188
|
+
private _wrapped = false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* AsyncLocalStorage-based context manager for edge runtimes
|
|
193
|
+
*/
|
|
194
|
+
export class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextManager {
|
|
195
|
+
private _asyncLocalStorage: AsyncLocalStorage<Context>;
|
|
196
|
+
|
|
197
|
+
constructor() {
|
|
198
|
+
super();
|
|
199
|
+
this._asyncLocalStorage = new AsyncLocalStorage();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
active(): Context {
|
|
203
|
+
return this._asyncLocalStorage.getStore() ?? ROOT_CONTEXT;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
|
|
207
|
+
context: Context,
|
|
208
|
+
fn: F,
|
|
209
|
+
thisArg?: ThisParameterType<F>,
|
|
210
|
+
...args: A
|
|
211
|
+
): ReturnType<F> {
|
|
212
|
+
const cb = thisArg == null ? fn : fn.bind(thisArg);
|
|
213
|
+
return this._asyncLocalStorage.run(context, cb as never, ...args);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
enable(): this {
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
disable(): this {
|
|
221
|
+
this._asyncLocalStorage.disable();
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight OTLP exporter for edge environments
|
|
3
|
+
* Ported and adapted from @microlabs/
|
|
4
|
+
*
|
|
5
|
+
* This exporter is much smaller than the standard @opentelemetry/exporter-trace-otlp-http
|
|
6
|
+
* because it uses fetch() directly instead of Node.js http/https modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExportResult } from '@opentelemetry/core';
|
|
10
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
11
|
+
import { OTLPExporterError } from '@opentelemetry/otlp-exporter-base';
|
|
12
|
+
import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer';
|
|
13
|
+
import type { SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
14
|
+
import type { OTLPExporterConfig } from '../types';
|
|
15
|
+
|
|
16
|
+
// Version is injected at build time via tsup define
|
|
17
|
+
// This avoids runtime filesystem access which isn't available in edge environments
|
|
18
|
+
const PACKAGE_VERSION = process.env.AUTOLEMETRY_EDGE_VERSION || '0.1.1';
|
|
19
|
+
|
|
20
|
+
const defaultHeaders: Record<string, string> = {
|
|
21
|
+
accept: 'application/json',
|
|
22
|
+
'content-type': 'application/json',
|
|
23
|
+
'user-agent': `autotel-edge v${PACKAGE_VERSION}`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Minimal OTLP exporter using fetch()
|
|
28
|
+
*/
|
|
29
|
+
export class OTLPExporter implements SpanExporter {
|
|
30
|
+
private headers: Record<string, string>;
|
|
31
|
+
private url: string;
|
|
32
|
+
|
|
33
|
+
constructor(config: OTLPExporterConfig) {
|
|
34
|
+
this.url = config.url;
|
|
35
|
+
this.headers = Object.assign({}, defaultHeaders, config.headers);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export(items: any[], resultCallback: (result: ExportResult) => void): void {
|
|
39
|
+
this._export(items)
|
|
40
|
+
.then(() => {
|
|
41
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
42
|
+
})
|
|
43
|
+
.catch((error) => {
|
|
44
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _export(items: any[]): Promise<unknown> {
|
|
49
|
+
return new Promise<void>((resolve, reject) => {
|
|
50
|
+
try {
|
|
51
|
+
this.send(items, resolve, reject);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
reject(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
send(
|
|
59
|
+
items: any[],
|
|
60
|
+
onSuccess: () => void,
|
|
61
|
+
onError: (error: OTLPExporterError) => void,
|
|
62
|
+
): void {
|
|
63
|
+
const decoder = new TextDecoder();
|
|
64
|
+
const exportMessage = JsonTraceSerializer.serializeRequest(items);
|
|
65
|
+
|
|
66
|
+
const body = decoder.decode(exportMessage);
|
|
67
|
+
const params: RequestInit = {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: this.headers,
|
|
70
|
+
body,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
fetch(this.url, params)
|
|
74
|
+
.then((response) => {
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
onSuccess();
|
|
77
|
+
} else {
|
|
78
|
+
onError(
|
|
79
|
+
new OTLPExporterError(
|
|
80
|
+
`Exporter received a statusCode: ${response.status}`,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.catch((error) => {
|
|
86
|
+
onError(
|
|
87
|
+
new OTLPExporterError(
|
|
88
|
+
`Exception during export: ${error.toString()}`,
|
|
89
|
+
error.code,
|
|
90
|
+
error.stack,
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async shutdown(): Promise<void> {
|
|
97
|
+
// No-op for edge environments
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracer provider for edge environments
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { trace } from '@opentelemetry/api';
|
|
6
|
+
import type { Resource } from '@opentelemetry/resources';
|
|
7
|
+
import type { SpanProcessor, TracerConfig } from '@opentelemetry/sdk-trace-base';
|
|
8
|
+
import { WorkerTracer } from './tracer';
|
|
9
|
+
import { AsyncLocalStorageContextManager } from './context';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* WorkerTracerProvider - Registers tracer globally
|
|
13
|
+
*/
|
|
14
|
+
export class WorkerTracerProvider {
|
|
15
|
+
private tracer: WorkerTracer;
|
|
16
|
+
private contextManager: AsyncLocalStorageContextManager;
|
|
17
|
+
|
|
18
|
+
constructor(spanProcessors: SpanProcessor[], resource: Resource) {
|
|
19
|
+
this.tracer = new WorkerTracer(spanProcessors, resource);
|
|
20
|
+
this.contextManager = new AsyncLocalStorageContextManager();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the tracer instance
|
|
25
|
+
*/
|
|
26
|
+
getTracer(_name: string, _version?: string, _config?: TracerConfig): WorkerTracer {
|
|
27
|
+
return this.tracer;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register this provider as the global tracer
|
|
32
|
+
*/
|
|
33
|
+
register(): void {
|
|
34
|
+
// Enable context manager
|
|
35
|
+
this.contextManager.enable();
|
|
36
|
+
|
|
37
|
+
// Set tracer provider
|
|
38
|
+
const provider = {
|
|
39
|
+
getTracer: (_name: string, _version?: string) => this.tracer,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// @ts-ignore - OTel types
|
|
43
|
+
trace.setGlobalTracerProvider(provider);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/core/span.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight Span implementation for edge environments
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Attributes,
|
|
7
|
+
AttributeValue,
|
|
8
|
+
Exception,
|
|
9
|
+
HrTime,
|
|
10
|
+
Link,
|
|
11
|
+
Span,
|
|
12
|
+
SpanContext,
|
|
13
|
+
SpanKind,
|
|
14
|
+
SpanStatus,
|
|
15
|
+
SpanStatusCode,
|
|
16
|
+
TimeInput,
|
|
17
|
+
} from '@opentelemetry/api';
|
|
18
|
+
import {
|
|
19
|
+
hrTimeDuration,
|
|
20
|
+
type InstrumentationScope,
|
|
21
|
+
isAttributeValue,
|
|
22
|
+
isTimeInput,
|
|
23
|
+
sanitizeAttributes,
|
|
24
|
+
} from '@opentelemetry/core';
|
|
25
|
+
import type { Resource } from '@opentelemetry/resources';
|
|
26
|
+
import type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base';
|
|
27
|
+
import { SEMATTRS_EXCEPTION_MESSAGE, SEMATTRS_EXCEPTION_STACKTRACE, SEMATTRS_EXCEPTION_TYPE } from '@opentelemetry/semantic-conventions';
|
|
28
|
+
|
|
29
|
+
type OnSpanEnd = (span: Span) => void;
|
|
30
|
+
|
|
31
|
+
interface SpanInit {
|
|
32
|
+
attributes: unknown;
|
|
33
|
+
name: string;
|
|
34
|
+
onEnd: OnSpanEnd;
|
|
35
|
+
resource: Resource;
|
|
36
|
+
spanContext: SpanContext;
|
|
37
|
+
parentSpanContext?: SpanContext;
|
|
38
|
+
links?: Link[];
|
|
39
|
+
parentSpanId?: string;
|
|
40
|
+
spanKind?: SpanKind;
|
|
41
|
+
startTime?: TimeInput;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function transformExceptionAttributes(exception: Exception): Attributes {
|
|
45
|
+
const attributes: Attributes = {};
|
|
46
|
+
if (typeof exception === 'string') {
|
|
47
|
+
attributes[SEMATTRS_EXCEPTION_MESSAGE] = exception;
|
|
48
|
+
} else {
|
|
49
|
+
if (exception.code) {
|
|
50
|
+
attributes[SEMATTRS_EXCEPTION_TYPE] = exception.code.toString();
|
|
51
|
+
} else if (exception.name) {
|
|
52
|
+
attributes[SEMATTRS_EXCEPTION_TYPE] = exception.name;
|
|
53
|
+
}
|
|
54
|
+
if (exception.message) {
|
|
55
|
+
attributes[SEMATTRS_EXCEPTION_MESSAGE] = exception.message;
|
|
56
|
+
}
|
|
57
|
+
if (exception.stack) {
|
|
58
|
+
attributes[SEMATTRS_EXCEPTION_STACKTRACE] = exception.stack;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return attributes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function millisToHr(millis: number): HrTime {
|
|
65
|
+
return [Math.trunc(millis / 1000), (millis % 1000) * 1e6];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getHrTime(input?: TimeInput): HrTime {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
if (!input) {
|
|
71
|
+
return millisToHr(now);
|
|
72
|
+
} else if (input instanceof Date) {
|
|
73
|
+
return millisToHr(input.getTime());
|
|
74
|
+
} else if (typeof input === 'number') {
|
|
75
|
+
return millisToHr(input);
|
|
76
|
+
} else if (Array.isArray(input)) {
|
|
77
|
+
return input;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const v: never = input;
|
|
81
|
+
throw new Error(`unreachable value: ${JSON.stringify(v)}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Previously exported from OTel, now private
|
|
85
|
+
function isAttributeKey(key: unknown): key is string {
|
|
86
|
+
return typeof key === 'string' && key.length > 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lightweight Span implementation for edge runtimes
|
|
91
|
+
*/
|
|
92
|
+
export class SpanImpl implements Span, ReadableSpan {
|
|
93
|
+
name: string;
|
|
94
|
+
private readonly _spanContext: SpanContext;
|
|
95
|
+
private readonly onEnd: OnSpanEnd;
|
|
96
|
+
readonly parentSpanId?: string;
|
|
97
|
+
readonly parentSpanContext?: SpanContext | undefined;
|
|
98
|
+
readonly kind: SpanKind;
|
|
99
|
+
readonly attributes: Attributes;
|
|
100
|
+
status: SpanStatus = {
|
|
101
|
+
code: 0 as SpanStatusCode, // SpanStatusCode.UNSET
|
|
102
|
+
};
|
|
103
|
+
endTime: HrTime = [0, 0];
|
|
104
|
+
private _duration: HrTime = [0, 0];
|
|
105
|
+
readonly startTime: HrTime;
|
|
106
|
+
readonly events: TimedEvent[] = [];
|
|
107
|
+
readonly links: Link[];
|
|
108
|
+
readonly resource: Resource;
|
|
109
|
+
instrumentationScope: InstrumentationScope = {
|
|
110
|
+
name: 'autotel-edge',
|
|
111
|
+
};
|
|
112
|
+
private _ended: boolean = false;
|
|
113
|
+
private _droppedAttributesCount: number = 0;
|
|
114
|
+
private _droppedEventsCount: number = 0;
|
|
115
|
+
private _droppedLinksCount: number = 0;
|
|
116
|
+
|
|
117
|
+
constructor(init: SpanInit) {
|
|
118
|
+
this.name = init.name;
|
|
119
|
+
this._spanContext = init.spanContext;
|
|
120
|
+
this.parentSpanId = init.parentSpanId;
|
|
121
|
+
this.parentSpanContext = init.parentSpanContext;
|
|
122
|
+
this.kind = init.spanKind || (0 as SpanKind); // SpanKind.INTERNAL
|
|
123
|
+
this.attributes = sanitizeAttributes(init.attributes);
|
|
124
|
+
this.startTime = getHrTime(init.startTime);
|
|
125
|
+
this.links = init.links || [];
|
|
126
|
+
this.resource = init.resource;
|
|
127
|
+
this.onEnd = init.onEnd;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
addLink(link: Link): this {
|
|
131
|
+
this.links.push(link);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
addLinks(links: Link[]): this {
|
|
136
|
+
this.links.push(...links);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
spanContext(): SpanContext {
|
|
141
|
+
return this._spanContext;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setAttribute(key: string, value?: AttributeValue): this {
|
|
145
|
+
if (isAttributeKey(key) && isAttributeValue(value)) {
|
|
146
|
+
this.attributes[key] = value;
|
|
147
|
+
}
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setAttributes(attributes: Attributes): this {
|
|
152
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
153
|
+
this.setAttribute(key, value);
|
|
154
|
+
}
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
addEvent(
|
|
159
|
+
name: string,
|
|
160
|
+
attributesOrStartTime?: Attributes | TimeInput,
|
|
161
|
+
startTime?: TimeInput,
|
|
162
|
+
): this {
|
|
163
|
+
if (isTimeInput(attributesOrStartTime)) {
|
|
164
|
+
startTime = attributesOrStartTime;
|
|
165
|
+
attributesOrStartTime = undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const attributes = sanitizeAttributes(attributesOrStartTime);
|
|
169
|
+
const time = getHrTime(startTime);
|
|
170
|
+
this.events.push({ name, attributes, time });
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setStatus(status: SpanStatus): this {
|
|
175
|
+
this.status = status;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
updateName(name: string): this {
|
|
180
|
+
this.name = name;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
end(endTime?: TimeInput): void {
|
|
185
|
+
if (this._ended) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this._ended = true;
|
|
189
|
+
this.endTime = getHrTime(endTime);
|
|
190
|
+
this._duration = hrTimeDuration(this.startTime, this.endTime);
|
|
191
|
+
this.onEnd(this);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
isRecording(): boolean {
|
|
195
|
+
return !this._ended;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
recordException(exception: Exception, time?: TimeInput): void {
|
|
199
|
+
const attributes = transformExceptionAttributes(exception);
|
|
200
|
+
this.addEvent('exception', attributes, time);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get duration(): HrTime {
|
|
204
|
+
return this._duration;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get ended(): boolean {
|
|
208
|
+
return this._ended;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
get droppedAttributesCount(): number {
|
|
212
|
+
return this._droppedAttributesCount;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get droppedEventsCount(): number {
|
|
216
|
+
return this._droppedEventsCount;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get droppedLinksCount(): number {
|
|
220
|
+
return this._droppedLinksCount;
|
|
221
|
+
}
|
|
222
|
+
}
|