autotel 2.25.1 → 2.25.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.
Files changed (58) hide show
  1. package/dist/auto.cjs +2 -2
  2. package/dist/auto.js +1 -1
  3. package/dist/{chunk-HCFBEOBM.cjs → chunk-4KGC5N3J.cjs} +13 -13
  4. package/dist/{chunk-HCFBEOBM.cjs.map → chunk-4KGC5N3J.cjs.map} +1 -1
  5. package/dist/{chunk-NYTCPK4J.js → chunk-4SCBD22Z.js} +3 -3
  6. package/dist/{chunk-NYTCPK4J.js.map → chunk-4SCBD22Z.js.map} +1 -1
  7. package/dist/{chunk-JZKMXKJX.js → chunk-77PLEJ54.js} +3 -3
  8. package/dist/{chunk-JZKMXKJX.js.map → chunk-77PLEJ54.js.map} +1 -1
  9. package/dist/{chunk-JDV3LYOI.js → chunk-IKRHEUS7.js} +3 -3
  10. package/dist/{chunk-JDV3LYOI.js.map → chunk-IKRHEUS7.js.map} +1 -1
  11. package/dist/{chunk-GIND746I.cjs → chunk-JVICEM6W.cjs} +78 -2
  12. package/dist/chunk-JVICEM6W.cjs.map +1 -0
  13. package/dist/{chunk-P5YCN2Z3.js → chunk-KUSYIHW7.js} +3 -3
  14. package/dist/{chunk-P5YCN2Z3.js.map → chunk-KUSYIHW7.js.map} +1 -1
  15. package/dist/{chunk-LRFG6HRL.cjs → chunk-L627YSSP.cjs} +5 -5
  16. package/dist/{chunk-LRFG6HRL.cjs.map → chunk-L627YSSP.cjs.map} +1 -1
  17. package/dist/{chunk-DBUNRUDB.cjs → chunk-MNMLCLHH.cjs} +5 -5
  18. package/dist/{chunk-DBUNRUDB.cjs.map → chunk-MNMLCLHH.cjs.map} +1 -1
  19. package/dist/{chunk-BKIAH2YS.js → chunk-QUW4I2OI.js} +3 -3
  20. package/dist/{chunk-BKIAH2YS.js.map → chunk-QUW4I2OI.js.map} +1 -1
  21. package/dist/{chunk-7ZYUFMWU.cjs → chunk-RWOVNF3V.cjs} +26 -26
  22. package/dist/{chunk-7ZYUFMWU.cjs.map → chunk-RWOVNF3V.cjs.map} +1 -1
  23. package/dist/{chunk-73SCHI7V.cjs → chunk-UKUYBUFQ.cjs} +7 -7
  24. package/dist/{chunk-73SCHI7V.cjs.map → chunk-UKUYBUFQ.cjs.map} +1 -1
  25. package/dist/{chunk-7RV6L24E.js → chunk-VXLEJWLY.js} +79 -3
  26. package/dist/chunk-VXLEJWLY.js.map +1 -0
  27. package/dist/decorators.cjs +2 -2
  28. package/dist/decorators.js +2 -2
  29. package/dist/event.cjs +5 -5
  30. package/dist/event.js +2 -2
  31. package/dist/functional.cjs +9 -9
  32. package/dist/functional.js +2 -2
  33. package/dist/index.cjs +41 -41
  34. package/dist/index.d.cts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +9 -9
  37. package/dist/{init-Co9CWOY6.d.cts → init-Q4uIQKbq.d.cts} +25 -0
  38. package/dist/{init-oLqNJTpj.d.ts → init-ls4xSZe5.d.ts} +25 -0
  39. package/dist/instrumentation.cjs +8 -8
  40. package/dist/instrumentation.js +1 -1
  41. package/dist/messaging.cjs +6 -6
  42. package/dist/messaging.js +3 -3
  43. package/dist/semantic-helpers.cjs +7 -7
  44. package/dist/semantic-helpers.js +3 -3
  45. package/dist/webhook.cjs +3 -3
  46. package/dist/webhook.js +2 -2
  47. package/dist/workflow-distributed.cjs +4 -4
  48. package/dist/workflow-distributed.js +2 -2
  49. package/dist/workflow.cjs +7 -7
  50. package/dist/workflow.js +3 -3
  51. package/dist/yaml-config.d.cts +1 -1
  52. package/dist/yaml-config.d.ts +1 -1
  53. package/package.json +3 -11
  54. package/src/init.customization.test.ts +152 -0
  55. package/src/init.ts +158 -5
  56. package/src/posthog-logs.ts +1 -1
  57. package/dist/chunk-7RV6L24E.js.map +0 -1
  58. package/dist/chunk-GIND746I.cjs.map +0 -1
