@telemetry-dev/otel 0.1.0 → 0.1.2
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/README.md +37 -1
- package/dist/index.d.mts +54 -13
- package/dist/index.mjs +467 -95
- package/package.json +1 -1
- package/src/attrs.ts +9 -1
- package/src/config.ts +2 -3
- package/src/context.ts +2 -2
- package/src/debug.ts +7 -4
- package/src/index.ts +9 -0
- package/src/metrics.ts +5 -5
- package/src/otel.ts +9 -3
- package/src/processor.ts +1 -1
- package/src/session.ts +260 -0
- package/src/transport.ts +260 -90
package/src/transport.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExportResultCode } from "@opentelemetry/core";
|
|
1
|
+
import { type ExportResult, ExportResultCode } from "@opentelemetry/core";
|
|
2
2
|
import {
|
|
3
3
|
ProtobufLogsSerializer,
|
|
4
4
|
ProtobufMetricsSerializer,
|
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
} from "@opentelemetry/sdk-metrics";
|
|
13
13
|
import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
14
14
|
|
|
15
|
+
import { DEFAULT_BATCH } from "./config.ts";
|
|
15
16
|
import { reportError } from "./debug.ts";
|
|
16
17
|
|
|
17
18
|
export interface Transport {
|
|
18
19
|
fetchImpl: typeof fetch;
|
|
19
|
-
onError?: (error:
|
|
20
|
+
onError?: (error: Error) => void;
|
|
21
|
+
exportTimeoutMillis?: number;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface OtlpTarget {
|
|
@@ -25,12 +27,132 @@ export interface OtlpTarget {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const RETRY_DELAYS_MS = [100, 500] as const;
|
|
30
|
+
const MAX_RETRY_AFTER_MS = 2_000;
|
|
31
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
28
32
|
|
|
29
33
|
const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
|
|
30
34
|
|
|
31
35
|
const isRetryableStatus = (status: number) => RETRYABLE_STATUSES.has(status);
|
|
32
36
|
|
|
33
|
-
const
|
|
37
|
+
const HTTP_MONTHS = [
|
|
38
|
+
"Jan",
|
|
39
|
+
"Feb",
|
|
40
|
+
"Mar",
|
|
41
|
+
"Apr",
|
|
42
|
+
"May",
|
|
43
|
+
"Jun",
|
|
44
|
+
"Jul",
|
|
45
|
+
"Aug",
|
|
46
|
+
"Sep",
|
|
47
|
+
"Oct",
|
|
48
|
+
"Nov",
|
|
49
|
+
"Dec",
|
|
50
|
+
];
|
|
51
|
+
const HTTP_WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
52
|
+
const HTTP_WEEKDAYS_LONG = [
|
|
53
|
+
"Sunday",
|
|
54
|
+
"Monday",
|
|
55
|
+
"Tuesday",
|
|
56
|
+
"Wednesday",
|
|
57
|
+
"Thursday",
|
|
58
|
+
"Friday",
|
|
59
|
+
"Saturday",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const httpDateTimestamp = (
|
|
63
|
+
weekday: string,
|
|
64
|
+
dayText: string,
|
|
65
|
+
monthText: string,
|
|
66
|
+
yearText: string,
|
|
67
|
+
hourText: string,
|
|
68
|
+
minuteText: string,
|
|
69
|
+
secondText: string,
|
|
70
|
+
weekdays: string[],
|
|
71
|
+
): number | undefined => {
|
|
72
|
+
const day = Number(dayText);
|
|
73
|
+
const month = HTTP_MONTHS.indexOf(monthText);
|
|
74
|
+
const year = Number(yearText);
|
|
75
|
+
const hour = Number(hourText);
|
|
76
|
+
const minute = Number(minuteText);
|
|
77
|
+
const second = Number(secondText);
|
|
78
|
+
if (hour > 23 || minute > 59 || second > 59) return undefined;
|
|
79
|
+
const date = new Date(0);
|
|
80
|
+
date.setUTCFullYear(year, month, day);
|
|
81
|
+
date.setUTCHours(hour, minute, second, 0);
|
|
82
|
+
if (
|
|
83
|
+
date.getUTCFullYear() !== year ||
|
|
84
|
+
date.getUTCMonth() !== month ||
|
|
85
|
+
date.getUTCDate() !== day ||
|
|
86
|
+
weekdays[date.getUTCDay()] !== weekday
|
|
87
|
+
) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return date.getTime();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const parseHttpDate = (value: string): number | undefined => {
|
|
94
|
+
const imf =
|
|
95
|
+
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(
|
|
96
|
+
value,
|
|
97
|
+
);
|
|
98
|
+
if (imf)
|
|
99
|
+
return httpDateTimestamp(
|
|
100
|
+
...(imf.slice(1) as [string, string, string, string, string, string, string]),
|
|
101
|
+
HTTP_WEEKDAYS,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const rfc850 =
|
|
105
|
+
/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(
|
|
106
|
+
value,
|
|
107
|
+
);
|
|
108
|
+
if (rfc850) {
|
|
109
|
+
const fields = rfc850.slice(1) as [string, string, string, string, string, string, string];
|
|
110
|
+
const currentYear = new Date().getUTCFullYear();
|
|
111
|
+
let year = Math.floor(currentYear / 100) * 100 + Number(fields[3]);
|
|
112
|
+
if (year > currentYear + 50) year -= 100;
|
|
113
|
+
fields[3] = String(year);
|
|
114
|
+
return httpDateTimestamp(...fields, HTTP_WEEKDAYS_LONG);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const asctime =
|
|
118
|
+
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( [1-9]|[12]\d|3[01]) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(
|
|
119
|
+
value,
|
|
120
|
+
);
|
|
121
|
+
if (!asctime) return undefined;
|
|
122
|
+
const [, weekday, month, day, hour, minute, second, year] = asctime;
|
|
123
|
+
return httpDateTimestamp(
|
|
124
|
+
weekday!,
|
|
125
|
+
day!.trim(),
|
|
126
|
+
month!,
|
|
127
|
+
year!,
|
|
128
|
+
hour!,
|
|
129
|
+
minute!,
|
|
130
|
+
second!,
|
|
131
|
+
HTTP_WEEKDAYS,
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const retryAfterMs = (res: Response): number | undefined => {
|
|
136
|
+
const header = res.headers.get("retry-after")?.trim();
|
|
137
|
+
if (header === undefined) return undefined;
|
|
138
|
+
if (/^\d+$/.test(header)) return Number(header) * 1000;
|
|
139
|
+
const at = parseHttpDate(header);
|
|
140
|
+
return at === undefined ? undefined : Math.max(0, at - Date.now());
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const delay = (ms: number, signal?: AbortSignal) =>
|
|
144
|
+
new Promise<void>((resolve, reject) => {
|
|
145
|
+
const onAbort = () => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
reject(signal?.reason ?? new Error("telemetry.dev export aborted"));
|
|
148
|
+
};
|
|
149
|
+
const timer = setTimeout(() => {
|
|
150
|
+
signal?.removeEventListener("abort", onAbort);
|
|
151
|
+
resolve();
|
|
152
|
+
}, ms);
|
|
153
|
+
if (signal?.aborted) onAbort();
|
|
154
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
155
|
+
});
|
|
34
156
|
|
|
35
157
|
const cancelBody = async (res: Response) => {
|
|
36
158
|
try {
|
|
@@ -43,20 +165,30 @@ export const postOtlp = async ({
|
|
|
43
165
|
url,
|
|
44
166
|
headers,
|
|
45
167
|
body,
|
|
168
|
+
signal,
|
|
46
169
|
}: {
|
|
47
170
|
fetchImpl: typeof fetch;
|
|
48
171
|
url: string;
|
|
49
172
|
headers: Record<string, string>;
|
|
50
173
|
body: Uint8Array;
|
|
174
|
+
signal?: AbortSignal;
|
|
51
175
|
}) => {
|
|
52
176
|
for (let attempt = 0; ; attempt += 1) {
|
|
177
|
+
let retryAfter: number | undefined;
|
|
53
178
|
try {
|
|
54
179
|
const res = await fetchImpl(url, {
|
|
55
180
|
method: "POST",
|
|
56
181
|
headers,
|
|
57
182
|
body: body as RequestInit["body"],
|
|
183
|
+
signal,
|
|
58
184
|
});
|
|
59
|
-
|
|
185
|
+
retryAfter = retryAfterMs(res);
|
|
186
|
+
if (
|
|
187
|
+
res.ok ||
|
|
188
|
+
!isRetryableStatus(res.status) ||
|
|
189
|
+
attempt === RETRY_DELAYS_MS.length ||
|
|
190
|
+
(retryAfter !== undefined && retryAfter > MAX_RETRY_AFTER_MS)
|
|
191
|
+
) {
|
|
60
192
|
await cancelBody(res);
|
|
61
193
|
return res;
|
|
62
194
|
}
|
|
@@ -65,7 +197,7 @@ export const postOtlp = async ({
|
|
|
65
197
|
if (attempt === RETRY_DELAYS_MS.length) throw error;
|
|
66
198
|
}
|
|
67
199
|
|
|
68
|
-
await delay(RETRY_DELAYS_MS[attempt]
|
|
200
|
+
await delay(retryAfter ?? RETRY_DELAYS_MS[attempt]!, signal);
|
|
69
201
|
}
|
|
70
202
|
};
|
|
71
203
|
|
|
@@ -74,7 +206,7 @@ const GZIP_THRESHOLD_BYTES = 1024;
|
|
|
74
206
|
export async function maybeGzip(
|
|
75
207
|
body: Uint8Array,
|
|
76
208
|
): Promise<{ body: Uint8Array; contentEncoding?: "gzip" }> {
|
|
77
|
-
if (body.byteLength <= GZIP_THRESHOLD_BYTES ||
|
|
209
|
+
if (body.byteLength <= GZIP_THRESHOLD_BYTES || globalThis.CompressionStream === undefined) {
|
|
78
210
|
return { body };
|
|
79
211
|
}
|
|
80
212
|
try {
|
|
@@ -89,6 +221,99 @@ export async function maybeGzip(
|
|
|
89
221
|
}
|
|
90
222
|
}
|
|
91
223
|
|
|
224
|
+
async function postSerialized(
|
|
225
|
+
body: Uint8Array,
|
|
226
|
+
target: OtlpTarget,
|
|
227
|
+
transport: Transport,
|
|
228
|
+
label: string,
|
|
229
|
+
post: typeof postOtlp = postOtlp,
|
|
230
|
+
signal?: AbortSignal,
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
const { body: finalBody, contentEncoding } = await maybeGzip(body);
|
|
233
|
+
const headers = contentEncoding
|
|
234
|
+
? { ...target.headers, "content-encoding": contentEncoding }
|
|
235
|
+
: target.headers;
|
|
236
|
+
const res = await post({
|
|
237
|
+
fetchImpl: transport.fetchImpl,
|
|
238
|
+
url: target.url,
|
|
239
|
+
headers,
|
|
240
|
+
body: finalBody,
|
|
241
|
+
signal,
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
const retryAfter = retryAfterMs(res);
|
|
245
|
+
const retryAfterHeader = res.headers.get("retry-after")?.trim();
|
|
246
|
+
const retryDetail =
|
|
247
|
+
retryAfter !== undefined && retryAfter > MAX_RETRY_AFTER_MS && retryAfterHeader !== undefined
|
|
248
|
+
? `; retry-after ${retryAfterHeader} exceeds ${MAX_RETRY_AFTER_MS / 1000}s retry cap`
|
|
249
|
+
: "";
|
|
250
|
+
throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}${retryDetail}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const createExportLifecycle = (transport: Transport) => {
|
|
255
|
+
const inFlight = new Set<Promise<void>>();
|
|
256
|
+
let shutDown = false;
|
|
257
|
+
const configuredTimeoutMillis =
|
|
258
|
+
transport.exportTimeoutMillis ?? DEFAULT_BATCH.exportTimeoutMillis;
|
|
259
|
+
const timeoutMillis =
|
|
260
|
+
Number.isFinite(configuredTimeoutMillis) &&
|
|
261
|
+
configuredTimeoutMillis >= 0 &&
|
|
262
|
+
configuredTimeoutMillis <= MAX_TIMER_DELAY_MS
|
|
263
|
+
? configuredTimeoutMillis
|
|
264
|
+
: DEFAULT_BATCH.exportTimeoutMillis;
|
|
265
|
+
|
|
266
|
+
const callback = (resultCallback: (result: ExportResult) => void, result: ExportResult) => {
|
|
267
|
+
try {
|
|
268
|
+
resultCallback(result);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
reportError(transport.onError, error);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const exportBatch = (
|
|
275
|
+
send: (signal: AbortSignal) => Promise<void>,
|
|
276
|
+
resultCallback: (result: ExportResult) => void,
|
|
277
|
+
) => {
|
|
278
|
+
if (shutDown) {
|
|
279
|
+
const error = new Error("telemetry.dev exporter is shut down");
|
|
280
|
+
callback(resultCallback, { code: ExportResultCode.FAILED, error });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const controller = new AbortController();
|
|
284
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
285
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
286
|
+
timer = setTimeout(() => {
|
|
287
|
+
controller.abort();
|
|
288
|
+
reject(new Error(`telemetry.dev export timed out after ${timeoutMillis}ms`));
|
|
289
|
+
}, timeoutMillis);
|
|
290
|
+
});
|
|
291
|
+
let tracked: Promise<void>;
|
|
292
|
+
tracked = Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout])
|
|
293
|
+
.then(
|
|
294
|
+
() => callback(resultCallback, { code: ExportResultCode.SUCCESS }),
|
|
295
|
+
(cause: unknown) => {
|
|
296
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
297
|
+
reportError(transport.onError, error);
|
|
298
|
+
callback(resultCallback, { code: ExportResultCode.FAILED, error });
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
.finally(() => {
|
|
302
|
+
clearTimeout(timer);
|
|
303
|
+
inFlight.delete(tracked);
|
|
304
|
+
});
|
|
305
|
+
inFlight.add(tracked);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const forceFlush = () => Promise.all(inFlight).then(() => undefined);
|
|
309
|
+
const shutdown = () => {
|
|
310
|
+
shutDown = true;
|
|
311
|
+
return forceFlush();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return { exportBatch, forceFlush, shutdown };
|
|
315
|
+
};
|
|
316
|
+
|
|
92
317
|
// Stay under the ingest's 4 MiB raw body limit with headroom; oversized batches are halved.
|
|
93
318
|
const MAX_BODY_BYTES = 3_500_000;
|
|
94
319
|
|
|
@@ -101,15 +326,15 @@ function createOtlpBatchSender<T>(
|
|
|
101
326
|
target: OtlpTarget,
|
|
102
327
|
transport: Transport,
|
|
103
328
|
label: string,
|
|
104
|
-
): (items: T[]) => Promise<void> {
|
|
105
|
-
const send = async (items: T[]): Promise<void> => {
|
|
329
|
+
): (items: T[], signal: AbortSignal) => Promise<void> {
|
|
330
|
+
const send = async (items: T[], signal: AbortSignal): Promise<void> => {
|
|
106
331
|
const body = serializer.serializeRequest(items);
|
|
107
332
|
if (!body || body.byteLength === 0) return;
|
|
108
333
|
if (body.byteLength > MAX_BODY_BYTES) {
|
|
109
334
|
if (items.length > 1) {
|
|
110
335
|
const mid = Math.ceil(items.length / 2);
|
|
111
|
-
await send(items.slice(0, mid));
|
|
112
|
-
await send(items.slice(mid));
|
|
336
|
+
await send(items.slice(0, mid), signal);
|
|
337
|
+
await send(items.slice(mid), signal);
|
|
113
338
|
return;
|
|
114
339
|
}
|
|
115
340
|
// A single record beyond the limit can never be accepted; drop it instead of wedging the batch.
|
|
@@ -119,19 +344,7 @@ function createOtlpBatchSender<T>(
|
|
|
119
344
|
);
|
|
120
345
|
return;
|
|
121
346
|
}
|
|
122
|
-
|
|
123
|
-
const headers = contentEncoding
|
|
124
|
-
? { ...target.headers, "content-encoding": contentEncoding }
|
|
125
|
-
: target.headers;
|
|
126
|
-
const res = await postOtlp({
|
|
127
|
-
fetchImpl: transport.fetchImpl,
|
|
128
|
-
url: target.url,
|
|
129
|
-
headers,
|
|
130
|
-
body: finalBody,
|
|
131
|
-
});
|
|
132
|
-
if (!res.ok) {
|
|
133
|
-
throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}`);
|
|
134
|
-
}
|
|
347
|
+
await postSerialized(body, target, transport, label, postOtlp, signal);
|
|
135
348
|
};
|
|
136
349
|
return send;
|
|
137
350
|
}
|
|
@@ -143,21 +356,13 @@ export function createTraceExporter(target: OtlpTarget, transport: Transport): S
|
|
|
143
356
|
transport,
|
|
144
357
|
"trace",
|
|
145
358
|
);
|
|
359
|
+
const lifecycle = createExportLifecycle(transport);
|
|
146
360
|
return {
|
|
147
361
|
export(spans, resultCallback) {
|
|
148
|
-
send(spans)
|
|
149
|
-
() => resultCallback({ code: ExportResultCode.SUCCESS }),
|
|
150
|
-
(error: unknown) => {
|
|
151
|
-
reportError(transport.onError, error);
|
|
152
|
-
resultCallback({
|
|
153
|
-
code: ExportResultCode.FAILED,
|
|
154
|
-
error: error instanceof Error ? error : undefined,
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
);
|
|
362
|
+
lifecycle.exportBatch((signal) => send(spans, signal), resultCallback);
|
|
158
363
|
},
|
|
159
|
-
forceFlush:
|
|
160
|
-
shutdown:
|
|
364
|
+
forceFlush: lifecycle.forceFlush,
|
|
365
|
+
shutdown: lifecycle.shutdown,
|
|
161
366
|
};
|
|
162
367
|
}
|
|
163
368
|
|
|
@@ -168,77 +373,42 @@ export function createLogExporter(target: OtlpTarget, transport: Transport): Log
|
|
|
168
373
|
transport,
|
|
169
374
|
"log",
|
|
170
375
|
);
|
|
376
|
+
const lifecycle = createExportLifecycle(transport);
|
|
171
377
|
return {
|
|
172
378
|
export(logs, resultCallback) {
|
|
173
|
-
send(logs)
|
|
174
|
-
() => resultCallback({ code: ExportResultCode.SUCCESS }),
|
|
175
|
-
(error: unknown) => {
|
|
176
|
-
reportError(transport.onError, error);
|
|
177
|
-
resultCallback({
|
|
178
|
-
code: ExportResultCode.FAILED,
|
|
179
|
-
error: error instanceof Error ? error : undefined,
|
|
180
|
-
});
|
|
181
|
-
},
|
|
182
|
-
);
|
|
379
|
+
lifecycle.exportBatch((signal) => send(logs, signal), resultCallback);
|
|
183
380
|
},
|
|
184
|
-
forceFlush:
|
|
185
|
-
shutdown:
|
|
381
|
+
forceFlush: lifecycle.forceFlush,
|
|
382
|
+
shutdown: lifecycle.shutdown,
|
|
186
383
|
};
|
|
187
384
|
}
|
|
188
385
|
|
|
189
386
|
export function createMetricExporter(target: OtlpTarget, transport: Transport): PushMetricExporter {
|
|
190
|
-
const
|
|
387
|
+
const lifecycle = createExportLifecycle(transport);
|
|
191
388
|
return {
|
|
192
389
|
export(resourceMetrics: ResourceMetrics, resultCallback) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
resultCallback({ code: 0 });
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
// DELTA metric batches are not idempotent; a retry after an already-ingested response would double-count.
|
|
208
|
-
maybeGzip(body)
|
|
209
|
-
.then(({ body: finalBody, contentEncoding }) => {
|
|
210
|
-
const headers = contentEncoding
|
|
211
|
-
? { ...target.headers, "content-encoding": contentEncoding }
|
|
212
|
-
: target.headers;
|
|
213
|
-
return fetchImpl(target.url, {
|
|
214
|
-
method: "POST",
|
|
215
|
-
headers,
|
|
216
|
-
body: finalBody as RequestInit["body"],
|
|
217
|
-
});
|
|
218
|
-
})
|
|
219
|
-
.then((res) => {
|
|
220
|
-
if (!res.ok) {
|
|
221
|
-
const error = new Error(`telemetry.dev metric ingest failed: ${res.status}`);
|
|
222
|
-
reportError(onError, error);
|
|
223
|
-
resultCallback({ code: 1, error });
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
resultCallback({ code: 0 });
|
|
227
|
-
})
|
|
228
|
-
.catch((error: unknown) => {
|
|
229
|
-
reportError(onError, error);
|
|
230
|
-
resultCallback({ code: 1, error: error instanceof Error ? error : undefined });
|
|
231
|
-
});
|
|
390
|
+
lifecycle.exportBatch(async (signal) => {
|
|
391
|
+
const hasData = resourceMetrics.scopeMetrics.some((scope) =>
|
|
392
|
+
scope.metrics.some((metric) => metric.dataPoints.length > 0),
|
|
393
|
+
);
|
|
394
|
+
const body = hasData
|
|
395
|
+
? ProtobufMetricsSerializer.serializeRequest(resourceMetrics)
|
|
396
|
+
: undefined;
|
|
397
|
+
if (body !== undefined && body.byteLength > 0) {
|
|
398
|
+
await postSerialized(body, target, transport, "metric", postOtlp, signal);
|
|
399
|
+
}
|
|
400
|
+
}, resultCallback);
|
|
232
401
|
},
|
|
233
402
|
selectAggregationTemporality: () => AggregationTemporality.DELTA,
|
|
234
|
-
forceFlush:
|
|
235
|
-
shutdown:
|
|
403
|
+
forceFlush: lifecycle.forceFlush,
|
|
404
|
+
shutdown: lifecycle.shutdown,
|
|
236
405
|
};
|
|
237
406
|
}
|
|
238
407
|
|
|
239
|
-
export function otlpHeaders(apiKey: string
|
|
408
|
+
export function otlpHeaders(apiKey: string, sdkName = "@telemetry-dev/otel") {
|
|
240
409
|
return {
|
|
241
410
|
"content-type": "application/x-protobuf",
|
|
242
411
|
authorization: `Bearer ${apiKey}`,
|
|
412
|
+
"x-telemetry-dev-sdk": sdkName,
|
|
243
413
|
};
|
|
244
414
|
}
|