autotel 4.0.0 → 4.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 (83) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -1
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/src/init.customization.test.ts +71 -0
  72. package/src/init.ts +167 -40
  73. package/src/yaml-config.test.ts +36 -0
  74. package/src/yaml-config.ts +10 -1
  75. package/dist/functional-BGkT8J-h.js.map +0 -1
  76. package/dist/init-CNp-ee80.d.cts.map +0 -1
  77. package/dist/init-Ch6t7MNI.js.map +0 -1
  78. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  79. package/dist/init-DvapOXCc.cjs.map +0 -1
  80. package/dist/logger.cjs.map +0 -1
  81. package/dist/logger.js.map +0 -1
  82. package/dist/track-nsKVy-pj.js.map +0 -1
  83. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
@@ -74,7 +74,7 @@ export function register() {
74
74
  if (process.env.NEXT_RUNTIME === 'nodejs') {
75
75
  init({
76
76
  service: 'my-app',
77
- exporter: { url: process.env.OTLP_ENDPOINT! },
77
+ endpoint: process.env.OTLP_ENDPOINT!,
78
78
  sampling: { rates: { server: 25, client: 5 } },
79
79
  attributeRedactor: 'default',
80
80
  });
@@ -201,7 +201,7 @@ export const handler = withLambda(async (event) => {
201
201
  ```typescript
202
202
  import { init, trace } from 'autotel';
203
203
 
204
- init({ service: 'my-worker', exporter: { url: process.env.OTLP_ENDPOINT! } });
204
+ init({ service: 'my-worker', endpoint: process.env.OTLP_ENDPOINT! });
205
205
 
206
206
  const processJob = trace(async (job: Job) => {
207
207
  // span auto-named after the function
@@ -219,7 +219,8 @@ All options work with `init()`, framework adapters, and `wrapModule` / `defineWo
219
219
  | Option | Type | Default | Description |
220
220
  | --------------------------------------- | --------------------------------------------------------------- | ----------------- | --------------------------------------------------------------- |
221
221
  | `service` / `service.name` | `string` | `'app'` | Service name in `service.name` resource attribute |
222
- | `exporter` | `{ url, headers?, protocol? }` | — | OTLP HTTP/JSON or HTTP/protobuf endpoint |
222
+ | `endpoint` | `string` | — | Single OTLP destination shorthand |
223
+ | `destinations` | `Array<{ endpoint, headers?, protocol?, signals? }>` | — | Declarative OTLP fan-out to multiple backends |
223
224
  | `spanProcessors` | `SpanProcessor[]` | — | Use **instead of** `exporter` for full control |
224
225
  | `sampling.rates` | `{ server?: number, client?: number, internal?: number }` | `100%` | Head sampling per span kind (0–100%) |
225
226
  | `sampling.tail` | `TailSampleFn` | — | Keep traces matching predicate (e.g. errors, slow) |
@@ -291,7 +292,7 @@ Switch backends with **no code changes** — autotel speaks OTLP HTTP/JSON and H
291
292
  | New Relic | `https://otlp.nr-data.net/v1/traces` | `{ 'api-key': '<key>' }` |
292
293
  | Local Jaeger / Tempo / Collector | `http://localhost:4318/v1/traces` | — |
293
294
 
294
- Use `init({ exporter: { url, headers } })`. Multiple destinations? Use `composeSpanProcessors([batchA, batchB])` (see Composition below).
295
+ Use `init({ endpoint, headers })` for one backend. For multiple OTLP backends, prefer `init({ destinations: [...] })`. Drop to `composeSpanProcessors([batchA, batchB])` only when you need custom processor-level control.
295
296
 
296
297
  ---
297
298
 
@@ -325,6 +325,77 @@ describe('init() customization', () => {
325
325
  });
326
326
  });
327
327
 
328
+ it('supports declarative multi-destination OTLP fan-out', async () => {
329
+ const {
330
+ init,
331
+ traceExporterOptions,
332
+ metricExporterOptions,
333
+ logExporterOptions,
334
+ metricReaderOptions,
335
+ } = await loadInitWithMocks();
336
+
337
+ init({
338
+ service: 'fanout-app',
339
+ logs: true,
340
+ destinations: [
341
+ {
342
+ endpoint: 'https://otlp-gateway.grafana.net/otlp',
343
+ headers: { Authorization: 'Basic grafana' },
344
+ },
345
+ {
346
+ endpoint: 'https://api.honeycomb.io',
347
+ headers: { 'x-honeycomb-team': 'hny' },
348
+ signals: ['traces'],
349
+ },
350
+ ],
351
+ });
352
+
353
+ expect(traceExporterOptions).toHaveLength(2);
354
+ expect(traceExporterOptions[0]).toMatchObject({
355
+ url: 'https://otlp-gateway.grafana.net/otlp/v1/traces',
356
+ headers: { Authorization: 'Basic grafana' },
357
+ });
358
+ expect(traceExporterOptions[1]).toMatchObject({
359
+ url: 'https://api.honeycomb.io/v1/traces',
360
+ headers: { 'x-honeycomb-team': 'hny' },
361
+ });
362
+
363
+ expect(metricExporterOptions).toHaveLength(1);
364
+ expect(metricExporterOptions[0]).toMatchObject({
365
+ url: 'https://otlp-gateway.grafana.net/otlp/v1/metrics',
366
+ });
367
+ expect(metricReaderOptions).toHaveLength(1);
368
+
369
+ expect(logExporterOptions).toHaveLength(1);
370
+ expect(logExporterOptions[0]).toMatchObject({
371
+ url: 'https://otlp-gateway.grafana.net/otlp/v1/logs',
372
+ });
373
+ });
374
+
375
+ it('lets destinations inherit top-level protocol and headers', async () => {
376
+ const { init, traceExporterOptions, metricExporterOptions } =
377
+ await loadInitWithMocks();
378
+
379
+ init({
380
+ service: 'fanout-inherited',
381
+ protocol: 'http',
382
+ headers: 'Authorization=Bearer shared',
383
+ destinations: [
384
+ { endpoint: 'https://grafana.example.com/otlp' },
385
+ { endpoint: 'https://honeycomb.example.com' },
386
+ ],
387
+ });
388
+
389
+ expect(traceExporterOptions).toHaveLength(2);
390
+ expect(traceExporterOptions[0]).toMatchObject({
391
+ headers: { Authorization: 'Bearer shared' },
392
+ });
393
+ expect(traceExporterOptions[1]).toMatchObject({
394
+ headers: { Authorization: 'Bearer shared' },
395
+ });
396
+ expect(metricExporterOptions).toHaveLength(2);
397
+ });
398
+
328
399
  it('resolves sampling preset shorthand to a sampler instance', async () => {
329
400
  const { init, getDefaultSampler } = await loadInitWithMocks();
330
401
 
package/src/init.ts CHANGED
@@ -126,6 +126,33 @@ type OTLPExporterConfig = {
126
126
  concurrencyLimit?: number;
127
127
  };
128
128
 
129
+ export type OtlpSignal = 'traces' | 'metrics' | 'logs';
130
+
131
+ export interface OtlpDestinationConfig {
132
+ /**
133
+ * Base OTLP endpoint for this destination.
134
+ * HTTP destinations may omit `/v1/{signal}`; autotel appends it automatically.
135
+ * gRPC destinations should point at the collector host:port.
136
+ */
137
+ endpoint: string;
138
+
139
+ /**
140
+ * Headers for this destination. Falls back to top-level `headers`.
141
+ */
142
+ headers?: Record<string, string> | string;
143
+
144
+ /**
145
+ * Protocol for this destination. Falls back to top-level `protocol`.
146
+ */
147
+ protocol?: 'http' | 'grpc';
148
+
149
+ /**
150
+ * Signals to send to this destination.
151
+ * Defaults to all signals supported by the current init() config.
152
+ */
153
+ signals?: OtlpSignal[];
154
+ }
155
+
129
156
  // Lazy-load gRPC exporters (optional peer dependencies)
130
157
  let OTLPTraceExporterGRPC:
131
158
  | (new (config: OTLPExporterConfig) => SpanExporter)
@@ -415,11 +442,45 @@ export interface AutotelConfig {
415
442
 
416
443
  /**
417
444
  * OTLP endpoint for traces/metrics/logs
445
+ * Single-destination shorthand. For multi-backend OTLP fan-out, use
446
+ * `destinations` instead.
418
447
  * Only used if you don't provide custom exporters/processors
419
448
  * @default process.env.OTLP_ENDPOINT || 'http://localhost:4318'
420
449
  */
421
450
  endpoint?: string;
422
451
 
452
+ /**
453
+ * Declarative OTLP multi-destination config.
454
+ * Each destination can override endpoint, headers, protocol, and signals.
455
+ *
456
+ * This is the high-level alternative to wiring `spanExporters`,
457
+ * `spanProcessors`, `metricReaders`, and `logRecordProcessors` manually when
458
+ * you want to fan telemetry out to multiple OTLP backends.
459
+ *
460
+ * When provided, `destinations` takes precedence over the single `endpoint`
461
+ * shorthand for built-in OTLP exporters/readers/processors.
462
+ *
463
+ * @example Grafana + Honeycomb for traces, Grafana only for metrics/logs
464
+ * ```typescript
465
+ * init({
466
+ * service: 'my-app',
467
+ * logs: true,
468
+ * destinations: [
469
+ * {
470
+ * endpoint: 'https://otlp-gateway-prod-eu-west-2.grafana.net/otlp',
471
+ * headers: { Authorization: 'Basic ...' },
472
+ * },
473
+ * {
474
+ * endpoint: 'https://api.honeycomb.io',
475
+ * headers: { 'x-honeycomb-team': '...' },
476
+ * signals: ['traces'],
477
+ * },
478
+ * ],
479
+ * })
480
+ * ```
481
+ */
482
+ destinations?: OtlpDestinationConfig[];
483
+
423
484
  /**
424
485
  * Custom span processors for traces (supports multiple processors)
425
486
  * Allows you to use any backend: Jaeger, Zipkin, Datadog, New Relic, etc.
@@ -1406,6 +1467,45 @@ function normalizeOtlpHeaders(
1406
1467
  return parsed;
1407
1468
  }
1408
1469
 
1470
+ type ResolvedOtlpDestination = {
1471
+ endpoint: string;
1472
+ protocol: 'http' | 'grpc';
1473
+ headers?: Record<string, string>;
1474
+ signals?: Set<OtlpSignal>;
1475
+ };
1476
+
1477
+ function resolveOtlpDestinations(
1478
+ config: AutotelConfig,
1479
+ fallbackEndpoint?: string,
1480
+ ): ResolvedOtlpDestination[] {
1481
+ const rawDestinations =
1482
+ config.destinations !== undefined
1483
+ ? config.destinations
1484
+ : fallbackEndpoint
1485
+ ? [
1486
+ {
1487
+ endpoint: fallbackEndpoint,
1488
+ headers: config.headers,
1489
+ protocol: config.protocol,
1490
+ },
1491
+ ]
1492
+ : [];
1493
+
1494
+ return rawDestinations.map((destination) => ({
1495
+ endpoint: destination.endpoint,
1496
+ protocol: resolveProtocol(destination.protocol ?? config.protocol),
1497
+ headers: normalizeOtlpHeaders(destination.headers ?? config.headers),
1498
+ signals: destination.signals ? new Set(destination.signals) : undefined,
1499
+ }));
1500
+ }
1501
+
1502
+ function destinationSupportsSignal(
1503
+ destination: ResolvedOtlpDestination,
1504
+ signal: OtlpSignal,
1505
+ ): boolean {
1506
+ return destination.signals ? destination.signals.has(signal) : true;
1507
+ }
1508
+
1409
1509
  /**
1410
1510
  * Initialize autotel - Write Once, Observe Everywhere
1411
1511
  *
@@ -1533,7 +1633,6 @@ export function init(cfg: AutotelConfig): void {
1533
1633
  // Initialize OpenTelemetry
1534
1634
  // Only use endpoint if explicitly configured (no default fallback)
1535
1635
  let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint;
1536
- const otlpHeaders = normalizeOtlpHeaders(mergedConfig.headers);
1537
1636
  const version = mergedConfig.version || detectVersion();
1538
1637
  const environment =
1539
1638
  mergedConfig.environment || process.env.NODE_ENV || 'development';
@@ -1600,8 +1699,7 @@ export function init(cfg: AutotelConfig): void {
1600
1699
  );
1601
1700
  }
1602
1701
 
1603
- // Resolve OTLP protocol (http or grpc)
1604
- const protocol = resolveProtocol(mergedConfig.protocol);
1702
+ const otlpDestinations = resolveOtlpDestinations(mergedConfig, endpoint);
1605
1703
 
1606
1704
  // Backward-compatible singular aliases. Plural forms take precedence when both are provided.
1607
1705
  const configuredSpanProcessors =
@@ -1643,16 +1741,23 @@ export function init(cfg: AutotelConfig): void {
1643
1741
  new TailSamplingSpanProcessor(new BatchSpanProcessor(exporter)),
1644
1742
  );
1645
1743
  }
1646
- } else if (endpoint) {
1647
- // Default: OTLP with tail sampling (only if endpoint is configured)
1648
- const traceExporter = createTraceExporter(protocol, {
1649
- url: formatEndpointUrl(endpoint, 'traces', protocol),
1650
- headers: otlpHeaders,
1651
- });
1652
-
1653
- spanProcessors.push(
1654
- new TailSamplingSpanProcessor(new BatchSpanProcessor(traceExporter)),
1655
- );
1744
+ } else {
1745
+ for (const destination of otlpDestinations) {
1746
+ if (!destinationSupportsSignal(destination, 'traces')) continue;
1747
+
1748
+ const traceExporter = createTraceExporter(destination.protocol, {
1749
+ url: formatEndpointUrl(
1750
+ destination.endpoint,
1751
+ 'traces',
1752
+ destination.protocol,
1753
+ ),
1754
+ headers: destination.headers,
1755
+ });
1756
+
1757
+ spanProcessors.push(
1758
+ new TailSamplingSpanProcessor(new BatchSpanProcessor(traceExporter)),
1759
+ );
1760
+ }
1656
1761
  }
1657
1762
  // If no endpoint and no custom processors/exporters, array remains empty
1658
1763
  // SDK will still work but won't export traces
@@ -1760,18 +1865,25 @@ export function init(cfg: AutotelConfig): void {
1760
1865
  if (configuredMetricReaders && configuredMetricReaders.length > 0) {
1761
1866
  // User provided custom metric readers
1762
1867
  metricReaders.push(...configuredMetricReaders);
1763
- } else if (metricsEnabled && endpoint) {
1764
- // Default: OTLP metrics exporter (only if endpoint is configured)
1765
- const metricExporter = createMetricExporter(protocol, {
1766
- url: formatEndpointUrl(endpoint, 'metrics', protocol),
1767
- headers: otlpHeaders,
1768
- });
1769
-
1770
- metricReaders.push(
1771
- new PeriodicExportingMetricReader({
1772
- exporter: metricExporter,
1773
- }),
1774
- );
1868
+ } else if (metricsEnabled) {
1869
+ for (const destination of otlpDestinations) {
1870
+ if (!destinationSupportsSignal(destination, 'metrics')) continue;
1871
+
1872
+ const metricExporter = createMetricExporter(destination.protocol, {
1873
+ url: formatEndpointUrl(
1874
+ destination.endpoint,
1875
+ 'metrics',
1876
+ destination.protocol,
1877
+ ),
1878
+ headers: destination.headers,
1879
+ });
1880
+
1881
+ metricReaders.push(
1882
+ new PeriodicExportingMetricReader({
1883
+ exporter: metricExporter,
1884
+ }),
1885
+ );
1886
+ }
1775
1887
  }
1776
1888
 
1777
1889
  let logRecordProcessors: LogRecordProcessor[] | undefined;
@@ -1782,24 +1894,39 @@ export function init(cfg: AutotelConfig): void {
1782
1894
  logRecordProcessors = [...configuredLogRecordProcessors];
1783
1895
  }
1784
1896
 
1785
- // Auto-configure OTLP log exporter when logs are enabled and endpoint is set
1786
- if (logsEnabled && endpoint) {
1787
- const logExporter = createLogExporter(protocol, {
1788
- url: formatEndpointUrl(endpoint, 'logs', protocol),
1789
- headers: otlpHeaders,
1790
- });
1897
+ // Auto-configure OTLP log exporters when logs are enabled.
1898
+ if (logsEnabled) {
1899
+ for (const destination of otlpDestinations) {
1900
+ if (!destinationSupportsSignal(destination, 'logs')) continue;
1901
+
1902
+ const logExporter = createLogExporter(destination.protocol, {
1903
+ url: formatEndpointUrl(
1904
+ destination.endpoint,
1905
+ 'logs',
1906
+ destination.protocol,
1907
+ ),
1908
+ headers: destination.headers,
1909
+ });
1791
1910
 
1792
- let processor: LogRecordProcessor = new BatchLogRecordProcessor(
1793
- logExporter,
1794
- );
1795
- if (_stringRedactor) {
1796
- processor = new RedactingLogRecordProcessor(processor, _stringRedactor);
1911
+ let processor: LogRecordProcessor = new BatchLogRecordProcessor(
1912
+ logExporter,
1913
+ );
1914
+ if (_stringRedactor) {
1915
+ processor = new RedactingLogRecordProcessor(processor, _stringRedactor);
1916
+ }
1917
+ if (!logRecordProcessors) {
1918
+ logRecordProcessors = [];
1919
+ }
1920
+ logRecordProcessors.push(processor);
1797
1921
  }
1798
- if (!logRecordProcessors) {
1799
- logRecordProcessors = [];
1922
+
1923
+ if (
1924
+ otlpDestinations.some((destination) =>
1925
+ destinationSupportsSignal(destination, 'logs'),
1926
+ )
1927
+ ) {
1928
+ logger.info({}, '[autotel] OTLP log exporter configured');
1800
1929
  }
1801
- logRecordProcessors.push(processor);
1802
- logger.info({}, '[autotel] OTLP log exporter configured');
1803
1930
  }
1804
1931
 
1805
1932
  // PostHog OTLP logs integration
@@ -70,6 +70,42 @@ exporter:
70
70
  });
71
71
  });
72
72
 
73
+ it('should parse exporter destinations configuration', () => {
74
+ const yaml = `
75
+ exporter:
76
+ destinations:
77
+ - endpoint: https://otlp-gateway.grafana.net/otlp
78
+ headers:
79
+ Authorization: Basic grafana
80
+ - endpoint: https://api.honeycomb.io
81
+ protocol: grpc
82
+ headers:
83
+ x-honeycomb-team: secret-key
84
+ signals:
85
+ - traces
86
+ `;
87
+ const filePath = path.join(testDir, 'exporter-destinations.yaml');
88
+ writeFileSync(filePath, yaml);
89
+
90
+ const config = loadYamlConfigFromFile(filePath);
91
+ expect(config.destinations).toEqual([
92
+ {
93
+ endpoint: 'https://otlp-gateway.grafana.net/otlp',
94
+ headers: {
95
+ Authorization: 'Basic grafana',
96
+ },
97
+ },
98
+ {
99
+ endpoint: 'https://api.honeycomb.io',
100
+ protocol: 'grpc',
101
+ headers: {
102
+ 'x-honeycomb-team': 'secret-key',
103
+ },
104
+ signals: ['traces'],
105
+ },
106
+ ]);
107
+ });
108
+
73
109
  it('should parse resource attributes', () => {
74
110
  const yaml = `
75
111
  resource:
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { readFileSync, existsSync } from 'node:fs';
25
25
  import path from 'node:path';
26
- import type { AutotelConfig } from './init';
26
+ import type { AutotelConfig, OtlpSignal } from './init';
27
27
  import {
28
28
  AdaptiveSampler,
29
29
  AlwaysSampler,
@@ -61,6 +61,12 @@ export interface YamlConfig {
61
61
  endpoint?: string;
62
62
  protocol?: 'http' | 'grpc';
63
63
  headers?: Record<string, string>;
64
+ destinations?: Array<{
65
+ endpoint: string;
66
+ protocol?: 'http' | 'grpc';
67
+ headers?: Record<string, string>;
68
+ signals?: OtlpSignal[];
69
+ }>;
64
70
  };
65
71
  resource?: Record<string, string | number | boolean>;
66
72
  sampling?: {
@@ -179,6 +185,9 @@ function yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {
179
185
  if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;
180
186
  if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;
181
187
  if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;
188
+ if (yaml.exporter?.destinations) {
189
+ config.destinations = yaml.exporter.destinations;
190
+ }
182
191
 
183
192
  // Resource attributes (flattened)
184
193
  if (yaml.resource) config.resourceAttributes = yaml.resource;