package/dist/webhook.js CHANGED
@@ -1,9 +1,9 @@
1
- import { trace } from './chunk-JDV3LYOI.js';
1
+ import { trace } from './chunk-IKRHEUS7.js';
2
2
  import './chunk-SR35DG5A.js';
3
3
  import './chunk-B3ZHLLMP.js';
4
4
  import './chunk-WD4RP6IV.js';
5
5
  import './chunk-USSL3D6L.js';
6
- import './chunk-7RV6L24E.js';
6
+ import './chunk-VXLEJWLY.js';
7
7
  import './chunk-YTGF4L2C.js';
8
8
  import './chunk-X4RMFFMR.js';
9
9
  import './chunk-WGWSHJ2N.js';
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkINJD3G4K_cjs = require('./chunk-INJD3G4K.cjs');
4
- var chunk7ZYUFMWU_cjs = require('./chunk-7ZYUFMWU.cjs');
4
+ var chunkRWOVNF3V_cjs = require('./chunk-RWOVNF3V.cjs');
5
5
  require('./chunk-W4EUTSB2.cjs');
6
6
  require('./chunk-GML3FBOT.cjs');
7
7
  require('./chunk-D5LMF53P.cjs');
8
8
  require('./chunk-XRKAL7WJ.cjs');
9
- require('./chunk-GIND746I.cjs');
9
+ require('./chunk-JVICEM6W.cjs');
10
10
  require('./chunk-6YIDHH2S.cjs');
11
11
  require('./chunk-GVLK7YUU.cjs');
12
12
  require('./chunk-ZNMBW67B.cjs');
