react-native-otel 0.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.
- package/LICENSE +20 -0
- package/README.md +37 -0
- package/lib/module/context/span-context.js +14 -0
- package/lib/module/context/span-context.js.map +1 -0
- package/lib/module/core/attributes.js +43 -0
- package/lib/module/core/attributes.js.map +1 -0
- package/lib/module/core/clock.js +8 -0
- package/lib/module/core/clock.js.map +1 -0
- package/lib/module/core/ids.js +16 -0
- package/lib/module/core/ids.js.map +1 -0
- package/lib/module/core/log-record.js +41 -0
- package/lib/module/core/log-record.js.map +1 -0
- package/lib/module/core/meter.js +70 -0
- package/lib/module/core/meter.js.map +1 -0
- package/lib/module/core/resource.js +20 -0
- package/lib/module/core/resource.js.map +1 -0
- package/lib/module/core/span.js +96 -0
- package/lib/module/core/span.js.map +1 -0
- package/lib/module/core/tracer.js +48 -0
- package/lib/module/core/tracer.js.map +1 -0
- package/lib/module/exporters/console-exporter.js +53 -0
- package/lib/module/exporters/console-exporter.js.map +1 -0
- package/lib/module/exporters/otlp-http-exporter.js +317 -0
- package/lib/module/exporters/otlp-http-exporter.js.map +1 -0
- package/lib/module/exporters/types.js +4 -0
- package/lib/module/exporters/types.js.map +1 -0
- package/lib/module/index.js +24 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/instrumentation/errors.js +63 -0
- package/lib/module/instrumentation/errors.js.map +1 -0
- package/lib/module/instrumentation/lifecycle.js +15 -0
- package/lib/module/instrumentation/lifecycle.js.map +1 -0
- package/lib/module/instrumentation/navigation.js +51 -0
- package/lib/module/instrumentation/navigation.js.map +1 -0
- package/lib/module/instrumentation/network.js +183 -0
- package/lib/module/instrumentation/network.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react/OtelProvider.js +57 -0
- package/lib/module/react/OtelProvider.js.map +1 -0
- package/lib/module/react/useOtel.js +12 -0
- package/lib/module/react/useOtel.js.map +1 -0
- package/lib/module/sdk.js +127 -0
- package/lib/module/sdk.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/context/span-context.d.ts +9 -0
- package/lib/typescript/src/context/span-context.d.ts.map +1 -0
- package/lib/typescript/src/core/attributes.d.ts +6 -0
- package/lib/typescript/src/core/attributes.d.ts.map +1 -0
- package/lib/typescript/src/core/clock.d.ts +2 -0
- package/lib/typescript/src/core/clock.d.ts.map +1 -0
- package/lib/typescript/src/core/ids.d.ts +3 -0
- package/lib/typescript/src/core/ids.d.ts.map +1 -0
- package/lib/typescript/src/core/log-record.d.ts +15 -0
- package/lib/typescript/src/core/log-record.d.ts.map +1 -0
- package/lib/typescript/src/core/meter.d.ts +30 -0
- package/lib/typescript/src/core/meter.d.ts.map +1 -0
- package/lib/typescript/src/core/resource.d.ts +25 -0
- package/lib/typescript/src/core/resource.d.ts.map +1 -0
- package/lib/typescript/src/core/span.d.ts +77 -0
- package/lib/typescript/src/core/span.d.ts.map +1 -0
- package/lib/typescript/src/core/tracer.d.ts +21 -0
- package/lib/typescript/src/core/tracer.d.ts.map +1 -0
- package/lib/typescript/src/exporters/console-exporter.d.ts +17 -0
- package/lib/typescript/src/exporters/console-exporter.d.ts.map +1 -0
- package/lib/typescript/src/exporters/otlp-http-exporter.d.ts +58 -0
- package/lib/typescript/src/exporters/otlp-http-exporter.d.ts.map +1 -0
- package/lib/typescript/src/exporters/types.d.ts +25 -0
- package/lib/typescript/src/exporters/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/instrumentation/errors.d.ts +15 -0
- package/lib/typescript/src/instrumentation/errors.d.ts.map +1 -0
- package/lib/typescript/src/instrumentation/lifecycle.d.ts +3 -0
- package/lib/typescript/src/instrumentation/lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/instrumentation/navigation.d.ts +7 -0
- package/lib/typescript/src/instrumentation/navigation.d.ts.map +1 -0
- package/lib/typescript/src/instrumentation/network.d.ts +33 -0
- package/lib/typescript/src/instrumentation/network.d.ts.map +1 -0
- package/lib/typescript/src/react/OtelProvider.d.ts +21 -0
- package/lib/typescript/src/react/OtelProvider.d.ts.map +1 -0
- package/lib/typescript/src/react/useOtel.d.ts +3 -0
- package/lib/typescript/src/react/useOtel.d.ts.map +1 -0
- package/lib/typescript/src/sdk.d.ts +50 -0
- package/lib/typescript/src/sdk.d.ts.map +1 -0
- package/package.json +125 -0
- package/src/context/span-context.ts +17 -0
- package/src/core/attributes.ts +61 -0
- package/src/core/clock.ts +5 -0
- package/src/core/ids.ts +15 -0
- package/src/core/log-record.ts +58 -0
- package/src/core/meter.ts +82 -0
- package/src/core/resource.ts +50 -0
- package/src/core/span.ts +161 -0
- package/src/core/tracer.ts +75 -0
- package/src/exporters/console-exporter.ts +89 -0
- package/src/exporters/otlp-http-exporter.ts +377 -0
- package/src/exporters/types.ts +29 -0
- package/src/index.ts +63 -0
- package/src/instrumentation/errors.ts +95 -0
- package/src/instrumentation/lifecycle.ts +17 -0
- package/src/instrumentation/navigation.ts +61 -0
- package/src/instrumentation/network.ts +253 -0
- package/src/react/OtelProvider.tsx +98 -0
- package/src/react/useOtel.ts +14 -0
- package/src/sdk.ts +179 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Attributes } from './attributes';
|
|
2
|
+
import type { MetricExporter, MetricRecord } from '../exporters/types';
|
|
3
|
+
import { sanitizeAttributes } from './attributes';
|
|
4
|
+
import { now } from './clock';
|
|
5
|
+
|
|
6
|
+
export class Counter {
|
|
7
|
+
constructor(
|
|
8
|
+
private name: string,
|
|
9
|
+
private pushToBuffer: (record: MetricRecord) => void
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
add(value: number, attrs?: Attributes): void {
|
|
13
|
+
this.pushToBuffer({
|
|
14
|
+
type: 'counter',
|
|
15
|
+
name: this.name,
|
|
16
|
+
value,
|
|
17
|
+
timestampMs: now(),
|
|
18
|
+
attributes: attrs ? sanitizeAttributes(attrs) : {},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Histogram {
|
|
24
|
+
constructor(
|
|
25
|
+
private name: string,
|
|
26
|
+
private pushToBuffer: (record: MetricRecord) => void
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
record(value: number, attrs?: Attributes): void {
|
|
30
|
+
this.pushToBuffer({
|
|
31
|
+
type: 'histogram',
|
|
32
|
+
name: this.name,
|
|
33
|
+
value,
|
|
34
|
+
timestampMs: now(),
|
|
35
|
+
attributes: attrs ? sanitizeAttributes(attrs) : {},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class Gauge {
|
|
41
|
+
constructor(
|
|
42
|
+
private name: string,
|
|
43
|
+
private pushToBuffer: (record: MetricRecord) => void
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
set(value: number, attrs?: Attributes): void {
|
|
47
|
+
this.pushToBuffer({
|
|
48
|
+
type: 'gauge',
|
|
49
|
+
name: this.name,
|
|
50
|
+
value,
|
|
51
|
+
timestampMs: now(),
|
|
52
|
+
attributes: attrs ? sanitizeAttributes(attrs) : {},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class Meter {
|
|
58
|
+
private buffer: MetricRecord[] = [];
|
|
59
|
+
private exporter: MetricExporter | undefined;
|
|
60
|
+
|
|
61
|
+
constructor(exporter?: MetricExporter) {
|
|
62
|
+
this.exporter = exporter;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
createCounter(name: string): Counter {
|
|
66
|
+
return new Counter(name, (r) => this.buffer.push(r));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
createHistogram(name: string): Histogram {
|
|
70
|
+
return new Histogram(name, (r) => this.buffer.push(r));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createGauge(name: string): Gauge {
|
|
74
|
+
return new Gauge(name, (r) => this.buffer.push(r));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
flush(): void {
|
|
78
|
+
if (this.buffer.length === 0) return;
|
|
79
|
+
const toExport = this.buffer.splice(0, this.buffer.length);
|
|
80
|
+
this.exporter?.export(toExport);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTR_SERVICE_NAME,
|
|
3
|
+
ATTR_SERVICE_VERSION,
|
|
4
|
+
} from '@opentelemetry/semantic-conventions';
|
|
5
|
+
import {
|
|
6
|
+
ATTR_APP_BUILD_ID,
|
|
7
|
+
ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
|
|
8
|
+
ATTR_DEVICE_MANUFACTURER,
|
|
9
|
+
ATTR_DEVICE_MODEL_NAME,
|
|
10
|
+
ATTR_OS_NAME,
|
|
11
|
+
ATTR_OS_VERSION,
|
|
12
|
+
} from '@opentelemetry/semantic-conventions/incubating';
|
|
13
|
+
|
|
14
|
+
export interface Resource {
|
|
15
|
+
[ATTR_SERVICE_NAME]: string; // 'service.name'
|
|
16
|
+
[ATTR_SERVICE_VERSION]: string; // 'service.version'
|
|
17
|
+
[ATTR_OS_NAME]: string; // 'os.name'
|
|
18
|
+
[ATTR_OS_VERSION]: string; // 'os.version'
|
|
19
|
+
[ATTR_DEVICE_MANUFACTURER]: string; // 'device.manufacturer'
|
|
20
|
+
[ATTR_DEVICE_MODEL_NAME]: string; // 'device.model.name'
|
|
21
|
+
'device.type': string | number; // custom — no OTel equivalent
|
|
22
|
+
[ATTR_APP_BUILD_ID]: string; // 'app.build_id'
|
|
23
|
+
[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: string; // 'deployment.environment.name'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Populated at init time from device/app info passed in by the caller.
|
|
27
|
+
// Immutable after creation — user identity is NOT stored here.
|
|
28
|
+
export function buildResource(params: {
|
|
29
|
+
serviceName: string;
|
|
30
|
+
serviceVersion: string;
|
|
31
|
+
osName: string;
|
|
32
|
+
osVersion: string;
|
|
33
|
+
deviceBrand: string;
|
|
34
|
+
deviceModel: string;
|
|
35
|
+
deviceType: string | number;
|
|
36
|
+
appBuild: string;
|
|
37
|
+
environment: string;
|
|
38
|
+
}): Readonly<Resource> {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
[ATTR_SERVICE_NAME]: params.serviceName,
|
|
41
|
+
[ATTR_SERVICE_VERSION]: params.serviceVersion,
|
|
42
|
+
[ATTR_OS_NAME]: params.osName,
|
|
43
|
+
[ATTR_OS_VERSION]: params.osVersion,
|
|
44
|
+
[ATTR_DEVICE_MANUFACTURER]: params.deviceBrand,
|
|
45
|
+
[ATTR_DEVICE_MODEL_NAME]: params.deviceModel,
|
|
46
|
+
'device.type': params.deviceType,
|
|
47
|
+
[ATTR_APP_BUILD_ID]: params.appBuild,
|
|
48
|
+
[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: params.environment,
|
|
49
|
+
});
|
|
50
|
+
}
|
package/src/core/span.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTR_EXCEPTION_MESSAGE,
|
|
3
|
+
ATTR_EXCEPTION_STACKTRACE,
|
|
4
|
+
ATTR_EXCEPTION_TYPE,
|
|
5
|
+
} from '@opentelemetry/semantic-conventions';
|
|
6
|
+
|
|
7
|
+
import type { Attributes } from './attributes';
|
|
8
|
+
import { sanitizeAttributes, sanitizeValue } from './attributes';
|
|
9
|
+
import { now } from './clock';
|
|
10
|
+
import { generateSpanId, generateTraceId } from './ids';
|
|
11
|
+
|
|
12
|
+
// Defined here to avoid circular dep with exporters/types.ts
|
|
13
|
+
export interface SpanExporter {
|
|
14
|
+
export(spans: ReadonlySpan[]): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SpanKind =
|
|
18
|
+
| 'INTERNAL'
|
|
19
|
+
| 'CLIENT'
|
|
20
|
+
| 'SERVER'
|
|
21
|
+
| 'PRODUCER'
|
|
22
|
+
| 'CONSUMER';
|
|
23
|
+
export type SpanStatus = 'UNSET' | 'OK' | 'ERROR';
|
|
24
|
+
|
|
25
|
+
export interface SpanEvent {
|
|
26
|
+
name: string;
|
|
27
|
+
timestampMs: number;
|
|
28
|
+
attributes: Attributes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ReadonlySpan {
|
|
32
|
+
readonly traceId: string;
|
|
33
|
+
readonly spanId: string;
|
|
34
|
+
readonly parentSpanId: string | undefined;
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly kind: SpanKind;
|
|
37
|
+
readonly startTimeMs: number;
|
|
38
|
+
readonly endTimeMs: number | undefined;
|
|
39
|
+
readonly attributes: Readonly<Attributes>;
|
|
40
|
+
readonly events: readonly SpanEvent[];
|
|
41
|
+
readonly droppedEventsCount: number;
|
|
42
|
+
readonly status: SpanStatus;
|
|
43
|
+
readonly statusMessage: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Carries both IDs needed to link a child span into an existing trace.
|
|
47
|
+
export interface SpanContext {
|
|
48
|
+
traceId: string;
|
|
49
|
+
spanId: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class Span implements ReadonlySpan {
|
|
53
|
+
private static readonly MAX_EVENTS = 128;
|
|
54
|
+
|
|
55
|
+
readonly traceId: string;
|
|
56
|
+
readonly spanId: string;
|
|
57
|
+
readonly parentSpanId: string | undefined;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly kind: SpanKind;
|
|
60
|
+
readonly startTimeMs: number;
|
|
61
|
+
|
|
62
|
+
endTimeMs: number | undefined = undefined;
|
|
63
|
+
// Mutable plain object — setAttribute writes directly, no full clone.
|
|
64
|
+
attributes: Attributes;
|
|
65
|
+
events: SpanEvent[] = [];
|
|
66
|
+
droppedEventsCount = 0;
|
|
67
|
+
status: SpanStatus = 'UNSET';
|
|
68
|
+
statusMessage: string | undefined = undefined;
|
|
69
|
+
|
|
70
|
+
private exporter: SpanExporter | undefined;
|
|
71
|
+
|
|
72
|
+
constructor(params: {
|
|
73
|
+
name: string;
|
|
74
|
+
kind?: SpanKind;
|
|
75
|
+
attributes?: Attributes;
|
|
76
|
+
// Pass the full parent context so the child inherits the same traceId.
|
|
77
|
+
parent?: SpanContext;
|
|
78
|
+
exporter?: SpanExporter;
|
|
79
|
+
}) {
|
|
80
|
+
this.traceId = params.parent?.traceId ?? generateTraceId();
|
|
81
|
+
this.spanId = generateSpanId();
|
|
82
|
+
this.parentSpanId = params.parent?.spanId;
|
|
83
|
+
this.name = params.name;
|
|
84
|
+
this.kind = params.kind ?? 'INTERNAL';
|
|
85
|
+
this.startTimeMs = now();
|
|
86
|
+
this.attributes = params.attributes
|
|
87
|
+
? sanitizeAttributes(params.attributes)
|
|
88
|
+
: {};
|
|
89
|
+
this.exporter = params.exporter;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setAttribute(key: string, value: Attributes[string]): void {
|
|
93
|
+
if (this.endTimeMs !== undefined) return;
|
|
94
|
+
// sanitizeValue handles one value — no object clone needed.
|
|
95
|
+
const sanitized = sanitizeValue(value);
|
|
96
|
+
if (sanitized !== undefined) {
|
|
97
|
+
this.attributes[key] = sanitized;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
addEvent(name: string, attrs?: Attributes): void {
|
|
102
|
+
if (this.endTimeMs !== undefined) return;
|
|
103
|
+
if (this.events.length >= Span.MAX_EVENTS) {
|
|
104
|
+
this.droppedEventsCount++;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.events.push({
|
|
108
|
+
name,
|
|
109
|
+
timestampMs: now(),
|
|
110
|
+
attributes: attrs ? sanitizeAttributes(attrs) : {},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setStatus(status: SpanStatus, message?: string): void {
|
|
115
|
+
if (this.endTimeMs !== undefined) return;
|
|
116
|
+
this.status = status;
|
|
117
|
+
this.statusMessage = message;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
recordException(error: Error, attrs?: Attributes): void {
|
|
121
|
+
this.addEvent('exception', {
|
|
122
|
+
[ATTR_EXCEPTION_TYPE]: error.name,
|
|
123
|
+
[ATTR_EXCEPTION_MESSAGE]: error.message,
|
|
124
|
+
[ATTR_EXCEPTION_STACKTRACE]: error.stack ?? '',
|
|
125
|
+
...attrs,
|
|
126
|
+
});
|
|
127
|
+
this.setStatus('ERROR', error.message);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
end(): void {
|
|
131
|
+
if (this.endTimeMs !== undefined) return;
|
|
132
|
+
this.endTimeMs = now();
|
|
133
|
+
if (this.status === 'UNSET') {
|
|
134
|
+
this.status = 'OK';
|
|
135
|
+
}
|
|
136
|
+
this.exporter?.export([this]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// No-op span used when sampling drops a span.
|
|
141
|
+
// Implements the same interface so callers never null-check.
|
|
142
|
+
export class NoopSpan implements ReadonlySpan {
|
|
143
|
+
readonly traceId = '';
|
|
144
|
+
readonly spanId = '';
|
|
145
|
+
readonly parentSpanId = undefined;
|
|
146
|
+
readonly name = '';
|
|
147
|
+
readonly kind: SpanKind = 'INTERNAL';
|
|
148
|
+
readonly startTimeMs = 0;
|
|
149
|
+
readonly endTimeMs = undefined;
|
|
150
|
+
readonly attributes = {};
|
|
151
|
+
readonly events: SpanEvent[] = [];
|
|
152
|
+
readonly droppedEventsCount = 0;
|
|
153
|
+
readonly status: SpanStatus = 'UNSET';
|
|
154
|
+
readonly statusMessage = undefined;
|
|
155
|
+
|
|
156
|
+
setAttribute(_key: string, _value: Attributes[string]): void {}
|
|
157
|
+
addEvent(_name: string, _attrs?: Attributes): void {}
|
|
158
|
+
setStatus(_status: SpanStatus, _message?: string): void {}
|
|
159
|
+
recordException(_error: Error, _attrs?: Attributes): void {}
|
|
160
|
+
end(): void {}
|
|
161
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTR_EXCEPTION_MESSAGE,
|
|
3
|
+
ATTR_EXCEPTION_STACKTRACE,
|
|
4
|
+
ATTR_EXCEPTION_TYPE,
|
|
5
|
+
} from '@opentelemetry/semantic-conventions';
|
|
6
|
+
|
|
7
|
+
import type { Attributes } from './attributes';
|
|
8
|
+
import type { SpanContext, SpanExporter, SpanKind } from './span';
|
|
9
|
+
import { Span, NoopSpan } from './span';
|
|
10
|
+
import { spanContext } from '../context/span-context';
|
|
11
|
+
|
|
12
|
+
export class Tracer {
|
|
13
|
+
private exporter: SpanExporter | undefined;
|
|
14
|
+
private sampleRate: number;
|
|
15
|
+
private getUserAttributes: () => Attributes;
|
|
16
|
+
|
|
17
|
+
constructor(params: {
|
|
18
|
+
exporter?: SpanExporter;
|
|
19
|
+
sampleRate?: number;
|
|
20
|
+
getUserAttributes: () => Attributes;
|
|
21
|
+
}) {
|
|
22
|
+
this.exporter = params.exporter;
|
|
23
|
+
this.sampleRate = params.sampleRate ?? 1.0;
|
|
24
|
+
this.getUserAttributes = params.getUserAttributes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
startSpan(
|
|
28
|
+
name: string,
|
|
29
|
+
options?: {
|
|
30
|
+
kind?: SpanKind;
|
|
31
|
+
attributes?: Attributes;
|
|
32
|
+
// Pass the full parent context to inherit traceId.
|
|
33
|
+
// If omitted, the current screen span is used automatically.
|
|
34
|
+
// Pass null explicitly to force a new root trace.
|
|
35
|
+
parent?: SpanContext | null;
|
|
36
|
+
}
|
|
37
|
+
): Span | NoopSpan {
|
|
38
|
+
if (this.sampleRate < 1.0 && Math.random() > this.sampleRate) {
|
|
39
|
+
return new NoopSpan();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Resolve parent: explicit > current screen span > none (new trace)
|
|
43
|
+
const parent: SpanContext | undefined =
|
|
44
|
+
options?.parent !== undefined
|
|
45
|
+
? options.parent ?? undefined
|
|
46
|
+
: spanContext.current() ?? undefined;
|
|
47
|
+
|
|
48
|
+
const userAttrs = this.getUserAttributes();
|
|
49
|
+
return new Span({
|
|
50
|
+
name,
|
|
51
|
+
kind: options?.kind,
|
|
52
|
+
attributes: { ...userAttrs, ...options?.attributes },
|
|
53
|
+
parent,
|
|
54
|
+
exporter: this.exporter,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
recordEvent(name: string, attributes?: Attributes): void {
|
|
59
|
+
spanContext.current()?.addEvent(name, attributes);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
recordException(error: Error, attributes?: Attributes): void {
|
|
63
|
+
const span = this.startSpan(`exception.${error.name}`, {
|
|
64
|
+
kind: 'INTERNAL',
|
|
65
|
+
attributes: {
|
|
66
|
+
[ATTR_EXCEPTION_TYPE]: error.name,
|
|
67
|
+
[ATTR_EXCEPTION_MESSAGE]: error.message,
|
|
68
|
+
[ATTR_EXCEPTION_STACKTRACE]: error.stack ?? '',
|
|
69
|
+
...attributes,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
span.setStatus('ERROR', error.message);
|
|
73
|
+
span.end();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SpanExporter,
|
|
3
|
+
MetricExporter,
|
|
4
|
+
LogExporter,
|
|
5
|
+
ReadonlySpan,
|
|
6
|
+
MetricRecord,
|
|
7
|
+
LogRecord,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
function shouldLog(debug: boolean): boolean {
|
|
11
|
+
return debug || process.env.NODE_ENV === 'development';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ConsoleSpanExporter implements SpanExporter {
|
|
15
|
+
constructor(private debug = false) {}
|
|
16
|
+
|
|
17
|
+
export(spans: ReadonlySpan[]): void {
|
|
18
|
+
if (!shouldLog(this.debug)) return;
|
|
19
|
+
|
|
20
|
+
for (const span of spans) {
|
|
21
|
+
const duration =
|
|
22
|
+
span.endTimeMs !== undefined
|
|
23
|
+
? `${span.endTimeMs - span.startTimeMs}ms`
|
|
24
|
+
: 'ongoing';
|
|
25
|
+
const lines: string[] = [
|
|
26
|
+
`[OTEL SPAN] ${span.name} (traceId=${span.traceId} spanId=${
|
|
27
|
+
span.spanId
|
|
28
|
+
})${span.parentSpanId ? ` parentSpanId=${span.parentSpanId}` : ''}`,
|
|
29
|
+
` duration: ${duration} status: ${span.status}${
|
|
30
|
+
span.statusMessage ? ` (${span.statusMessage})` : ''
|
|
31
|
+
}`,
|
|
32
|
+
` attributes: ${JSON.stringify(span.attributes)}`,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
if (span.events.length > 0) {
|
|
36
|
+
lines.push(' events:');
|
|
37
|
+
for (const event of span.events) {
|
|
38
|
+
const offset = event.timestampMs - span.startTimeMs;
|
|
39
|
+
const attrsStr =
|
|
40
|
+
Object.keys(event.attributes).length > 0
|
|
41
|
+
? ` ${JSON.stringify(event.attributes)}`
|
|
42
|
+
: '{}';
|
|
43
|
+
lines.push(` [+${offset}ms] ${event.name} ${attrsStr}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (span.droppedEventsCount > 0) {
|
|
48
|
+
lines.push(` dropped_events: ${span.droppedEventsCount}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(lines.join('\n'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ConsoleMetricExporter implements MetricExporter {
|
|
57
|
+
constructor(private debug = false) {}
|
|
58
|
+
|
|
59
|
+
export(metrics: MetricRecord[]): void {
|
|
60
|
+
if (!shouldLog(this.debug)) return;
|
|
61
|
+
|
|
62
|
+
for (const metric of metrics) {
|
|
63
|
+
console.log(
|
|
64
|
+
`[OTEL METRIC] ${metric.name} ${metric.type} value=${metric.value}`,
|
|
65
|
+
Object.keys(metric.attributes).length > 0 ? metric.attributes : ''
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class ConsoleLogExporter implements LogExporter {
|
|
72
|
+
constructor(private debug = false) {}
|
|
73
|
+
|
|
74
|
+
export(logs: LogRecord[]): void {
|
|
75
|
+
if (!shouldLog(this.debug)) return;
|
|
76
|
+
|
|
77
|
+
for (const log of logs) {
|
|
78
|
+
const traceInfo = log.traceId
|
|
79
|
+
? `{ trace: ${log.traceId}, span: ${log.spanId} }`
|
|
80
|
+
: '';
|
|
81
|
+
|
|
82
|
+
console.log(
|
|
83
|
+
`[OTEL LOG] ${log.severity.padEnd(5)} ${log.body}`,
|
|
84
|
+
traceInfo,
|
|
85
|
+
Object.keys(log.attributes).length > 0 ? log.attributes : ''
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|