@sebspark/otel 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -27,72 +37,543 @@ __export(index_exports, {
27
37
  module.exports = __toCommonJS(index_exports);
28
38
 
29
39
  // src/otel.ts
30
- var import_api = require("@opentelemetry/api");
40
+ var import_api3 = require("@opentelemetry/api");
31
41
  var import_api_logs = require("@opentelemetry/api-logs");
32
42
  var import_auto_instrumentations_node = require("@opentelemetry/auto-instrumentations-node");
43
+ var import_context_async_hooks = require("@opentelemetry/context-async-hooks");
44
+ var import_sdk_node = require("@opentelemetry/sdk-node");
45
+
46
+ // src/providers.ts
33
47
  var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
34
48
  var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
35
49
  var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
36
- var import_resources = require("@opentelemetry/resources");
37
50
  var import_sdk_logs = require("@opentelemetry/sdk-logs");
38
51
  var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
39
- var import_sdk_node = require("@opentelemetry/sdk-node");
52
+ var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
53
+
54
+ // src/loggers/console-log-pretty-exporter.ts
55
+ var import_core = require("@opentelemetry/core");
56
+
57
+ // src/consts.ts
58
+ var LOG_SEVERITY_MAP = {
59
+ TRACE: 1,
60
+ DEBUG: 5,
61
+ INFO: 9,
62
+ NOTICE: 10,
63
+ WARNING: 13,
64
+ WARN: 13,
65
+ ERROR: 17,
66
+ FATAL: 21,
67
+ CRITICAL: 21,
68
+ ALERT: 22,
69
+ EMERGENCY: 23
70
+ };
71
+
72
+ // src/loggers/formatters/shared.ts
40
73
  var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
41
- import_api.diag.setLogger(new import_api.DiagConsoleLogger(), import_api.DiagLogLevel.ERROR);
42
- var serviceName = process.env.OTEL_SERVICE_NAME ?? "unknown-service";
43
- var otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318";
44
- var traceExporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({
45
- url: `${otlpEndpoint}/v1/traces`
46
- });
47
- var metricExporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
48
- url: `${otlpEndpoint}/v1/metrics`
49
- });
50
- var logExporter = new import_exporter_logs_otlp_http.OTLPLogExporter({ url: `${otlpEndpoint}/v1/logs` });
51
- var metricReader = new import_sdk_metrics.PeriodicExportingMetricReader({
52
- exporter: metricExporter
53
- });
54
- async function initialize() {
55
- try {
56
- const baseRes = await (0, import_resources.detectResources)();
57
- const customRes = (0, import_resources.resourceFromAttributes)({
58
- [import_semantic_conventions.ATTR_SERVICE_NAME]: serviceName,
59
- [import_semantic_conventions.ATTR_SERVICE_VERSION]: "1.0.0"
60
- });
61
- const resource = baseRes.merge(customRes);
62
- const logProvider = new import_sdk_logs.LoggerProvider({
63
- resource,
64
- processors: [new import_sdk_logs.BatchLogRecordProcessor(logExporter)]
65
- });
66
- import_api_logs.logs.setGlobalLoggerProvider(logProvider);
67
- const sdk = new import_sdk_node.NodeSDK({
68
- traceExporter,
69
- metricReader,
70
- instrumentations: [(0, import_auto_instrumentations_node.getNodeAutoInstrumentations)()],
71
- resource
72
- });
73
- await sdk.start();
74
- console.log(`[otel] Telemetry initialized for "${serviceName}"`);
75
- process.on("SIGTERM", async () => {
76
- console.log("[otel] Shutting down...");
77
- await Promise.all([sdk.shutdown(), logProvider.shutdown()]);
78
- console.log("[otel] Shutdown complete.");
79
- process.exit(0);
80
- });
81
- } catch (err) {
82
- console.error("[otel] Startup error:", err);
74
+ var import_fast_safe_stringify = __toESM(require("fast-safe-stringify"));
75
+
76
+ // src/loggers/formatters/style.ts
77
+ var import_kleur = __toESM(require("kleur"));
78
+ var colors = {
79
+ gray: import_kleur.default.gray,
80
+ dim: import_kleur.default.dim,
81
+ cyan: import_kleur.default.cyan,
82
+ white: import_kleur.default.white,
83
+ green: import_kleur.default.green,
84
+ yellow: import_kleur.default.yellow,
85
+ red: import_kleur.default.red,
86
+ magenta: import_kleur.default.magenta
87
+ };
88
+ var levelColorMap = {
89
+ DEBUG: import_kleur.default.magenta,
90
+ INFO: import_kleur.default.green,
91
+ WARN: import_kleur.default.yellow,
92
+ ERROR: import_kleur.default.red,
93
+ FATAL: import_kleur.default.red
94
+ };
95
+ var levelIconMap = {
96
+ DEBUG: "\u{1F41B}",
97
+ INFO: "\u2139\uFE0F ",
98
+ WARN: "\u26A0\uFE0F ",
99
+ ERROR: "\u274C",
100
+ FATAL: "\u{1F480}"
101
+ };
102
+ var statusLabelMap = {
103
+ 0: "UNSET",
104
+ 1: "OK",
105
+ 2: "ERROR"
106
+ };
107
+ var statusColorMap = {
108
+ 0: colors.gray,
109
+ // UNSET
110
+ 1: colors.green,
111
+ // OK
112
+ 2: colors.red
113
+ // ERROR
114
+ };
115
+ var kindColorMap = {
116
+ 0: colors.white,
117
+ // INTERNAL
118
+ 1: colors.cyan,
119
+ // SERVER
120
+ 2: colors.yellow,
121
+ // CLIENT
122
+ 3: colors.magenta,
123
+ // PRODUCER
124
+ 4: colors.green
125
+ // CONSUMER
126
+ };
127
+
128
+ // src/loggers/formatters/shared.ts
129
+ function formatTimestamp(time) {
130
+ const date = new Date(hrTimeToMillis(time));
131
+ return colors.dim(date.toISOString().slice(11, 23));
132
+ }
133
+ function formatService(resource) {
134
+ const name = resource.attributes[import_semantic_conventions.ATTR_SERVICE_NAME] ?? "unknown-service";
135
+ const version = resource.attributes[import_semantic_conventions.ATTR_SERVICE_VERSION] ?? "1.0.0";
136
+ return colors.gray(`[${name}@${version}]`);
137
+ }
138
+ function formatLevel(record) {
139
+ const text = (record.severityText ?? "INFO").toUpperCase();
140
+ const colorFn = levelColorMap[text] ?? colors.white;
141
+ const icon = levelIconMap[text] ?? "\u2022";
142
+ return `${icon} ${colorFn(text.padEnd(5))}`;
143
+ }
144
+ function formatScope(resource, instrumentationScope) {
145
+ const component = resource.attributes["component.name"];
146
+ const { name, version } = instrumentationScope;
147
+ const scopeLabel = component || (name && name !== "unknown" ? name : void 0);
148
+ if (!scopeLabel) return "";
149
+ const versionLabel = version ? `@${version}` : "";
150
+ return colors.cyan(`${scopeLabel}${versionLabel} `);
151
+ }
152
+ function formatMessage(record) {
153
+ return typeof record.body === "string" ? record.body : (0, import_fast_safe_stringify.default)(record.body);
154
+ }
155
+ function formatAttributes(attrs) {
156
+ const keys = Object.keys(attrs).filter(
157
+ (k) => !k.startsWith("service.") && !k.startsWith("serviceContext.")
158
+ );
159
+ if (keys.length === 0) return "";
160
+ const formatted = keys.map((k) => {
161
+ const val = attrs[k];
162
+ return `${k}=${typeof val === "object" ? (0, import_fast_safe_stringify.default)(val) : val}`;
163
+ });
164
+ return ` ${colors.gray(formatted.join(" "))}`;
165
+ }
166
+ function hrTimeToMillis(hrTime) {
167
+ return hrTime[0] * 1e3 + Math.floor(hrTime[1] / 1e6);
168
+ }
169
+ function calculateDuration(span) {
170
+ const start = hrTimeToMillis(span.startTime);
171
+ const end = hrTimeToMillis(span.endTime);
172
+ return Math.max(0, Math.round(end - start));
173
+ }
174
+
175
+ // src/loggers/formatters/log-record.ts
176
+ function formatLogRecord(record) {
177
+ const timestamp = formatTimestamp(record.hrTime);
178
+ const service = formatService(record.resource);
179
+ const level = formatLevel(record);
180
+ const scope = formatScope(record.resource, record.instrumentationScope);
181
+ const message = formatMessage(record);
182
+ const attrs = formatAttributes(record.attributes);
183
+ return `${service} ${timestamp} ${level} ${scope}${message}${attrs}`;
184
+ }
185
+
186
+ // src/loggers/formatters/metrics.ts
187
+ function formatMetrics(resourceMetrics) {
188
+ const { resource, scopeMetrics } = resourceMetrics;
189
+ return scopeMetrics.map((scopeMetric) => formatScopeMetric(scopeMetric, resource)).join("\n");
190
+ }
191
+ function formatScopeMetric(scopeMetric, resource) {
192
+ return scopeMetric.metrics.map(
193
+ (metric) => formatMetricData(metric, resource, scopeMetric.scope)
194
+ ).join("\n");
195
+ }
196
+ function formatMetricData(metric, resource, scope) {
197
+ const scopeStr = formatScope(resource, scope);
198
+ const serviceStr = formatService(resource);
199
+ const lines = [];
200
+ for (const dp of metric.dataPoints) {
201
+ const ts = formatTimestamp(dp.startTime);
202
+ const value = extractMetricValue(dp);
203
+ const attrs = formatAttributes(dp.attributes ?? {});
204
+ lines.push(
205
+ `${serviceStr} ${ts} \u{1F4CA} ${scopeStr}${colors.white(metric.descriptor.name)} ${colors.dim(value)}${attrs}`
206
+ );
83
207
  }
208
+ return lines.join("\n");
209
+ }
210
+ function extractMetricValue(dp) {
211
+ const value = dp.value;
212
+ if (typeof value === "number") {
213
+ return value.toString();
214
+ }
215
+ if (isHistogramLike(value)) {
216
+ return value.sum.toString();
217
+ }
218
+ return "[complex]";
219
+ }
220
+ function isHistogramLike(val) {
221
+ return typeof val === "object" && val !== null && "sum" in val && typeof val.sum === "number";
84
222
  }
85
- initialize();
86
223
 
87
- // src/logger.ts
224
+ // src/loggers/formatters/span.ts
225
+ var import_api = require("@opentelemetry/api");
226
+ function formatSpans(spans) {
227
+ const rootSpan = spans[0];
228
+ const rootStart = hrTimeToMillis(rootSpan.startTime);
229
+ const rootEnd = hrTimeToMillis(rootSpan.endTime);
230
+ const totalDuration = rootEnd - rootStart;
231
+ const service = formatService(rootSpan.resource);
232
+ const timestamp = formatTimestamp(rootSpan.startTime);
233
+ const lines = [`${service} ${timestamp}`];
234
+ for (const span of spans) {
235
+ const offset = hrTimeToMillis(span.startTime) - rootStart;
236
+ const depth = computeDepth(span, spans);
237
+ lines.push(
238
+ formatSpan(span, {
239
+ offsetMs: offset,
240
+ totalDurationMs: totalDuration,
241
+ depth
242
+ })
243
+ );
244
+ }
245
+ return lines.join("\n");
246
+ }
247
+ function formatSpan(span, opts) {
248
+ const label = formatLabel(span, opts.depth);
249
+ const bar = buildBar(span, opts?.offsetMs, opts?.totalDurationMs);
250
+ const barColor = span.status.code === import_api.SpanStatusCode.OK ? colors.green : span.status.code === import_api.SpanStatusCode.ERROR ? colors.red : colors.gray;
251
+ const desc = formatDescription(span);
252
+ const status = formatStatus(span);
253
+ const duration = formatDuration(span, opts?.offsetMs);
254
+ return `${label} ${barColor(bar)} ${desc} ${status} (${duration})`;
255
+ }
256
+ var LABEL_WIDTH = 25;
257
+ function formatLabel(span, depth) {
258
+ const indent = " ".repeat(depth);
259
+ const label = `${indent}\u2514\u2500 ${span.name}`;
260
+ return label.padEnd(LABEL_WIDTH);
261
+ }
262
+ var BAR_MIN_WIDTH = 1;
263
+ var BAR_MAX_WIDTH = 20;
264
+ function buildBar(span, offsetMs, totalDurationMs) {
265
+ const duration = calculateDuration(span);
266
+ if (typeof offsetMs !== "number" || typeof totalDurationMs !== "number" || totalDurationMs === 0) {
267
+ const capped = Math.min(duration, 1e3);
268
+ const barLength = Math.max(
269
+ BAR_MIN_WIDTH,
270
+ Math.round(capped / 1e3 * BAR_MAX_WIDTH)
271
+ );
272
+ return "\u2588".repeat(barLength).padEnd(BAR_MAX_WIDTH + 2);
273
+ }
274
+ const offsetRatio = Math.max(0, Math.min(offsetMs / totalDurationMs, 1));
275
+ const durationRatio = Math.max(0, Math.min(duration / totalDurationMs, 1));
276
+ const offsetChars = Math.floor(offsetRatio * BAR_MAX_WIDTH);
277
+ const barChars = Math.max(
278
+ BAR_MIN_WIDTH,
279
+ Math.round(durationRatio * BAR_MAX_WIDTH)
280
+ );
281
+ const empty = " ".repeat(offsetChars);
282
+ const bar = "\u2588".repeat(barChars);
283
+ return (empty + bar).padEnd(BAR_MAX_WIDTH + 2);
284
+ }
285
+ var DESCRIPTION_MAX_WIDTH = 20;
286
+ function formatDescription(span) {
287
+ const keyPriority = [
288
+ // HTTP
289
+ ["http.method", "http.target"],
290
+ // → GET /users/123
291
+ ["http.route"],
292
+ // → /users/:id
293
+ ["http.url"],
294
+ // → https://...
295
+ // GraphQL
296
+ ["graphql.operation.name"],
297
+ // → getUsers
298
+ ["graphql.operation.type"],
299
+ // → query
300
+ ["graphql.document"],
301
+ // → full query text (maybe too long)
302
+ // WebSocket
303
+ ["ws.event"],
304
+ // → connection, message, disconnect
305
+ ["ws.message_type"],
306
+ // → ping/pong/text/binary
307
+ ["ws.url"],
308
+ // → wss://...
309
+ // Redis
310
+ ["db.system", "db.statement"],
311
+ // → redis, "SET foo bar"
312
+ ["db.operation"],
313
+ // → GET, SET
314
+ // Spanner
315
+ ["db.statement"],
316
+ // → SELECT * FROM...
317
+ ["db.operation"],
318
+ // → SELECT, INSERT
319
+ ["db.name"],
320
+ // → projects/.../instances/.../databases/...
321
+ // OpenSearch
322
+ ["db.operation"],
323
+ // → search, index, bulk
324
+ ["db.statement"],
325
+ // → { query DSL... }
326
+ // Pub/Sub (GCP)
327
+ ["messaging.operation"],
328
+ // → publish, receive
329
+ ["messaging.destination"],
330
+ // → topic-a
331
+ ["messaging.gcp_pubsub.topic"],
332
+ // → projects/x/topics/y
333
+ // General FaaS
334
+ ["faas.invoked_name"],
335
+ // → myFunction
336
+ ["faas.trigger"],
337
+ // → http, pubsub, etc.
338
+ // Custom or fallback
339
+ ["otel.description"]
340
+ ];
341
+ for (const keys of keyPriority) {
342
+ const parts = keys.map((k) => span.attributes[k]).filter((v) => v !== void 0 && v !== null).map((v) => String(v));
343
+ if (parts.length > 0) {
344
+ return truncate(parts.join(" "), DESCRIPTION_MAX_WIDTH - 1).padEnd(
345
+ DESCRIPTION_MAX_WIDTH
346
+ );
347
+ }
348
+ }
349
+ return "".padEnd(DESCRIPTION_MAX_WIDTH);
350
+ }
351
+ function formatStatus(span) {
352
+ const code = span.status.code;
353
+ const label = statusLabelMap[code] ?? "UNSET";
354
+ const colorFn = statusColorMap[code] ?? colors.gray;
355
+ return colorFn(label).padEnd(6);
356
+ }
357
+ function formatDuration(span, offsetMs) {
358
+ const duration = calculateDuration(span);
359
+ const format = (ms) => ms >= 1e3 ? `${(ms / 1e3).toFixed(2)} s` : `${ms} ms`;
360
+ return `${format(offsetMs || 0)}\u2013${format(duration)}`;
361
+ }
362
+ function truncate(input, maxLength) {
363
+ const str = String(input ?? "");
364
+ return str.length > maxLength ? `${str.slice(0, maxLength - 1)}\u2026` : str;
365
+ }
366
+ function computeDepth(span, allSpans) {
367
+ let depth = 0;
368
+ let currentParentId = span.parentSpanContext?.spanId;
369
+ while (currentParentId) {
370
+ const parentSpan = allSpans.find(
371
+ (s) => s.spanContext().spanId === currentParentId
372
+ );
373
+ if (!parentSpan) break;
374
+ depth += 1;
375
+ currentParentId = parentSpan.parentSpanContext?.spanId;
376
+ }
377
+ return depth;
378
+ }
379
+
380
+ // src/loggers/console-log-pretty-exporter.ts
381
+ var ConsoleLogPrettyExporter = class {
382
+ logThreshold;
383
+ constructor() {
384
+ const defaultLogLevel = "INFO";
385
+ const env = process.env.LOG_LEVEL?.toUpperCase() ?? defaultLogLevel;
386
+ this.logThreshold = LOG_SEVERITY_MAP[env] ?? LOG_SEVERITY_MAP[defaultLogLevel];
387
+ }
388
+ export(logs3, resultCallback) {
389
+ this._sendLogRecords(logs3, resultCallback);
390
+ }
391
+ shutdown() {
392
+ return Promise.resolve();
393
+ }
394
+ forceFlush() {
395
+ return Promise.resolve();
396
+ }
397
+ _sendLogRecords(logRecords, done) {
398
+ for (const record of logRecords) {
399
+ if ((record.severityNumber ?? 0) >= this.logThreshold) {
400
+ console.log(formatLogRecord(record));
401
+ }
402
+ }
403
+ done?.({ code: import_core.ExportResultCode.SUCCESS });
404
+ }
405
+ };
406
+
407
+ // src/loggers/console-span-pretty-exporter.ts
88
408
  var import_api2 = require("@opentelemetry/api");
89
- var import_api_logs2 = require("@opentelemetry/api-logs");
409
+ var import_core2 = require("@opentelemetry/core");
410
+ var ConsoleSpanPrettyExporter = class {
411
+ allowedStatuses;
412
+ constructor() {
413
+ const env = process.env.SPAN_LEVEL?.toUpperCase();
414
+ if (!env) {
415
+ this.allowedStatuses = /* @__PURE__ */ new Set([
416
+ import_api2.SpanStatusCode.UNSET,
417
+ import_api2.SpanStatusCode.OK,
418
+ import_api2.SpanStatusCode.ERROR
419
+ ]);
420
+ } else {
421
+ const map = {
422
+ UNSET: import_api2.SpanStatusCode.UNSET,
423
+ OK: import_api2.SpanStatusCode.OK,
424
+ ERROR: import_api2.SpanStatusCode.ERROR
425
+ };
426
+ this.allowedStatuses = new Set(
427
+ env.split(",").map((s) => s.trim()).map((s) => map[s]).filter((v) => typeof v === "number")
428
+ );
429
+ }
430
+ }
431
+ shouldExport(spans) {
432
+ if (this.allowedStatuses.size === 3) {
433
+ return true;
434
+ }
435
+ return spans.some((span) => this.allowedStatuses.has(span.status.code));
436
+ }
437
+ export(spans, resultCallback) {
438
+ if (this.shouldExport(spans)) {
439
+ console.log(formatSpans(spans));
440
+ }
441
+ resultCallback({ code: import_core2.ExportResultCode.SUCCESS });
442
+ }
443
+ shutdown() {
444
+ return Promise.resolve();
445
+ }
446
+ };
447
+
448
+ // src/loggers/console-metric-pretty-exporter.ts
449
+ var import_core3 = require("@opentelemetry/core");
450
+ var ConsoleMetricPrettyExporter = class {
451
+ patterns;
452
+ constructor() {
453
+ const raw = process.env.METRIC_FILTER ?? "";
454
+ const entries = raw.split(",").map((e) => e.trim()).filter(Boolean);
455
+ this.patterns = entries.map(globToRegex);
456
+ }
457
+ filterMetrics(resourceMetrics) {
458
+ if (this.patterns.length === 0) return void 0;
459
+ const filteredScopes = resourceMetrics.scopeMetrics.map((scopeMetric) => {
460
+ const filteredMetrics = scopeMetric.metrics.filter(
461
+ (metric) => this.patterns.some((pattern) => pattern.test(metric.descriptor.name))
462
+ );
463
+ if (filteredMetrics.length === 0) return void 0;
464
+ return {
465
+ ...scopeMetric,
466
+ metrics: filteredMetrics
467
+ };
468
+ }).filter((s) => s !== void 0);
469
+ if (filteredScopes.length === 0) return void 0;
470
+ return {
471
+ ...resourceMetrics,
472
+ scopeMetrics: filteredScopes
473
+ };
474
+ }
475
+ export(metrics2, resultCallback) {
476
+ const filtered = this.filterMetrics(metrics2);
477
+ if (filtered) {
478
+ console.log(formatMetrics(filtered));
479
+ }
480
+ resultCallback({ code: import_core3.ExportResultCode.SUCCESS });
481
+ }
482
+ shutdown() {
483
+ return Promise.resolve();
484
+ }
485
+ forceFlush() {
486
+ return Promise.resolve();
487
+ }
488
+ };
489
+ function globToRegex(glob) {
490
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
491
+ const regex = `^${escaped.replace(/\*/g, ".*")}$`;
492
+ return new RegExp(regex);
493
+ }
494
+
495
+ // src/loggers/tree-span-processor.ts
496
+ var TreeSpanProcessor = class {
497
+ exporter;
498
+ orphans = /* @__PURE__ */ new Map();
499
+ constructor(exporter) {
500
+ this.exporter = exporter;
501
+ }
502
+ onStart() {
503
+ }
504
+ onEnd(span) {
505
+ const parentId = span.parentSpanContext?.spanId;
506
+ if (parentId) {
507
+ const siblings = this.orphans.get(parentId) || [];
508
+ this.orphans.set(parentId, [...siblings, span]);
509
+ return;
510
+ }
511
+ const children = this.getChildrenRecursively(span);
512
+ const sorted = [span, ...children].sort((s1, s2) => {
513
+ const [sec1, nano1] = s1.startTime;
514
+ const [sec2, nano2] = s2.startTime;
515
+ if (sec1 !== sec2) return sec1 - sec2;
516
+ return nano1 - nano2;
517
+ });
518
+ this.exporter.export(sorted, () => {
519
+ });
520
+ }
521
+ getChildrenRecursively(span) {
522
+ const spanId = span.spanContext().spanId;
523
+ const children = this.orphans.get(spanId) || [];
524
+ this.orphans.delete(spanId);
525
+ const result = [...children];
526
+ for (const child of children) {
527
+ result.push(...this.getChildrenRecursively(child));
528
+ }
529
+ return result;
530
+ }
531
+ shutdown() {
532
+ return this.exporter.shutdown();
533
+ }
534
+ async forceFlush() {
535
+ await this.exporter.forceFlush?.();
536
+ }
537
+ };
538
+
539
+ // src/providers.ts
540
+ var getLogProvider = (resource, otlpEndpoint) => {
541
+ const exporter = otlpEndpoint ? new import_exporter_logs_otlp_http.OTLPLogExporter({ url: `${otlpEndpoint}/v1/logs` }) : new ConsoleLogPrettyExporter();
542
+ const processor = otlpEndpoint ? new import_sdk_logs.BatchLogRecordProcessor(exporter) : new import_sdk_logs.SimpleLogRecordProcessor(exporter);
543
+ const provider = new import_sdk_logs.LoggerProvider({
544
+ resource,
545
+ processors: [processor]
546
+ });
547
+ return provider;
548
+ };
549
+ var getSpanProcessor = (otlpEndpoint) => {
550
+ const exporter = otlpEndpoint ? new import_exporter_trace_otlp_http.OTLPTraceExporter({
551
+ url: `${otlpEndpoint}/v1/traces`
552
+ }) : new ConsoleSpanPrettyExporter();
553
+ const processor = otlpEndpoint ? new import_sdk_trace_node.BatchSpanProcessor(exporter) : new TreeSpanProcessor(exporter);
554
+ return processor;
555
+ };
556
+ var getMetricReader = (otlpEndpoint) => {
557
+ const metricExporter = otlpEndpoint ? new import_exporter_metrics_otlp_http.OTLPMetricExporter({
558
+ url: `${otlpEndpoint}/v1/metrics`
559
+ }) : new ConsoleMetricPrettyExporter();
560
+ const metricReader = new import_sdk_metrics.PeriodicExportingMetricReader({
561
+ exporter: metricExporter
562
+ });
563
+ return metricReader;
564
+ };
565
+
566
+ // src/resource.ts
567
+ var import_resources = require("@opentelemetry/resources");
90
568
 
91
569
  // src/otel-context.ts
92
- function detectTelemetryContext(serviceOverride) {
570
+ var import_semantic_conventions2 = require("@opentelemetry/semantic-conventions");
571
+ function detectTelemetryContext(componentNameOverride) {
93
572
  const {
94
573
  OTEL_SERVICE_NAME,
574
+ // e.g. "UserSystem"
95
575
  OTEL_SERVICE_VERSION,
576
+ // e.g. "1.2.3"
96
577
  K_SERVICE,
97
578
  K_REVISION,
98
579
  K_CONFIGURATION,
@@ -102,13 +583,14 @@ function detectTelemetryContext(serviceOverride) {
102
583
  GCP_PROJECT,
103
584
  CLOUD_PROVIDER
104
585
  } = process.env;
105
- const serviceName2 = serviceOverride || OTEL_SERVICE_NAME || "unknown-service";
106
- const serviceVersion = OTEL_SERVICE_VERSION || "1.0.0";
586
+ const systemName = OTEL_SERVICE_NAME || "unknown-service";
587
+ const systemVersion = OTEL_SERVICE_VERSION || "1.0.0";
588
+ const componentName = componentNameOverride || void 0;
107
589
  const resourceAttributes = {
108
- "service.name": serviceName2,
109
- "service.version": serviceVersion,
110
- "serviceContext.service": serviceName2,
111
- "serviceContext.version": serviceVersion,
590
+ [import_semantic_conventions2.ATTR_SERVICE_NAME]: systemName,
591
+ [import_semantic_conventions2.ATTR_SERVICE_VERSION]: systemVersion,
592
+ "serviceContext.service": systemName,
593
+ "serviceContext.version": systemVersion,
112
594
  ...K_SERVICE && { "cloud.run.service": K_SERVICE },
113
595
  ...K_REVISION && { "cloud.run.revision": K_REVISION },
114
596
  ...K_CONFIGURATION && { "cloud.run.configuration": K_CONFIGURATION },
@@ -116,28 +598,75 @@ function detectTelemetryContext(serviceOverride) {
116
598
  ...POD_NAMESPACE && { "k8s.namespace_name": POD_NAMESPACE },
117
599
  ...KUBERNETES_SERVICE_HOST && { "cloud.orchestrator": "kubernetes" },
118
600
  ...GCP_PROJECT && { "cloud.account.id": GCP_PROJECT },
119
- ...CLOUD_PROVIDER && { "cloud.provider": CLOUD_PROVIDER }
601
+ ...CLOUD_PROVIDER && { "cloud.provider": CLOUD_PROVIDER },
602
+ ...componentName && { "component.name": componentName }
120
603
  };
121
604
  return {
122
- serviceName: serviceName2,
123
- serviceVersion,
605
+ systemName,
606
+ systemVersion,
607
+ componentName,
124
608
  resourceAttributes
125
609
  };
126
610
  }
127
611
 
612
+ // src/resource.ts
613
+ var getResource = async () => {
614
+ const baseRes = await (0, import_resources.detectResources)();
615
+ const { resourceAttributes } = detectTelemetryContext();
616
+ const customRes = (0, import_resources.resourceFromAttributes)(resourceAttributes);
617
+ const resource = baseRes.merge(customRes);
618
+ return resource;
619
+ };
620
+
621
+ // src/otel.ts
622
+ import_api3.diag.setLogger(new import_api3.DiagConsoleLogger(), import_api3.DiagLogLevel.ERROR);
623
+ async function initialize() {
624
+ try {
625
+ const serviceName = process.env.OTEL_SERVICE_NAME ?? "unknown-service";
626
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
627
+ const resource = await getResource();
628
+ import_api3.context.setGlobalContextManager(
629
+ new import_context_async_hooks.AsyncLocalStorageContextManager().enable()
630
+ );
631
+ const logProvider = getLogProvider(resource, otlpEndpoint);
632
+ import_api_logs.logs.setGlobalLoggerProvider(logProvider);
633
+ const spanProcessor = getSpanProcessor(otlpEndpoint);
634
+ const metricReader = getMetricReader(otlpEndpoint);
635
+ const sdk = new import_sdk_node.NodeSDK({
636
+ spanProcessor,
637
+ metricReader,
638
+ instrumentations: [(0, import_auto_instrumentations_node.getNodeAutoInstrumentations)()],
639
+ resource
640
+ });
641
+ await sdk.start();
642
+ console.log(`[otel] Telemetry initialized for "${serviceName}"`);
643
+ process.on("SIGTERM", async () => {
644
+ console.log("[otel] Shutting down...");
645
+ await Promise.all([sdk.shutdown(), logProvider.shutdown()]);
646
+ console.log("[otel] Shutdown complete.");
647
+ process.exit(0);
648
+ });
649
+ } catch (err) {
650
+ console.error("[otel] Startup error:", err);
651
+ }
652
+ }
653
+
128
654
  // src/logger.ts
655
+ var import_api4 = require("@opentelemetry/api");
656
+ var import_api_logs2 = require("@opentelemetry/api-logs");
129
657
  function getLogger(serviceOverride, extraAttrs = {}) {
130
- const { serviceName: serviceName2, serviceVersion, resourceAttributes } = detectTelemetryContext(serviceOverride);
131
- const logger = import_api_logs2.logs.getLogger(serviceName2, serviceVersion, {});
658
+ const { systemName, systemVersion, resourceAttributes } = detectTelemetryContext(serviceOverride);
659
+ const logger = import_api_logs2.logs.getLogger(systemName, systemVersion);
132
660
  const defaultAttrs = {
133
661
  ...resourceAttributes,
134
662
  ...extraAttrs
135
663
  };
136
664
  function emit(severityText, body, attrs = {}) {
137
- const span = import_api2.trace.getSpan(import_api2.context.active());
665
+ const span = import_api4.trace.getSpan(import_api4.context.active());
138
666
  const spanContext = span?.spanContext();
139
667
  logger.emit({
140
668
  severityText,
669
+ severityNumber: LOG_SEVERITY_MAP[severityText],
141
670
  body,
142
671
  attributes: {
143
672
  ...defaultAttrs,
@@ -165,30 +694,40 @@ function getLogger(serviceOverride, extraAttrs = {}) {
165
694
  }
166
695
 
167
696
  // src/metrics.ts
168
- var import_api3 = require("@opentelemetry/api");
169
- function getMeter(serviceOverride) {
170
- const { serviceName: serviceName2 } = detectTelemetryContext(serviceOverride);
171
- return import_api3.metrics.getMeter(serviceName2);
697
+ var import_api5 = require("@opentelemetry/api");
698
+ function getMeter(componentNameOverride) {
699
+ const { componentName, systemName, systemVersion } = detectTelemetryContext(
700
+ componentNameOverride
701
+ );
702
+ return import_api5.metrics.getMeter(componentName ?? systemName, systemVersion);
172
703
  }
173
704
 
174
705
  // src/tracer.ts
175
- var import_api4 = require("@opentelemetry/api");
176
- function getTracer(serviceOverride) {
177
- const { serviceName: serviceName2 } = detectTelemetryContext(serviceOverride);
178
- const tracer = import_api4.trace.getTracer(serviceName2);
179
- const withTrace = async (name, optionsOrFn, maybeFn) => {
180
- const options = typeof maybeFn === "function" ? optionsOrFn : {};
181
- const fn = typeof maybeFn === "function" ? maybeFn : optionsOrFn;
182
- const ctx = import_api4.context.active();
183
- const span = tracer.startSpan(name, options, ctx);
184
- return await import_api4.context.with(import_api4.trace.setSpan(ctx, span), async () => {
706
+ var import_api6 = require("@opentelemetry/api");
707
+ function getTracer(componentNameOverride) {
708
+ const { componentName, systemName, systemVersion } = detectTelemetryContext(
709
+ componentNameOverride
710
+ );
711
+ const tracer = import_api6.trace.getTracer(
712
+ componentName ?? systemName,
713
+ systemVersion
714
+ );
715
+ const withTrace = async (name, spanOptionsSpanOrFunc, spanOrFunc, func) => {
716
+ const { options, parent, fn } = extractArgs(
717
+ spanOptionsSpanOrFunc,
718
+ spanOrFunc,
719
+ func
720
+ );
721
+ const parentContext = parent ? import_api6.trace.setSpan(import_api6.context.active(), parent) : import_api6.context.active();
722
+ const span = tracer.startSpan(name, options, parentContext);
723
+ return await import_api6.context.with(import_api6.trace.setSpan(parentContext, span), async () => {
185
724
  try {
186
725
  const result = await fn(span);
187
- span.setStatus({ code: import_api4.SpanStatusCode.OK });
726
+ span.setStatus({ code: import_api6.SpanStatusCode.OK });
188
727
  return result;
189
728
  } catch (err) {
190
729
  const error = err;
191
- span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: error.message });
730
+ span.setStatus({ code: import_api6.SpanStatusCode.ERROR, message: error.message });
192
731
  span.recordException?.(error);
193
732
  throw err;
194
733
  } finally {
@@ -196,19 +735,22 @@ function getTracer(serviceOverride) {
196
735
  }
197
736
  });
198
737
  };
199
- const withTraceSync = (name, optionsOrFn, maybeFn) => {
200
- const options = typeof maybeFn === "function" ? optionsOrFn : {};
201
- const fn = typeof maybeFn === "function" ? maybeFn : optionsOrFn;
202
- const ctx = import_api4.context.active();
203
- const span = tracer.startSpan(name, options, ctx);
204
- return import_api4.context.with(import_api4.trace.setSpan(ctx, span), () => {
738
+ const withTraceSync = (name, spanOptionsSpanOrFunc, spanOrFunc, func) => {
739
+ const { options, parent, fn } = extractArgs(
740
+ spanOptionsSpanOrFunc,
741
+ spanOrFunc,
742
+ func
743
+ );
744
+ const parentContext = parent ? import_api6.trace.setSpan(import_api6.context.active(), parent) : import_api6.context.active();
745
+ const span = tracer.startSpan(name, options, parentContext);
746
+ return import_api6.context.with(import_api6.trace.setSpan(parentContext, span), () => {
205
747
  try {
206
748
  const result = fn(span);
207
- span.setStatus({ code: import_api4.SpanStatusCode.OK });
749
+ span.setStatus({ code: import_api6.SpanStatusCode.OK });
208
750
  return result;
209
751
  } catch (err) {
210
752
  const error = err;
211
- span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: error.message });
753
+ span.setStatus({ code: import_api6.SpanStatusCode.ERROR, message: error.message });
212
754
  span.recordException?.(error);
213
755
  throw err;
214
756
  } finally {
@@ -220,6 +762,30 @@ function getTracer(serviceOverride) {
220
762
  tracer.withTraceSync = withTraceSync;
221
763
  return tracer;
222
764
  }
765
+ function extractArgs(spanOptionsSpanOrFunc, spanOrFunc, func) {
766
+ let options = {};
767
+ let parent;
768
+ let fn;
769
+ if (typeof spanOptionsSpanOrFunc === "function") {
770
+ fn = spanOptionsSpanOrFunc;
771
+ } else if (typeof spanOrFunc === "function") {
772
+ const spanOrSpanOptions = spanOptionsSpanOrFunc;
773
+ if ("startTime" in spanOrSpanOptions || "attributes" in spanOrSpanOptions || "kind" in spanOrSpanOptions) {
774
+ options = spanOrSpanOptions;
775
+ } else {
776
+ parent = spanOrSpanOptions;
777
+ }
778
+ fn = spanOrFunc;
779
+ } else {
780
+ options = spanOptionsSpanOrFunc;
781
+ parent = spanOrFunc;
782
+ fn = func;
783
+ }
784
+ return { options, parent, fn };
785
+ }
786
+
787
+ // src/index.ts
788
+ initialize();
223
789
  // Annotate the CommonJS export names for ESM import in node:
224
790
  0 && (module.exports = {
225
791
  getLogger,