@@ -59,7 +59,7 @@ var WorkflowBaggage = chunkINJD3G4K_cjs.createSafeBaggageSchema(workflowBaggageF
59
59
  function traceDistributedWorkflow(config) {
60
60
  const spanName = `workflow.${config.name}`;
61
61
  return (fnFactory) => {
62
- return chunk7ZYUFMWU_cjs.trace(
62
+ return chunkRWOVNF3V_cjs.trace(
63
63
  { name: spanName, spanKind: api.SpanKind.INTERNAL },
64
64
  (baseCtx) => {
65
65
  return async (...args) => {
@@ -158,7 +158,7 @@ function traceDistributedWorkflow(config) {
158
158
  function traceDistributedStep(config) {
159
159
  const spanName = `workflow.step.${config.name}`;
160
160
  return (fnFactory) => {
161
- return chunk7ZYUFMWU_cjs.trace(
161
+ return chunkRWOVNF3V_cjs.trace(
162
162
  { name: spanName, spanKind: api.SpanKind.INTERNAL },
163
163
  (baseCtx) => {
164
164
  return async (...args) => {
@@ -1,10 +1,10 @@
1
1
  import { createSafeBaggageSchema } from './chunk-4IFSYQVX.js';
2
- import { trace } from './chunk-JDV3LYOI.js';
2
+ import { trace } from './chunk-IKRHEUS7.js';
3
3
  import './chunk-SR35DG5A.js';
4
4
  import './chunk-B3ZHLLMP.js';
5
5
  import './chunk-WD4RP6IV.js';
6
6
  import './chunk-USSL3D6L.js';
7
- import './chunk-7RV6L24E.js';
7
+ import './chunk-VXLEJWLY.js';
8
8
  import './chunk-YTGF4L2C.js';
9
9
  import './chunk-X4RMFFMR.js';
10
10
  import './chunk-WGWSHJ2N.js';
package/dist/workflow.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkDBUNRUDB_cjs = require('./chunk-DBUNRUDB.cjs');
4
- require('./chunk-7ZYUFMWU.cjs');
3
+ var chunkMNMLCLHH_cjs = require('./chunk-MNMLCLHH.cjs');
4
+ require('./chunk-RWOVNF3V.cjs');
5
5
  require('./chunk-W4EUTSB2.cjs');
6
6
  require('./chunk-GML3FBOT.cjs');
7
7
  require('./chunk-D5LMF53P.cjs');
8
8
  require('./chunk-XRKAL7WJ.cjs');
9
- require('./chunk-GIND746I.cjs');
9
+ require('./chunk-JVICEM6W.cjs');
10
10
  require('./chunk-6YIDHH2S.cjs');
11
11
  require('./chunk-GVLK7YUU.cjs');
12
12
  require('./chunk-ZNMBW67B.cjs');
@@ -25,19 +25,19 @@ require('./chunk-JEQ2X3Z6.cjs');
25
25
 
26
26
  Object.defineProperty(exports, "getCurrentWorkflowContext", {
27
27
  enumerable: true,
28
- get: function () { return chunkDBUNRUDB_cjs.getCurrentWorkflowContext; }
28
+ get: function () { return chunkMNMLCLHH_cjs.getCurrentWorkflowContext; }
29
29
  });
30
30
  Object.defineProperty(exports, "isInWorkflow", {
31
31
  enumerable: true,
32
- get: function () { return chunkDBUNRUDB_cjs.isInWorkflow; }
32
+ get: function () { return chunkMNMLCLHH_cjs.isInWorkflow; }
33
33
  });
34
34
  Object.defineProperty(exports, "traceStep", {
35
35
  enumerable: true,
36
- get: function () { return chunkDBUNRUDB_cjs.traceStep; }
36
+ get: function () { return chunkMNMLCLHH_cjs.traceStep; }
37
37
  });
38
38
  Object.defineProperty(exports, "traceWorkflow", {
39
39
  enumerable: true,
40
- get: function () { return chunkDBUNRUDB_cjs.traceWorkflow; }
40
+ get: function () { return chunkMNMLCLHH_cjs.traceWorkflow; }
41
41
  });
42
42
  //# sourceMappingURL=workflow.cjs.map
43
43
  //# sourceMappingURL=workflow.cjs.map
package/dist/workflow.js CHANGED
@@ -1,10 +1,10 @@
1
- export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-P5YCN2Z3.js';
2
- import './chunk-JDV3LYOI.js';
1
+ export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-KUSYIHW7.js';
2
+ import './chunk-IKRHEUS7.js';
3
3
  import './chunk-SR35DG5A.js';
4
4
  import './chunk-B3ZHLLMP.js';
5
5
  import './chunk-WD4RP6IV.js';
6
6
  import './chunk-USSL3D6L.js';
7
- import './chunk-7RV6L24E.js';
7
+ import './chunk-VXLEJWLY.js';
8
8
  import './chunk-YTGF4L2C.js';
9
9
  import './chunk-X4RMFFMR.js';
10
10
  import './chunk-WGWSHJ2N.js';
@@ -1,4 +1,4 @@
1
- import { A as AutotelConfig } from './init-Co9CWOY6.cjs';
1
+ import { A as AutotelConfig } from './init-Q4uIQKbq.cjs';
2
2
  import '@opentelemetry/sdk-trace-base';
3
3
  import '@opentelemetry/sdk-node';
4
4
  import '@opentelemetry/resources';
@@ -1,4 +1,4 @@
1
- import { A as AutotelConfig } from './init-oLqNJTpj.js';
1
+ import { A as AutotelConfig } from './init-ls4xSZe5.js';
2
2
  import '@opentelemetry/sdk-trace-base';
3
3
  import '@opentelemetry/sdk-node';
4
4
  import '@opentelemetry/resources';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel",
3
- "version": "2.25.1",
3
+ "version": "2.25.2",
4
4
  "description": "Write Once, Observe Anywhere",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -251,10 +251,12 @@
251
251
  "dependencies": {
252
252
  "@opentelemetry/api": "^1.9.0",
253
253
  "@opentelemetry/api-logs": "^0.213.0",
254
+ "@opentelemetry/exporter-logs-otlp-http": "^0.213.0",
254
255
  "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
255
256
  "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
256
257
  "@opentelemetry/instrumentation": "^0.213.0",
257
258
  "@opentelemetry/resources": "^2.6.0",
259
+ "@opentelemetry/sdk-logs": "^0.213.0",
258
260
  "@opentelemetry/sdk-metrics": "^2.6.0",
259
261
  "@opentelemetry/sdk-node": "^0.213.0",
260
262
  "@opentelemetry/sdk-trace-base": "^2.6.0",
@@ -265,13 +267,11 @@
265
267
  "peerDependencies": {
266
268
  "@opentelemetry/auto-instrumentations-node": "^0.71.0",
267
269
  "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0",
268
- "@opentelemetry/exporter-logs-otlp-http": "^0.213.0",
269
270
  "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0",
270
271
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
271
272
  "@opentelemetry/resource-detector-aws": "^2.13.0",
272
273
  "@opentelemetry/resource-detector-container": "^0.8.4",
273
274
  "@opentelemetry/resource-detector-gcp": "^0.48.0",
274
- "@opentelemetry/sdk-logs": "^0.213.0",
275
275
  "@opentelemetry/sdk-trace-node": "^2.6.0",
276
276
  "@traceloop/node-server-sdk": "^0.22.8",
277
277
  "pino": "^10.3.1",
@@ -285,18 +285,12 @@
285
285
  "@opentelemetry/exporter-logs-otlp-grpc": {
286
286
  "optional": true
287
287
  },
288
- "@opentelemetry/exporter-logs-otlp-http": {
289
- "optional": true
290
- },
291
288
  "@opentelemetry/exporter-metrics-otlp-grpc": {
292
289
  "optional": true
293
290
  },
294
291
  "@opentelemetry/exporter-trace-otlp-grpc": {
295
292
  "optional": true
296
293
  },
297
- "@opentelemetry/sdk-logs": {
298
- "optional": true
299
- },
300
294
  "@opentelemetry/sdk-trace-node": {
301
295
  "optional": true
302
296
  },
@@ -328,13 +322,11 @@
328
322
  "@opentelemetry/auto-instrumentations-node": "^0.71.0",
329
323
  "@opentelemetry/context-async-hooks": "^2.6.0",
330
324
  "@opentelemetry/exporter-logs-otlp-grpc": "^0.213.0",
331
- "@opentelemetry/exporter-logs-otlp-http": "^0.213.0",
332
325
  "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0",
333
326
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
334
327
  "@opentelemetry/resource-detector-aws": "^2.13.0",
335
328
  "@opentelemetry/resource-detector-container": "^0.8.4",
336
329
  "@opentelemetry/resource-detector-gcp": "^0.48.0",
337
- "@opentelemetry/sdk-logs": "^0.213.0",
338
330
  "@opentelemetry/sdk-trace-node": "^2.6.0",
339
331
  "@swc/core": "^1.15.21",
340
332
  "@total-typescript/ts-reset": "^0.6.1",
@@ -14,6 +14,8 @@ async function loadInitWithMocks() {
14
14
  const traceExporterOptions: Record<string, unknown>[] = [];
15
15
  const metricExporterOptions: Record<string, unknown>[] = [];
16
16
  const metricReaderOptions: Record<string, unknown>[] = [];
17
+ const logExporterOptions: Record<string, unknown>[] = [];
18
+ const logProcessorOptions: Record<string, unknown>[] = [];
17
19
 
18
20
  class MockNodeSDK {
19
21
  constructor(options: Record<string, unknown>) {
@@ -71,15 +73,52 @@ async function loadInitWithMocks() {
71
73
  PeriodicExportingMetricReader: MockPeriodicExportingMetricReader,
72
74
  }));
73
75
 
76
+ class MockOTLPLogExporter {
77
+ options: Record<string, unknown>;
78
+
79
+ constructor(options: Record<string, unknown>) {
80
+ this.options = options;
81
+ logExporterOptions.push(options);
82
+ }
83
+ }
84
+
85
+ class MockBatchLogRecordProcessor {
86
+ exporter: unknown;
87
+
88
+ constructor(exporter: unknown) {
89
+ this.exporter = exporter;
90
+ logProcessorOptions.push({ exporter });
91
+ }
92
+
93
+ onEmit() {}
94
+ shutdown() {
95
+ return Promise.resolve();
96
+ }
97
+ forceFlush() {
98
+ return Promise.resolve();
99
+ }
100
+ }
101
+
102
+ vi.doMock('@opentelemetry/exporter-logs-otlp-http', () => ({
103
+ OTLPLogExporter: MockOTLPLogExporter,
104
+ }));
105
+
106
+ vi.doMock('@opentelemetry/sdk-logs', () => ({
107
+ BatchLogRecordProcessor: MockBatchLogRecordProcessor,
108
+ }));
109
+
74
110
  const mod = await import('./init');
75
111
 
76
112
  return {
77
113
  init: mod.init,
78
114
  getConfig: mod.getConfig,
115
+ resolveLogsFlag: mod.resolveLogsFlag,
79
116
  sdkInstances,
80
117
  traceExporterOptions,
81
118
  metricExporterOptions,
82
119
  metricReaderOptions,
120
+ logExporterOptions,
121
+ logProcessorOptions,
83
122
  };
84
123
  }
85
124
 
@@ -87,6 +126,8 @@ describe('init() customization', () => {
87
126
  afterEach(() => {
88
127
  vi.restoreAllMocks();
89
128
  delete process.env.AUTOTEL_METRICS;
129
+ delete process.env.AUTOTEL_LOGS;
130
+ delete process.env.OTEL_LOGS_EXPORTER;
90
131
  delete process.env.NODE_ENV;
91
132
  });
92
133
 
@@ -217,4 +258,115 @@ describe('init() customization', () => {
217
258
  const options = sdkInstances.at(-1)?.options as Record<string, unknown>;
218
259
  expect(options.spanProcessors).toEqual([customProcessor]);
219
260
  });
261
+
262
+ it('auto-configures OTLP log exporter when logs enabled with endpoint', async () => {
263
+ const { init, sdkInstances, logExporterOptions } =
264
+ await loadInitWithMocks();
265
+
266
+ init({
267
+ service: 'log-app',
268
+ endpoint: 'http://localhost:4318',
269
+ logs: true,
270
+ });
271
+
272
+ expect(logExporterOptions).toHaveLength(1);
273
+ expect(logExporterOptions[0]!.url).toBe('http://localhost:4318/v1/logs');
274
+ const options = sdkInstances.at(-1)?.options as Record<string, unknown>;
275
+ expect(options.logRecordProcessors).toBeDefined();
276
+ expect(
277
+ (options.logRecordProcessors as unknown[]).length,
278
+ ).toBeGreaterThanOrEqual(1);
279
+ });
280
+
281
+ it('does not auto-configure logs when logRecordProcessors are omitted', async () => {
282
+ const { init, sdkInstances, logExporterOptions } =
283
+ await loadInitWithMocks();
284
+
285
+ init({
286
+ service: 'default-logs',
287
+ endpoint: 'http://localhost:4318',
288
+ });
289
+
290
+ expect(logExporterOptions).toHaveLength(0);
291
+ const options = sdkInstances.at(-1)?.options as Record<string, unknown>;
292
+ expect(options.logRecordProcessors).toBeUndefined();
293
+ });
294
+
295
+ it('does not override OTEL_LOGS_EXPORTER env configuration by default', async () => {
296
+ const { init, sdkInstances, logExporterOptions } =
297
+ await loadInitWithMocks();
298
+
299
+ process.env.OTEL_LOGS_EXPORTER = 'none';
300
+
301
+ init({
302
+ service: 'env-logs',
303
+ endpoint: 'http://localhost:4318',
304
+ });
305
+
306
+ expect(logExporterOptions).toHaveLength(0);
307
+ const options = sdkInstances.at(-1)?.options as Record<string, unknown>;
308
+ expect(options.logRecordProcessors).toBeUndefined();
309
+ });
310
+
311
+ it('auto-configures logs when logs: true is set', async () => {
312
+ const { init, logExporterOptions } = await loadInitWithMocks();
313
+
314
+ init({
315
+ service: 'default-logs',
316
+ endpoint: 'http://localhost:4318',
317
+ logs: true,
318
+ });
319
+
320
+ expect(logExporterOptions).toHaveLength(1);
321
+ });
322
+
323
+ it('skips log exporter when logs: false', async () => {
324
+ const { init, logExporterOptions } = await loadInitWithMocks();
325
+
326
+ init({
327
+ service: 'no-logs',
328
+ endpoint: 'http://localhost:4318',
329
+ logs: false,
330
+ });
331
+
332
+ expect(logExporterOptions).toHaveLength(0);
333
+ });
334
+
335
+ it('skips log exporter when no endpoint', async () => {
336
+ const { init, logExporterOptions } = await loadInitWithMocks();
337
+
338
+ init({ service: 'no-endpoint', logs: true });
339
+
340
+ expect(logExporterOptions).toHaveLength(0);
341
+ });
342
+
343
+ it('respects AUTOTEL_LOGS env var override', async () => {
344
+ const { resolveLogsFlag } = await loadInitWithMocks();
345
+
346
+ process.env.AUTOTEL_LOGS = 'off';
347
+ expect(resolveLogsFlag(true)).toBe(false);
348
+
349
+ process.env.AUTOTEL_LOGS = 'on';
350
+ expect(resolveLogsFlag(false)).toBe(true);
351
+
352
+ delete process.env.AUTOTEL_LOGS;
353
+ expect(resolveLogsFlag(true)).toBe(true);
354
+ expect(resolveLogsFlag(false)).toBe(false);
355
+ });
356
+
357
+ it('passes OTLP headers to log exporter', async () => {
358
+ const { init, logExporterOptions } = await loadInitWithMocks();
359
+
360
+ init({
361
+ service: 'headers-logs',
362
+ endpoint: 'http://localhost:4318',
363
+ logs: true,
364
+ headers: { Authorization: 'Bearer token' },
365
+ });
366
+
367
+ expect(logExporterOptions).toHaveLength(1);
368
+ expect(logExporterOptions[0]!.headers).toEqual({
369
+ Authorization: 'Bearer token',
370
+ });
371
+ });
220
372
  });
package/src/init.ts CHANGED
@@ -12,8 +12,11 @@ import {
12
12
  type SpanProcessor,
13
13
  SimpleSpanProcessor,
14
14
  ConsoleSpanExporter,
15
+ SamplingDecision,
16
+ type SpanExporter,
17
+ type Sampler as OtelSampler,
18
+ type SamplingResult,
15
19
  } from '@opentelemetry/sdk-trace-base';
16
- import type { SpanExporter } from '@opentelemetry/sdk-trace-base';
17
20
  import {
18
21
  resourceFromAttributes,
19
22
  type Resource,
@@ -26,7 +29,7 @@ import type { Sampler } from './sampling';
26
29
  import { AdaptiveSampler } from './sampling';
27
30
  import type { EventSubscriber } from './event-subscriber';
28
31
  import type { Logger } from './logger';
29
- import type { Attributes } from '@opentelemetry/api';
32
+ import type { Attributes, Context, SpanKind, Link } from '@opentelemetry/api';
30
33
  import type { ValidationConfig } from './validation';
31
34
  import {
32
35
  PeriodicExportingMetricReader,
@@ -34,9 +37,17 @@ import {
34
37
  } from '@opentelemetry/sdk-metrics';
35
38
  import { OTLPMetricExporter as OTLPMetricExporterHTTP } from '@opentelemetry/exporter-metrics-otlp-http';
36
39
  import { OTLPTraceExporter as OTLPTraceExporterHTTP } from '@opentelemetry/exporter-trace-otlp-http';
40
+ import { OTLPLogExporter as OTLPLogExporterHTTP } from '@opentelemetry/exporter-logs-otlp-http';
37
41
  import type { PushMetricExporter } from '@opentelemetry/sdk-metrics';
38
- import type { LogRecordProcessor } from '@opentelemetry/sdk-logs';
39
- import { buildPostHogLogProcessors } from './posthog-logs';
42
+ import {
43
+ BatchLogRecordProcessor,
44
+ type LogRecordExporter,
45
+ type LogRecordProcessor,
46
+ } from '@opentelemetry/sdk-logs';
47
+ import {
48
+ buildPostHogLogProcessors,
49
+ RedactingLogRecordProcessor,
50
+ } from './posthog-logs';
40
51
  import { TailSamplingSpanProcessor } from './tail-sampling-processor';
41
52
  import { BaggageSpanProcessor } from './baggage-span-processor';
42
53
  import {
@@ -75,6 +86,36 @@ const silentLogger: Logger = {
75
86
  debug: () => {},
76
87
  };
77
88
 
89
+ /**
90
+ * Adapts an Autotel Sampler to the OTel SDK Sampler interface.
91
+ */
92
+ function toOtelSampler(sampler: Sampler): OtelSampler {
93
+ return {
94
+ shouldSample(
95
+ _context: Context,
96
+ _traceId: string,
97
+ spanName: string,
98
+ _spanKind: SpanKind,
99
+ _attributes: Attributes,
100
+ links: Link[],
101
+ ): SamplingResult {
102
+ const shouldTrace = sampler.shouldSample({
103
+ operationName: spanName,
104
+ args: [],
105
+ links,
106
+ });
107
+ return {
108
+ decision: shouldTrace
109
+ ? SamplingDecision.RECORD_AND_SAMPLED
110
+ : SamplingDecision.NOT_RECORD,
111
+ };
112
+ },
113
+ toString(): string {
114
+ return `AutotelSamplerAdapter`;
115
+ },
116
+ };
117
+ }
118
+
78
119
  // Type imports for exporters
79
120
  type OTLPExporterConfig = {
80
121
  url?: string;
@@ -90,6 +131,9 @@ let OTLPTraceExporterGRPC:
90
131
  let OTLPMetricExporterGRPC:
91
132
  | (new (config: OTLPExporterConfig) => PushMetricExporter)
92
133
  | undefined;
134
+ let OTLPLogExporterGRPC:
135
+ | (new (config: OTLPExporterConfig) => LogRecordExporter)
136
+ | undefined;
93
137
 
94
138
  /**
95
139
  * Helper: Lazy-load gRPC trace exporter
@@ -169,6 +213,43 @@ function createMetricExporter(
169
213
  return new OTLPMetricExporterHTTP(config);
170
214
  }
171
215
 
216
+ /**
217
+ * Helper: Lazy-load gRPC log exporter
218
+ */
219
+ function loadGRPCLogExporter(): new (
220
+ config: OTLPExporterConfig,
221
+ ) => LogRecordExporter {
222
+ if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC;
223
+
224
+ try {
225
+ const grpcModule = requireModule<{
226
+ OTLPLogExporter: new (config: OTLPExporterConfig) => LogRecordExporter;
227
+ }>('@opentelemetry/exporter-logs-otlp-grpc');
228
+ OTLPLogExporterGRPC = grpcModule.OTLPLogExporter;
229
+ return OTLPLogExporterGRPC;
230
+ } catch {
231
+ throw new Error(
232
+ 'gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc',
233
+ );
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Helper: Create log exporter based on protocol
239
+ */
240
+ function createLogExporter(
241
+ protocol: 'http' | 'grpc',
242
+ config: OTLPExporterConfig,
243
+ ): LogRecordExporter {
244
+ if (protocol === 'grpc') {
245
+ const Exporter = loadGRPCLogExporter();
246
+ return new Exporter(config);
247
+ }
248
+
249
+ // Default: HTTP
250
+ return new OTLPLogExporterHTTP(config);
251
+ }
252
+
172
253
  /**
173
254
  * Helper: Resolve protocol from config and environment
174
255
  */
@@ -194,7 +275,7 @@ function resolveProtocol(configProtocol?: 'http' | 'grpc'): 'http' | 'grpc' {
194
275
  */
195
276
  function formatEndpointUrl(
196
277
  endpoint: string,
197
- signal: 'traces' | 'metrics',
278
+ signal: 'traces' | 'metrics' | 'logs',
198
279
  protocol: 'http' | 'grpc',
199
280
  ): string {
200
281
  if (protocol === 'grpc') {
@@ -496,6 +577,32 @@ export interface AutotelConfig {
496
577
  */
497
578
  metrics?: boolean | 'auto';
498
579
 
580
+ /**
581
+ * OTLP logs configuration
582
+ * - true: auto-configure OTLP log exporter from endpoint
583
+ * - false: disabled (default)
584
+ * - 'auto': same as false (opt-in only)
585
+ *
586
+ * When enabled and an endpoint is configured, autotel will automatically
587
+ * create a BatchLogRecordProcessor with an OTLPLogExporter - no manual
588
+ * imports needed. Works alongside logRecordProcessors (additive).
589
+ *
590
+ * Requires @opentelemetry/sdk-logs and @opentelemetry/exporter-logs-otlp-http
591
+ * (or -grpc) as peer dependencies.
592
+ *
593
+ * Can be overridden with AUTOTEL_LOGS=on|off env var.
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * init({
598
+ * service: 'my-app',
599
+ * endpoint: 'http://localhost:4318',
600
+ * logs: true,
601
+ * });
602
+ * ```
603
+ */
604
+ logs?: boolean | 'auto';
605
+
499
606
  /** Sampling strategy (default: AdaptiveSampler with 10% baseline) */
500
607
  sampler?: Sampler;
501
608
 
@@ -1043,6 +1150,27 @@ export function resolveMetricsFlag(
1043
1150
  return true;
1044
1151
  }
1045
1152
 
1153
+ /**
1154
+ * Resolve logs flag with env var override support.
1155
+ * Defaults to disabled (opt-in only) to avoid unexpected log export
1156
+ * and to preserve the upstream SDK's OTEL_LOGS_EXPORTER handling.
1157
+ */
1158
+ export function resolveLogsFlag(
1159
+ configFlag: boolean | 'auto' = 'auto',
1160
+ ): boolean {
1161
+ // 1. Check env var override (highest priority)
1162
+ const envFlag = process.env.AUTOTEL_LOGS;
1163
+ if (envFlag === 'on' || envFlag === 'true') return true;
1164
+ if (envFlag === 'off' || envFlag === 'false') return false;
1165
+
1166
+ // 2. Check config flag
1167
+ if (configFlag === true) return true;
1168
+ if (configFlag === false) return false;
1169
+
1170
+ // 3. Default: disabled (opt-in only)
1171
+ return false;
1172
+ }
1173
+
1046
1174
  /**
1047
1175
  * Resolve debug flag with env var override support
1048
1176
  *
@@ -1184,6 +1312,7 @@ export function init(cfg: AutotelConfig): void {
1184
1312
  const environment =
1185
1313
  mergedConfig.environment || process.env.NODE_ENV || 'development';
1186
1314
  const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
1315
+ const logsEnabled = resolveLogsFlag(mergedConfig.logs);
1187
1316
 
1188
1317
  // Detect hostname for proper Datadog correlation and Service Catalog discovery
1189
1318
  const hostname = detectHostname();
@@ -1373,6 +1502,26 @@ export function init(cfg: AutotelConfig): void {
1373
1502
  logRecordProcessors = [...mergedConfig.logRecordProcessors];
1374
1503
  }
1375
1504
 
1505
+ // Auto-configure OTLP log exporter when logs are enabled and endpoint is set
1506
+ if (logsEnabled && endpoint) {
1507
+ const logExporter = createLogExporter(protocol, {
1508
+ url: formatEndpointUrl(endpoint, 'logs', protocol),
1509
+ headers: otlpHeaders,
1510
+ });
1511
+
1512
+ let processor: LogRecordProcessor = new BatchLogRecordProcessor(
1513
+ logExporter,
1514
+ );
1515
+ if (_stringRedactor) {
1516
+ processor = new RedactingLogRecordProcessor(processor, _stringRedactor);
1517
+ }
1518
+ if (!logRecordProcessors) {
1519
+ logRecordProcessors = [];
1520
+ }
1521
+ logRecordProcessors.push(processor);
1522
+ logger.info({}, '[autotel] OTLP log exporter configured');
1523
+ }
1524
+
1376
1525
  // PostHog OTLP logs integration
1377
1526
  const posthogProcessors = buildPostHogLogProcessors(
1378
1527
  mergedConfig.posthog,
@@ -1443,8 +1592,12 @@ export function init(cfg: AutotelConfig): void {
1443
1592
  }
1444
1593
  }
1445
1594
 
1595
+ const autotelSampler = mergedConfig.sampler || getDefaultSampler();
1596
+ const sampler: OtelSampler = toOtelSampler(autotelSampler);
1597
+
1446
1598
  const sdkOptions: Partial<NodeSDKConfiguration> = {
1447
1599
  resource,
1600
+ sampler,
1448
1601
  instrumentations: finalInstrumentations,
1449
1602
  };
1450
1603
 
@@ -2,7 +2,7 @@ import type { LogRecordProcessor } from '@opentelemetry/sdk-logs';
2
2
  import { safeRequire } from './node-require';
3
3
  import type { StringRedactor } from './redact-values';
4
4
 
5
- class RedactingLogRecordProcessor implements LogRecordProcessor {
5
+ export class RedactingLogRecordProcessor implements LogRecordProcessor {
6
6
  constructor(
7
7
  private wrapped: LogRecordProcessor,
8
8
  private redact: StringRedactor,