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.
Files changed (105) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +37 -0
  3. package/lib/module/context/span-context.js +14 -0
  4. package/lib/module/context/span-context.js.map +1 -0
  5. package/lib/module/core/attributes.js +43 -0
  6. package/lib/module/core/attributes.js.map +1 -0
  7. package/lib/module/core/clock.js +8 -0
  8. package/lib/module/core/clock.js.map +1 -0
  9. package/lib/module/core/ids.js +16 -0
  10. package/lib/module/core/ids.js.map +1 -0
  11. package/lib/module/core/log-record.js +41 -0
  12. package/lib/module/core/log-record.js.map +1 -0
  13. package/lib/module/core/meter.js +70 -0
  14. package/lib/module/core/meter.js.map +1 -0
  15. package/lib/module/core/resource.js +20 -0
  16. package/lib/module/core/resource.js.map +1 -0
  17. package/lib/module/core/span.js +96 -0
  18. package/lib/module/core/span.js.map +1 -0
  19. package/lib/module/core/tracer.js +48 -0
  20. package/lib/module/core/tracer.js.map +1 -0
  21. package/lib/module/exporters/console-exporter.js +53 -0
  22. package/lib/module/exporters/console-exporter.js.map +1 -0
  23. package/lib/module/exporters/otlp-http-exporter.js +317 -0
  24. package/lib/module/exporters/otlp-http-exporter.js.map +1 -0
  25. package/lib/module/exporters/types.js +4 -0
  26. package/lib/module/exporters/types.js.map +1 -0
  27. package/lib/module/index.js +24 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/module/instrumentation/errors.js +63 -0
  30. package/lib/module/instrumentation/errors.js.map +1 -0
  31. package/lib/module/instrumentation/lifecycle.js +15 -0
  32. package/lib/module/instrumentation/lifecycle.js.map +1 -0
  33. package/lib/module/instrumentation/navigation.js +51 -0
  34. package/lib/module/instrumentation/navigation.js.map +1 -0
  35. package/lib/module/instrumentation/network.js +183 -0
  36. package/lib/module/instrumentation/network.js.map +1 -0
  37. package/lib/module/package.json +1 -0
  38. package/lib/module/react/OtelProvider.js +57 -0
  39. package/lib/module/react/OtelProvider.js.map +1 -0
  40. package/lib/module/react/useOtel.js +12 -0
  41. package/lib/module/react/useOtel.js.map +1 -0
  42. package/lib/module/sdk.js +127 -0
  43. package/lib/module/sdk.js.map +1 -0
  44. package/lib/typescript/package.json +1 -0
  45. package/lib/typescript/src/context/span-context.d.ts +9 -0
  46. package/lib/typescript/src/context/span-context.d.ts.map +1 -0
  47. package/lib/typescript/src/core/attributes.d.ts +6 -0
  48. package/lib/typescript/src/core/attributes.d.ts.map +1 -0
  49. package/lib/typescript/src/core/clock.d.ts +2 -0
  50. package/lib/typescript/src/core/clock.d.ts.map +1 -0
  51. package/lib/typescript/src/core/ids.d.ts +3 -0
  52. package/lib/typescript/src/core/ids.d.ts.map +1 -0
  53. package/lib/typescript/src/core/log-record.d.ts +15 -0
  54. package/lib/typescript/src/core/log-record.d.ts.map +1 -0
  55. package/lib/typescript/src/core/meter.d.ts +30 -0
  56. package/lib/typescript/src/core/meter.d.ts.map +1 -0
  57. package/lib/typescript/src/core/resource.d.ts +25 -0
  58. package/lib/typescript/src/core/resource.d.ts.map +1 -0
  59. package/lib/typescript/src/core/span.d.ts +77 -0
  60. package/lib/typescript/src/core/span.d.ts.map +1 -0
  61. package/lib/typescript/src/core/tracer.d.ts +21 -0
  62. package/lib/typescript/src/core/tracer.d.ts.map +1 -0
  63. package/lib/typescript/src/exporters/console-exporter.d.ts +17 -0
  64. package/lib/typescript/src/exporters/console-exporter.d.ts.map +1 -0
  65. package/lib/typescript/src/exporters/otlp-http-exporter.d.ts +58 -0
  66. package/lib/typescript/src/exporters/otlp-http-exporter.d.ts.map +1 -0
  67. package/lib/typescript/src/exporters/types.d.ts +25 -0
  68. package/lib/typescript/src/exporters/types.d.ts.map +1 -0
  69. package/lib/typescript/src/index.d.ts +25 -0
  70. package/lib/typescript/src/index.d.ts.map +1 -0
  71. package/lib/typescript/src/instrumentation/errors.d.ts +15 -0
  72. package/lib/typescript/src/instrumentation/errors.d.ts.map +1 -0
  73. package/lib/typescript/src/instrumentation/lifecycle.d.ts +3 -0
  74. package/lib/typescript/src/instrumentation/lifecycle.d.ts.map +1 -0
  75. package/lib/typescript/src/instrumentation/navigation.d.ts +7 -0
  76. package/lib/typescript/src/instrumentation/navigation.d.ts.map +1 -0
  77. package/lib/typescript/src/instrumentation/network.d.ts +33 -0
  78. package/lib/typescript/src/instrumentation/network.d.ts.map +1 -0
  79. package/lib/typescript/src/react/OtelProvider.d.ts +21 -0
  80. package/lib/typescript/src/react/OtelProvider.d.ts.map +1 -0
  81. package/lib/typescript/src/react/useOtel.d.ts +3 -0
  82. package/lib/typescript/src/react/useOtel.d.ts.map +1 -0
  83. package/lib/typescript/src/sdk.d.ts +50 -0
  84. package/lib/typescript/src/sdk.d.ts.map +1 -0
  85. package/package.json +125 -0
  86. package/src/context/span-context.ts +17 -0
  87. package/src/core/attributes.ts +61 -0
  88. package/src/core/clock.ts +5 -0
  89. package/src/core/ids.ts +15 -0
  90. package/src/core/log-record.ts +58 -0
  91. package/src/core/meter.ts +82 -0
  92. package/src/core/resource.ts +50 -0
  93. package/src/core/span.ts +161 -0
  94. package/src/core/tracer.ts +75 -0
  95. package/src/exporters/console-exporter.ts +89 -0
  96. package/src/exporters/otlp-http-exporter.ts +377 -0
  97. package/src/exporters/types.ts +29 -0
  98. package/src/index.ts +63 -0
  99. package/src/instrumentation/errors.ts +95 -0
  100. package/src/instrumentation/lifecycle.ts +17 -0
  101. package/src/instrumentation/navigation.ts +61 -0
  102. package/src/instrumentation/network.ts +253 -0
  103. package/src/react/OtelProvider.tsx +98 -0
  104. package/src/react/useOtel.ts +14 -0
  105. 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
+ }
@@ -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
+ }