autotel 4.3.0 → 6.0.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/README.md +125 -103
- package/dist/analysis.cjs +87 -0
- package/dist/analysis.cjs.map +1 -0
- package/dist/analysis.d.cts +87 -0
- package/dist/analysis.d.cts.map +1 -0
- package/dist/analysis.d.ts +87 -0
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +85 -0
- package/dist/analysis.js.map +1 -0
- package/dist/{attribute-redacting-processor-DtTS9xxh.d.ts → attribute-redacting-processor-Pk7lAzWd.d.cts} +13 -8
- package/dist/attribute-redacting-processor-Pk7lAzWd.d.cts.map +1 -0
- package/dist/{attribute-redacting-processor-DtTS9xxh.d.cts → attribute-redacting-processor-Pk7lAzWd.d.ts} +13 -8
- package/dist/attribute-redacting-processor-Pk7lAzWd.d.ts.map +1 -0
- package/dist/attribute-redacting-processor.cjs +4 -4
- package/dist/attribute-redacting-processor.cjs.map +1 -1
- package/dist/attribute-redacting-processor.d.cts +1 -1
- package/dist/attribute-redacting-processor.d.ts +1 -1
- package/dist/attribute-redacting-processor.js +4 -4
- package/dist/attribute-redacting-processor.js.map +1 -1
- package/dist/attributes.d.cts +1 -1
- package/dist/attributes.d.ts +1 -1
- package/dist/auto.cjs +1 -1
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.d.cts +1 -1
- package/dist/auto.d.ts +1 -1
- package/dist/auto.js +1 -1
- package/dist/auto.js.map +1 -1
- package/dist/business-baggage.d.cts +2 -3
- package/dist/business-baggage.d.cts.map +1 -1
- package/dist/business-baggage.d.ts +2 -3
- package/dist/business-baggage.d.ts.map +1 -1
- package/dist/{canonical-log-line-processor-xvjMgtpF.d.cts → canonical-log-line-processor-B4WgG-VS.d.cts} +2 -3
- package/dist/canonical-log-line-processor-B4WgG-VS.d.cts.map +1 -0
- package/dist/{canonical-log-line-processor-BcRuAdRk.d.ts → canonical-log-line-processor-CL_J7gb5.d.ts} +2 -3
- package/dist/canonical-log-line-processor-CL_J7gb5.d.ts.map +1 -0
- package/dist/{canonical-log-line-processor-CcrDi6TO.cjs → canonical-log-line-processor-DOjb6Kxy.cjs} +5 -5
- package/dist/canonical-log-line-processor-DOjb6Kxy.cjs.map +1 -0
- package/dist/{canonical-log-line-processor-FLezvFCB.js → canonical-log-line-processor-mKABvGP0.js} +5 -5
- package/dist/canonical-log-line-processor-mKABvGP0.js.map +1 -0
- package/dist/config.d.cts +12 -7
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.ts +12 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/correlation-id.cjs +9 -9
- package/dist/correlation-id.d.cts +0 -1
- package/dist/correlation-id.d.cts.map +1 -1
- package/dist/correlation-id.d.ts +0 -1
- package/dist/correlation-id.d.ts.map +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/db.d.cts.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/decorators.cjs +2 -2
- package/dist/decorators.cjs.map +1 -1
- package/dist/decorators.d.cts +1 -2
- package/dist/decorators.d.cts.map +1 -1
- package/dist/decorators.d.ts +1 -2
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +1 -1
- package/dist/decorators.js.map +1 -1
- package/dist/{define-event-CreknLm3.d.ts → define-event-B2z1SBe7.d.ts} +2 -3
- package/dist/define-event-B2z1SBe7.d.ts.map +1 -0
- package/dist/{define-event-HZRizPwz.d.cts → define-event-CGG048HE.d.cts} +2 -3
- package/dist/define-event-CGG048HE.d.cts.map +1 -0
- package/dist/diagnostics.cjs +10 -1
- package/dist/diagnostics.cjs.map +1 -1
- package/dist/diagnostics.d.cts +0 -1
- package/dist/diagnostics.d.cts.map +1 -1
- package/dist/diagnostics.d.ts +0 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +10 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/drain-pipeline.d.cts +12 -5
- package/dist/drain-pipeline.d.cts.map +1 -1
- package/dist/drain-pipeline.d.ts +12 -5
- package/dist/drain-pipeline.d.ts.map +1 -1
- package/dist/enrichers.d.cts.map +1 -1
- package/dist/enrichers.d.ts.map +1 -1
- package/dist/{event-subscriber-D1XLkPzi.d.ts → event-subscriber-CWh8sTGh.d.cts} +2 -2
- package/dist/event-subscriber-CWh8sTGh.d.cts.map +1 -0
- package/dist/{event-subscriber-D1XLkPzi.d.cts → event-subscriber-CWh8sTGh.d.ts} +2 -2
- package/dist/event-subscriber-CWh8sTGh.d.ts.map +1 -0
- package/dist/event-subscriber.d.cts +1 -1
- package/dist/event-subscriber.d.ts +1 -1
- package/dist/{event-testing-CfFs3to2.d.ts → event-testing-CA2mIx1E.d.ts} +2 -3
- package/dist/event-testing-CA2mIx1E.d.ts.map +1 -0
- package/dist/{event-testing-BqRnI0z4.d.cts → event-testing-D0qWfqAy.d.cts} +2 -3
- package/dist/event-testing-D0qWfqAy.d.cts.map +1 -0
- package/dist/event-testing.d.cts +1 -1
- package/dist/event-testing.d.ts +1 -1
- package/dist/event.cjs +4 -4
- package/dist/event.d.cts +3 -4
- package/dist/event.d.cts.map +1 -1
- package/dist/event.d.ts +3 -4
- package/dist/event.d.ts.map +1 -1
- package/dist/event.js +1 -1
- package/dist/exporters.d.cts +0 -4
- package/dist/exporters.d.cts.map +1 -1
- package/dist/exporters.d.ts +0 -4
- package/dist/exporters.d.ts.map +1 -1
- package/dist/{filtering-span-processor-B8R8B7Uk.d.ts → filtering-span-processor-BpDagWNa.d.cts} +1 -2
- package/dist/filtering-span-processor-BpDagWNa.d.cts.map +1 -0
- package/dist/{filtering-span-processor-B8R8B7Uk.d.cts → filtering-span-processor-BpDagWNa.d.ts} +1 -2
- package/dist/filtering-span-processor-BpDagWNa.d.ts.map +1 -0
- package/dist/filtering-span-processor.d.cts +1 -1
- package/dist/filtering-span-processor.d.ts +1 -1
- package/dist/functional-BnDUxnSl.js +838 -0
- package/dist/functional-BnDUxnSl.js.map +1 -0
- package/dist/functional-DcLBuCPz.cjs +888 -0
- package/dist/functional-DcLBuCPz.cjs.map +1 -0
- package/dist/functional.cjs +2 -2
- package/dist/functional.d.cts +79 -130
- package/dist/functional.d.cts.map +1 -1
- package/dist/functional.d.ts +79 -130
- package/dist/functional.d.ts.map +1 -1
- package/dist/functional.js +2 -2
- package/dist/http.cjs +6 -6
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +4 -5
- package/dist/http.d.cts.map +1 -1
- package/dist/http.d.ts +4 -5
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +5 -5
- package/dist/http.js.map +1 -1
- package/dist/{index-eKuioqT1.d.cts → index-BPcxVucm.d.cts} +2 -3
- package/dist/index-BPcxVucm.d.cts.map +1 -0
- package/dist/{index-Ck06vlW2.d.ts → index-DolOVX6F.d.ts} +2 -3
- package/dist/index-DolOVX6F.d.ts.map +1 -0
- package/dist/index.cjs +25 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -148
- package/dist/index.js.map +1 -1
- package/dist/init-C3RApHKJ.cjs +3371 -0
- package/dist/init-C3RApHKJ.cjs.map +1 -0
- package/dist/{init-DSrRmVnz.d.cts → init-D6n2diBy.d.ts} +46 -20
- package/dist/init-D6n2diBy.d.ts.map +1 -0
- package/dist/init-R-gSEXkJ.js +3190 -0
- package/dist/init-R-gSEXkJ.js.map +1 -0
- package/dist/{init-B7u-DjxM.d.ts → init-XzXzrarP.d.cts} +46 -20
- package/dist/init-XzXzrarP.d.cts.map +1 -0
- package/dist/instrumentation.cjs +7 -13
- package/dist/instrumentation.cjs.map +1 -1
- package/dist/instrumentation.d.cts +0 -1
- package/dist/instrumentation.d.cts.map +1 -1
- package/dist/instrumentation.d.ts +0 -1
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +8 -14
- package/dist/instrumentation.js.map +1 -1
- package/dist/{logger-BauSUhUv.d.ts → logger-CN4xpzMd.d.cts} +1 -2
- package/dist/logger-CN4xpzMd.d.cts.map +1 -0
- package/dist/{logger-BauSUhUv.d.cts → logger-CN4xpzMd.d.ts} +1 -2
- package/dist/logger-CN4xpzMd.d.ts.map +1 -0
- package/dist/logger-D3Ej3DII.js.map +1 -1
- package/dist/logger-thMPLpOG.cjs.map +1 -1
- package/dist/logger.d.cts +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/messaging-adapters.cjs.map +1 -1
- package/dist/messaging-adapters.d.cts +0 -1
- package/dist/messaging-adapters.d.cts.map +1 -1
- package/dist/messaging-adapters.d.ts +0 -1
- package/dist/messaging-adapters.d.ts.map +1 -1
- package/dist/messaging-adapters.js.map +1 -1
- package/dist/messaging-testing.d.cts +0 -1
- package/dist/messaging-testing.d.cts.map +1 -1
- package/dist/messaging-testing.d.ts +0 -1
- package/dist/messaging-testing.d.ts.map +1 -1
- package/dist/messaging.cjs +27 -10
- package/dist/messaging.cjs.map +1 -1
- package/dist/messaging.d.cts +6 -7
- package/dist/messaging.d.cts.map +1 -1
- package/dist/messaging.d.ts +6 -7
- package/dist/messaging.d.ts.map +1 -1
- package/dist/messaging.js +27 -10
- package/dist/messaging.js.map +1 -1
- package/dist/metric-helpers.d.cts +0 -1
- package/dist/metric-helpers.d.cts.map +1 -1
- package/dist/metric-helpers.d.ts +0 -1
- package/dist/metric-helpers.d.ts.map +1 -1
- package/dist/{metric-testing-DXdK3-Q3.d.ts → metric-testing-CWm6UhaR.d.ts} +2 -3
- package/dist/metric-testing-CWm6UhaR.d.ts.map +1 -0
- package/dist/{metric-testing-MxvzChDp.d.cts → metric-testing-CpvP3ZIa.d.cts} +2 -3
- package/dist/metric-testing-CpvP3ZIa.d.cts.map +1 -0
- package/dist/metric-testing.d.cts +1 -1
- package/dist/metric-testing.d.ts +1 -1
- package/dist/metric.d.cts +3 -4
- package/dist/metric.d.cts.map +1 -1
- package/dist/metric.d.ts +3 -4
- package/dist/metric.d.ts.map +1 -1
- package/dist/parse-error.d.cts.map +1 -1
- package/dist/parse-error.d.ts.map +1 -1
- package/dist/processors.cjs +1 -1
- package/dist/processors.d.cts +1 -1
- package/dist/processors.d.ts +1 -1
- package/dist/processors.js +1 -1
- package/dist/register.d.cts +1 -1
- package/dist/register.d.ts +1 -1
- package/dist/{sampling-DfYhDJij.d.cts → sampling-BR9fkSwx.d.cts} +127 -10
- package/dist/sampling-BR9fkSwx.d.cts.map +1 -0
- package/dist/{sampling-CR0Va1VB.d.ts → sampling-CP3e7-Yr.d.ts} +127 -10
- package/dist/sampling-CP3e7-Yr.d.ts.map +1 -0
- package/dist/sampling.cjs +160 -12
- package/dist/sampling.cjs.map +1 -1
- package/dist/sampling.d.cts +2 -2
- package/dist/sampling.d.ts +2 -2
- package/dist/sampling.js +157 -13
- package/dist/sampling.js.map +1 -1
- package/dist/security-schema.d.cts +9 -4
- package/dist/security-schema.d.cts.map +1 -1
- package/dist/security-schema.d.ts +9 -4
- package/dist/security-schema.d.ts.map +1 -1
- package/dist/semantic-conventions-FhSxv-bK.d.cts.map +1 -1
- package/dist/semantic-conventions-FhSxv-bK.d.ts.map +1 -1
- package/dist/semantic-conventions.cjs.map +1 -1
- package/dist/semantic-conventions.js.map +1 -1
- package/dist/semantic-helpers.cjs +85 -304
- package/dist/semantic-helpers.cjs.map +1 -1
- package/dist/semantic-helpers.d.cts +21 -12
- package/dist/semantic-helpers.d.cts.map +1 -1
- package/dist/semantic-helpers.d.ts +21 -12
- package/dist/semantic-helpers.d.ts.map +1 -1
- package/dist/semantic-helpers.js +85 -304
- package/dist/semantic-helpers.js.map +1 -1
- package/dist/slo.cjs +225 -0
- package/dist/slo.cjs.map +1 -0
- package/dist/slo.d.cts +97 -0
- package/dist/slo.d.cts.map +1 -0
- package/dist/slo.d.ts +97 -0
- package/dist/slo.d.ts.map +1 -0
- package/dist/slo.js +223 -0
- package/dist/slo.js.map +1 -0
- package/dist/{span-name-normalizer-8ZOGJuwv.d.ts → span-name-normalizer-CN1-8C76.d.cts} +1 -2
- package/dist/span-name-normalizer-CN1-8C76.d.cts.map +1 -0
- package/dist/{span-name-normalizer-8ZOGJuwv.d.cts → span-name-normalizer-CN1-8C76.d.ts} +1 -2
- package/dist/span-name-normalizer-CN1-8C76.d.ts.map +1 -0
- package/dist/span-name-normalizer.cjs.map +1 -1
- package/dist/span-name-normalizer.d.cts +1 -1
- package/dist/span-name-normalizer.d.ts +1 -1
- package/dist/span-name-normalizer.js.map +1 -1
- package/dist/tail-sampling-processor.d.cts +0 -1
- package/dist/tail-sampling-processor.d.cts.map +1 -1
- package/dist/tail-sampling-processor.d.ts +0 -1
- package/dist/tail-sampling-processor.d.ts.map +1 -1
- package/dist/test-span-collector.cjs.map +1 -1
- package/dist/test-span-collector.d.cts +0 -1
- package/dist/test-span-collector.d.cts.map +1 -1
- package/dist/test-span-collector.d.ts +0 -1
- package/dist/test-span-collector.d.ts.map +1 -1
- package/dist/test-span-collector.js.map +1 -1
- package/dist/testing.cjs +76 -11
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +30 -5
- package/dist/testing.d.cts.map +1 -1
- package/dist/testing.d.ts +30 -5
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +73 -12
- package/dist/testing.js.map +1 -1
- package/dist/{trace-context-Cijqoi6e.d.ts → trace-context-IuyoPyNq.d.cts} +9 -9
- package/dist/trace-context-IuyoPyNq.d.cts.map +1 -0
- package/dist/{trace-context-Cijqoi6e.d.cts → trace-context-IuyoPyNq.d.ts} +9 -9
- package/dist/trace-context-IuyoPyNq.d.ts.map +1 -0
- package/dist/trace-factory-validation-BjVAinz6.js +13 -0
- package/dist/trace-factory-validation-BjVAinz6.js.map +1 -0
- package/dist/trace-factory-validation-Dc8epn87.cjs +19 -0
- package/dist/trace-factory-validation-Dc8epn87.cjs.map +1 -0
- package/dist/trace-helpers.cjs +7 -7
- package/dist/trace-helpers.cjs.map +1 -1
- package/dist/trace-helpers.d.cts +6 -7
- package/dist/trace-helpers.d.cts.map +1 -1
- package/dist/trace-helpers.d.ts +6 -7
- package/dist/trace-helpers.d.ts.map +1 -1
- package/dist/trace-helpers.js +7 -7
- package/dist/trace-helpers.js.map +1 -1
- package/dist/tracer-provider.d.cts +0 -1
- package/dist/tracer-provider.d.cts.map +1 -1
- package/dist/tracer-provider.d.ts +0 -1
- package/dist/tracer-provider.d.ts.map +1 -1
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +1 -2
- package/dist/validate.d.cts.map +1 -1
- package/dist/validate.d.ts +1 -2
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js.map +1 -1
- package/dist/validation-attributes.d.cts +17 -8
- package/dist/validation-attributes.d.cts.map +1 -1
- package/dist/validation-attributes.d.ts +17 -8
- package/dist/validation-attributes.d.ts.map +1 -1
- package/dist/webhook.cjs +5 -4
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.cts.map +1 -1
- package/dist/webhook.d.ts +4 -4
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +5 -4
- package/dist/webhook.js.map +1 -1
- package/dist/workflow-distributed.cjs +5 -5
- package/dist/workflow-distributed.cjs.map +1 -1
- package/dist/workflow-distributed.d.cts +25 -15
- package/dist/workflow-distributed.d.cts.map +1 -1
- package/dist/workflow-distributed.d.ts +25 -15
- package/dist/workflow-distributed.d.ts.map +1 -1
- package/dist/workflow-distributed.js +5 -5
- package/dist/workflow-distributed.js.map +1 -1
- package/dist/workflow.cjs +24 -30
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +11 -6
- package/dist/workflow.d.cts.map +1 -1
- package/dist/workflow.d.ts +11 -6
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +24 -30
- package/dist/workflow.js.map +1 -1
- package/dist/yaml-config.d.cts +2 -3
- package/dist/yaml-config.d.cts.map +1 -1
- package/dist/yaml-config.d.ts +2 -3
- package/dist/yaml-config.d.ts.map +1 -1
- package/package.json +24 -16
- package/dist/attribute-redacting-processor-DtTS9xxh.d.cts.map +0 -1
- package/dist/attribute-redacting-processor-DtTS9xxh.d.ts.map +0 -1
- package/dist/canonical-log-line-processor-BcRuAdRk.d.ts.map +0 -1
- package/dist/canonical-log-line-processor-CcrDi6TO.cjs.map +0 -1
- package/dist/canonical-log-line-processor-FLezvFCB.js.map +0 -1
- package/dist/canonical-log-line-processor-xvjMgtpF.d.cts.map +0 -1
- package/dist/define-event-CreknLm3.d.ts.map +0 -1
- package/dist/define-event-HZRizPwz.d.cts.map +0 -1
- package/dist/event-BEm5IJ8U.js +0 -761
- package/dist/event-BEm5IJ8U.js.map +0 -1
- package/dist/event-rzA1X5h3.cjs +0 -778
- package/dist/event-rzA1X5h3.cjs.map +0 -1
- package/dist/event-subscriber-D1XLkPzi.d.cts.map +0 -1
- package/dist/event-subscriber-D1XLkPzi.d.ts.map +0 -1
- package/dist/event-testing-BqRnI0z4.d.cts.map +0 -1
- package/dist/event-testing-CfFs3to2.d.ts.map +0 -1
- package/dist/filtering-span-processor-B8R8B7Uk.d.cts.map +0 -1
- package/dist/filtering-span-processor-B8R8B7Uk.d.ts.map +0 -1
- package/dist/functional-CwsPyc4l.cjs +0 -1298
- package/dist/functional-CwsPyc4l.cjs.map +0 -1
- package/dist/functional-dAYhXQPA.js +0 -1248
- package/dist/functional-dAYhXQPA.js.map +0 -1
- package/dist/index-Ck06vlW2.d.ts.map +0 -1
- package/dist/index-eKuioqT1.d.cts.map +0 -1
- package/dist/init-B7u-DjxM.d.ts.map +0 -1
- package/dist/init-BvBB45-T.js +0 -1034
- package/dist/init-BvBB45-T.js.map +0 -1
- package/dist/init-BxJEJ9Tn.cjs +0 -1111
- package/dist/init-BxJEJ9Tn.cjs.map +0 -1
- package/dist/init-DSrRmVnz.d.cts.map +0 -1
- package/dist/logger-BauSUhUv.d.cts.map +0 -1
- package/dist/logger-BauSUhUv.d.ts.map +0 -1
- package/dist/metric-testing-DXdK3-Q3.d.ts.map +0 -1
- package/dist/metric-testing-MxvzChDp.d.cts.map +0 -1
- package/dist/operation-context-CKBoA4Qy.js +0 -59
- package/dist/operation-context-CKBoA4Qy.js.map +0 -1
- package/dist/operation-context-hzKbTKnt.cjs +0 -72
- package/dist/operation-context-hzKbTKnt.cjs.map +0 -1
- package/dist/sampling-CR0Va1VB.d.ts.map +0 -1
- package/dist/sampling-DfYhDJij.d.cts.map +0 -1
- package/dist/span-name-normalizer-8ZOGJuwv.d.cts.map +0 -1
- package/dist/span-name-normalizer-8ZOGJuwv.d.ts.map +0 -1
- package/dist/trace-context-Cijqoi6e.d.cts.map +0 -1
- package/dist/trace-context-Cijqoi6e.d.ts.map +0 -1
- package/dist/track-D7O7oIZa.js +0 -1115
- package/dist/track-D7O7oIZa.js.map +0 -1
- package/dist/track-eovhCJ-I.cjs +0 -1218
- package/dist/track-eovhCJ-I.cjs.map +0 -1
- package/skills/analyze-traces/SKILL.md +0 -180
- package/skills/autotel-core/SKILL.md +0 -159
- package/skills/autotel-events/SKILL.md +0 -84
- package/skills/autotel-frameworks/SKILL.md +0 -90
- package/skills/autotel-instrumentation/SKILL.md +0 -145
- package/skills/autotel-request-logging/SKILL.md +0 -94
- package/skills/autotel-structured-errors/SKILL.md +0 -125
- package/skills/build-audit-trails/SKILL.md +0 -460
- package/skills/build-audit-trails/references/audit-queries.md +0 -73
- package/skills/build-audit-trails/references/framework-wiring.md +0 -196
- package/skills/debug-missing-spans/SKILL.md +0 -250
- package/skills/migrate-to-autotel/SKILL.md +0 -269
- package/skills/review-otel-patterns/SKILL.md +0 -538
- package/skills/review-otel-patterns/references/code-review.md +0 -76
- package/skills/review-otel-patterns/references/processor-pipeline.md +0 -205
- package/skills/review-otel-patterns/references/structured-errors.md +0 -102
- package/skills/review-otel-patterns/references/wide-spans.md +0 -85
- package/skills/tune-sampling/SKILL.md +0 -215
|
@@ -0,0 +1,3371 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
|
|
2
|
+
const require_tracer_provider = require('./tracer-provider.cjs');
|
|
3
|
+
const require_config = require('./config.cjs');
|
|
4
|
+
const require_sampling = require('./sampling.cjs');
|
|
5
|
+
const require_node_require = require('./node-require-PFqDihze.cjs');
|
|
6
|
+
const require_tail_sampling_processor = require('./tail-sampling-processor.cjs');
|
|
7
|
+
const require_filtering_span_processor = require('./filtering-span-processor.cjs');
|
|
8
|
+
const require_span_name_normalizer = require('./span-name-normalizer.cjs');
|
|
9
|
+
const require_attribute_redacting_processor = require('./attribute-redacting-processor.cjs');
|
|
10
|
+
const require_pretty_console_exporter = require('./pretty-console-exporter-CMzlrRNg.cjs');
|
|
11
|
+
const require_yaml_config = require('./yaml-config.cjs');
|
|
12
|
+
const require_canonical_log_line_processor = require('./canonical-log-line-processor-DOjb6Kxy.cjs');
|
|
13
|
+
const require_structured_error = require('./structured-error-CHg7DoIQ.cjs');
|
|
14
|
+
const require_metric = require('./metric.cjs');
|
|
15
|
+
let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
|
|
16
|
+
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
17
|
+
let _opentelemetry_resources = require("@opentelemetry/resources");
|
|
18
|
+
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
19
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
20
|
+
let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
21
|
+
let _opentelemetry_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
22
|
+
let _opentelemetry_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
23
|
+
let _opentelemetry_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
|
|
24
|
+
let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs");
|
|
25
|
+
let node_os = require("node:os");
|
|
26
|
+
let node_async_hooks = require("node:async_hooks");
|
|
27
|
+
node_async_hooks = require_rolldown_runtime.__toESM(node_async_hooks, 1);
|
|
28
|
+
|
|
29
|
+
//#region src/posthog-logs.ts
|
|
30
|
+
var RedactingLogRecordProcessor = class {
|
|
31
|
+
wrapped;
|
|
32
|
+
redact;
|
|
33
|
+
constructor(wrapped, redact) {
|
|
34
|
+
this.wrapped = wrapped;
|
|
35
|
+
this.redact = redact;
|
|
36
|
+
}
|
|
37
|
+
onEmit(logRecord, context) {
|
|
38
|
+
if (logRecord.body && typeof logRecord.body === "string") logRecord.body = this.redact(logRecord.body);
|
|
39
|
+
if (logRecord.attributes) {
|
|
40
|
+
for (const [key, value] of Object.entries(logRecord.attributes)) if (typeof value === "string") logRecord.attributes[key] = this.redact(value);
|
|
41
|
+
else if (Array.isArray(value)) logRecord.attributes[key] = value.map((item) => typeof item === "string" ? this.redact(item) : item);
|
|
42
|
+
}
|
|
43
|
+
this.wrapped.onEmit(logRecord, context);
|
|
44
|
+
}
|
|
45
|
+
shutdown() {
|
|
46
|
+
return this.wrapped.shutdown();
|
|
47
|
+
}
|
|
48
|
+
forceFlush() {
|
|
49
|
+
return this.wrapped.forceFlush();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build log record processors for PostHog OTLP logs integration.
|
|
54
|
+
*
|
|
55
|
+
* Resolution order:
|
|
56
|
+
* 1. config.url if provided
|
|
57
|
+
* 2. POSTHOG_LOGS_URL env var
|
|
58
|
+
* 3. Empty array (disabled)
|
|
59
|
+
*/
|
|
60
|
+
function buildPostHogLogProcessors(config, stringRedactor) {
|
|
61
|
+
const url = config?.url || process.env.POSTHOG_LOGS_URL;
|
|
62
|
+
if (!url) return [];
|
|
63
|
+
const sdkLogs = require_node_require.safeRequire("@opentelemetry/sdk-logs");
|
|
64
|
+
const exporterModule = require_node_require.safeRequire("@opentelemetry/exporter-logs-otlp-http");
|
|
65
|
+
if (!sdkLogs || !exporterModule) return [];
|
|
66
|
+
const exporter = new exporterModule.OTLPLogExporter({ url });
|
|
67
|
+
let processor = new sdkLogs.BatchLogRecordProcessor({ exporter });
|
|
68
|
+
if (stringRedactor) processor = new RedactingLogRecordProcessor(processor, stringRedactor);
|
|
69
|
+
return [processor];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/baggage-span-processor.ts
|
|
74
|
+
/**
|
|
75
|
+
* Span processor that automatically copies baggage entries to span attributes
|
|
76
|
+
*
|
|
77
|
+
* This makes baggage visible in trace UIs (Jaeger, Grafana, DataDog, etc.)
|
|
78
|
+
* without manually calling ctx.setAttribute() for each baggage entry.
|
|
79
|
+
*
|
|
80
|
+
* @example Enable in init()
|
|
81
|
+
* ```typescript
|
|
82
|
+
* init({
|
|
83
|
+
* service: 'my-app',
|
|
84
|
+
* baggage: true // Uses default 'baggage.' prefix
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Now baggage automatically appears as span attributes
|
|
88
|
+
* await withBaggage({
|
|
89
|
+
* baggage: { 'tenant.id': 't1', 'user.id': 'u1' },
|
|
90
|
+
* fn: async () => {
|
|
91
|
+
* // Span has baggage.tenant.id and baggage.user.id attributes!
|
|
92
|
+
* }
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example Custom prefix
|
|
97
|
+
* ```typescript
|
|
98
|
+
* init({
|
|
99
|
+
* service: 'my-app',
|
|
100
|
+
* baggage: 'ctx' // Uses 'ctx.' prefix
|
|
101
|
+
* });
|
|
102
|
+
* // Creates attributes: ctx.tenant.id, ctx.user.id
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
var BaggageSpanProcessor = class {
|
|
106
|
+
prefix;
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
this.prefix = options.prefix ?? "baggage.";
|
|
109
|
+
}
|
|
110
|
+
onStart(span, parentContext) {
|
|
111
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(parentContext);
|
|
112
|
+
if (!baggage) baggage = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active());
|
|
113
|
+
if (!baggage) try {
|
|
114
|
+
const { getActiveContextWithBaggage } = require_node_require.requireModule("./trace-context");
|
|
115
|
+
const storedContext = getActiveContextWithBaggage();
|
|
116
|
+
baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
|
|
117
|
+
} catch {}
|
|
118
|
+
if (!baggage) return;
|
|
119
|
+
for (const [key, entry] of baggage.getAllEntries()) span.setAttribute(`${this.prefix}${key}`, entry.value);
|
|
120
|
+
}
|
|
121
|
+
onEnd(_span) {}
|
|
122
|
+
async shutdown() {}
|
|
123
|
+
async forceFlush() {}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/redact-values.ts
|
|
128
|
+
/** Standalone string redaction for use outside the span processor pipeline. */
|
|
129
|
+
function createStringRedactor(config) {
|
|
130
|
+
const resolved = typeof config === "string" ? require_attribute_redacting_processor.REDACTOR_PRESETS[config] : config;
|
|
131
|
+
const valuePatterns = resolved.valuePatterns ?? [];
|
|
132
|
+
const defaultReplacement = resolved.replacement ?? "[REDACTED]";
|
|
133
|
+
return (value) => {
|
|
134
|
+
let result = value;
|
|
135
|
+
for (const { pattern, replacement, mask } of valuePatterns) {
|
|
136
|
+
pattern.lastIndex = 0;
|
|
137
|
+
result = mask ? result.replaceAll(pattern, (match) => mask(match)) : result.replaceAll(pattern, replacement ?? defaultReplacement);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/env-config.ts
|
|
145
|
+
/**
|
|
146
|
+
* Validate URL format
|
|
147
|
+
*/
|
|
148
|
+
function isValidUrl(urlString) {
|
|
149
|
+
try {
|
|
150
|
+
const url = new URL(urlString);
|
|
151
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolve OpenTelemetry environment variables from process.env
|
|
158
|
+
*/
|
|
159
|
+
function resolveOtelEnv() {
|
|
160
|
+
const env = {};
|
|
161
|
+
if (process.env.OTEL_SERVICE_NAME) {
|
|
162
|
+
const value = process.env.OTEL_SERVICE_NAME.trim();
|
|
163
|
+
if (value) env.OTEL_SERVICE_NAME = value;
|
|
164
|
+
}
|
|
165
|
+
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
166
|
+
const value = process.env.OTEL_EXPORTER_OTLP_ENDPOINT.trim();
|
|
167
|
+
if (value && isValidUrl(value)) env.OTEL_EXPORTER_OTLP_ENDPOINT = value;
|
|
168
|
+
}
|
|
169
|
+
if (process.env.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
170
|
+
const value = process.env.OTEL_EXPORTER_OTLP_HEADERS.trim();
|
|
171
|
+
if (value) env.OTEL_EXPORTER_OTLP_HEADERS = value;
|
|
172
|
+
}
|
|
173
|
+
if (process.env.OTEL_RESOURCE_ATTRIBUTES) {
|
|
174
|
+
const value = process.env.OTEL_RESOURCE_ATTRIBUTES.trim();
|
|
175
|
+
if (value) env.OTEL_RESOURCE_ATTRIBUTES = value;
|
|
176
|
+
}
|
|
177
|
+
if (process.env.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
178
|
+
const value = process.env.OTEL_EXPORTER_OTLP_PROTOCOL.trim().toLowerCase();
|
|
179
|
+
if (value === "http" || value === "grpc") env.OTEL_EXPORTER_OTLP_PROTOCOL = value;
|
|
180
|
+
}
|
|
181
|
+
if (process.env.OTEL_TRACES_SAMPLER) {
|
|
182
|
+
const value = process.env.OTEL_TRACES_SAMPLER.trim();
|
|
183
|
+
if (value) env.OTEL_TRACES_SAMPLER = value;
|
|
184
|
+
}
|
|
185
|
+
if (process.env.OTEL_TRACES_SAMPLER_ARG) {
|
|
186
|
+
const value = process.env.OTEL_TRACES_SAMPLER_ARG.trim();
|
|
187
|
+
if (value) env.OTEL_TRACES_SAMPLER_ARG = value;
|
|
188
|
+
}
|
|
189
|
+
return env;
|
|
190
|
+
}
|
|
191
|
+
function parseRatioSamplerArg(samplerName, samplerArg) {
|
|
192
|
+
if (samplerArg === void 0) return 1;
|
|
193
|
+
const ratio = Number(samplerArg);
|
|
194
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
|
|
195
|
+
console.error(`[autotel] Invalid OTEL_TRACES_SAMPLER_ARG="${samplerArg}" for ${samplerName}. Expected a number in [0..1]. Falling back to 1.0.`);
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
return ratio;
|
|
199
|
+
}
|
|
200
|
+
function warnOnUnusedSamplerArg(samplerName, samplerArg) {
|
|
201
|
+
if (samplerArg !== void 0) console.error(`[autotel] OTEL_TRACES_SAMPLER_ARG is not used by OTEL_TRACES_SAMPLER="${samplerName}". Ignoring value "${samplerArg}".`);
|
|
202
|
+
}
|
|
203
|
+
function createSamplerFromEnv(env) {
|
|
204
|
+
const samplerName = env.OTEL_TRACES_SAMPLER;
|
|
205
|
+
if (!samplerName) return;
|
|
206
|
+
switch (samplerName) {
|
|
207
|
+
case "always_on":
|
|
208
|
+
warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
|
|
209
|
+
return new _opentelemetry_sdk_trace_base.AlwaysOnSampler();
|
|
210
|
+
case "always_off":
|
|
211
|
+
warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
|
|
212
|
+
return new _opentelemetry_sdk_trace_base.AlwaysOffSampler();
|
|
213
|
+
case "traceidratio": return new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG));
|
|
214
|
+
case "parentbased_always_on":
|
|
215
|
+
warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
|
|
216
|
+
return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOnSampler() });
|
|
217
|
+
case "parentbased_always_off":
|
|
218
|
+
warnOnUnusedSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG);
|
|
219
|
+
return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.AlwaysOffSampler() });
|
|
220
|
+
case "parentbased_traceidratio": return new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(parseRatioSamplerArg(samplerName, env.OTEL_TRACES_SAMPLER_ARG)) });
|
|
221
|
+
case "jaeger_remote":
|
|
222
|
+
case "parentbased_jaeger_remote":
|
|
223
|
+
case "xray":
|
|
224
|
+
console.error(`[autotel] OTEL_TRACES_SAMPLER="${samplerName}" is not supported yet by autotel. Falling back to the next sampler source.`);
|
|
225
|
+
return;
|
|
226
|
+
default:
|
|
227
|
+
console.error(`[autotel] Unknown OTEL_TRACES_SAMPLER="${samplerName}". Falling back to the next sampler source.`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Parse OTEL_RESOURCE_ATTRIBUTES from comma-separated key=value pairs
|
|
233
|
+
* Example: "service.version=1.0.0,deployment.environment=production"
|
|
234
|
+
*/
|
|
235
|
+
function parseResourceAttributes(input) {
|
|
236
|
+
if (!input || input.trim() === "") return {};
|
|
237
|
+
const attributes = {};
|
|
238
|
+
const pairs = input.split(",");
|
|
239
|
+
for (const pair of pairs) {
|
|
240
|
+
const trimmedPair = pair.trim();
|
|
241
|
+
if (!trimmedPair) continue;
|
|
242
|
+
const equalIndex = trimmedPair.indexOf("=");
|
|
243
|
+
if (equalIndex === -1) continue;
|
|
244
|
+
const key = trimmedPair.slice(0, equalIndex).trim();
|
|
245
|
+
const value = trimmedPair.slice(equalIndex + 1).trim();
|
|
246
|
+
if (key && value) attributes[key] = value;
|
|
247
|
+
}
|
|
248
|
+
return attributes;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Parse OTEL_EXPORTER_OTLP_HEADERS from comma-separated key=value pairs
|
|
252
|
+
* Example: "api-key=secret123,x-custom-header=value"
|
|
253
|
+
*/
|
|
254
|
+
function parseOtlpHeaders(input) {
|
|
255
|
+
if (!input || input.trim() === "") return {};
|
|
256
|
+
const headers = {};
|
|
257
|
+
const pairs = input.split(",");
|
|
258
|
+
for (const pair of pairs) {
|
|
259
|
+
const trimmedPair = pair.trim();
|
|
260
|
+
if (!trimmedPair) continue;
|
|
261
|
+
const equalIndex = trimmedPair.indexOf("=");
|
|
262
|
+
if (equalIndex === -1) continue;
|
|
263
|
+
const key = trimmedPair.slice(0, equalIndex).trim();
|
|
264
|
+
const value = trimmedPair.slice(equalIndex + 1).trim();
|
|
265
|
+
if (key && value) headers[key] = value;
|
|
266
|
+
}
|
|
267
|
+
return headers;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Convert resolved environment variables to config
|
|
271
|
+
*/
|
|
272
|
+
function envToConfig(env) {
|
|
273
|
+
const config = {};
|
|
274
|
+
if (env.OTEL_SERVICE_NAME) config.service = env.OTEL_SERVICE_NAME;
|
|
275
|
+
if (env.OTEL_EXPORTER_OTLP_ENDPOINT) config.endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
276
|
+
if (env.OTEL_EXPORTER_OTLP_PROTOCOL) config.protocol = env.OTEL_EXPORTER_OTLP_PROTOCOL;
|
|
277
|
+
if (env.OTEL_EXPORTER_OTLP_HEADERS) config.headers = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
|
|
278
|
+
const resourceAttrs = parseResourceAttributes(env.OTEL_RESOURCE_ATTRIBUTES);
|
|
279
|
+
if (Object.keys(resourceAttrs).length > 0) config.resourceAttributes = resourceAttrs;
|
|
280
|
+
const sampler = createSamplerFromEnv(env);
|
|
281
|
+
if (sampler) config.otelSampler = sampler;
|
|
282
|
+
return config;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Main function to resolve config from environment variables
|
|
286
|
+
*/
|
|
287
|
+
function resolveConfigFromEnv() {
|
|
288
|
+
return envToConfig(resolveOtelEnv());
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/devtools.ts
|
|
293
|
+
const defaultHost = "127.0.0.1";
|
|
294
|
+
const defaultPort = 4318;
|
|
295
|
+
function resolveDevtoolsConfig(config) {
|
|
296
|
+
if (!config) return {
|
|
297
|
+
enabled: false,
|
|
298
|
+
endpoint: void 0,
|
|
299
|
+
embedded: false,
|
|
300
|
+
host: defaultHost,
|
|
301
|
+
port: defaultPort,
|
|
302
|
+
verbose: false
|
|
303
|
+
};
|
|
304
|
+
if (config === true) return {
|
|
305
|
+
enabled: true,
|
|
306
|
+
endpoint: `http://${defaultHost}:${defaultPort}`,
|
|
307
|
+
embedded: false,
|
|
308
|
+
host: defaultHost,
|
|
309
|
+
port: defaultPort,
|
|
310
|
+
verbose: false
|
|
311
|
+
};
|
|
312
|
+
const enabled = config.enabled ?? true;
|
|
313
|
+
const host = config.host ?? defaultHost;
|
|
314
|
+
const port = config.port ?? defaultPort;
|
|
315
|
+
const endpoint = config.endpoint ?? `http://${host}:${port}`;
|
|
316
|
+
return {
|
|
317
|
+
enabled,
|
|
318
|
+
endpoint: enabled ? endpoint : void 0,
|
|
319
|
+
embedded: enabled && (config.embedded ?? false),
|
|
320
|
+
host,
|
|
321
|
+
port,
|
|
322
|
+
verbose: config.verbose ?? false
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/process-handlers.ts
|
|
328
|
+
let removeOwnedHandlers = [];
|
|
329
|
+
const DEFAULT_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
330
|
+
function signalExitCode(signal) {
|
|
331
|
+
return 128 + node_os.constants.signals[signal];
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Surface a fatal error before shutting down.
|
|
335
|
+
*
|
|
336
|
+
* Registering an `uncaughtException` / `unhandledRejection` listener overrides
|
|
337
|
+
* Node's default of printing the stack to stderr, so without this a crash under
|
|
338
|
+
* `fatalErrors` would exit silently. Autotel's own logger is silent by default,
|
|
339
|
+
* so we print to stderr directly to guarantee the crash stays visible.
|
|
340
|
+
*/
|
|
341
|
+
function reportFatalError(error, event) {
|
|
342
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
343
|
+
console.error(`[autotel] ${event}, flushing telemetry then exiting`, err);
|
|
344
|
+
}
|
|
345
|
+
function installProcessHandlers(config, shutdown) {
|
|
346
|
+
uninstallProcessHandlers();
|
|
347
|
+
let shutdownInFlight;
|
|
348
|
+
const runShutdownOnce = () => {
|
|
349
|
+
if (shutdownInFlight) return shutdownInFlight;
|
|
350
|
+
const timeoutMs = config.shutdownTimeoutMs ?? 2e3;
|
|
351
|
+
let timeoutHandle;
|
|
352
|
+
const shutdownAttempt = Promise.resolve().then(shutdown).catch(() => void 0);
|
|
353
|
+
const timeout = new Promise((resolve) => {
|
|
354
|
+
timeoutHandle = setTimeout(resolve, timeoutMs);
|
|
355
|
+
timeoutHandle.unref();
|
|
356
|
+
});
|
|
357
|
+
shutdownInFlight = Promise.race([shutdownAttempt, timeout]).then(() => {
|
|
358
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
359
|
+
});
|
|
360
|
+
return shutdownInFlight;
|
|
361
|
+
};
|
|
362
|
+
let exitScheduled = false;
|
|
363
|
+
let resolvedExitCode = 0;
|
|
364
|
+
let fatalLatched = false;
|
|
365
|
+
const shutdownAndExit = (exitCode, fatal) => {
|
|
366
|
+
if (fatal) {
|
|
367
|
+
if (!fatalLatched) {
|
|
368
|
+
resolvedExitCode = exitCode;
|
|
369
|
+
fatalLatched = true;
|
|
370
|
+
}
|
|
371
|
+
} else if (!exitScheduled && !fatalLatched) resolvedExitCode = exitCode;
|
|
372
|
+
if (exitScheduled) return;
|
|
373
|
+
exitScheduled = true;
|
|
374
|
+
runShutdownOnce().then(() => {
|
|
375
|
+
process.exit(resolvedExitCode);
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
for (const signal of config.signals ?? DEFAULT_SIGNALS) {
|
|
379
|
+
const listener = () => {
|
|
380
|
+
shutdownAndExit(signalExitCode(signal), false);
|
|
381
|
+
};
|
|
382
|
+
process.on(signal, listener);
|
|
383
|
+
removeOwnedHandlers.push(() => {
|
|
384
|
+
process.removeListener(signal, listener);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (config.fatalErrors ?? true) {
|
|
388
|
+
const uncaughtExceptionListener = (error) => {
|
|
389
|
+
reportFatalError(error, "uncaughtException");
|
|
390
|
+
shutdownAndExit(1, true);
|
|
391
|
+
};
|
|
392
|
+
const unhandledRejectionListener = (reason) => {
|
|
393
|
+
reportFatalError(reason, "unhandledRejection");
|
|
394
|
+
shutdownAndExit(1, true);
|
|
395
|
+
};
|
|
396
|
+
process.on("uncaughtException", uncaughtExceptionListener);
|
|
397
|
+
process.on("unhandledRejection", unhandledRejectionListener);
|
|
398
|
+
removeOwnedHandlers.push(() => {
|
|
399
|
+
process.removeListener("uncaughtException", uncaughtExceptionListener);
|
|
400
|
+
}, () => {
|
|
401
|
+
process.removeListener("unhandledRejection", unhandledRejectionListener);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function uninstallProcessHandlers() {
|
|
406
|
+
for (const removeHandler of removeOwnedHandlers) removeHandler();
|
|
407
|
+
removeOwnedHandlers = [];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/rate-limiter.ts
|
|
412
|
+
/**
|
|
413
|
+
* Token bucket rate limiter
|
|
414
|
+
*
|
|
415
|
+
* Allows bursts up to burstCapacity, then smooths to maxEventsPerSecond.
|
|
416
|
+
* Thread-safe for async operations.
|
|
417
|
+
*/
|
|
418
|
+
var TokenBucketRateLimiter = class {
|
|
419
|
+
tokens;
|
|
420
|
+
maxTokens;
|
|
421
|
+
refillRate;
|
|
422
|
+
lastRefill;
|
|
423
|
+
constructor(config) {
|
|
424
|
+
this.maxTokens = config.burstCapacity || config.maxEventsPerSecond * 2;
|
|
425
|
+
this.tokens = this.maxTokens;
|
|
426
|
+
this.refillRate = config.maxEventsPerSecond / 1e3;
|
|
427
|
+
this.lastRefill = Date.now();
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Try to consume a token (allow an event)
|
|
431
|
+
* Returns true if allowed, false if rate limit exceeded
|
|
432
|
+
*/
|
|
433
|
+
tryConsume(count = 1) {
|
|
434
|
+
this.refill();
|
|
435
|
+
if (this.tokens >= count) {
|
|
436
|
+
this.tokens -= count;
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Wait until a token is available (async rate limiting)
|
|
443
|
+
* Returns a promise that resolves when the event can be processed
|
|
444
|
+
*/
|
|
445
|
+
async waitForToken(count = 1) {
|
|
446
|
+
this.refill();
|
|
447
|
+
if (this.tokens >= count) {
|
|
448
|
+
this.tokens -= count;
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const tokensNeeded = count - this.tokens;
|
|
452
|
+
const waitMs = Math.ceil(tokensNeeded / this.refillRate);
|
|
453
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
454
|
+
return this.waitForToken(count);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Refill tokens based on elapsed time
|
|
458
|
+
*/
|
|
459
|
+
refill() {
|
|
460
|
+
const now = Date.now();
|
|
461
|
+
const tokensToAdd = (now - this.lastRefill) * this.refillRate;
|
|
462
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
|
|
463
|
+
this.lastRefill = now;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Get current available tokens (for testing/debugging)
|
|
467
|
+
*/
|
|
468
|
+
getAvailableTokens() {
|
|
469
|
+
this.refill();
|
|
470
|
+
return Math.floor(this.tokens);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Reset the rate limiter (for testing)
|
|
474
|
+
*/
|
|
475
|
+
reset() {
|
|
476
|
+
this.tokens = this.maxTokens;
|
|
477
|
+
this.lastRefill = Date.now();
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/trace-context.ts
|
|
483
|
+
/**
|
|
484
|
+
* AsyncLocalStorage for storing the active context with baggage
|
|
485
|
+
* This allows setters to update the context and have it persist
|
|
486
|
+
*/
|
|
487
|
+
const contextStorage = new node_async_hooks.AsyncLocalStorage();
|
|
488
|
+
/**
|
|
489
|
+
* Get the context storage instance (for initialization in functional.ts)
|
|
490
|
+
*/
|
|
491
|
+
function getContextStorage() {
|
|
492
|
+
return contextStorage;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get the active context, checking our stored context first
|
|
496
|
+
* This ensures baggage setters work with OpenTelemetry's propagation
|
|
497
|
+
*/
|
|
498
|
+
function getActiveContextWithBaggage() {
|
|
499
|
+
return contextStorage.getStore()?.value ?? _opentelemetry_api.context.active();
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Set a value in AsyncLocalStorage, preferring enterWith() when available
|
|
503
|
+
* (Node.js) and falling back to run() for environments that only support
|
|
504
|
+
* run() (e.g. Cloudflare Workers).
|
|
505
|
+
*
|
|
506
|
+
* On runtimes without enterWith() we mutate the existing run() scope when one
|
|
507
|
+
* exists. This is what allows baggage/correlation updates to remain visible
|
|
508
|
+
* for the rest of the traced callback in Workers.
|
|
509
|
+
*/
|
|
510
|
+
function enterOrRun(storage, value) {
|
|
511
|
+
const existingStore = storage.getStore();
|
|
512
|
+
if (existingStore) {
|
|
513
|
+
existingStore.value = value;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const boxedValue = { value };
|
|
517
|
+
try {
|
|
518
|
+
storage.enterWith(boxedValue);
|
|
519
|
+
} catch {
|
|
520
|
+
storage.run(boxedValue, () => {});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function updateActiveContext(newContext) {
|
|
524
|
+
enterOrRun(contextStorage, newContext);
|
|
525
|
+
const manager = _opentelemetry_api.context._getContextManager?.();
|
|
526
|
+
if (!manager) return;
|
|
527
|
+
const asyncLocal = manager._asyncLocalStorage ?? void 0;
|
|
528
|
+
if (asyncLocal?.enterWith) {
|
|
529
|
+
asyncLocal.enterWith(newContext);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (typeof manager.with === "function") manager.with(newContext, () => {});
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Create a TraceContext from an OpenTelemetry Span
|
|
536
|
+
*
|
|
537
|
+
* This utility extracts trace context information from a span
|
|
538
|
+
* and provides span manipulation methods and baggage operations in a consistent format.
|
|
539
|
+
*
|
|
540
|
+
* Note: Baggage methods always operate on the currently active context,
|
|
541
|
+
* which may differ from the context when createTraceContext was called.
|
|
542
|
+
*/
|
|
543
|
+
function createTraceContext(span) {
|
|
544
|
+
const spanContext = span.spanContext();
|
|
545
|
+
if (!contextStorage.getStore()?.value) {
|
|
546
|
+
const activeContext = _opentelemetry_api.context.active();
|
|
547
|
+
enterOrRun(contextStorage, activeContext);
|
|
548
|
+
}
|
|
549
|
+
const traceCtx = {
|
|
550
|
+
traceId: spanContext.traceId,
|
|
551
|
+
spanId: spanContext.spanId,
|
|
552
|
+
correlationId: spanContext.traceId.slice(0, 16),
|
|
553
|
+
setAttribute: span.setAttribute.bind(span),
|
|
554
|
+
setAttributes: span.setAttributes.bind(span),
|
|
555
|
+
setStatus: span.setStatus.bind(span),
|
|
556
|
+
recordException: span.recordException.bind(span),
|
|
557
|
+
addEvent: span.addEvent.bind(span),
|
|
558
|
+
addLink: span.addLink.bind(span),
|
|
559
|
+
addLinks: span.addLinks.bind(span),
|
|
560
|
+
updateName: span.updateName.bind(span),
|
|
561
|
+
isRecording: span.isRecording.bind(span),
|
|
562
|
+
recordError: (error) => {
|
|
563
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
564
|
+
require_structured_error.recordStructuredError(traceCtx, err);
|
|
565
|
+
},
|
|
566
|
+
track: (event, data) => {
|
|
567
|
+
track(event, data);
|
|
568
|
+
},
|
|
569
|
+
getBaggage(key) {
|
|
570
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
571
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
|
|
572
|
+
if (!baggage) {
|
|
573
|
+
const storedContext = contextStorage.getStore()?.value;
|
|
574
|
+
if (storedContext) baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
|
|
575
|
+
}
|
|
576
|
+
return baggage?.getEntry(key)?.value;
|
|
577
|
+
},
|
|
578
|
+
setBaggage(key, value) {
|
|
579
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
580
|
+
const currentContext = contextStorage.getStore()?.value ?? activeCtx;
|
|
581
|
+
const updated = (_opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.propagation.createBaggage()).setEntry(key, { value });
|
|
582
|
+
updateActiveContext(_opentelemetry_api.propagation.setBaggage(currentContext, updated));
|
|
583
|
+
return value;
|
|
584
|
+
},
|
|
585
|
+
deleteBaggage(key) {
|
|
586
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
587
|
+
const currentContext = contextStorage.getStore()?.value ?? activeCtx;
|
|
588
|
+
const baggage = _opentelemetry_api.propagation.getBaggage(currentContext);
|
|
589
|
+
if (baggage) {
|
|
590
|
+
const updated = baggage.removeEntry(key);
|
|
591
|
+
updateActiveContext(_opentelemetry_api.propagation.setBaggage(currentContext, updated));
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
getAllBaggage() {
|
|
595
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
596
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
|
|
597
|
+
if (!baggage) {
|
|
598
|
+
const storedContext = contextStorage.getStore()?.value;
|
|
599
|
+
if (storedContext) baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
|
|
600
|
+
}
|
|
601
|
+
if (!baggage) return /* @__PURE__ */ new Map();
|
|
602
|
+
const entries = /* @__PURE__ */ new Map();
|
|
603
|
+
for (const [key, entry] of baggage.getAllEntries()) entries.set(key, entry);
|
|
604
|
+
return entries;
|
|
605
|
+
},
|
|
606
|
+
getTypedBaggage: ((namespace) => {
|
|
607
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
608
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(activeCtx);
|
|
609
|
+
if (!baggage) {
|
|
610
|
+
const storedContext = contextStorage.getStore()?.value;
|
|
611
|
+
if (storedContext) baggage = _opentelemetry_api.propagation.getBaggage(storedContext);
|
|
612
|
+
}
|
|
613
|
+
if (!baggage) return;
|
|
614
|
+
const prefix = namespace ? `${namespace}.` : "";
|
|
615
|
+
const result = {};
|
|
616
|
+
for (const [key, entry] of baggage.getAllEntries()) if (namespace && key.startsWith(prefix)) {
|
|
617
|
+
const fieldName = key.slice(prefix.length);
|
|
618
|
+
result[fieldName] = entry.value;
|
|
619
|
+
} else if (!namespace) result[key] = entry.value;
|
|
620
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
621
|
+
}),
|
|
622
|
+
setTypedBaggage: ((namespace, value) => {
|
|
623
|
+
const activeCtx = _opentelemetry_api.context.active();
|
|
624
|
+
const currentContext = contextStorage.getStore()?.value ?? activeCtx;
|
|
625
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.propagation.createBaggage();
|
|
626
|
+
const prefix = namespace ? `${namespace}.` : "";
|
|
627
|
+
for (const [key, val] of Object.entries(value)) if (val !== void 0) {
|
|
628
|
+
const baggageKey = `${prefix}${key}`;
|
|
629
|
+
baggage = baggage.setEntry(baggageKey, { value: String(val) });
|
|
630
|
+
}
|
|
631
|
+
updateActiveContext(_opentelemetry_api.propagation.setBaggage(currentContext, baggage));
|
|
632
|
+
})
|
|
633
|
+
};
|
|
634
|
+
return traceCtx;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Define a typed baggage schema for type-safe baggage operations
|
|
638
|
+
*
|
|
639
|
+
* This helper provides a type-safe API for working with baggage entries.
|
|
640
|
+
* The namespace parameter is optional and prefixes all keys to avoid collisions.
|
|
641
|
+
*
|
|
642
|
+
* @template T - The baggage schema type (all fields are treated as optional)
|
|
643
|
+
* @param namespace - Optional namespace to prefix baggage keys
|
|
644
|
+
*
|
|
645
|
+
* @example Basic usage
|
|
646
|
+
* ```typescript
|
|
647
|
+
* type TenantBaggage = { tenantId: string; region?: string };
|
|
648
|
+
* const tenantBaggage = defineBaggageSchema<TenantBaggage>('tenant');
|
|
649
|
+
*
|
|
650
|
+
* export const handler = trace<TenantBaggage>((ctx) => async () => {
|
|
651
|
+
* // Get typed baggage
|
|
652
|
+
* const tenant = tenantBaggage.get(ctx);
|
|
653
|
+
* if (tenant?.tenantId) {
|
|
654
|
+
* console.log('Tenant:', tenant.tenantId);
|
|
655
|
+
* }
|
|
656
|
+
*
|
|
657
|
+
* // Set typed baggage
|
|
658
|
+
* tenantBaggage.set(ctx, { tenantId: 't1', region: 'us-east-1' });
|
|
659
|
+
* });
|
|
660
|
+
* ```
|
|
661
|
+
*
|
|
662
|
+
* @example With withBaggage helper
|
|
663
|
+
* ```typescript
|
|
664
|
+
* const tenantBaggage = defineBaggageSchema<TenantBaggage>('tenant');
|
|
665
|
+
*
|
|
666
|
+
* export const handler = trace<TenantBaggage>((ctx) => async () => {
|
|
667
|
+
* return await tenantBaggage.with(ctx, { tenantId: 't1' }, async () => {
|
|
668
|
+
* // Baggage is available here and in child spans
|
|
669
|
+
* const tenant = tenantBaggage.get(ctx);
|
|
670
|
+
* });
|
|
671
|
+
* });
|
|
672
|
+
* ```
|
|
673
|
+
*/
|
|
674
|
+
function defineBaggageSchema(namespace) {
|
|
675
|
+
return {
|
|
676
|
+
/**
|
|
677
|
+
* Get typed baggage from context
|
|
678
|
+
* @param ctx - Trace context
|
|
679
|
+
* @returns Partial baggage object or undefined if no baggage is set
|
|
680
|
+
*/
|
|
681
|
+
get: (ctx) => {
|
|
682
|
+
if (!ctx.getTypedBaggage) return void 0;
|
|
683
|
+
return ctx.getTypedBaggage(namespace);
|
|
684
|
+
},
|
|
685
|
+
/**
|
|
686
|
+
* Set typed baggage in context
|
|
687
|
+
*
|
|
688
|
+
* Note: For proper scoping across async boundaries, use the `with` method instead
|
|
689
|
+
*
|
|
690
|
+
* @param ctx - Trace context
|
|
691
|
+
* @param value - Partial baggage object to set
|
|
692
|
+
*/
|
|
693
|
+
set: (ctx, value) => {
|
|
694
|
+
if (!ctx.setTypedBaggage) return;
|
|
695
|
+
ctx.setTypedBaggage(namespace, value);
|
|
696
|
+
},
|
|
697
|
+
/**
|
|
698
|
+
* Run a function with typed baggage properly scoped
|
|
699
|
+
*
|
|
700
|
+
* This is the recommended way to set baggage as it ensures proper
|
|
701
|
+
* scoping across async boundaries.
|
|
702
|
+
*
|
|
703
|
+
* @param ctx - Trace context (can be omitted, will use active context)
|
|
704
|
+
* @param value - Partial baggage object to set
|
|
705
|
+
* @param fn - Function to execute with the baggage
|
|
706
|
+
*/
|
|
707
|
+
with: (ctxOrValue, valueOrFn, maybeFn) => {
|
|
708
|
+
const value = maybeFn ? valueOrFn : ctxOrValue;
|
|
709
|
+
const fn = maybeFn || valueOrFn;
|
|
710
|
+
const prefix = namespace ? `${namespace}.` : "";
|
|
711
|
+
const flatBaggage = {};
|
|
712
|
+
for (const [key, val] of Object.entries(value)) if (val !== void 0) flatBaggage[`${prefix}${key}`] = String(val);
|
|
713
|
+
const currentContext = _opentelemetry_api.context.active();
|
|
714
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(currentContext) ?? _opentelemetry_api.propagation.createBaggage();
|
|
715
|
+
for (const [key, val] of Object.entries(flatBaggage)) baggage = baggage.setEntry(key, { value: val });
|
|
716
|
+
const newContext = _opentelemetry_api.propagation.setBaggage(currentContext, baggage);
|
|
717
|
+
return _opentelemetry_api.context.with(newContext, fn);
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/correlation-id.ts
|
|
724
|
+
/**
|
|
725
|
+
* Correlation ID utilities for event-driven observability
|
|
726
|
+
*
|
|
727
|
+
* Provides a stable join key across events, logs, and spans even when traces fragment.
|
|
728
|
+
* Format: 16 hex chars (64 bits), crypto-random, URL-safe.
|
|
729
|
+
*
|
|
730
|
+
* Lifecycle:
|
|
731
|
+
* 1. Generated at boundary root (HTTP server span, message process span, cron job span)
|
|
732
|
+
* 2. Reused within context (nested work shares it via AsyncLocalStorage)
|
|
733
|
+
* 3. Propagated via baggage (optional, default OFF to avoid header bloat)
|
|
734
|
+
*
|
|
735
|
+
* @example Basic usage
|
|
736
|
+
* ```typescript
|
|
737
|
+
* import { generateCorrelationId, getCorrelationId } from 'autotel/correlation-id';
|
|
738
|
+
*
|
|
739
|
+
* // Generate a new correlation ID
|
|
740
|
+
* const id = generateCorrelationId();
|
|
741
|
+
* // Returns: 'a1b2c3d4e5f67890'
|
|
742
|
+
*
|
|
743
|
+
* // Get current correlation ID from context
|
|
744
|
+
* const currentId = getCorrelationId();
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
747
|
+
/**
|
|
748
|
+
* AsyncLocalStorage for storing correlation ID
|
|
749
|
+
* This allows correlation IDs to persist across async boundaries
|
|
750
|
+
*/
|
|
751
|
+
const correlationStorage = new node_async_hooks.AsyncLocalStorage();
|
|
752
|
+
/**
|
|
753
|
+
* Baggage key for correlation ID propagation
|
|
754
|
+
*/
|
|
755
|
+
const CORRELATION_ID_BAGGAGE_KEY = "autotel.correlation_id";
|
|
756
|
+
/**
|
|
757
|
+
* Generate a new correlation ID
|
|
758
|
+
*
|
|
759
|
+
* Format: 16 hex chars (64 bits), crypto-random, URL-safe
|
|
760
|
+
*
|
|
761
|
+
* @returns A new correlation ID
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* const id = generateCorrelationId();
|
|
766
|
+
* // Returns: 'a1b2c3d4e5f67890'
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
function generateCorrelationId() {
|
|
770
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
771
|
+
crypto.getRandomValues(bytes);
|
|
772
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Get the current correlation ID from context
|
|
776
|
+
*
|
|
777
|
+
* Resolution order:
|
|
778
|
+
* 1. AsyncLocalStorage (from explicit setCorrelationId or runWithCorrelationId)
|
|
779
|
+
* 2. Baggage (if propagated from upstream)
|
|
780
|
+
* 3. Active span's trace ID (first 16 chars as fallback)
|
|
781
|
+
* 4. undefined (if not in any context)
|
|
782
|
+
*
|
|
783
|
+
* @returns Current correlation ID or undefined
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* ```typescript
|
|
787
|
+
* const id = getCorrelationId();
|
|
788
|
+
* if (id) {
|
|
789
|
+
* console.log('Correlation ID:', id);
|
|
790
|
+
* }
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
function getCorrelationId() {
|
|
794
|
+
const storedId = correlationStorage.getStore()?.value;
|
|
795
|
+
if (storedId) return storedId;
|
|
796
|
+
const activeContext = _opentelemetry_api.context.active();
|
|
797
|
+
const baggageEntry = _opentelemetry_api.propagation.getBaggage(activeContext)?.getEntry(CORRELATION_ID_BAGGAGE_KEY);
|
|
798
|
+
if (baggageEntry?.value) return baggageEntry.value;
|
|
799
|
+
const span = _opentelemetry_api.trace.getActiveSpan();
|
|
800
|
+
if (span) return span.spanContext().traceId.slice(0, 16);
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Get or create a correlation ID
|
|
804
|
+
*
|
|
805
|
+
* If a correlation ID exists in the current context, returns it.
|
|
806
|
+
* Otherwise, generates a new one.
|
|
807
|
+
*
|
|
808
|
+
* @returns Existing or new correlation ID
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* ```typescript
|
|
812
|
+
* const id = getOrCreateCorrelationId();
|
|
813
|
+
* // Always returns a valid correlation ID
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
function getOrCreateCorrelationId() {
|
|
817
|
+
return getCorrelationId() ?? generateCorrelationId();
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Run a function with a specific correlation ID in context
|
|
821
|
+
*
|
|
822
|
+
* The correlation ID will be available via getCorrelationId() throughout
|
|
823
|
+
* the execution of the function and any async operations it spawns.
|
|
824
|
+
*
|
|
825
|
+
* @param correlationId - Correlation ID to use
|
|
826
|
+
* @param fn - Function to execute
|
|
827
|
+
* @returns The return value of the function
|
|
828
|
+
*
|
|
829
|
+
* @example
|
|
830
|
+
* ```typescript
|
|
831
|
+
* await runWithCorrelationId('abc123', async () => {
|
|
832
|
+
* // getCorrelationId() returns 'abc123' here
|
|
833
|
+
* await processRequest();
|
|
834
|
+
* });
|
|
835
|
+
* ```
|
|
836
|
+
*/
|
|
837
|
+
function runWithCorrelationId(correlationId, fn) {
|
|
838
|
+
return correlationStorage.run({ value: correlationId }, fn);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Set correlation ID in the current context (mutates context)
|
|
842
|
+
*
|
|
843
|
+
* Note: This updates the AsyncLocalStorage context. For proper scoping
|
|
844
|
+
* across async boundaries, prefer runWithCorrelationId() instead.
|
|
845
|
+
*
|
|
846
|
+
* @param correlationId - Correlation ID to set
|
|
847
|
+
*
|
|
848
|
+
* @example
|
|
849
|
+
* ```typescript
|
|
850
|
+
* setCorrelationId('abc123');
|
|
851
|
+
* // Now getCorrelationId() returns 'abc123'
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
854
|
+
function setCorrelationId(correlationId) {
|
|
855
|
+
enterOrRun(correlationStorage, correlationId);
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Set correlation ID in baggage for propagation
|
|
859
|
+
*
|
|
860
|
+
* This adds the correlation ID to the W3C baggage header, allowing it
|
|
861
|
+
* to be propagated to downstream services.
|
|
862
|
+
*
|
|
863
|
+
* Note: Only use this when you explicitly want cross-service propagation.
|
|
864
|
+
* Default is OFF to avoid header bloat.
|
|
865
|
+
*
|
|
866
|
+
* @param correlationId - Correlation ID to propagate
|
|
867
|
+
* @returns New context with baggage set
|
|
868
|
+
*
|
|
869
|
+
* @example
|
|
870
|
+
* ```typescript
|
|
871
|
+
* const newContext = setCorrelationIdInBaggage('abc123');
|
|
872
|
+
* context.with(newContext, () => {
|
|
873
|
+
* // Baggage will be propagated in outgoing requests
|
|
874
|
+
* });
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
function setCorrelationIdInBaggage(correlationId) {
|
|
878
|
+
const activeContext = _opentelemetry_api.context.active();
|
|
879
|
+
let baggage = _opentelemetry_api.propagation.getBaggage(activeContext) ?? _opentelemetry_api.propagation.createBaggage();
|
|
880
|
+
baggage = baggage.setEntry(CORRELATION_ID_BAGGAGE_KEY, { value: correlationId });
|
|
881
|
+
return _opentelemetry_api.propagation.setBaggage(activeContext, baggage);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Get the correlation storage instance (for internal use in init/shutdown)
|
|
885
|
+
*/
|
|
886
|
+
function getCorrelationStorage() {
|
|
887
|
+
return correlationStorage;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/event-queue.ts
|
|
892
|
+
const DEFAULT_CONFIG$2 = {
|
|
893
|
+
maxSize: 5e4,
|
|
894
|
+
batchSize: 100,
|
|
895
|
+
flushInterval: 1e4,
|
|
896
|
+
maxRetries: 3,
|
|
897
|
+
rateLimit: {
|
|
898
|
+
maxEventsPerSecond: 100,
|
|
899
|
+
burstCapacity: 200
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* Get subscriber name for metrics (stable, low-cardinality)
|
|
904
|
+
*
|
|
905
|
+
* Priority:
|
|
906
|
+
* 1. Explicit config: subscriber.name
|
|
907
|
+
* 2. Class static property (if available)
|
|
908
|
+
* 3. Fallback: lowercase class name without "Subscriber" suffix
|
|
909
|
+
*/
|
|
910
|
+
function getSubscriberName(subscriber) {
|
|
911
|
+
if (subscriber.name) return subscriber.name.toLowerCase();
|
|
912
|
+
return (subscriber.constructor?.name || "unknown").replace(/Subscriber$/i, "").toLowerCase();
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Events queue with batching and backpressure
|
|
916
|
+
*
|
|
917
|
+
* Features:
|
|
918
|
+
* - Batches events for efficient sending
|
|
919
|
+
* - Bounded queue with drop-oldest policy (prod) or blocking (dev)
|
|
920
|
+
* - Exponential backoff retry
|
|
921
|
+
* - Rate limiting to prevent overwhelming subscribers
|
|
922
|
+
* - Graceful flush on shutdown
|
|
923
|
+
*/
|
|
924
|
+
var EventQueue = class {
|
|
925
|
+
queue = [];
|
|
926
|
+
flushTimer = null;
|
|
927
|
+
config;
|
|
928
|
+
subscribers;
|
|
929
|
+
rateLimiter;
|
|
930
|
+
flushPromise = null;
|
|
931
|
+
isShuttingDown = false;
|
|
932
|
+
metrics = null;
|
|
933
|
+
observableCleanups = [];
|
|
934
|
+
subscriberHealthy = /* @__PURE__ */ new Map();
|
|
935
|
+
constructor(subscribers, config) {
|
|
936
|
+
this.subscribers = subscribers;
|
|
937
|
+
this.config = {
|
|
938
|
+
...DEFAULT_CONFIG$2,
|
|
939
|
+
...config
|
|
940
|
+
};
|
|
941
|
+
this.rateLimiter = this.config.rateLimit ? new TokenBucketRateLimiter(this.config.rateLimit) : null;
|
|
942
|
+
for (const subscriber of subscribers) {
|
|
943
|
+
const name = getSubscriberName(subscriber);
|
|
944
|
+
this.subscriberHealthy.set(name, true);
|
|
945
|
+
}
|
|
946
|
+
this.initMetrics();
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Initialize OTel metrics for queue observability
|
|
950
|
+
*/
|
|
951
|
+
initMetrics() {
|
|
952
|
+
const meter = require_config.getConfig().meter;
|
|
953
|
+
const queueSize = meter.createObservableGauge("autotel.event_delivery.queue.size", {
|
|
954
|
+
description: "Current number of events in the delivery queue",
|
|
955
|
+
unit: "count"
|
|
956
|
+
});
|
|
957
|
+
const queueSizeCallback = (observableResult) => {
|
|
958
|
+
observableResult.observe(this.queue.length);
|
|
959
|
+
};
|
|
960
|
+
queueSize.addCallback(queueSizeCallback);
|
|
961
|
+
this.observableCleanups.push(() => queueSize.removeCallback(queueSizeCallback));
|
|
962
|
+
const oldestAge = meter.createObservableGauge("autotel.event_delivery.queue.oldest_age_ms", {
|
|
963
|
+
description: "Age of the oldest event in the queue in milliseconds",
|
|
964
|
+
unit: "ms"
|
|
965
|
+
});
|
|
966
|
+
const oldestAgeCallback = (observableResult) => {
|
|
967
|
+
if (this.queue.length > 0) {
|
|
968
|
+
const oldest = this.queue[0];
|
|
969
|
+
const ageMs = Date.now() - oldest.timestamp;
|
|
970
|
+
observableResult.observe(ageMs);
|
|
971
|
+
} else observableResult.observe(0);
|
|
972
|
+
};
|
|
973
|
+
oldestAge.addCallback(oldestAgeCallback);
|
|
974
|
+
this.observableCleanups.push(() => oldestAge.removeCallback(oldestAgeCallback));
|
|
975
|
+
const delivered = meter.createCounter("autotel.event_delivery.queue.delivered", {
|
|
976
|
+
description: "Number of events successfully delivered to subscribers",
|
|
977
|
+
unit: "count"
|
|
978
|
+
});
|
|
979
|
+
const failed = meter.createCounter("autotel.event_delivery.queue.failed", {
|
|
980
|
+
description: "Number of events that failed delivery after all retry attempts",
|
|
981
|
+
unit: "count"
|
|
982
|
+
});
|
|
983
|
+
const dropped = meter.createCounter("autotel.event_delivery.queue.dropped", {
|
|
984
|
+
description: "Number of events dropped from the queue",
|
|
985
|
+
unit: "count"
|
|
986
|
+
});
|
|
987
|
+
const latency = meter.createHistogram("autotel.event_delivery.queue.latency_ms", {
|
|
988
|
+
description: "Event delivery latency from enqueue to successful send",
|
|
989
|
+
unit: "ms"
|
|
990
|
+
});
|
|
991
|
+
const subscriberHealth = meter.createObservableGauge("autotel.event_delivery.subscriber.health", {
|
|
992
|
+
description: "Subscriber health status (1=healthy, 0=unhealthy)",
|
|
993
|
+
unit: "1"
|
|
994
|
+
});
|
|
995
|
+
const subscriberHealthCallback = (observableResult) => {
|
|
996
|
+
for (const [subscriberName, isHealthy] of this.subscriberHealthy) observableResult.observe(isHealthy ? 1 : 0, { subscriber: subscriberName });
|
|
997
|
+
};
|
|
998
|
+
subscriberHealth.addCallback(subscriberHealthCallback);
|
|
999
|
+
this.observableCleanups.push(() => subscriberHealth.removeCallback(subscriberHealthCallback));
|
|
1000
|
+
this.metrics = {
|
|
1001
|
+
queueSize,
|
|
1002
|
+
oldestAge,
|
|
1003
|
+
delivered,
|
|
1004
|
+
failed,
|
|
1005
|
+
dropped,
|
|
1006
|
+
latency,
|
|
1007
|
+
subscriberHealth
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Record a dropped event with reason and emit debug breadcrumb
|
|
1012
|
+
*/
|
|
1013
|
+
recordDropped(reason, event, subscriberName) {
|
|
1014
|
+
const attrs = { reason };
|
|
1015
|
+
if (subscriberName) attrs.subscriber = subscriberName;
|
|
1016
|
+
this.metrics?.dropped.add(1, attrs);
|
|
1017
|
+
const logLevel = reason === "payload_invalid" ? "error" : "warn";
|
|
1018
|
+
const logger = getLogger();
|
|
1019
|
+
if (logLevel === "error") logger.error({
|
|
1020
|
+
eventName: event?.name,
|
|
1021
|
+
subscriber: subscriberName,
|
|
1022
|
+
reason,
|
|
1023
|
+
correlationId: event?._correlationId,
|
|
1024
|
+
traceId: event?._traceId
|
|
1025
|
+
}, `[autotel] Event dropped: ${reason}`);
|
|
1026
|
+
else logger.warn({
|
|
1027
|
+
eventName: event?.name,
|
|
1028
|
+
subscriber: subscriberName,
|
|
1029
|
+
reason,
|
|
1030
|
+
correlationId: event?._correlationId,
|
|
1031
|
+
traceId: event?._traceId
|
|
1032
|
+
}, `[autotel] Event dropped: ${reason}`);
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Record permanent delivery failure (after all retries exhausted)
|
|
1036
|
+
* Increments failed counter and logs error
|
|
1037
|
+
*/
|
|
1038
|
+
recordFailed(event, subscriberName, error) {
|
|
1039
|
+
this.metrics?.failed.add(1, { subscriber: subscriberName });
|
|
1040
|
+
this.subscriberHealthy.set(subscriberName, false);
|
|
1041
|
+
getLogger().error({
|
|
1042
|
+
eventName: event.name,
|
|
1043
|
+
subscriber: subscriberName,
|
|
1044
|
+
correlationId: event._correlationId,
|
|
1045
|
+
traceId: event._traceId,
|
|
1046
|
+
err: error
|
|
1047
|
+
}, `[autotel] Event delivery failed after all retries`);
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Mark subscriber as unhealthy on transient failure (without incrementing failed counter)
|
|
1051
|
+
* Used during retry attempts - only recordFailed should increment the counter
|
|
1052
|
+
*/
|
|
1053
|
+
markSubscriberUnhealthy(subscriberName) {
|
|
1054
|
+
this.subscriberHealthy.set(subscriberName, false);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Record successful delivery
|
|
1058
|
+
*/
|
|
1059
|
+
recordDelivered(event, subscriberName, startTime) {
|
|
1060
|
+
const latencyMs = Date.now() - startTime;
|
|
1061
|
+
this.metrics?.delivered.add(1, { subscriber: subscriberName });
|
|
1062
|
+
this.metrics?.latency.record(latencyMs, { subscriber: subscriberName });
|
|
1063
|
+
this.subscriberHealthy.set(subscriberName, true);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Enqueue an event for sending
|
|
1067
|
+
*
|
|
1068
|
+
* Backpressure policy:
|
|
1069
|
+
* - Drops oldest event and logs warning if queue is full (same behavior in all environments)
|
|
1070
|
+
*/
|
|
1071
|
+
enqueue(event) {
|
|
1072
|
+
if (this.isShuttingDown) {
|
|
1073
|
+
this.recordDropped("shutdown", event);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (this.queue.length >= this.config.maxSize) {
|
|
1077
|
+
const droppedEvent = this.queue.shift();
|
|
1078
|
+
this.recordDropped("rate_limit", droppedEvent);
|
|
1079
|
+
getLogger().warn({ droppedEvent: droppedEvent?.name }, `[autotel] Events queue full (${this.config.maxSize} events). Dropping oldest event. Events are being produced faster than they can be sent. Check your subscribers or reduce tracking frequency.`);
|
|
1080
|
+
}
|
|
1081
|
+
const enrichedEvent = {
|
|
1082
|
+
...event,
|
|
1083
|
+
_correlationId: event._correlationId || getOrCreateCorrelationId()
|
|
1084
|
+
};
|
|
1085
|
+
this.queue.push(enrichedEvent);
|
|
1086
|
+
this.scheduleBatchFlush();
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Schedule a batch flush if not already scheduled
|
|
1090
|
+
*/
|
|
1091
|
+
scheduleBatchFlush() {
|
|
1092
|
+
if (this.flushTimer || this.flushPromise) return;
|
|
1093
|
+
this.flushTimer = setTimeout(() => {
|
|
1094
|
+
this.flushTimer = null;
|
|
1095
|
+
this.flushBatch();
|
|
1096
|
+
}, this.config.flushInterval);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Flush a batch of events
|
|
1100
|
+
* Uses promise-based concurrency control to prevent race conditions
|
|
1101
|
+
*/
|
|
1102
|
+
async flushBatch() {
|
|
1103
|
+
if (this.queue.length === 0) return;
|
|
1104
|
+
if (this.flushPromise) {
|
|
1105
|
+
await this.flushPromise;
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
this.flushPromise = this.doFlushBatch();
|
|
1109
|
+
try {
|
|
1110
|
+
await this.flushPromise;
|
|
1111
|
+
} finally {
|
|
1112
|
+
this.flushPromise = null;
|
|
1113
|
+
if (this.queue.length > 0) this.scheduleBatchFlush();
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Internal flush implementation
|
|
1118
|
+
*/
|
|
1119
|
+
async doFlushBatch() {
|
|
1120
|
+
const batch = this.queue.splice(0, this.config.batchSize);
|
|
1121
|
+
await this.sendWithRetry(batch, this.config.maxRetries);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Send events with exponential backoff retry
|
|
1125
|
+
* Tracks per-event, per-subscriber failures so failed counter reflects actual failed deliveries.
|
|
1126
|
+
* On retry, only failed (event, subscriber) pairs are re-sent to avoid double-counting delivered.
|
|
1127
|
+
*/
|
|
1128
|
+
async sendWithRetry(events, retriesLeft, subscribersByEventIndex) {
|
|
1129
|
+
const failedDeliveries = await this.sendToSubscribers(events, subscribersByEventIndex);
|
|
1130
|
+
if (failedDeliveries.length > 0) if (retriesLeft > 0) {
|
|
1131
|
+
const failedEventIndicesOrdered = [...new Set(failedDeliveries.map((f) => f.eventIndex))].toSorted((a, b) => a - b);
|
|
1132
|
+
const eventsToRetry = failedEventIndicesOrdered.map((i) => events[i]);
|
|
1133
|
+
const failedSubscribersByRetryIndex = /* @__PURE__ */ new Map();
|
|
1134
|
+
for (const [j, origIndex] of failedEventIndicesOrdered.entries()) {
|
|
1135
|
+
const set = /* @__PURE__ */ new Set();
|
|
1136
|
+
for (const { eventIndex, subscriberName } of failedDeliveries) if (eventIndex === origIndex) set.add(subscriberName);
|
|
1137
|
+
failedSubscribersByRetryIndex.set(j, set);
|
|
1138
|
+
}
|
|
1139
|
+
const delay = Math.pow(2, this.config.maxRetries - retriesLeft) * 1e3;
|
|
1140
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1141
|
+
return this.sendWithRetry(eventsToRetry, retriesLeft - 1, failedSubscribersByRetryIndex);
|
|
1142
|
+
} else {
|
|
1143
|
+
for (const { eventIndex, subscriberName, error } of failedDeliveries) {
|
|
1144
|
+
const event = events[eventIndex];
|
|
1145
|
+
if (event) this.recordFailed(event, subscriberName, error);
|
|
1146
|
+
}
|
|
1147
|
+
const failedSubscriberNames = [...new Set(failedDeliveries.map((f) => f.subscriberName))];
|
|
1148
|
+
getLogger().error({
|
|
1149
|
+
failedSubscribers: failedSubscriberNames,
|
|
1150
|
+
retriesAttempted: this.config.maxRetries
|
|
1151
|
+
}, "[autotel] Failed to send events after retries");
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Send events to configured subscribers with rate limiting and metrics.
|
|
1156
|
+
* When subscribersByEventIndex is provided (retry path), only those subscribers are tried per event.
|
|
1157
|
+
* Returns per-event, per-subscriber failures (empty if all succeeded).
|
|
1158
|
+
*/
|
|
1159
|
+
async sendToSubscribers(events, subscribersByEventIndex) {
|
|
1160
|
+
const failedDeliveries = [];
|
|
1161
|
+
const sendOne = async (event, eventIndex) => {
|
|
1162
|
+
const subscriberNames = subscribersByEventIndex?.get(eventIndex);
|
|
1163
|
+
const failures = await this.sendEventToSubscribers(event, subscriberNames ?? void 0);
|
|
1164
|
+
for (const failure of failures) failedDeliveries.push({
|
|
1165
|
+
eventIndex,
|
|
1166
|
+
subscriberName: failure.subscriberName,
|
|
1167
|
+
error: failure.error
|
|
1168
|
+
});
|
|
1169
|
+
};
|
|
1170
|
+
if (!this.rateLimiter) {
|
|
1171
|
+
for (const [i, event] of events.entries()) if (event) await sendOne(event, i);
|
|
1172
|
+
return failedDeliveries;
|
|
1173
|
+
}
|
|
1174
|
+
for (const [i, event] of events.entries()) {
|
|
1175
|
+
await this.rateLimiter.waitForToken();
|
|
1176
|
+
if (event) await sendOne(event, i);
|
|
1177
|
+
}
|
|
1178
|
+
return failedDeliveries;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Send a single event to subscribers.
|
|
1182
|
+
* - When subscriberNames is undefined (initial attempt): send to all subscribers.
|
|
1183
|
+
* - When subscriberNames is provided (retry): send only to those subscribers (never re-send to healthy ones).
|
|
1184
|
+
* Returns list of subscribers that failed (empty if all succeeded).
|
|
1185
|
+
*/
|
|
1186
|
+
async sendEventToSubscribers(event, subscriberNames) {
|
|
1187
|
+
const startTime = event.timestamp;
|
|
1188
|
+
const failures = [];
|
|
1189
|
+
const subscribersToTry = subscriberNames === void 0 ? this.subscribers : this.subscribers.filter((s) => subscriberNames.has(getSubscriberName(s)));
|
|
1190
|
+
const results = await Promise.allSettled(subscribersToTry.map(async (subscriber) => {
|
|
1191
|
+
const subscriberName = getSubscriberName(subscriber);
|
|
1192
|
+
try {
|
|
1193
|
+
await subscriber.trackEvent(event.name, event.attributes, {
|
|
1194
|
+
autotel: event.autotel,
|
|
1195
|
+
schema: event.schema
|
|
1196
|
+
});
|
|
1197
|
+
this.recordDelivered(event, subscriberName, startTime);
|
|
1198
|
+
return {
|
|
1199
|
+
subscriberName,
|
|
1200
|
+
success: true
|
|
1201
|
+
};
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
this.markSubscriberUnhealthy(subscriberName);
|
|
1204
|
+
return {
|
|
1205
|
+
subscriberName,
|
|
1206
|
+
success: false,
|
|
1207
|
+
error: error instanceof Error ? error : void 0
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
}));
|
|
1211
|
+
for (const result of results) if (result.status === "fulfilled" && !result.value.success) failures.push({
|
|
1212
|
+
subscriberName: result.value.subscriberName,
|
|
1213
|
+
error: result.value.error
|
|
1214
|
+
});
|
|
1215
|
+
return failures;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Flush all remaining events. Queue remains usable after flush (e.g. for
|
|
1219
|
+
* auto-flush at root span end). Use shutdown() when tearing down the queue.
|
|
1220
|
+
*/
|
|
1221
|
+
async flush() {
|
|
1222
|
+
if (this.flushTimer) {
|
|
1223
|
+
clearTimeout(this.flushTimer);
|
|
1224
|
+
this.flushTimer = null;
|
|
1225
|
+
}
|
|
1226
|
+
if (this.flushPromise) await this.flushPromise;
|
|
1227
|
+
while (this.queue.length > 0) await this.doFlushBatch();
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Flush remaining events and permanently disable the queue (reject new events).
|
|
1231
|
+
* Use for process/SDK shutdown; use flush() for periodic or span-end drain.
|
|
1232
|
+
*/
|
|
1233
|
+
async shutdown() {
|
|
1234
|
+
this.isShuttingDown = true;
|
|
1235
|
+
await this.flush();
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Cleanup observable metric callbacks to prevent memory leaks
|
|
1239
|
+
* Call this when destroying the EventQueue instance
|
|
1240
|
+
*/
|
|
1241
|
+
cleanup() {
|
|
1242
|
+
for (const cleanupFn of this.observableCleanups) try {
|
|
1243
|
+
cleanupFn();
|
|
1244
|
+
} catch {}
|
|
1245
|
+
this.observableCleanups = [];
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Get queue size (for testing/debugging)
|
|
1249
|
+
*/
|
|
1250
|
+
size() {
|
|
1251
|
+
return this.queue.length;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Get subscriber health status (for testing/debugging)
|
|
1255
|
+
*/
|
|
1256
|
+
getSubscriberHealth() {
|
|
1257
|
+
return new Map(this.subscriberHealthy);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Check if a specific subscriber is healthy
|
|
1261
|
+
*/
|
|
1262
|
+
isSubscriberHealthy(subscriberName) {
|
|
1263
|
+
return this.subscriberHealthy.get(subscriberName.toLowerCase()) ?? true;
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Manually mark a subscriber as healthy or unhealthy
|
|
1267
|
+
* (used for circuit breaker integration)
|
|
1268
|
+
*/
|
|
1269
|
+
setSubscriberHealth(subscriberName, healthy) {
|
|
1270
|
+
this.subscriberHealthy.set(subscriberName.toLowerCase(), healthy);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
//#endregion
|
|
1275
|
+
//#region src/validation.ts
|
|
1276
|
+
const DEFAULT_CONFIG$1 = {
|
|
1277
|
+
maxEventNameLength: 100,
|
|
1278
|
+
maxAttributeKeyLength: 100,
|
|
1279
|
+
maxAttributeValueLength: 1e3,
|
|
1280
|
+
maxAttributeCount: 50,
|
|
1281
|
+
maxNestingDepth: 3,
|
|
1282
|
+
sensitivePatterns: [
|
|
1283
|
+
/password/i,
|
|
1284
|
+
/secret/i,
|
|
1285
|
+
/token/i,
|
|
1286
|
+
/api[_-]?key/i,
|
|
1287
|
+
/access[_-]?key/i,
|
|
1288
|
+
/private[_-]?key/i,
|
|
1289
|
+
/auth/i,
|
|
1290
|
+
/credential/i,
|
|
1291
|
+
/ssn/i,
|
|
1292
|
+
/credit[_-]?card/i
|
|
1293
|
+
]
|
|
1294
|
+
};
|
|
1295
|
+
var ValidationError = class extends Error {
|
|
1296
|
+
constructor(message) {
|
|
1297
|
+
super(message);
|
|
1298
|
+
this.name = "ValidationError";
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
/**
|
|
1302
|
+
* Validate and sanitize event name
|
|
1303
|
+
* Throws ValidationError if invalid
|
|
1304
|
+
*/
|
|
1305
|
+
function validateEventName(eventName, config = DEFAULT_CONFIG$1) {
|
|
1306
|
+
if (typeof eventName !== "string") throw new ValidationError(`Event name must be a string, got ${typeof eventName}`);
|
|
1307
|
+
const trimmed = eventName.trim();
|
|
1308
|
+
if (trimmed.length === 0) throw new ValidationError("Event name cannot be empty");
|
|
1309
|
+
if (trimmed.length > config.maxEventNameLength) throw new ValidationError(`Event name too long (${trimmed.length} chars). Max: ${config.maxEventNameLength}`);
|
|
1310
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) throw new ValidationError(`Event name contains invalid characters: "${trimmed}". Use only letters, numbers, dots, underscores, and hyphens.`);
|
|
1311
|
+
return trimmed;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Validate and sanitize attributes
|
|
1315
|
+
* Returns sanitized attributes (sensitive data redacted)
|
|
1316
|
+
*/
|
|
1317
|
+
function validateAttributes(attributes, config = DEFAULT_CONFIG$1) {
|
|
1318
|
+
if (attributes === void 0 || attributes === null) return;
|
|
1319
|
+
if (typeof attributes !== "object" || Array.isArray(attributes)) throw new ValidationError("Attributes must be an object");
|
|
1320
|
+
const keys = Object.keys(attributes);
|
|
1321
|
+
if (keys.length > config.maxAttributeCount) throw new ValidationError(`Too many attributes (${keys.length}). Max: ${config.maxAttributeCount}`);
|
|
1322
|
+
const sanitized = {};
|
|
1323
|
+
for (const key of keys) {
|
|
1324
|
+
if (key.length > config.maxAttributeKeyLength) throw new ValidationError(`Attribute key too long: "${key.slice(0, 20)}..." (${key.length} chars). Max: ${config.maxAttributeKeyLength}`);
|
|
1325
|
+
const value = attributes[key];
|
|
1326
|
+
if (typeof value === "string" && config.sensitivePatterns.some((pattern) => pattern.test(key))) {
|
|
1327
|
+
sanitized[key] = "[REDACTED]";
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
sanitized[key] = sanitizeValue(value, config, 1);
|
|
1331
|
+
}
|
|
1332
|
+
return sanitized;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Sanitize attribute value (recursive)
|
|
1336
|
+
*/
|
|
1337
|
+
function sanitizeValue(value, config, depth) {
|
|
1338
|
+
if (depth > config.maxNestingDepth) return "[MAX_DEPTH_EXCEEDED]";
|
|
1339
|
+
if (value === null || value === void 0) return value;
|
|
1340
|
+
if (typeof value === "string") {
|
|
1341
|
+
if (value.length > config.maxAttributeValueLength) return value.slice(0, config.maxAttributeValueLength) + "...";
|
|
1342
|
+
return value;
|
|
1343
|
+
}
|
|
1344
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1345
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeValue(item, config, depth + 1));
|
|
1346
|
+
if (typeof value === "object") try {
|
|
1347
|
+
JSON.stringify(value);
|
|
1348
|
+
const sanitized = {};
|
|
1349
|
+
for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
1350
|
+
const nested = value[key];
|
|
1351
|
+
sanitized[key] = typeof nested === "string" && config.sensitivePatterns.some((pattern) => pattern.test(key)) ? "[REDACTED]" : sanitizeValue(nested, config, depth + 1);
|
|
1352
|
+
}
|
|
1353
|
+
return sanitized;
|
|
1354
|
+
} catch {
|
|
1355
|
+
return "[CIRCULAR]";
|
|
1356
|
+
}
|
|
1357
|
+
return `[${typeof value}]`;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Validate and sanitize an events event
|
|
1361
|
+
* Returns { eventName, attributes } with sanitized values
|
|
1362
|
+
*/
|
|
1363
|
+
function validateEvent(eventName, attributes, config) {
|
|
1364
|
+
const fullConfig = {
|
|
1365
|
+
...DEFAULT_CONFIG$1,
|
|
1366
|
+
...config
|
|
1367
|
+
};
|
|
1368
|
+
return {
|
|
1369
|
+
eventName: validateEventName(eventName, fullConfig),
|
|
1370
|
+
attributes: validateAttributes(attributes, fullConfig)
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
//#endregion
|
|
1375
|
+
//#region src/track.ts
|
|
1376
|
+
/**
|
|
1377
|
+
* Global track() function for business events
|
|
1378
|
+
*
|
|
1379
|
+
* Simple, no instantiation needed, auto-attaches trace context
|
|
1380
|
+
*/
|
|
1381
|
+
let eventsQueue = null;
|
|
1382
|
+
/**
|
|
1383
|
+
* Build autotel event context for trace correlation
|
|
1384
|
+
*
|
|
1385
|
+
* Works in multiple contexts:
|
|
1386
|
+
* 1. Inside a span → use current span's trace_id + span_id
|
|
1387
|
+
* 2. Outside span → use correlation_id only
|
|
1388
|
+
* 3. With trace URL config → include clickable trace URL
|
|
1389
|
+
*/
|
|
1390
|
+
function buildAutotelContext(span) {
|
|
1391
|
+
const eventsConfig = getEventsConfig();
|
|
1392
|
+
const config = getConfig();
|
|
1393
|
+
const correlationId = getOrCreateCorrelationId();
|
|
1394
|
+
if (!eventsConfig?.includeTraceContext) return { correlation_id: correlationId };
|
|
1395
|
+
const autotelContext = { correlation_id: correlationId };
|
|
1396
|
+
const spanContext = span?.spanContext();
|
|
1397
|
+
if (spanContext) {
|
|
1398
|
+
autotelContext.trace_id = spanContext.traceId;
|
|
1399
|
+
autotelContext.span_id = spanContext.spanId;
|
|
1400
|
+
autotelContext.trace_flags = spanContext.traceFlags.toString(16).padStart(2, "0");
|
|
1401
|
+
const traceState = spanContext.traceState;
|
|
1402
|
+
if (traceState) try {
|
|
1403
|
+
if (typeof traceState.serialize === "function") {
|
|
1404
|
+
const traceStateStr = traceState.serialize();
|
|
1405
|
+
if (traceStateStr) autotelContext.trace_state = traceStateStr;
|
|
1406
|
+
}
|
|
1407
|
+
} catch {}
|
|
1408
|
+
if (eventsConfig.traceUrl && config) {
|
|
1409
|
+
const traceUrl = eventsConfig.traceUrl({
|
|
1410
|
+
traceId: spanContext.traceId,
|
|
1411
|
+
spanId: spanContext.spanId,
|
|
1412
|
+
correlationId,
|
|
1413
|
+
serviceName: config.service,
|
|
1414
|
+
environment: config.environment
|
|
1415
|
+
});
|
|
1416
|
+
if (traceUrl) autotelContext.trace_url = traceUrl;
|
|
1417
|
+
}
|
|
1418
|
+
} else if (eventsConfig.traceUrl && config) {
|
|
1419
|
+
const traceUrl = eventsConfig.traceUrl({
|
|
1420
|
+
correlationId,
|
|
1421
|
+
serviceName: config.service,
|
|
1422
|
+
environment: config.environment
|
|
1423
|
+
});
|
|
1424
|
+
if (traceUrl) autotelContext.trace_url = traceUrl;
|
|
1425
|
+
}
|
|
1426
|
+
return autotelContext;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Initialize events queue lazily
|
|
1430
|
+
*/
|
|
1431
|
+
function getOrCreateQueue() {
|
|
1432
|
+
if (!isInitialized()) {
|
|
1433
|
+
warnIfNotInitialized("track()");
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
if (!eventsQueue) {
|
|
1437
|
+
const config = getConfig();
|
|
1438
|
+
if (!config?.subscribers || config.subscribers.length === 0) return null;
|
|
1439
|
+
eventsQueue = new EventQueue(config.subscribers);
|
|
1440
|
+
}
|
|
1441
|
+
return eventsQueue;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Track a business events event
|
|
1445
|
+
*
|
|
1446
|
+
* Features:
|
|
1447
|
+
* - Auto-attaches traceId and spanId if in active span
|
|
1448
|
+
* - Batched sending with retry
|
|
1449
|
+
* - Type-safe with optional generic
|
|
1450
|
+
* - No-op if init() not called or no subscribers configured
|
|
1451
|
+
*
|
|
1452
|
+
* @example Basic usage
|
|
1453
|
+
* ```typescript
|
|
1454
|
+
* track('user.signup', { userId: '123', plan: 'pro' })
|
|
1455
|
+
* ```
|
|
1456
|
+
*
|
|
1457
|
+
* @example With type safety
|
|
1458
|
+
* ```typescript
|
|
1459
|
+
* interface EventDatas {
|
|
1460
|
+
* 'user.signup': { userId: string; plan: string }
|
|
1461
|
+
* 'plan.upgraded': { userId: string; revenue: number }
|
|
1462
|
+
* }
|
|
1463
|
+
*
|
|
1464
|
+
* track<EventDatas>('user.signup', { userId: '123', plan: 'pro' })
|
|
1465
|
+
* ```
|
|
1466
|
+
*
|
|
1467
|
+
* @example Trace correlation (automatic)
|
|
1468
|
+
* ```typescript
|
|
1469
|
+
* @Instrumented()
|
|
1470
|
+
* class UserService {
|
|
1471
|
+
* async createUser(data: CreateUserData) {
|
|
1472
|
+
* // This track call automatically includes traceId + spanId
|
|
1473
|
+
* track('user.signup', { userId: data.id })
|
|
1474
|
+
* }
|
|
1475
|
+
* }
|
|
1476
|
+
* ```
|
|
1477
|
+
*/
|
|
1478
|
+
function track(event, data, options) {
|
|
1479
|
+
const queue = getOrCreateQueue();
|
|
1480
|
+
if (!queue) return;
|
|
1481
|
+
const validated = validateEvent(event, data, getValidationConfig() || void 0);
|
|
1482
|
+
const span = _opentelemetry_api.trace.getActiveSpan();
|
|
1483
|
+
const enrichedData = span ? {
|
|
1484
|
+
...validated.attributes,
|
|
1485
|
+
traceId: span.spanContext().traceId,
|
|
1486
|
+
spanId: span.spanContext().spanId
|
|
1487
|
+
} : validated.attributes;
|
|
1488
|
+
const autotelContext = buildAutotelContext(span);
|
|
1489
|
+
queue.enqueue({
|
|
1490
|
+
name: validated.eventName,
|
|
1491
|
+
attributes: enrichedData,
|
|
1492
|
+
timestamp: Date.now(),
|
|
1493
|
+
autotel: autotelContext,
|
|
1494
|
+
schema: options?.schema
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Get events queue (for flush/shutdown)
|
|
1499
|
+
* @internal
|
|
1500
|
+
*/
|
|
1501
|
+
function getEventQueue() {
|
|
1502
|
+
return eventsQueue;
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Reset events queue (for shutdown/cleanup)
|
|
1506
|
+
* @internal
|
|
1507
|
+
*/
|
|
1508
|
+
function resetEventQueue() {
|
|
1509
|
+
eventsQueue = null;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/circuit-breaker.ts
|
|
1514
|
+
const DEFAULT_CONFIG = {
|
|
1515
|
+
failureThreshold: 5,
|
|
1516
|
+
resetTimeout: 3e4,
|
|
1517
|
+
windowSize: 6e4
|
|
1518
|
+
};
|
|
1519
|
+
const CircuitState = {
|
|
1520
|
+
CLOSED: "CLOSED",
|
|
1521
|
+
OPEN: "OPEN",
|
|
1522
|
+
HALF_OPEN: "HALF_OPEN"
|
|
1523
|
+
};
|
|
1524
|
+
/**
|
|
1525
|
+
* Circuit breaker implementation
|
|
1526
|
+
*
|
|
1527
|
+
* Tracks failures and automatically opens the circuit to prevent
|
|
1528
|
+
* overwhelming failing subscribers.
|
|
1529
|
+
*/
|
|
1530
|
+
var CircuitBreaker = class {
|
|
1531
|
+
state = CircuitState.CLOSED;
|
|
1532
|
+
failures = [];
|
|
1533
|
+
lastFailureTime = 0;
|
|
1534
|
+
config;
|
|
1535
|
+
name;
|
|
1536
|
+
constructor(name, config) {
|
|
1537
|
+
this.name = name;
|
|
1538
|
+
this.config = {
|
|
1539
|
+
...DEFAULT_CONFIG,
|
|
1540
|
+
...config
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Execute a function with circuit breaker protection
|
|
1545
|
+
* Throws CircuitOpenError if circuit is open
|
|
1546
|
+
*/
|
|
1547
|
+
async execute(fn) {
|
|
1548
|
+
if (this.state === CircuitState.OPEN) {
|
|
1549
|
+
const now = Date.now();
|
|
1550
|
+
if (now - this.lastFailureTime >= this.config.resetTimeout) this.state = CircuitState.HALF_OPEN;
|
|
1551
|
+
else throw new CircuitOpenError(`Circuit breaker is OPEN for ${this.name}. Will retry in ${Math.ceil((this.config.resetTimeout - (now - this.lastFailureTime)) / 1e3)}s`);
|
|
1552
|
+
}
|
|
1553
|
+
try {
|
|
1554
|
+
const result = await fn();
|
|
1555
|
+
if (this.state === CircuitState.HALF_OPEN) this.reset();
|
|
1556
|
+
return result;
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
this.recordFailure(error);
|
|
1559
|
+
throw error;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Record a failure and potentially open the circuit
|
|
1564
|
+
*/
|
|
1565
|
+
recordFailure(error) {
|
|
1566
|
+
const now = Date.now();
|
|
1567
|
+
this.failures = this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
|
|
1568
|
+
this.failures.push({
|
|
1569
|
+
timestamp: now,
|
|
1570
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1571
|
+
});
|
|
1572
|
+
this.lastFailureTime = now;
|
|
1573
|
+
if (this.failures.length >= this.config.failureThreshold) {
|
|
1574
|
+
if (this.state === CircuitState.HALF_OPEN) this.state = CircuitState.OPEN;
|
|
1575
|
+
else if (this.state === CircuitState.CLOSED) this.state = CircuitState.OPEN;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Reset the circuit breaker (on success)
|
|
1580
|
+
*/
|
|
1581
|
+
reset() {
|
|
1582
|
+
this.state = CircuitState.CLOSED;
|
|
1583
|
+
this.failures = [];
|
|
1584
|
+
this.lastFailureTime = 0;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Get current state (for monitoring)
|
|
1588
|
+
*/
|
|
1589
|
+
getState() {
|
|
1590
|
+
return this.state;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Get failure count in current window
|
|
1594
|
+
*/
|
|
1595
|
+
getFailureCount() {
|
|
1596
|
+
const now = Date.now();
|
|
1597
|
+
this.failures = this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
|
|
1598
|
+
return this.failures.length;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Get recent failures (for debugging)
|
|
1602
|
+
*/
|
|
1603
|
+
getRecentFailures() {
|
|
1604
|
+
const now = Date.now();
|
|
1605
|
+
return this.failures.filter((f) => now - f.timestamp < this.config.windowSize);
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Manually reset the circuit breaker (for testing or manual intervention)
|
|
1609
|
+
*/
|
|
1610
|
+
forceReset() {
|
|
1611
|
+
this.reset();
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Manually open the circuit (for testing or manual intervention)
|
|
1615
|
+
*/
|
|
1616
|
+
forceOpen() {
|
|
1617
|
+
this.state = CircuitState.OPEN;
|
|
1618
|
+
this.lastFailureTime = Date.now();
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
/**
|
|
1622
|
+
* Error thrown when circuit is open
|
|
1623
|
+
*/
|
|
1624
|
+
var CircuitOpenError = class extends Error {
|
|
1625
|
+
constructor(message) {
|
|
1626
|
+
super(message);
|
|
1627
|
+
this.name = "CircuitOpenError";
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
//#endregion
|
|
1632
|
+
//#region src/operation-context.ts
|
|
1633
|
+
/**
|
|
1634
|
+
* Operation context tracking using AsyncLocalStorage
|
|
1635
|
+
*
|
|
1636
|
+
* This module provides a way to track operation names across async boundaries
|
|
1637
|
+
* so they can be automatically captured in events events.
|
|
1638
|
+
*
|
|
1639
|
+
* We cannot read span attributes from OpenTelemetry's API (it's write-only),
|
|
1640
|
+
* so we maintain our own async context storage.
|
|
1641
|
+
*/
|
|
1642
|
+
/**
|
|
1643
|
+
* AsyncLocalStorage instance for tracking operation context
|
|
1644
|
+
*/
|
|
1645
|
+
const operationStorage = new node_async_hooks.AsyncLocalStorage();
|
|
1646
|
+
/**
|
|
1647
|
+
* Get the current operation context (if any)
|
|
1648
|
+
*
|
|
1649
|
+
* @returns The current operation context, or undefined if not in an operation
|
|
1650
|
+
*
|
|
1651
|
+
* @example
|
|
1652
|
+
* ```typescript
|
|
1653
|
+
* const ctx = getOperationContext();
|
|
1654
|
+
* if (ctx) {
|
|
1655
|
+
* console.log('Current operation:', ctx.name);
|
|
1656
|
+
* }
|
|
1657
|
+
* ```
|
|
1658
|
+
*/
|
|
1659
|
+
function getOperationContext() {
|
|
1660
|
+
return operationStorage.getStore();
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Run a function within an operation context
|
|
1664
|
+
*
|
|
1665
|
+
* This sets the operation name for the duration of the function execution,
|
|
1666
|
+
* including all async operations spawned from it.
|
|
1667
|
+
*
|
|
1668
|
+
* @param name - The operation name to set
|
|
1669
|
+
* @param fn - The function to execute within the context
|
|
1670
|
+
* @returns The result of the function
|
|
1671
|
+
*
|
|
1672
|
+
* @example
|
|
1673
|
+
* ```typescript
|
|
1674
|
+
* const result = await runInOperationContext('user.create', async () => {
|
|
1675
|
+
* // Any events.trackEvent() calls here will automatically capture
|
|
1676
|
+
* // 'operation.name': 'user.create'
|
|
1677
|
+
* await createUser();
|
|
1678
|
+
* return 'success';
|
|
1679
|
+
* });
|
|
1680
|
+
* ```
|
|
1681
|
+
*/
|
|
1682
|
+
function runInOperationContext(name, fn) {
|
|
1683
|
+
return operationStorage.run({ name }, fn);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
//#endregion
|
|
1687
|
+
//#region src/events-config.ts
|
|
1688
|
+
/**
|
|
1689
|
+
* Hash a string value for PII protection
|
|
1690
|
+
*
|
|
1691
|
+
* Uses a simple, fast hash function suitable for correlation.
|
|
1692
|
+
* NOT cryptographically secure - use for PII masking, not security.
|
|
1693
|
+
*/
|
|
1694
|
+
function hashValue(value) {
|
|
1695
|
+
let hash = 0;
|
|
1696
|
+
for (let i = 0; i < value.length; i++) {
|
|
1697
|
+
const char = value.charCodeAt(i);
|
|
1698
|
+
hash = (hash << 5) - hash + char;
|
|
1699
|
+
hash = hash & hash;
|
|
1700
|
+
}
|
|
1701
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
//#endregion
|
|
1705
|
+
//#region src/event.ts
|
|
1706
|
+
/**
|
|
1707
|
+
* Events API for product events platforms
|
|
1708
|
+
*
|
|
1709
|
+
* Track user behavior, business events, and critical actions.
|
|
1710
|
+
* Sends to product events platforms (PostHog, Mixpanel, Amplitude) via subscribers.
|
|
1711
|
+
* For business people who think in events/funnels.
|
|
1712
|
+
*
|
|
1713
|
+
* For OpenTelemetry metrics (Prometheus/Grafana), use the Metrics class instead.
|
|
1714
|
+
*
|
|
1715
|
+
* @example Recommended: Configure subscribers in init(), use track() function
|
|
1716
|
+
* ```typescript
|
|
1717
|
+
* import { init, track } from 'autotel';
|
|
1718
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
1719
|
+
*
|
|
1720
|
+
* init({
|
|
1721
|
+
* service: 'my-app',
|
|
1722
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
|
|
1723
|
+
* });
|
|
1724
|
+
*
|
|
1725
|
+
* // Track events - uses subscribers from init()
|
|
1726
|
+
* track('application.submitted', { jobId: '123', userId: '456' });
|
|
1727
|
+
* ```
|
|
1728
|
+
*
|
|
1729
|
+
* @example Create Event instance (inherits subscribers from init)
|
|
1730
|
+
* ```typescript
|
|
1731
|
+
* import { Event } from 'autotel/event';
|
|
1732
|
+
*
|
|
1733
|
+
* // Uses subscribers configured in init()
|
|
1734
|
+
* const event = new Event('job-application');
|
|
1735
|
+
* event.trackEvent('application.submitted', { jobId: '123' });
|
|
1736
|
+
* ```
|
|
1737
|
+
*
|
|
1738
|
+
* @example Override subscribers for specific Event instance
|
|
1739
|
+
* ```typescript
|
|
1740
|
+
* import { Event } from 'autotel/event';
|
|
1741
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
1742
|
+
*
|
|
1743
|
+
* // Override: use different subscribers for this instance
|
|
1744
|
+
* const event = new Event('job-application', {
|
|
1745
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
|
|
1746
|
+
* });
|
|
1747
|
+
*
|
|
1748
|
+
* event.trackEvent('application.submitted', { jobId: '123' });
|
|
1749
|
+
* ```
|
|
1750
|
+
*/
|
|
1751
|
+
var Event = class {
|
|
1752
|
+
serviceName;
|
|
1753
|
+
logger;
|
|
1754
|
+
collector;
|
|
1755
|
+
subscribers;
|
|
1756
|
+
hasSubscribers;
|
|
1757
|
+
circuitBreakers;
|
|
1758
|
+
/**
|
|
1759
|
+
* Create a new Event instance
|
|
1760
|
+
*
|
|
1761
|
+
* **Note**: Most users should use `init()` + `track()` instead of creating Event instances directly.
|
|
1762
|
+
*
|
|
1763
|
+
* **Subscriber Resolution**:
|
|
1764
|
+
* - If `subscribers` provided in options → uses those (instance override)
|
|
1765
|
+
* - If `subscribers` not provided → falls back to subscribers from `init()` (global config)
|
|
1766
|
+
* - If neither → no subscribers (events logged only)
|
|
1767
|
+
*
|
|
1768
|
+
* @param serviceName - Service name for identifying events
|
|
1769
|
+
* @param options - Optional configuration (logger, collector, subscribers)
|
|
1770
|
+
*
|
|
1771
|
+
* @example Recommended: Use track() with init()
|
|
1772
|
+
* ```typescript
|
|
1773
|
+
* import { init, track } from 'autotel';
|
|
1774
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
1775
|
+
*
|
|
1776
|
+
* init({
|
|
1777
|
+
* service: 'checkout',
|
|
1778
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_...' })]
|
|
1779
|
+
* });
|
|
1780
|
+
*
|
|
1781
|
+
* track('purchase.completed', { amount: 99.99 });
|
|
1782
|
+
* ```
|
|
1783
|
+
*
|
|
1784
|
+
* @example Inherit subscribers from init()
|
|
1785
|
+
* ```typescript
|
|
1786
|
+
* // Uses subscribers configured in init()
|
|
1787
|
+
* const event = new Event('checkout');
|
|
1788
|
+
* event.trackEvent('purchase.completed', { amount: 99.99 });
|
|
1789
|
+
* ```
|
|
1790
|
+
*
|
|
1791
|
+
* @example Override subscribers for this instance
|
|
1792
|
+
* ```typescript
|
|
1793
|
+
* import { Event } from 'autotel/event';
|
|
1794
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
1795
|
+
*
|
|
1796
|
+
* // Override: use different subscribers for this instance only
|
|
1797
|
+
* const event = new Event('checkout', {
|
|
1798
|
+
* subscribers: [new PostHogSubscriber({ apiKey: 'phc_different_project' })]
|
|
1799
|
+
* });
|
|
1800
|
+
* ```
|
|
1801
|
+
*/
|
|
1802
|
+
constructor(serviceName, options = {}) {
|
|
1803
|
+
this.serviceName = serviceName;
|
|
1804
|
+
this.logger = options.logger;
|
|
1805
|
+
this.collector = options.collector;
|
|
1806
|
+
this.subscribers = options.subscribers === void 0 ? getConfig()?.subscribers || [] : options.subscribers;
|
|
1807
|
+
this.hasSubscribers = this.subscribers.length > 0;
|
|
1808
|
+
this.circuitBreakers = /* @__PURE__ */ new Map();
|
|
1809
|
+
for (const subscriber of this.subscribers) {
|
|
1810
|
+
const subscriberName = subscriber.name || "Unknown";
|
|
1811
|
+
this.circuitBreakers.set(subscriber, new CircuitBreaker(subscriberName, {
|
|
1812
|
+
failureThreshold: 5,
|
|
1813
|
+
resetTimeout: 3e4,
|
|
1814
|
+
windowSize: 6e4
|
|
1815
|
+
}));
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Automatically enrich attributes with all available telemetry context
|
|
1820
|
+
*
|
|
1821
|
+
* Auto-captures:
|
|
1822
|
+
* - Resource attributes: service.version, deployment.environment
|
|
1823
|
+
* - Trace context: traceId, spanId, correlationId
|
|
1824
|
+
* - Operation context: operation.name
|
|
1825
|
+
*/
|
|
1826
|
+
enrichWithTelemetryContext(attributes = {}) {
|
|
1827
|
+
const enriched = {
|
|
1828
|
+
service: this.serviceName,
|
|
1829
|
+
...attributes
|
|
1830
|
+
};
|
|
1831
|
+
const config = getConfig();
|
|
1832
|
+
if (config) {
|
|
1833
|
+
if (config.version) enriched["service.version"] = config.version;
|
|
1834
|
+
if (config.environment) enriched["deployment.environment"] = config.environment;
|
|
1835
|
+
}
|
|
1836
|
+
const spanContext = _opentelemetry_api.trace.getActiveSpan()?.spanContext();
|
|
1837
|
+
if (spanContext) {
|
|
1838
|
+
enriched.traceId = spanContext.traceId;
|
|
1839
|
+
enriched.spanId = spanContext.spanId;
|
|
1840
|
+
enriched.correlationId = spanContext.traceId.slice(0, 16);
|
|
1841
|
+
}
|
|
1842
|
+
const operationContext = getOperationContext();
|
|
1843
|
+
if (operationContext) enriched["operation.name"] = operationContext.name;
|
|
1844
|
+
return enriched;
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Build autotel event context for trace correlation
|
|
1848
|
+
*
|
|
1849
|
+
* Works in 4 contexts:
|
|
1850
|
+
* 1. Inside a span → use current span's trace_id + span_id
|
|
1851
|
+
* 2. Outside span but in AsyncLocalStorage context → use trace_id + correlation_id
|
|
1852
|
+
* 3. Totally standalone → use correlation_id + service/env/version
|
|
1853
|
+
* 4. Batch/fan-in (multiple linked parents) → use count + hash or full array
|
|
1854
|
+
*
|
|
1855
|
+
* @returns AutotelEventContext or undefined if trace context is disabled
|
|
1856
|
+
*/
|
|
1857
|
+
buildAutotelContext() {
|
|
1858
|
+
const eventsConfig = getEventsConfig();
|
|
1859
|
+
if (!eventsConfig?.includeTraceContext) return { correlation_id: getOrCreateCorrelationId() };
|
|
1860
|
+
const config = getConfig();
|
|
1861
|
+
const spanContext = _opentelemetry_api.trace.getActiveSpan()?.spanContext();
|
|
1862
|
+
const correlationId = getOrCreateCorrelationId();
|
|
1863
|
+
const autotelContext = { correlation_id: correlationId };
|
|
1864
|
+
if (spanContext) {
|
|
1865
|
+
autotelContext.trace_id = spanContext.traceId;
|
|
1866
|
+
autotelContext.span_id = spanContext.spanId;
|
|
1867
|
+
autotelContext.trace_flags = spanContext.traceFlags.toString(16).padStart(2, "0");
|
|
1868
|
+
const traceState = spanContext.traceState;
|
|
1869
|
+
if (traceState) {
|
|
1870
|
+
let traceStateStr = "";
|
|
1871
|
+
try {
|
|
1872
|
+
if (typeof traceState.serialize === "function") traceStateStr = traceState.serialize();
|
|
1873
|
+
} catch {}
|
|
1874
|
+
if (traceStateStr) autotelContext.trace_state = traceStateStr;
|
|
1875
|
+
}
|
|
1876
|
+
if (eventsConfig.traceUrl) {
|
|
1877
|
+
const traceUrl = eventsConfig.traceUrl({
|
|
1878
|
+
traceId: spanContext.traceId,
|
|
1879
|
+
spanId: spanContext.spanId,
|
|
1880
|
+
correlationId,
|
|
1881
|
+
serviceName: config?.service || this.serviceName,
|
|
1882
|
+
environment: config?.environment
|
|
1883
|
+
});
|
|
1884
|
+
if (traceUrl) autotelContext.trace_url = traceUrl;
|
|
1885
|
+
}
|
|
1886
|
+
} else if (eventsConfig.traceUrl && config) {
|
|
1887
|
+
const traceUrl = eventsConfig.traceUrl({
|
|
1888
|
+
correlationId,
|
|
1889
|
+
serviceName: config.service,
|
|
1890
|
+
environment: config.environment
|
|
1891
|
+
});
|
|
1892
|
+
if (traceUrl) autotelContext.trace_url = traceUrl;
|
|
1893
|
+
}
|
|
1894
|
+
return autotelContext;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Enrich event attributes from baggage with guardrails
|
|
1898
|
+
*
|
|
1899
|
+
* @param attributes - Current event attributes
|
|
1900
|
+
* @returns Enriched attributes with baggage values
|
|
1901
|
+
*/
|
|
1902
|
+
enrichFromBaggage(attributes) {
|
|
1903
|
+
const enrichConfig = getEventsConfig()?.enrichFromBaggage;
|
|
1904
|
+
if (!enrichConfig) return attributes;
|
|
1905
|
+
const enriched = { ...attributes };
|
|
1906
|
+
const activeContext = _opentelemetry_api.context.active();
|
|
1907
|
+
const baggage = _opentelemetry_api.propagation.getBaggage(activeContext);
|
|
1908
|
+
if (!baggage) return enriched;
|
|
1909
|
+
let keyCount = 0;
|
|
1910
|
+
let byteCount = 0;
|
|
1911
|
+
const maxKeys = enrichConfig.maxKeys ?? 10;
|
|
1912
|
+
const maxBytes = enrichConfig.maxBytes ?? 1024;
|
|
1913
|
+
const prefix = enrichConfig.prefix ?? "";
|
|
1914
|
+
for (const [key, entry] of baggage.getAllEntries()) {
|
|
1915
|
+
if (!this.isBaggageKeyAllowed(key, enrichConfig)) continue;
|
|
1916
|
+
if (keyCount >= maxKeys) break;
|
|
1917
|
+
const value = entry.value;
|
|
1918
|
+
const transform = enrichConfig.transform?.[key];
|
|
1919
|
+
let transformedValue;
|
|
1920
|
+
if (transform === "hash") transformedValue = hashValue(value);
|
|
1921
|
+
else if (transform === "plain" || !transform) transformedValue = value;
|
|
1922
|
+
else if (typeof transform === "function") transformedValue = transform(value);
|
|
1923
|
+
else transformedValue = value;
|
|
1924
|
+
const valueBytes = new TextEncoder().encode(transformedValue).length;
|
|
1925
|
+
if (byteCount + valueBytes > maxBytes) continue;
|
|
1926
|
+
const enrichedKey = `${prefix}${key}`;
|
|
1927
|
+
enriched[enrichedKey] = transformedValue;
|
|
1928
|
+
keyCount++;
|
|
1929
|
+
byteCount += valueBytes;
|
|
1930
|
+
}
|
|
1931
|
+
return enriched;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Check if a baggage key is allowed based on config
|
|
1935
|
+
*/
|
|
1936
|
+
isBaggageKeyAllowed(key, config) {
|
|
1937
|
+
if (config.deny) {
|
|
1938
|
+
for (const pattern of config.deny) if (this.matchesBaggagePattern(key, pattern)) return false;
|
|
1939
|
+
}
|
|
1940
|
+
for (const pattern of config.allow) if (this.matchesBaggagePattern(key, pattern)) return true;
|
|
1941
|
+
return false;
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Check if a key matches a baggage pattern
|
|
1945
|
+
* Supports exact matches and wildcard patterns (e.g., 'tenant.*')
|
|
1946
|
+
*/
|
|
1947
|
+
matchesBaggagePattern(key, pattern) {
|
|
1948
|
+
if (pattern.endsWith(".*")) {
|
|
1949
|
+
const prefix = pattern.slice(0, -2);
|
|
1950
|
+
return key.startsWith(prefix + ".");
|
|
1951
|
+
}
|
|
1952
|
+
return key === pattern;
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Track a business event
|
|
1956
|
+
*
|
|
1957
|
+
* Use this for tracking user actions, business events, product usage:
|
|
1958
|
+
* - "user.signup"
|
|
1959
|
+
* - "order.completed"
|
|
1960
|
+
* - "feature.used"
|
|
1961
|
+
*
|
|
1962
|
+
* Events are sent to configured subscribers (PostHog, Mixpanel, etc.).
|
|
1963
|
+
*
|
|
1964
|
+
* @example
|
|
1965
|
+
* ```typescript
|
|
1966
|
+
* // Track user signup
|
|
1967
|
+
* events.trackEvent('user.signup', {
|
|
1968
|
+
* userId: '123',
|
|
1969
|
+
* plan: 'pro'
|
|
1970
|
+
* })
|
|
1971
|
+
*
|
|
1972
|
+
* // Track order
|
|
1973
|
+
* events.trackEvent('order.completed', {
|
|
1974
|
+
* orderId: 'ord_123',
|
|
1975
|
+
* amount: 99.99
|
|
1976
|
+
* })
|
|
1977
|
+
* ```
|
|
1978
|
+
*/
|
|
1979
|
+
trackEvent(eventName, attributes) {
|
|
1980
|
+
const validated = validateEvent(eventName, attributes, getValidationConfig() || void 0);
|
|
1981
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(validated.attributes);
|
|
1982
|
+
this.logger?.info({
|
|
1983
|
+
event: validated.eventName,
|
|
1984
|
+
attributes: enrichedAttributes
|
|
1985
|
+
}, "Event tracked");
|
|
1986
|
+
this.collector?.recordEvent({
|
|
1987
|
+
event: validated.eventName,
|
|
1988
|
+
attributes: enrichedAttributes,
|
|
1989
|
+
service: this.serviceName,
|
|
1990
|
+
timestamp: Date.now()
|
|
1991
|
+
});
|
|
1992
|
+
if (this.hasSubscribers) {
|
|
1993
|
+
const autotelContext = this.buildAutotelContext();
|
|
1994
|
+
const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
|
|
1995
|
+
this.notifySubscribers((subscriber) => subscriber.trackEvent(validated.eventName, finalAttributes, { autotel: autotelContext }));
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Notify all subscribers concurrently without blocking
|
|
2000
|
+
* Uses circuit breakers to protect against failing subscribers
|
|
2001
|
+
* Uses Promise.allSettled to prevent subscriber errors from affecting other subscribers
|
|
2002
|
+
*/
|
|
2003
|
+
async notifySubscribers(fn) {
|
|
2004
|
+
const promises = this.subscribers.map(async (subscriber) => {
|
|
2005
|
+
const circuitBreaker = this.circuitBreakers.get(subscriber);
|
|
2006
|
+
if (!circuitBreaker) return;
|
|
2007
|
+
try {
|
|
2008
|
+
await circuitBreaker.execute(() => fn(subscriber));
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
if (error instanceof CircuitOpenError) {
|
|
2011
|
+
getLogger().warn({ subscriberName: subscriber.name || "Unknown" }, `[Events] ${error.message}`);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
getLogger().error({
|
|
2015
|
+
err: error instanceof Error ? error : void 0,
|
|
2016
|
+
subscriberName: subscriber.name || "Unknown"
|
|
2017
|
+
}, `[Events] Subscriber ${subscriber.name || "Unknown"} failed`);
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
await Promise.allSettled(promises);
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Track conversion funnel steps
|
|
2024
|
+
*
|
|
2025
|
+
* Monitor where users drop off in multi-step processes.
|
|
2026
|
+
*
|
|
2027
|
+
* @example
|
|
2028
|
+
* ```typescript
|
|
2029
|
+
* // Track signup funnel
|
|
2030
|
+
* events.trackFunnelStep('signup', 'started', { userId: '123' })
|
|
2031
|
+
* events.trackFunnelStep('signup', 'email_verified', { userId: '123' })
|
|
2032
|
+
* events.trackFunnelStep('signup', 'completed', { userId: '123' })
|
|
2033
|
+
*
|
|
2034
|
+
* // Track checkout flow
|
|
2035
|
+
* events.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
|
|
2036
|
+
* events.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })
|
|
2037
|
+
* events.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
|
|
2038
|
+
* ```
|
|
2039
|
+
*/
|
|
2040
|
+
trackFunnelStep(funnelName, status, attributes) {
|
|
2041
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
|
|
2042
|
+
this.logger?.info({
|
|
2043
|
+
funnel: funnelName,
|
|
2044
|
+
status,
|
|
2045
|
+
attributes: enrichedAttributes
|
|
2046
|
+
}, "Funnel step tracked");
|
|
2047
|
+
this.collector?.recordFunnelStep({
|
|
2048
|
+
funnel: funnelName,
|
|
2049
|
+
status,
|
|
2050
|
+
attributes: enrichedAttributes,
|
|
2051
|
+
service: this.serviceName,
|
|
2052
|
+
timestamp: Date.now()
|
|
2053
|
+
});
|
|
2054
|
+
if (this.hasSubscribers) {
|
|
2055
|
+
const autotelContext = this.buildAutotelContext();
|
|
2056
|
+
const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
|
|
2057
|
+
this.notifySubscribers((subscriber) => subscriber.trackFunnelStep(funnelName, status, finalAttributes, { autotel: autotelContext }));
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Track outcomes (success/failure/partial)
|
|
2062
|
+
*
|
|
2063
|
+
* Monitor success rates of critical operations.
|
|
2064
|
+
*
|
|
2065
|
+
* @example
|
|
2066
|
+
* ```typescript
|
|
2067
|
+
* // Track email delivery
|
|
2068
|
+
* events.trackOutcome('email.delivery', 'success', {
|
|
2069
|
+
* recipientType: 'user',
|
|
2070
|
+
* emailType: 'welcome'
|
|
2071
|
+
* })
|
|
2072
|
+
*
|
|
2073
|
+
* events.trackOutcome('email.delivery', 'failure', {
|
|
2074
|
+
* recipientType: 'user',
|
|
2075
|
+
* errorCode: 'invalid_email'
|
|
2076
|
+
* })
|
|
2077
|
+
*
|
|
2078
|
+
* // Track payment processing
|
|
2079
|
+
* events.trackOutcome('payment.process', 'success', { amount: 99.99 })
|
|
2080
|
+
* events.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
|
|
2081
|
+
* ```
|
|
2082
|
+
*/
|
|
2083
|
+
trackOutcome(operationName, status, attributes) {
|
|
2084
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
|
|
2085
|
+
this.logger?.info({
|
|
2086
|
+
operation: operationName,
|
|
2087
|
+
status,
|
|
2088
|
+
attributes: enrichedAttributes
|
|
2089
|
+
}, "Outcome tracked");
|
|
2090
|
+
this.collector?.recordOutcome({
|
|
2091
|
+
operation: operationName,
|
|
2092
|
+
status,
|
|
2093
|
+
attributes: enrichedAttributes,
|
|
2094
|
+
service: this.serviceName,
|
|
2095
|
+
timestamp: Date.now()
|
|
2096
|
+
});
|
|
2097
|
+
if (this.hasSubscribers) {
|
|
2098
|
+
const autotelContext = this.buildAutotelContext();
|
|
2099
|
+
const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
|
|
2100
|
+
this.notifySubscribers((subscriber) => subscriber.trackOutcome(operationName, status, finalAttributes, { autotel: autotelContext }));
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Track value metrics
|
|
2105
|
+
*
|
|
2106
|
+
* Record numerical values like revenue, transaction amounts,
|
|
2107
|
+
* item counts, processing times, engagement scores, etc.
|
|
2108
|
+
*
|
|
2109
|
+
* @example
|
|
2110
|
+
* ```typescript
|
|
2111
|
+
* // Track revenue
|
|
2112
|
+
* events.trackValue('order.revenue', 149.99, {
|
|
2113
|
+
* currency: 'USD',
|
|
2114
|
+
* productCategory: 'electronics'
|
|
2115
|
+
* })
|
|
2116
|
+
*
|
|
2117
|
+
* // Track items per cart
|
|
2118
|
+
* events.trackValue('cart.item_count', 5, {
|
|
2119
|
+
* userId: '123'
|
|
2120
|
+
* })
|
|
2121
|
+
*
|
|
2122
|
+
* // Track processing time
|
|
2123
|
+
* events.trackValue('api.response_time', 250, {
|
|
2124
|
+
* unit: 'ms',
|
|
2125
|
+
* endpoint: '/api/checkout'
|
|
2126
|
+
* })
|
|
2127
|
+
* ```
|
|
2128
|
+
*/
|
|
2129
|
+
trackValue(metricName, value, attributes) {
|
|
2130
|
+
const enrichedAttributes = this.enrichWithTelemetryContext({
|
|
2131
|
+
metric: metricName,
|
|
2132
|
+
...attributes
|
|
2133
|
+
});
|
|
2134
|
+
this.logger?.debug({
|
|
2135
|
+
metric: metricName,
|
|
2136
|
+
value,
|
|
2137
|
+
attributes: enrichedAttributes
|
|
2138
|
+
}, "Value tracked");
|
|
2139
|
+
this.collector?.recordValue({
|
|
2140
|
+
metric: metricName,
|
|
2141
|
+
value,
|
|
2142
|
+
attributes: enrichedAttributes,
|
|
2143
|
+
service: this.serviceName,
|
|
2144
|
+
timestamp: Date.now()
|
|
2145
|
+
});
|
|
2146
|
+
if (this.hasSubscribers) {
|
|
2147
|
+
const autotelContext = this.buildAutotelContext();
|
|
2148
|
+
const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
|
|
2149
|
+
this.notifySubscribers((subscriber) => subscriber.trackValue(metricName, value, finalAttributes, { autotel: autotelContext }));
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Flush all subscribers and wait for pending events
|
|
2154
|
+
*
|
|
2155
|
+
* Call this before shutdown to ensure all events are delivered.
|
|
2156
|
+
*
|
|
2157
|
+
* @example
|
|
2158
|
+
* ```typescript
|
|
2159
|
+
* const event =new Event('app', { subscribers: [...] });
|
|
2160
|
+
*
|
|
2161
|
+
* // Before shutdown
|
|
2162
|
+
* await events.flush();
|
|
2163
|
+
* ```
|
|
2164
|
+
*/
|
|
2165
|
+
async flush() {
|
|
2166
|
+
if (!this.hasSubscribers) return;
|
|
2167
|
+
const shutdownPromises = this.subscribers.map(async (subscriber) => {
|
|
2168
|
+
if (subscriber.shutdown) try {
|
|
2169
|
+
await subscriber.shutdown();
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
getLogger().error({
|
|
2172
|
+
err: error instanceof Error ? error : void 0,
|
|
2173
|
+
subscriberName: subscriber.name || "Unknown"
|
|
2174
|
+
}, `[Events] Failed to shutdown subscriber ${subscriber.name || "Unknown"}`);
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
await Promise.allSettled(shutdownPromises);
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Shutdown the Event instance and all subscribers
|
|
2181
|
+
*
|
|
2182
|
+
* Unlike `flush()`, this method:
|
|
2183
|
+
* - Shuts down all subscribers
|
|
2184
|
+
* - Prevents further event tracking (hasSubscribers becomes false)
|
|
2185
|
+
* - Should only be called once at application shutdown
|
|
2186
|
+
*
|
|
2187
|
+
* @example
|
|
2188
|
+
* ```typescript
|
|
2189
|
+
* // In Next.js API route with after()
|
|
2190
|
+
* import { after } from 'next/server';
|
|
2191
|
+
*
|
|
2192
|
+
* export async function POST(req: Request) {
|
|
2193
|
+
* const event = new Event('checkout', { subscribers: [...] });
|
|
2194
|
+
* event.trackEvent('order.completed', { orderId: '123' });
|
|
2195
|
+
*
|
|
2196
|
+
* after(async () => {
|
|
2197
|
+
* await event.shutdown();
|
|
2198
|
+
* });
|
|
2199
|
+
*
|
|
2200
|
+
* return Response.json({ success: true });
|
|
2201
|
+
* }
|
|
2202
|
+
* ```
|
|
2203
|
+
*/
|
|
2204
|
+
async shutdown() {
|
|
2205
|
+
if (!this.hasSubscribers) return;
|
|
2206
|
+
await Promise.allSettled(this.subscribers.map(async (subscriber) => {
|
|
2207
|
+
if (subscriber.shutdown) try {
|
|
2208
|
+
await subscriber.shutdown();
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
getLogger().error({
|
|
2211
|
+
err: error instanceof Error ? error : void 0,
|
|
2212
|
+
subscriberName: subscriber.name || "Unknown"
|
|
2213
|
+
}, `[Events] Failed to shutdown subscriber ${subscriber.name || "Unknown"}`);
|
|
2214
|
+
}
|
|
2215
|
+
}));
|
|
2216
|
+
this.hasSubscribers = false;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Track funnel progression with custom step names
|
|
2220
|
+
*
|
|
2221
|
+
* Unlike trackFunnelStep which uses FunnelStatus enum values,
|
|
2222
|
+
* this method allows any string as the step name for flexible funnel tracking.
|
|
2223
|
+
*
|
|
2224
|
+
* @param funnelName - Name of the funnel (e.g., "checkout", "onboarding")
|
|
2225
|
+
* @param stepName - Custom step name (e.g., "cart_viewed", "payment_entered")
|
|
2226
|
+
* @param stepNumber - Optional numeric position in the funnel
|
|
2227
|
+
* @param attributes - Optional event attributes
|
|
2228
|
+
*
|
|
2229
|
+
* @example
|
|
2230
|
+
* ```typescript
|
|
2231
|
+
* // Track custom checkout steps
|
|
2232
|
+
* event.trackFunnelProgression('checkout', 'cart_viewed', 1);
|
|
2233
|
+
* event.trackFunnelProgression('checkout', 'shipping_selected', 2);
|
|
2234
|
+
* event.trackFunnelProgression('checkout', 'payment_entered', 3);
|
|
2235
|
+
* event.trackFunnelProgression('checkout', 'order_confirmed', 4);
|
|
2236
|
+
* ```
|
|
2237
|
+
*/
|
|
2238
|
+
trackFunnelProgression(funnelName, stepName, stepNumber, attributes) {
|
|
2239
|
+
const enrichedAttributes = this.enrichWithTelemetryContext(attributes);
|
|
2240
|
+
this.logger?.info({
|
|
2241
|
+
funnel: funnelName,
|
|
2242
|
+
stepName,
|
|
2243
|
+
stepNumber,
|
|
2244
|
+
attributes: enrichedAttributes
|
|
2245
|
+
}, "Funnel progression tracked");
|
|
2246
|
+
this.collector?.recordFunnelStep({
|
|
2247
|
+
funnel: funnelName,
|
|
2248
|
+
status: stepName,
|
|
2249
|
+
attributes: {
|
|
2250
|
+
...enrichedAttributes,
|
|
2251
|
+
step_name: stepName,
|
|
2252
|
+
...stepNumber === void 0 ? {} : { step_number: stepNumber }
|
|
2253
|
+
},
|
|
2254
|
+
service: this.serviceName,
|
|
2255
|
+
timestamp: Date.now()
|
|
2256
|
+
});
|
|
2257
|
+
if (this.hasSubscribers) {
|
|
2258
|
+
const autotelContext = this.buildAutotelContext();
|
|
2259
|
+
const finalAttributes = this.enrichFromBaggage(enrichedAttributes);
|
|
2260
|
+
this.notifySubscribers(async (subscriber) => {
|
|
2261
|
+
await (subscriber.trackFunnelProgression ? subscriber.trackFunnelProgression(funnelName, stepName, stepNumber, finalAttributes, { autotel: autotelContext }) : subscriber.trackFunnelStep(funnelName, stepName, {
|
|
2262
|
+
...finalAttributes,
|
|
2263
|
+
step_name: stepName,
|
|
2264
|
+
...stepNumber === void 0 ? {} : { step_number: stepNumber }
|
|
2265
|
+
}, { autotel: autotelContext }));
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Track multiple events in a batch
|
|
2271
|
+
*
|
|
2272
|
+
* Useful for bulk event tracking with consistent timestamps.
|
|
2273
|
+
* Events are sent to subscribers individually but processed together.
|
|
2274
|
+
*
|
|
2275
|
+
* @param events - Array of events to track
|
|
2276
|
+
*
|
|
2277
|
+
* @example
|
|
2278
|
+
* ```typescript
|
|
2279
|
+
* event.trackBatch([
|
|
2280
|
+
* { name: 'item.viewed', attributes: { itemId: '1' } },
|
|
2281
|
+
* { name: 'item.viewed', attributes: { itemId: '2' } },
|
|
2282
|
+
* { name: 'cart.updated', attributes: { itemCount: 2 } },
|
|
2283
|
+
* ]);
|
|
2284
|
+
* ```
|
|
2285
|
+
*/
|
|
2286
|
+
trackBatch(events) {
|
|
2287
|
+
for (const event of events) {
|
|
2288
|
+
const filteredAttributes = event.attributes ? Object.fromEntries(Object.entries(event.attributes).filter(([, v]) => v !== void 0 && v !== null)) : void 0;
|
|
2289
|
+
this.trackEvent(event.name, filteredAttributes);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
/**
|
|
2294
|
+
* Global events instances (singleton pattern)
|
|
2295
|
+
*/
|
|
2296
|
+
const eventsInstances = /* @__PURE__ */ new Map();
|
|
2297
|
+
/**
|
|
2298
|
+
* Get or create an Events instance for a service
|
|
2299
|
+
*
|
|
2300
|
+
* @param serviceName - Service name for identifying events
|
|
2301
|
+
* @param logger - Optional logger
|
|
2302
|
+
* @returns Events instance
|
|
2303
|
+
*
|
|
2304
|
+
* @example
|
|
2305
|
+
* ```typescript
|
|
2306
|
+
* const event =getEvents('job-application')
|
|
2307
|
+
* events.trackEvent('application.submitted', { jobId: '123' })
|
|
2308
|
+
* ```
|
|
2309
|
+
*/
|
|
2310
|
+
function getEvents(serviceName, logger) {
|
|
2311
|
+
if (!eventsInstances.has(serviceName)) eventsInstances.set(serviceName, new Event(serviceName, { logger }));
|
|
2312
|
+
return eventsInstances.get(serviceName);
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Reset all events instances (mainly for testing)
|
|
2316
|
+
*/
|
|
2317
|
+
function resetEvents() {
|
|
2318
|
+
eventsInstances.clear();
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
//#endregion
|
|
2322
|
+
//#region src/shutdown.ts
|
|
2323
|
+
/**
|
|
2324
|
+
* Graceful shutdown with flush and cleanup
|
|
2325
|
+
*/
|
|
2326
|
+
/**
|
|
2327
|
+
* Error codes that mean "the OTLP endpoint wasn't reachable" — expected and
|
|
2328
|
+
* harmless when no collector is configured. Deliberately limited to
|
|
2329
|
+
* connection-establishment failures (refused / DNS), not post-connection
|
|
2330
|
+
* errors like ECONNRESET or ETIMEDOUT, which can indicate a real problem
|
|
2331
|
+
* talking to a configured backend and should surface.
|
|
2332
|
+
*/
|
|
2333
|
+
const UNREACHABLE_ENDPOINT_CODES = /* @__PURE__ */ new Set([
|
|
2334
|
+
"ECONNREFUSED",
|
|
2335
|
+
"ENOTFOUND",
|
|
2336
|
+
"EAI_AGAIN"
|
|
2337
|
+
]);
|
|
2338
|
+
function errorCode(error) {
|
|
2339
|
+
if (typeof error === "object" && error !== null && "code" in error) {
|
|
2340
|
+
const code = error.code;
|
|
2341
|
+
if (typeof code === "string") return code;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* True when the error (or every error it wraps) is an unreachable-endpoint
|
|
2346
|
+
* failure. Traverses `AggregateError.errors` and the `cause` chain, since the
|
|
2347
|
+
* SDK often wraps the underlying network error.
|
|
2348
|
+
*/
|
|
2349
|
+
function isUnreachableEndpointError(error, depth = 0) {
|
|
2350
|
+
if (depth > 5 || error === null || typeof error !== "object") return false;
|
|
2351
|
+
if (error instanceof AggregateError) return error.errors.length > 0 && error.errors.every((e) => isUnreachableEndpointError(e, depth + 1));
|
|
2352
|
+
const code = errorCode(error);
|
|
2353
|
+
if (code && UNREACHABLE_ENDPOINT_CODES.has(code)) return true;
|
|
2354
|
+
const cause = error.cause;
|
|
2355
|
+
return cause !== void 0 && cause !== error ? isUnreachableEndpointError(cause, depth + 1) : false;
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Flush all pending telemetry
|
|
2359
|
+
*
|
|
2360
|
+
* Flushes both events events and OpenTelemetry spans to their destinations.
|
|
2361
|
+
* Includes timeout protection to prevent hanging in serverless environments.
|
|
2362
|
+
*
|
|
2363
|
+
* Safe to call multiple times.
|
|
2364
|
+
*
|
|
2365
|
+
* @param options - Optional configuration
|
|
2366
|
+
* @param options.timeout - Timeout in milliseconds (default: 2000ms)
|
|
2367
|
+
* @param options.forShutdown - If true, permanently disables the events queue after flush (used internally by shutdown())
|
|
2368
|
+
*
|
|
2369
|
+
* @example Manual flush in serverless
|
|
2370
|
+
* ```typescript
|
|
2371
|
+
* import { flush } from 'autotel';
|
|
2372
|
+
*
|
|
2373
|
+
* export const handler = async (event) => {
|
|
2374
|
+
* // ... process event
|
|
2375
|
+
* await flush(); // Flush before function returns
|
|
2376
|
+
* return result;
|
|
2377
|
+
* };
|
|
2378
|
+
* ```
|
|
2379
|
+
*
|
|
2380
|
+
* @example With custom timeout
|
|
2381
|
+
* ```typescript
|
|
2382
|
+
* await flush({ timeout: 5000 }); // 5 second timeout
|
|
2383
|
+
* ```
|
|
2384
|
+
*/
|
|
2385
|
+
async function flush(options) {
|
|
2386
|
+
const timeout = options?.timeout ?? 2e3;
|
|
2387
|
+
const forShutdown = options?.forShutdown ?? false;
|
|
2388
|
+
const doFlush = async () => {
|
|
2389
|
+
const eventsQueue = getEventQueue();
|
|
2390
|
+
if (eventsQueue) await (forShutdown ? eventsQueue.shutdown() : eventsQueue.flush());
|
|
2391
|
+
try {
|
|
2392
|
+
const tracerProvider = require_tracer_provider.getForceFlushableProvider(getSdk());
|
|
2393
|
+
if (tracerProvider) await tracerProvider.forceFlush();
|
|
2394
|
+
} catch {}
|
|
2395
|
+
};
|
|
2396
|
+
let timeoutHandle;
|
|
2397
|
+
try {
|
|
2398
|
+
await Promise.race([doFlush().finally(() => {
|
|
2399
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2400
|
+
}), new Promise((_, reject) => {
|
|
2401
|
+
timeoutHandle = setTimeout(() => reject(/* @__PURE__ */ new Error("Flush timeout")), timeout);
|
|
2402
|
+
timeoutHandle.unref();
|
|
2403
|
+
})]);
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2406
|
+
getLogger().error({ err: error instanceof Error ? error : new Error(String(error)) }, "[autotel] Flush error");
|
|
2407
|
+
throw error;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Shutdown telemetry and cleanup resources
|
|
2412
|
+
*
|
|
2413
|
+
* - Flushes all pending data
|
|
2414
|
+
* - Shuts down OpenTelemetry SDK
|
|
2415
|
+
* - Cleans up resources
|
|
2416
|
+
*
|
|
2417
|
+
* Call this before process exit.
|
|
2418
|
+
*
|
|
2419
|
+
* Always performs cleanup even if flush fails, preventing resource leaks
|
|
2420
|
+
* in serverless handlers or tests.
|
|
2421
|
+
*
|
|
2422
|
+
* @example Express server
|
|
2423
|
+
* ```typescript
|
|
2424
|
+
* const server = app.listen(3000)
|
|
2425
|
+
*
|
|
2426
|
+
* process.on('SIGTERM', async () => {
|
|
2427
|
+
* await server.close()
|
|
2428
|
+
* await shutdown()
|
|
2429
|
+
* process.exit(0)
|
|
2430
|
+
* })
|
|
2431
|
+
* ```
|
|
2432
|
+
*/
|
|
2433
|
+
async function shutdown() {
|
|
2434
|
+
const logger = getLogger();
|
|
2435
|
+
let shutdownError = null;
|
|
2436
|
+
try {
|
|
2437
|
+
await flush({ forShutdown: true });
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2440
|
+
shutdownError = err;
|
|
2441
|
+
logger.error({ err }, "[autotel] Flush failed during shutdown, continuing cleanup");
|
|
2442
|
+
}
|
|
2443
|
+
try {
|
|
2444
|
+
const sdk = getSdk();
|
|
2445
|
+
if (sdk) await sdk.shutdown();
|
|
2446
|
+
} catch (error) {
|
|
2447
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2448
|
+
if (!isUnreachableEndpointError(error)) {
|
|
2449
|
+
if (!shutdownError) shutdownError = err;
|
|
2450
|
+
logger.error({ err }, "[autotel] SDK shutdown failed");
|
|
2451
|
+
}
|
|
2452
|
+
} finally {
|
|
2453
|
+
uninstallProcessHandlers();
|
|
2454
|
+
await _closeEmbeddedDevtools();
|
|
2455
|
+
const eventsQueue = getEventQueue();
|
|
2456
|
+
if (eventsQueue && typeof eventsQueue.cleanup === "function") eventsQueue.cleanup();
|
|
2457
|
+
resetEvents();
|
|
2458
|
+
require_metric.resetMetrics();
|
|
2459
|
+
resetEventQueue();
|
|
2460
|
+
}
|
|
2461
|
+
if (shutdownError) throw shutdownError;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
//#endregion
|
|
2465
|
+
//#region src/init.ts
|
|
2466
|
+
/**
|
|
2467
|
+
* Simplified initialization for autotel
|
|
2468
|
+
*
|
|
2469
|
+
* Single init() function with sensible defaults.
|
|
2470
|
+
* Replaces initInstrumentation() and separate events config.
|
|
2471
|
+
*/
|
|
2472
|
+
/**
|
|
2473
|
+
* Silent logger (no-op) - used as default when user doesn't provide one.
|
|
2474
|
+
* Internal autotel logs are silent by default to avoid spam.
|
|
2475
|
+
* Users can import { autotelLogger } from 'autotel/logger' to create their own.
|
|
2476
|
+
*/
|
|
2477
|
+
const silentLogger = {
|
|
2478
|
+
info: () => {},
|
|
2479
|
+
warn: () => {},
|
|
2480
|
+
error: () => {},
|
|
2481
|
+
debug: () => {}
|
|
2482
|
+
};
|
|
2483
|
+
/**
|
|
2484
|
+
* Adapts an Autotel Sampler to the OTel SDK Sampler interface.
|
|
2485
|
+
*/
|
|
2486
|
+
function toOtelSampler(sampler) {
|
|
2487
|
+
return {
|
|
2488
|
+
shouldSample(_context, _traceId, spanName, _spanKind, _attributes, links) {
|
|
2489
|
+
const samplingContext = {
|
|
2490
|
+
operationName: spanName,
|
|
2491
|
+
args: [],
|
|
2492
|
+
links
|
|
2493
|
+
};
|
|
2494
|
+
const shouldTrace = sampler.shouldSample(samplingContext);
|
|
2495
|
+
const rate = sampler.sampleRate?.(samplingContext);
|
|
2496
|
+
return {
|
|
2497
|
+
decision: shouldTrace ? _opentelemetry_sdk_trace_base.SamplingDecision.RECORD_AND_SAMPLED : _opentelemetry_sdk_trace_base.SamplingDecision.NOT_RECORD,
|
|
2498
|
+
...shouldTrace && rate !== void 0 && rate > 1 ? { attributes: { [require_sampling.AUTOTEL_SAMPLING_RATE]: rate } } : {}
|
|
2499
|
+
};
|
|
2500
|
+
},
|
|
2501
|
+
toString() {
|
|
2502
|
+
return `AutotelSamplerAdapter`;
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
let OTLPTraceExporterGRPC;
|
|
2507
|
+
let OTLPMetricExporterGRPC;
|
|
2508
|
+
let OTLPLogExporterGRPC;
|
|
2509
|
+
/**
|
|
2510
|
+
* Helper: Lazy-load gRPC trace exporter
|
|
2511
|
+
*/
|
|
2512
|
+
function loadGRPCTraceExporter() {
|
|
2513
|
+
if (OTLPTraceExporterGRPC) return OTLPTraceExporterGRPC;
|
|
2514
|
+
try {
|
|
2515
|
+
OTLPTraceExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-trace-otlp-grpc").OTLPTraceExporter;
|
|
2516
|
+
return OTLPTraceExporterGRPC;
|
|
2517
|
+
} catch {
|
|
2518
|
+
throw new Error("gRPC trace exporter not found. Install @opentelemetry/exporter-trace-otlp-grpc");
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Helper: Lazy-load gRPC metric exporter
|
|
2523
|
+
*/
|
|
2524
|
+
function loadGRPCMetricExporter() {
|
|
2525
|
+
if (OTLPMetricExporterGRPC) return OTLPMetricExporterGRPC;
|
|
2526
|
+
try {
|
|
2527
|
+
OTLPMetricExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-metrics-otlp-grpc").OTLPMetricExporter;
|
|
2528
|
+
return OTLPMetricExporterGRPC;
|
|
2529
|
+
} catch {
|
|
2530
|
+
throw new Error("gRPC metric exporter not found. Install @opentelemetry/exporter-metrics-otlp-grpc");
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Helper: Create trace exporter based on protocol
|
|
2535
|
+
*/
|
|
2536
|
+
function createTraceExporter(protocol, config) {
|
|
2537
|
+
if (protocol === "grpc") return new (loadGRPCTraceExporter())(config);
|
|
2538
|
+
return new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter(config);
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Helper: Create metric exporter based on protocol
|
|
2542
|
+
*/
|
|
2543
|
+
function createMetricExporter(protocol, config) {
|
|
2544
|
+
if (protocol === "grpc") return new (loadGRPCMetricExporter())(config);
|
|
2545
|
+
return new _opentelemetry_exporter_metrics_otlp_http.OTLPMetricExporter(config);
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Helper: Lazy-load gRPC log exporter
|
|
2549
|
+
*/
|
|
2550
|
+
function loadGRPCLogExporter() {
|
|
2551
|
+
if (OTLPLogExporterGRPC) return OTLPLogExporterGRPC;
|
|
2552
|
+
try {
|
|
2553
|
+
OTLPLogExporterGRPC = require_node_require.requireModule("@opentelemetry/exporter-logs-otlp-grpc").OTLPLogExporter;
|
|
2554
|
+
return OTLPLogExporterGRPC;
|
|
2555
|
+
} catch {
|
|
2556
|
+
throw new Error("gRPC log exporter not found. Install @opentelemetry/exporter-logs-otlp-grpc");
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Helper: Create log exporter based on protocol
|
|
2561
|
+
*/
|
|
2562
|
+
function createLogExporter(protocol, config) {
|
|
2563
|
+
if (protocol === "grpc") return new (loadGRPCLogExporter())(config);
|
|
2564
|
+
return new _opentelemetry_exporter_logs_otlp_http.OTLPLogExporter(config);
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Helper: Resolve protocol from config and environment
|
|
2568
|
+
*/
|
|
2569
|
+
function resolveProtocol(configProtocol) {
|
|
2570
|
+
if (configProtocol === "grpc" || configProtocol === "http") return configProtocol;
|
|
2571
|
+
const envProtocol = process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
|
|
2572
|
+
if (envProtocol === "grpc") return "grpc";
|
|
2573
|
+
if (envProtocol === "http/protobuf" || envProtocol === "http") return "http";
|
|
2574
|
+
return "http";
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Helper: Adjust endpoint URL for protocol
|
|
2578
|
+
* gRPC exporters don't need the /v1/traces or /v1/metrics path
|
|
2579
|
+
* HTTP exporters need the full path
|
|
2580
|
+
*/
|
|
2581
|
+
function formatEndpointUrl(endpoint, signal, protocol) {
|
|
2582
|
+
if (protocol === "grpc") return endpoint.replace(/\/(v1\/)?(traces|metrics|logs)$/, "");
|
|
2583
|
+
if (!endpoint.endsWith(`/v1/${signal}`)) return `${endpoint}/v1/${signal}`;
|
|
2584
|
+
return endpoint;
|
|
2585
|
+
}
|
|
2586
|
+
let initialized = false;
|
|
2587
|
+
let locked = false;
|
|
2588
|
+
let config = null;
|
|
2589
|
+
let sdk = null;
|
|
2590
|
+
let warnedOnce = false;
|
|
2591
|
+
let logger = silentLogger;
|
|
2592
|
+
let validationConfig = null;
|
|
2593
|
+
let eventsConfig = null;
|
|
2594
|
+
let _stringRedactor = null;
|
|
2595
|
+
function acceptsStringRedactor(subscriber) {
|
|
2596
|
+
return typeof subscriber === "object" && subscriber !== null && "setStringRedactor" in subscriber && typeof subscriber.setStringRedactor === "function";
|
|
2597
|
+
}
|
|
2598
|
+
let _optionalRequire = require_node_require.safeRequire;
|
|
2599
|
+
let _devtoolsClose = null;
|
|
2600
|
+
const LOG_LEVELS = {
|
|
2601
|
+
debug: 0,
|
|
2602
|
+
info: 1,
|
|
2603
|
+
warn: 2,
|
|
2604
|
+
error: 3
|
|
2605
|
+
};
|
|
2606
|
+
/**
|
|
2607
|
+
* Lock the logger to prevent further `init()` calls.
|
|
2608
|
+
* Use this when framework plugins set up instrumentation and you want
|
|
2609
|
+
* to prevent accidental re-initialization from user code.
|
|
2610
|
+
*/
|
|
2611
|
+
function lockLogger() {
|
|
2612
|
+
locked = true;
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Check if the logger has been locked.
|
|
2616
|
+
*/
|
|
2617
|
+
function isLoggerLocked() {
|
|
2618
|
+
return locked;
|
|
2619
|
+
}
|
|
2620
|
+
function createSilentLogger() {
|
|
2621
|
+
return {
|
|
2622
|
+
info: () => {},
|
|
2623
|
+
warn: () => {},
|
|
2624
|
+
error: () => {},
|
|
2625
|
+
debug: () => {}
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
function wrapLogger(base, silent, minLevel) {
|
|
2629
|
+
if (silent) return createSilentLogger();
|
|
2630
|
+
const threshold = LOG_LEVELS[minLevel];
|
|
2631
|
+
const wrap = (fn, level) => {
|
|
2632
|
+
if (LOG_LEVELS[level] < threshold) return (() => {});
|
|
2633
|
+
return ((...args) => fn(...args));
|
|
2634
|
+
};
|
|
2635
|
+
return {
|
|
2636
|
+
debug: wrap(base.debug, "debug"),
|
|
2637
|
+
info: wrap(base.info, "info"),
|
|
2638
|
+
warn: wrap(base.warn, "warn"),
|
|
2639
|
+
error: wrap(base.error, "error")
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Resolve the effective attribute redactor. Explicit config wins (`false`
|
|
2644
|
+
* disables). Otherwise the `AUTOTEL_REDACT_PII` env var controls it, and as a
|
|
2645
|
+
* final default PII redaction is auto-enabled in production.
|
|
2646
|
+
*/
|
|
2647
|
+
function resolveAttributeRedactor(explicit, environment) {
|
|
2648
|
+
if (explicit === false) return void 0;
|
|
2649
|
+
if (explicit !== void 0) return explicit;
|
|
2650
|
+
const flag = process.env.AUTOTEL_REDACT_PII?.trim().toLowerCase();
|
|
2651
|
+
if (flag) {
|
|
2652
|
+
if ([
|
|
2653
|
+
"off",
|
|
2654
|
+
"false",
|
|
2655
|
+
"0",
|
|
2656
|
+
"none",
|
|
2657
|
+
"disabled"
|
|
2658
|
+
].includes(flag)) return;
|
|
2659
|
+
if (flag === "default" || flag === "strict" || flag === "pci-dss") return flag;
|
|
2660
|
+
if ([
|
|
2661
|
+
"on",
|
|
2662
|
+
"true",
|
|
2663
|
+
"1",
|
|
2664
|
+
"enabled"
|
|
2665
|
+
].includes(flag)) return "default";
|
|
2666
|
+
}
|
|
2667
|
+
return environment === "production" ? "default" : void 0;
|
|
2668
|
+
}
|
|
2669
|
+
function detectEnvironmentAttributes() {
|
|
2670
|
+
const attrs = {};
|
|
2671
|
+
const commitSha = process.env.COMMIT_SHA || process.env.GITHUB_SHA || process.env.VERCEL_GIT_COMMIT_SHA || process.env.CF_PAGES_COMMIT_SHA || process.env.AWS_CODEPIPELINE_EXECUTION_ID;
|
|
2672
|
+
if (commitSha) attrs["service.commit.sha"] = commitSha;
|
|
2673
|
+
const region = process.env.VERCEL_REGION || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || process.env.FLY_REGION || process.env.CF_REGION || process.env.GOOGLE_CLOUD_REGION;
|
|
2674
|
+
if (region) attrs["service.region"] = region;
|
|
2675
|
+
const version = process.env.APP_VERSION || process.env.HEROKU_RELEASE_VERSION || process.env.VERCEL_GIT_COMMIT_REF;
|
|
2676
|
+
if (version) attrs["service.deploy.version"] = version;
|
|
2677
|
+
return attrs;
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Resolve metrics flag with env var override support
|
|
2681
|
+
*/
|
|
2682
|
+
function resolveMetricsFlag(configFlag = "auto") {
|
|
2683
|
+
const envFlag = process.env.AUTOTEL_METRICS;
|
|
2684
|
+
if (envFlag === "on" || envFlag === "true") return true;
|
|
2685
|
+
if (envFlag === "off" || envFlag === "false") return false;
|
|
2686
|
+
if (configFlag === true) return true;
|
|
2687
|
+
if (configFlag === false) return false;
|
|
2688
|
+
return true;
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Resolve logs flag with env var override support.
|
|
2692
|
+
* Defaults to disabled (opt-in only) to avoid unexpected log export
|
|
2693
|
+
* and to preserve the upstream SDK's OTEL_LOGS_EXPORTER handling.
|
|
2694
|
+
*/
|
|
2695
|
+
function resolveLogsFlag(configFlag = "auto") {
|
|
2696
|
+
const envFlag = process.env.AUTOTEL_LOGS;
|
|
2697
|
+
if (envFlag === "on" || envFlag === "true") return true;
|
|
2698
|
+
if (envFlag === "off" || envFlag === "false") return false;
|
|
2699
|
+
if (configFlag === true) return true;
|
|
2700
|
+
if (configFlag === false) return false;
|
|
2701
|
+
return false;
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Resolve debug flag with env var override support
|
|
2705
|
+
*
|
|
2706
|
+
* Supports:
|
|
2707
|
+
* - `'pretty'`: Colorized, hierarchical output (PrettyConsoleExporter)
|
|
2708
|
+
* - `true` / `'true'` / `'1'`: Raw JSON output (ConsoleSpanExporter)
|
|
2709
|
+
* - `false` / `'false'` / `'0'`: Disabled
|
|
2710
|
+
*/
|
|
2711
|
+
function resolveDebugFlag(configFlag) {
|
|
2712
|
+
const envFlag = process.env.AUTOTEL_DEBUG;
|
|
2713
|
+
if (envFlag === "pretty") return "pretty";
|
|
2714
|
+
if (envFlag === "true" || envFlag === "1") return true;
|
|
2715
|
+
if (envFlag === "false" || envFlag === "0") return false;
|
|
2716
|
+
return configFlag ?? false;
|
|
2717
|
+
}
|
|
2718
|
+
function normalizeOtlpHeaders(headers) {
|
|
2719
|
+
if (!headers) return void 0;
|
|
2720
|
+
if (typeof headers !== "string") return headers;
|
|
2721
|
+
const parsed = {};
|
|
2722
|
+
for (const pair of headers.split(",")) {
|
|
2723
|
+
const [key, ...valueParts] = pair.split("=");
|
|
2724
|
+
if (!key || valueParts.length === 0) continue;
|
|
2725
|
+
parsed[key.trim()] = valueParts.join("=").trim();
|
|
2726
|
+
}
|
|
2727
|
+
return parsed;
|
|
2728
|
+
}
|
|
2729
|
+
function resolveOtlpDestinations(config, fallbackEndpoint) {
|
|
2730
|
+
return (config.destinations === void 0 ? fallbackEndpoint ? [{
|
|
2731
|
+
endpoint: fallbackEndpoint,
|
|
2732
|
+
headers: config.headers,
|
|
2733
|
+
protocol: config.protocol
|
|
2734
|
+
}] : [] : config.destinations).map((destination) => ({
|
|
2735
|
+
endpoint: destination.endpoint,
|
|
2736
|
+
protocol: resolveProtocol(destination.protocol ?? config.protocol),
|
|
2737
|
+
headers: normalizeOtlpHeaders(destination.headers ?? config.headers),
|
|
2738
|
+
signals: destination.signals ? new Set(destination.signals) : void 0
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2741
|
+
function destinationSupportsSignal(destination, signal) {
|
|
2742
|
+
return destination.signals ? destination.signals.has(signal) : true;
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Initialize autotel - Write Once, Observe Everywhere
|
|
2746
|
+
*
|
|
2747
|
+
* Follows OpenTelemetry standards: opinionated defaults with full flexibility
|
|
2748
|
+
* Idempotent: multiple calls are safe, last one wins
|
|
2749
|
+
*
|
|
2750
|
+
* @example Minimal setup (OTLP default)
|
|
2751
|
+
* ```typescript
|
|
2752
|
+
* init({ service: 'my-app' })
|
|
2753
|
+
* ```
|
|
2754
|
+
*
|
|
2755
|
+
* @example With events (observe in PostHog, Mixpanel, etc.)
|
|
2756
|
+
* ```typescript
|
|
2757
|
+
* import { PostHogSubscriber } from 'autotel-subscribers/posthog';
|
|
2758
|
+
*
|
|
2759
|
+
* init({
|
|
2760
|
+
* service: 'my-app',
|
|
2761
|
+
* subscribers: [new PostHogSubscriber({ apiKey: '...' })]
|
|
2762
|
+
* })
|
|
2763
|
+
* ```
|
|
2764
|
+
*
|
|
2765
|
+
* @example Observe in Jaeger
|
|
2766
|
+
* ```typescript
|
|
2767
|
+
* import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
|
|
2768
|
+
*
|
|
2769
|
+
* init({
|
|
2770
|
+
* service: 'my-app',
|
|
2771
|
+
* spanExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' })
|
|
2772
|
+
* })
|
|
2773
|
+
* ```
|
|
2774
|
+
*
|
|
2775
|
+
* @example Observe in Zipkin
|
|
2776
|
+
* ```typescript
|
|
2777
|
+
* import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'
|
|
2778
|
+
*
|
|
2779
|
+
* init({
|
|
2780
|
+
* service: 'my-app',
|
|
2781
|
+
* spanExporter: new ZipkinExporter({ url: 'http://localhost:9411/api/v2/spans' })
|
|
2782
|
+
* })
|
|
2783
|
+
* ```
|
|
2784
|
+
*
|
|
2785
|
+
* @example Observe in Datadog
|
|
2786
|
+
* ```typescript
|
|
2787
|
+
* import { DatadogSpanProcessor } from '@opentelemetry/exporter-datadog'
|
|
2788
|
+
*
|
|
2789
|
+
* init({
|
|
2790
|
+
* service: 'my-app',
|
|
2791
|
+
* spanProcessor: new DatadogSpanProcessor({ ... })
|
|
2792
|
+
* })
|
|
2793
|
+
* ```
|
|
2794
|
+
*
|
|
2795
|
+
* @example Console output (dev)
|
|
2796
|
+
* ```typescript
|
|
2797
|
+
* import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
2798
|
+
*
|
|
2799
|
+
* init({
|
|
2800
|
+
* service: 'my-app',
|
|
2801
|
+
* spanProcessor: new SimpleSpanProcessor(new ConsoleSpanExporter())
|
|
2802
|
+
* })
|
|
2803
|
+
* ```
|
|
2804
|
+
*/
|
|
2805
|
+
function init(cfg) {
|
|
2806
|
+
if (locked) return;
|
|
2807
|
+
const envConfig = resolveConfigFromEnv();
|
|
2808
|
+
const yamlConfig = require_yaml_config.loadYamlConfig() ?? {};
|
|
2809
|
+
const mergedConfig = {
|
|
2810
|
+
...envConfig,
|
|
2811
|
+
...yamlConfig,
|
|
2812
|
+
...cfg,
|
|
2813
|
+
resourceAttributes: {
|
|
2814
|
+
...envConfig.resourceAttributes,
|
|
2815
|
+
...yamlConfig.resourceAttributes,
|
|
2816
|
+
...detectEnvironmentAttributes(),
|
|
2817
|
+
...cfg.resourceAttributes
|
|
2818
|
+
},
|
|
2819
|
+
headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers
|
|
2820
|
+
};
|
|
2821
|
+
const resolvedRedactor = resolveAttributeRedactor(mergedConfig.attributeRedactor, mergedConfig.environment || process.env.NODE_ENV || "development");
|
|
2822
|
+
if (resolvedRedactor === void 0) mergedConfig.attributeRedactor = void 0;
|
|
2823
|
+
else {
|
|
2824
|
+
const normalizedRedactor = require_attribute_redacting_processor.normalizeAttributeRedactorConfig(resolvedRedactor);
|
|
2825
|
+
if (!normalizedRedactor) throw new Error("Invalid attributeRedactor config");
|
|
2826
|
+
mergedConfig.attributeRedactor = normalizedRedactor;
|
|
2827
|
+
}
|
|
2828
|
+
const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools);
|
|
2829
|
+
if (devtoolsConfig.enabled && mergedConfig.logs === void 0) mergedConfig.logs = true;
|
|
2830
|
+
const silent = mergedConfig.silent ?? false;
|
|
2831
|
+
const minLevel = mergedConfig.minLevel ?? "info";
|
|
2832
|
+
logger = wrapLogger(mergedConfig.logger || silentLogger, silent, minLevel);
|
|
2833
|
+
if (initialized) logger.warn({}, "[autotel] init() called again - last config wins. This may cause unexpected behavior.");
|
|
2834
|
+
config = mergedConfig;
|
|
2835
|
+
validationConfig = mergedConfig.validation || null;
|
|
2836
|
+
eventsConfig = mergedConfig.events || null;
|
|
2837
|
+
let endpoint = mergedConfig.endpoint ?? devtoolsConfig.endpoint;
|
|
2838
|
+
const version = mergedConfig.version || detectVersion();
|
|
2839
|
+
const environment = mergedConfig.environment || process.env.NODE_ENV || "development";
|
|
2840
|
+
const metricsEnabled = resolveMetricsFlag(mergedConfig.metrics);
|
|
2841
|
+
const logsEnabled = resolveLogsFlag(mergedConfig.logs);
|
|
2842
|
+
if (devtoolsConfig.enabled && devtoolsConfig.embedded) {
|
|
2843
|
+
const devtoolsModule = _optionalRequire("autotel-devtools");
|
|
2844
|
+
if (devtoolsModule?.createDevtools) {
|
|
2845
|
+
const devtoolsInstance = devtoolsModule.createDevtools({
|
|
2846
|
+
port: devtoolsConfig.port,
|
|
2847
|
+
host: devtoolsConfig.host,
|
|
2848
|
+
verbose: devtoolsConfig.verbose
|
|
2849
|
+
});
|
|
2850
|
+
_devtoolsClose = devtoolsInstance.close;
|
|
2851
|
+
endpoint = `http://${devtoolsConfig.host}:${devtoolsInstance.port}`;
|
|
2852
|
+
logger.info({}, `[autotel] autotel-devtools embedded server started at ${endpoint}`);
|
|
2853
|
+
} else logger.warn({}, "[autotel] devtools.embedded requested but autotel-devtools is not installed. Falling back to endpoint-only mode.");
|
|
2854
|
+
}
|
|
2855
|
+
const hostname = detectHostname();
|
|
2856
|
+
let resource = (0, _opentelemetry_resources.resourceFromAttributes)({
|
|
2857
|
+
[_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: mergedConfig.service,
|
|
2858
|
+
[_opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: version,
|
|
2859
|
+
"deployment.environment": environment,
|
|
2860
|
+
"deployment.environment.name": environment
|
|
2861
|
+
});
|
|
2862
|
+
if (hostname) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)({
|
|
2863
|
+
"host.name": hostname,
|
|
2864
|
+
"datadog.host.name": hostname
|
|
2865
|
+
}));
|
|
2866
|
+
if (mergedConfig.resource) resource = resource.merge(mergedConfig.resource);
|
|
2867
|
+
if (mergedConfig.resourceAttributes) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)(mergedConfig.resourceAttributes));
|
|
2868
|
+
const otlpDestinations = resolveOtlpDestinations(mergedConfig, endpoint);
|
|
2869
|
+
const configuredSpanProcessors = mergedConfig.spanProcessors && mergedConfig.spanProcessors.length > 0 ? mergedConfig.spanProcessors : mergedConfig.spanProcessor ? [mergedConfig.spanProcessor] : void 0;
|
|
2870
|
+
const configuredSpanExporters = mergedConfig.spanExporters && mergedConfig.spanExporters.length > 0 ? mergedConfig.spanExporters : mergedConfig.spanExporter ? [mergedConfig.spanExporter] : void 0;
|
|
2871
|
+
const configuredMetricReaders = mergedConfig.metricReaders && mergedConfig.metricReaders.length > 0 ? mergedConfig.metricReaders : mergedConfig.metricReader ? [mergedConfig.metricReader] : void 0;
|
|
2872
|
+
const configuredLogRecordProcessors = mergedConfig.logRecordProcessors && mergedConfig.logRecordProcessors.length > 0 ? mergedConfig.logRecordProcessors : mergedConfig.logRecordProcessor ? [mergedConfig.logRecordProcessor] : void 0;
|
|
2873
|
+
let spanProcessors = [];
|
|
2874
|
+
if (configuredSpanProcessors && configuredSpanProcessors.length > 0) spanProcessors.push(...configuredSpanProcessors);
|
|
2875
|
+
else if (configuredSpanExporters && configuredSpanExporters.length > 0) for (const exporter of configuredSpanExporters) spanProcessors.push(new require_tail_sampling_processor.TailSamplingSpanProcessor(new _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter)));
|
|
2876
|
+
else for (const destination of otlpDestinations) {
|
|
2877
|
+
if (!destinationSupportsSignal(destination, "traces")) continue;
|
|
2878
|
+
const traceExporter = createTraceExporter(destination.protocol, {
|
|
2879
|
+
url: formatEndpointUrl(destination.endpoint, "traces", destination.protocol),
|
|
2880
|
+
headers: destination.headers
|
|
2881
|
+
});
|
|
2882
|
+
spanProcessors.push(new require_tail_sampling_processor.TailSamplingSpanProcessor(new _opentelemetry_sdk_trace_base.BatchSpanProcessor(traceExporter)));
|
|
2883
|
+
}
|
|
2884
|
+
if (mergedConfig.baggage) {
|
|
2885
|
+
const prefix = typeof mergedConfig.baggage === "string" ? mergedConfig.baggage ? `${mergedConfig.baggage}.` : "" : "baggage.";
|
|
2886
|
+
spanProcessors.push(new BaggageSpanProcessor({ prefix }));
|
|
2887
|
+
}
|
|
2888
|
+
const debugMode = resolveDebugFlag(mergedConfig.debug);
|
|
2889
|
+
if (debugMode === "pretty") spanProcessors.push(new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new require_pretty_console_exporter.PrettyConsoleExporter()));
|
|
2890
|
+
else if (debugMode === true) spanProcessors.push(new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(new _opentelemetry_sdk_trace_base.ConsoleSpanExporter()));
|
|
2891
|
+
if (mergedConfig.canonicalLogLines?.enabled) {
|
|
2892
|
+
const canonicalOptions = {
|
|
2893
|
+
logger: mergedConfig.canonicalLogLines.logger || mergedConfig.logger,
|
|
2894
|
+
rootSpansOnly: mergedConfig.canonicalLogLines.rootSpansOnly,
|
|
2895
|
+
minLevel: mergedConfig.canonicalLogLines.minLevel,
|
|
2896
|
+
messageFormat: mergedConfig.canonicalLogLines.messageFormat,
|
|
2897
|
+
includeResourceAttributes: mergedConfig.canonicalLogLines.includeResourceAttributes,
|
|
2898
|
+
shouldEmit: mergedConfig.canonicalLogLines.shouldEmit,
|
|
2899
|
+
keep: mergedConfig.canonicalLogLines.keep,
|
|
2900
|
+
drain: mergedConfig.canonicalLogLines.drain,
|
|
2901
|
+
onDrainError: mergedConfig.canonicalLogLines.onDrainError,
|
|
2902
|
+
pretty: mergedConfig.canonicalLogLines.pretty
|
|
2903
|
+
};
|
|
2904
|
+
spanProcessors.push(new require_canonical_log_line_processor.CanonicalLogLineProcessor(canonicalOptions));
|
|
2905
|
+
}
|
|
2906
|
+
if (mergedConfig.attributeRedactor && spanProcessors.length > 0) {
|
|
2907
|
+
const redactor = mergedConfig.attributeRedactor;
|
|
2908
|
+
spanProcessors = spanProcessors.map((processor) => new require_attribute_redacting_processor.AttributeRedactingProcessor(processor, { redactor }));
|
|
2909
|
+
}
|
|
2910
|
+
if (mergedConfig.attributeRedactor) _stringRedactor = createStringRedactor(mergedConfig.attributeRedactor);
|
|
2911
|
+
if (_stringRedactor && mergedConfig.subscribers) {
|
|
2912
|
+
for (const subscriber of mergedConfig.subscribers) if (acceptsStringRedactor(subscriber)) subscriber.setStringRedactor(_stringRedactor);
|
|
2913
|
+
}
|
|
2914
|
+
if (mergedConfig.spanNameNormalizer && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_span_name_normalizer.SpanNameNormalizingProcessor(processor, { normalizer: mergedConfig.spanNameNormalizer }));
|
|
2915
|
+
if (mergedConfig.spanFilter && spanProcessors.length > 0) spanProcessors = spanProcessors.map((processor) => new require_filtering_span_processor.FilteringSpanProcessor(processor, { filter: mergedConfig.spanFilter }));
|
|
2916
|
+
const metricReaders = [];
|
|
2917
|
+
if (configuredMetricReaders && configuredMetricReaders.length > 0) metricReaders.push(...configuredMetricReaders);
|
|
2918
|
+
else if (metricsEnabled) for (const destination of otlpDestinations) {
|
|
2919
|
+
if (!destinationSupportsSignal(destination, "metrics")) continue;
|
|
2920
|
+
const metricExporter = createMetricExporter(destination.protocol, {
|
|
2921
|
+
url: formatEndpointUrl(destination.endpoint, "metrics", destination.protocol),
|
|
2922
|
+
headers: destination.headers
|
|
2923
|
+
});
|
|
2924
|
+
metricReaders.push(new _opentelemetry_sdk_metrics.PeriodicExportingMetricReader({ exporter: metricExporter }));
|
|
2925
|
+
}
|
|
2926
|
+
let logRecordProcessors;
|
|
2927
|
+
if (configuredLogRecordProcessors && configuredLogRecordProcessors.length > 0) logRecordProcessors = [...configuredLogRecordProcessors];
|
|
2928
|
+
if (logsEnabled) {
|
|
2929
|
+
for (const destination of otlpDestinations) {
|
|
2930
|
+
if (!destinationSupportsSignal(destination, "logs")) continue;
|
|
2931
|
+
let processor = new _opentelemetry_sdk_logs.BatchLogRecordProcessor({ exporter: createLogExporter(destination.protocol, {
|
|
2932
|
+
url: formatEndpointUrl(destination.endpoint, "logs", destination.protocol),
|
|
2933
|
+
headers: destination.headers
|
|
2934
|
+
}) });
|
|
2935
|
+
if (_stringRedactor) processor = new RedactingLogRecordProcessor(processor, _stringRedactor);
|
|
2936
|
+
if (!logRecordProcessors) logRecordProcessors = [];
|
|
2937
|
+
logRecordProcessors.push(processor);
|
|
2938
|
+
}
|
|
2939
|
+
if (otlpDestinations.some((destination) => destinationSupportsSignal(destination, "logs"))) logger.info({}, "[autotel] OTLP log exporter configured");
|
|
2940
|
+
}
|
|
2941
|
+
const posthogProcessors = buildPostHogLogProcessors(mergedConfig.posthog, _stringRedactor);
|
|
2942
|
+
if (posthogProcessors.length > 0) {
|
|
2943
|
+
if (!logRecordProcessors) logRecordProcessors = [];
|
|
2944
|
+
logRecordProcessors.push(...posthogProcessors);
|
|
2945
|
+
logger.info({}, "[autotel] PostHog OTLP logs configured");
|
|
2946
|
+
}
|
|
2947
|
+
let finalInstrumentations = mergedConfig.instrumentations ? [...mergedConfig.instrumentations] : [];
|
|
2948
|
+
if (mergedConfig.autoInstrumentations !== void 0 && mergedConfig.autoInstrumentations !== false) {
|
|
2949
|
+
if (isESMMode()) logger.info({}, "[autotel] ESM mode detected. For auto-instrumentation to work:\n 1. Install @opentelemetry/auto-instrumentations-node as a direct dependency\n 2. Import autotel/register FIRST in your instrumentation file\n 3. Use getNodeAutoInstrumentations() directly instead of autoInstrumentations\n See: https://github.com/jagreehal/autotel#esm-setup");
|
|
2950
|
+
try {
|
|
2951
|
+
const manualInstrumentationNames = getInstrumentationNames(mergedConfig.instrumentations ?? []);
|
|
2952
|
+
if (manualInstrumentationNames.size > 0) {
|
|
2953
|
+
const manualNames = [...manualInstrumentationNames].join(", ");
|
|
2954
|
+
logger.info({}, `[autotel] Detected manual instrumentations (${manualNames}). These will take precedence over auto-instrumentations. Tip: Set autoInstrumentations:false if you want full manual control, or remove manual configs to use auto-instrumentations.`);
|
|
2955
|
+
}
|
|
2956
|
+
const autoInstrumentations = getAutoInstrumentations(mergedConfig.autoInstrumentations, manualInstrumentationNames);
|
|
2957
|
+
if (autoInstrumentations && autoInstrumentations.length > 0) finalInstrumentations = [...finalInstrumentations, ...autoInstrumentations];
|
|
2958
|
+
} catch (error) {
|
|
2959
|
+
logger.warn({}, `[autotel] Failed to configure auto-instrumentations: ${error instanceof Error ? error.message : String(error)}`);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
const autotelSampler = mergedConfig.sampler ?? (mergedConfig.sampling ? require_sampling.resolveSamplingPreset(mergedConfig.sampling) : void 0);
|
|
2963
|
+
if (autotelSampler) mergedConfig.sampler = autotelSampler;
|
|
2964
|
+
const sampler = autotelSampler ? toOtelSampler(autotelSampler) : envConfig.otelSampler ?? toOtelSampler(require_sampling.samplingPresets.production());
|
|
2965
|
+
const sdkOptions = {
|
|
2966
|
+
resource,
|
|
2967
|
+
serviceName: mergedConfig.service,
|
|
2968
|
+
sampler,
|
|
2969
|
+
instrumentations: finalInstrumentations
|
|
2970
|
+
};
|
|
2971
|
+
if (spanProcessors.length > 0) sdkOptions.spanProcessors = spanProcessors;
|
|
2972
|
+
if (metricReaders.length > 0) sdkOptions.metricReaders = metricReaders;
|
|
2973
|
+
if (logRecordProcessors && logRecordProcessors.length > 0) sdkOptions.logRecordProcessors = logRecordProcessors;
|
|
2974
|
+
sdk = mergedConfig.sdkFactory ? mergedConfig.sdkFactory(sdkOptions) : new _opentelemetry_sdk_node.NodeSDK(sdkOptions);
|
|
2975
|
+
if (!sdk) throw new Error("[autotel] sdkFactory must return a NodeSDK instance");
|
|
2976
|
+
sdk.start();
|
|
2977
|
+
if (mergedConfig.openllmetry?.enabled) {
|
|
2978
|
+
const traceloop = _optionalRequire("@traceloop/node-server-sdk");
|
|
2979
|
+
if (traceloop) {
|
|
2980
|
+
const initOptions = { ...mergedConfig.openllmetry.options };
|
|
2981
|
+
try {
|
|
2982
|
+
initOptions.tracerProvider = sdk.getTracerProvider();
|
|
2983
|
+
} catch {}
|
|
2984
|
+
if (configuredSpanExporters?.[0]) initOptions.exporter = configuredSpanExporters[0];
|
|
2985
|
+
if (typeof traceloop.initialize === "function") {
|
|
2986
|
+
traceloop.initialize(initOptions);
|
|
2987
|
+
logger.info({}, "[autotel] OpenLLMetry initialized successfully");
|
|
2988
|
+
} else logger.warn({}, "[autotel] OpenLLMetry initialize function not found. Check @traceloop/node-server-sdk version.");
|
|
2989
|
+
} else logger.warn({}, "[autotel] OpenLLMetry enabled but @traceloop/node-server-sdk is not installed. Install it as a peer dependency to use OpenLLMetry integration.");
|
|
2990
|
+
}
|
|
2991
|
+
initialized = true;
|
|
2992
|
+
const processHandlers = mergedConfig.processHandlers;
|
|
2993
|
+
if (processHandlers) installProcessHandlers(processHandlers === true ? {} : processHandlers, shutdown);
|
|
2994
|
+
else uninstallProcessHandlers();
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Extract instrumentation class names from instrumentation instances
|
|
2998
|
+
* Used to detect duplicates between manual and auto instrumentations
|
|
2999
|
+
*/
|
|
3000
|
+
function getInstrumentationNames(instrumentations) {
|
|
3001
|
+
const names = /* @__PURE__ */ new Set();
|
|
3002
|
+
if (!instrumentations) return names;
|
|
3003
|
+
for (const instrumentation of instrumentations) if (instrumentation && typeof instrumentation === "object") names.add(instrumentation.constructor.name);
|
|
3004
|
+
return names;
|
|
3005
|
+
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Map common instrumentation class names to their package names
|
|
3008
|
+
* Used to disable auto-instrumentations when user provides manual configs
|
|
3009
|
+
*/
|
|
3010
|
+
const INSTRUMENTATION_CLASS_TO_PACKAGE = {
|
|
3011
|
+
HttpInstrumentation: "@opentelemetry/instrumentation-http",
|
|
3012
|
+
HttpsInstrumentation: "@opentelemetry/instrumentation-http",
|
|
3013
|
+
ExpressInstrumentation: "@opentelemetry/instrumentation-express",
|
|
3014
|
+
FastifyInstrumentation: "@opentelemetry/instrumentation-fastify",
|
|
3015
|
+
MongoDBInstrumentation: "@opentelemetry/instrumentation-mongodb",
|
|
3016
|
+
MongooseInstrumentation: "@opentelemetry/instrumentation-mongoose",
|
|
3017
|
+
PrismaInstrumentation: "@opentelemetry/instrumentation-prisma",
|
|
3018
|
+
PinoInstrumentation: "@opentelemetry/instrumentation-pino",
|
|
3019
|
+
WinstonInstrumentation: "@opentelemetry/instrumentation-winston",
|
|
3020
|
+
RedisInstrumentation: "@opentelemetry/instrumentation-redis",
|
|
3021
|
+
GraphQLInstrumentation: "@opentelemetry/instrumentation-graphql",
|
|
3022
|
+
GrpcInstrumentation: "@opentelemetry/instrumentation-grpc",
|
|
3023
|
+
IORedisInstrumentation: "@opentelemetry/instrumentation-ioredis",
|
|
3024
|
+
KnexInstrumentation: "@opentelemetry/instrumentation-knex",
|
|
3025
|
+
NestJsInstrumentation: "@opentelemetry/instrumentation-nestjs-core",
|
|
3026
|
+
PgInstrumentation: "@opentelemetry/instrumentation-pg",
|
|
3027
|
+
MySQLInstrumentation: "@opentelemetry/instrumentation-mysql",
|
|
3028
|
+
MySQL2Instrumentation: "@opentelemetry/instrumentation-mysql2"
|
|
3029
|
+
};
|
|
3030
|
+
/**
|
|
3031
|
+
* Detect if we're running in ESM mode
|
|
3032
|
+
*/
|
|
3033
|
+
function isESMMode() {
|
|
3034
|
+
try {
|
|
3035
|
+
const fs = require_node_require.requireModule("node:fs");
|
|
3036
|
+
try {
|
|
3037
|
+
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).type === "module";
|
|
3038
|
+
} catch {
|
|
3039
|
+
return false;
|
|
3040
|
+
}
|
|
3041
|
+
} catch {
|
|
3042
|
+
return false;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Lazy-load auto-instrumentations (optional peer dependency)
|
|
3047
|
+
* Only loads when integrations config is truthy, avoiding ~40+ package imports at startup.
|
|
3048
|
+
*/
|
|
3049
|
+
function loadNodeAutoInstrumentations() {
|
|
3050
|
+
try {
|
|
3051
|
+
return require_node_require.requireModule("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
|
|
3052
|
+
} catch {
|
|
3053
|
+
const isESM = isESMMode();
|
|
3054
|
+
const baseMessage = "@opentelemetry/auto-instrumentations-node not found.";
|
|
3055
|
+
if (isESM) throw new Error(`${baseMessage}\n\nESM Setup Required:
|
|
3056
|
+
1. Install as a direct dependency: pnpm add @opentelemetry/auto-instrumentations-node
|
|
3057
|
+
2. Create instrumentation.mjs with:
|
|
3058
|
+
import 'autotel/register'; // MUST be first!
|
|
3059
|
+
import { init } from 'autotel';
|
|
3060
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
3061
|
+
init({ service: "my-app", instrumentations: getNodeAutoInstrumentations() });
|
|
3062
|
+
3. Run with: tsx --import ./instrumentation.mjs src/index.ts
|
|
3063
|
+
|
|
3064
|
+
See: https://github.com/jagreehal/autotel#esm-setup`);
|
|
3065
|
+
throw new Error(`${baseMessage} Install it: pnpm add @opentelemetry/auto-instrumentations-node`);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Injectable loader for testing. Set to override the default loader.
|
|
3070
|
+
* @internal
|
|
3071
|
+
*/
|
|
3072
|
+
let _autoInstrumentationsLoader = null;
|
|
3073
|
+
/**
|
|
3074
|
+
* Get auto-instrumentations based on simple integration names
|
|
3075
|
+
* Excludes instrumentations that are manually provided to avoid conflicts
|
|
3076
|
+
*/
|
|
3077
|
+
function getAutoInstrumentations(integrations, manualInstrumentationNames = /* @__PURE__ */ new Set()) {
|
|
3078
|
+
if (integrations === false) return [];
|
|
3079
|
+
const getNodeAutoInstrumentations = _autoInstrumentationsLoader ? _autoInstrumentationsLoader() : loadNodeAutoInstrumentations();
|
|
3080
|
+
const exclusionConfig = {};
|
|
3081
|
+
for (const className of manualInstrumentationNames) {
|
|
3082
|
+
const packageName = INSTRUMENTATION_CLASS_TO_PACKAGE[className];
|
|
3083
|
+
if (packageName) exclusionConfig[packageName] = { enabled: false };
|
|
3084
|
+
}
|
|
3085
|
+
if (integrations === true) {
|
|
3086
|
+
if (Object.keys(exclusionConfig).length > 0) return getNodeAutoInstrumentations(exclusionConfig);
|
|
3087
|
+
return getNodeAutoInstrumentations();
|
|
3088
|
+
}
|
|
3089
|
+
if (Array.isArray(integrations)) {
|
|
3090
|
+
const config = { ...exclusionConfig };
|
|
3091
|
+
for (const name of integrations) {
|
|
3092
|
+
const packageName = `@opentelemetry/instrumentation-${name}`;
|
|
3093
|
+
if (!exclusionConfig[packageName]) config[packageName] = { enabled: true };
|
|
3094
|
+
}
|
|
3095
|
+
return getNodeAutoInstrumentations(config);
|
|
3096
|
+
}
|
|
3097
|
+
const config = {
|
|
3098
|
+
...exclusionConfig,
|
|
3099
|
+
...integrations
|
|
3100
|
+
};
|
|
3101
|
+
for (const packageName of Object.keys(exclusionConfig)) if (Object.keys(integrations).find((key) => packageName.includes(key))) config[packageName] = { enabled: false };
|
|
3102
|
+
return getNodeAutoInstrumentations(config);
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Check if autotel has been initialized
|
|
3106
|
+
*/
|
|
3107
|
+
function isInitialized() {
|
|
3108
|
+
return initialized;
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Get current config (internal use)
|
|
3112
|
+
*/
|
|
3113
|
+
function getConfig() {
|
|
3114
|
+
return config;
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* Get current logger (internal use)
|
|
3118
|
+
*/
|
|
3119
|
+
function getLogger() {
|
|
3120
|
+
return logger;
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Get validation config (internal use)
|
|
3124
|
+
*/
|
|
3125
|
+
function getValidationConfig() {
|
|
3126
|
+
return validationConfig;
|
|
3127
|
+
}
|
|
3128
|
+
/**
|
|
3129
|
+
* Get events config (internal use)
|
|
3130
|
+
*/
|
|
3131
|
+
function getEventsConfig() {
|
|
3132
|
+
return eventsConfig;
|
|
3133
|
+
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Warn once if not initialized (same behavior in all environments)
|
|
3136
|
+
*/
|
|
3137
|
+
function warnIfNotInitialized(context) {
|
|
3138
|
+
if (!initialized && !warnedOnce) {
|
|
3139
|
+
logger.warn({}, `[autotel] ${context} used before init() called. Call init({ service: "..." }) first. See: https://docs.autotel.dev/quickstart`);
|
|
3140
|
+
warnedOnce = true;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* Auto-detect version from package.json
|
|
3145
|
+
*/
|
|
3146
|
+
function detectVersion() {
|
|
3147
|
+
try {
|
|
3148
|
+
const fs = require_node_require.requireModule("node:fs");
|
|
3149
|
+
return JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")).version || "1.0.0";
|
|
3150
|
+
} catch {
|
|
3151
|
+
return "1.0.0";
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Detect hostname for resource attributes.
|
|
3156
|
+
* Supports Datadog conventions (DD_HOSTNAME) and falls back to system hostname.
|
|
3157
|
+
*
|
|
3158
|
+
* Priority order:
|
|
3159
|
+
* 1. DD_HOSTNAME environment variable (Datadog convention)
|
|
3160
|
+
* 2. HOSTNAME environment variable (common Unix convention)
|
|
3161
|
+
* 3. os.hostname() (system hostname)
|
|
3162
|
+
*
|
|
3163
|
+
* @returns hostname string or undefined if detection fails
|
|
3164
|
+
*/
|
|
3165
|
+
function detectHostname() {
|
|
3166
|
+
if (process.env.DD_HOSTNAME) return process.env.DD_HOSTNAME;
|
|
3167
|
+
if (process.env.HOSTNAME) return process.env.HOSTNAME;
|
|
3168
|
+
try {
|
|
3169
|
+
return require_node_require.requireModule("node:os").hostname();
|
|
3170
|
+
} catch {
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* @internal Close embedded devtools if running.
|
|
3176
|
+
*/
|
|
3177
|
+
async function _closeEmbeddedDevtools() {
|
|
3178
|
+
if (_devtoolsClose) {
|
|
3179
|
+
await _devtoolsClose();
|
|
3180
|
+
_devtoolsClose = null;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Get SDK instance (for shutdown)
|
|
3185
|
+
*/
|
|
3186
|
+
function getSdk() {
|
|
3187
|
+
return sdk;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
//#endregion
|
|
3191
|
+
Object.defineProperty(exports, 'BaggageSpanProcessor', {
|
|
3192
|
+
enumerable: true,
|
|
3193
|
+
get: function () {
|
|
3194
|
+
return BaggageSpanProcessor;
|
|
3195
|
+
}
|
|
3196
|
+
});
|
|
3197
|
+
Object.defineProperty(exports, 'CORRELATION_ID_BAGGAGE_KEY', {
|
|
3198
|
+
enumerable: true,
|
|
3199
|
+
get: function () {
|
|
3200
|
+
return CORRELATION_ID_BAGGAGE_KEY;
|
|
3201
|
+
}
|
|
3202
|
+
});
|
|
3203
|
+
Object.defineProperty(exports, 'Event', {
|
|
3204
|
+
enumerable: true,
|
|
3205
|
+
get: function () {
|
|
3206
|
+
return Event;
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
Object.defineProperty(exports, 'createStringRedactor', {
|
|
3210
|
+
enumerable: true,
|
|
3211
|
+
get: function () {
|
|
3212
|
+
return createStringRedactor;
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
Object.defineProperty(exports, 'createTraceContext', {
|
|
3216
|
+
enumerable: true,
|
|
3217
|
+
get: function () {
|
|
3218
|
+
return createTraceContext;
|
|
3219
|
+
}
|
|
3220
|
+
});
|
|
3221
|
+
Object.defineProperty(exports, 'defineBaggageSchema', {
|
|
3222
|
+
enumerable: true,
|
|
3223
|
+
get: function () {
|
|
3224
|
+
return defineBaggageSchema;
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
Object.defineProperty(exports, 'flush', {
|
|
3228
|
+
enumerable: true,
|
|
3229
|
+
get: function () {
|
|
3230
|
+
return flush;
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
Object.defineProperty(exports, 'generateCorrelationId', {
|
|
3234
|
+
enumerable: true,
|
|
3235
|
+
get: function () {
|
|
3236
|
+
return generateCorrelationId;
|
|
3237
|
+
}
|
|
3238
|
+
});
|
|
3239
|
+
Object.defineProperty(exports, 'getActiveContextWithBaggage', {
|
|
3240
|
+
enumerable: true,
|
|
3241
|
+
get: function () {
|
|
3242
|
+
return getActiveContextWithBaggage;
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
Object.defineProperty(exports, 'getConfig', {
|
|
3246
|
+
enumerable: true,
|
|
3247
|
+
get: function () {
|
|
3248
|
+
return getConfig;
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
Object.defineProperty(exports, 'getContextStorage', {
|
|
3252
|
+
enumerable: true,
|
|
3253
|
+
get: function () {
|
|
3254
|
+
return getContextStorage;
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
Object.defineProperty(exports, 'getCorrelationId', {
|
|
3258
|
+
enumerable: true,
|
|
3259
|
+
get: function () {
|
|
3260
|
+
return getCorrelationId;
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
Object.defineProperty(exports, 'getCorrelationStorage', {
|
|
3264
|
+
enumerable: true,
|
|
3265
|
+
get: function () {
|
|
3266
|
+
return getCorrelationStorage;
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
Object.defineProperty(exports, 'getEventQueue', {
|
|
3270
|
+
enumerable: true,
|
|
3271
|
+
get: function () {
|
|
3272
|
+
return getEventQueue;
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
Object.defineProperty(exports, 'getEvents', {
|
|
3276
|
+
enumerable: true,
|
|
3277
|
+
get: function () {
|
|
3278
|
+
return getEvents;
|
|
3279
|
+
}
|
|
3280
|
+
});
|
|
3281
|
+
Object.defineProperty(exports, 'getLogger', {
|
|
3282
|
+
enumerable: true,
|
|
3283
|
+
get: function () {
|
|
3284
|
+
return getLogger;
|
|
3285
|
+
}
|
|
3286
|
+
});
|
|
3287
|
+
Object.defineProperty(exports, 'getOperationContext', {
|
|
3288
|
+
enumerable: true,
|
|
3289
|
+
get: function () {
|
|
3290
|
+
return getOperationContext;
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
Object.defineProperty(exports, 'getOrCreateCorrelationId', {
|
|
3294
|
+
enumerable: true,
|
|
3295
|
+
get: function () {
|
|
3296
|
+
return getOrCreateCorrelationId;
|
|
3297
|
+
}
|
|
3298
|
+
});
|
|
3299
|
+
Object.defineProperty(exports, 'getSdk', {
|
|
3300
|
+
enumerable: true,
|
|
3301
|
+
get: function () {
|
|
3302
|
+
return getSdk;
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
Object.defineProperty(exports, 'init', {
|
|
3306
|
+
enumerable: true,
|
|
3307
|
+
get: function () {
|
|
3308
|
+
return init;
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
3311
|
+
Object.defineProperty(exports, 'isInitialized', {
|
|
3312
|
+
enumerable: true,
|
|
3313
|
+
get: function () {
|
|
3314
|
+
return isInitialized;
|
|
3315
|
+
}
|
|
3316
|
+
});
|
|
3317
|
+
Object.defineProperty(exports, 'isLoggerLocked', {
|
|
3318
|
+
enumerable: true,
|
|
3319
|
+
get: function () {
|
|
3320
|
+
return isLoggerLocked;
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
Object.defineProperty(exports, 'lockLogger', {
|
|
3324
|
+
enumerable: true,
|
|
3325
|
+
get: function () {
|
|
3326
|
+
return lockLogger;
|
|
3327
|
+
}
|
|
3328
|
+
});
|
|
3329
|
+
Object.defineProperty(exports, 'resetEvents', {
|
|
3330
|
+
enumerable: true,
|
|
3331
|
+
get: function () {
|
|
3332
|
+
return resetEvents;
|
|
3333
|
+
}
|
|
3334
|
+
});
|
|
3335
|
+
Object.defineProperty(exports, 'runInOperationContext', {
|
|
3336
|
+
enumerable: true,
|
|
3337
|
+
get: function () {
|
|
3338
|
+
return runInOperationContext;
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
Object.defineProperty(exports, 'runWithCorrelationId', {
|
|
3342
|
+
enumerable: true,
|
|
3343
|
+
get: function () {
|
|
3344
|
+
return runWithCorrelationId;
|
|
3345
|
+
}
|
|
3346
|
+
});
|
|
3347
|
+
Object.defineProperty(exports, 'setCorrelationId', {
|
|
3348
|
+
enumerable: true,
|
|
3349
|
+
get: function () {
|
|
3350
|
+
return setCorrelationId;
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
Object.defineProperty(exports, 'setCorrelationIdInBaggage', {
|
|
3354
|
+
enumerable: true,
|
|
3355
|
+
get: function () {
|
|
3356
|
+
return setCorrelationIdInBaggage;
|
|
3357
|
+
}
|
|
3358
|
+
});
|
|
3359
|
+
Object.defineProperty(exports, 'shutdown', {
|
|
3360
|
+
enumerable: true,
|
|
3361
|
+
get: function () {
|
|
3362
|
+
return shutdown;
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
Object.defineProperty(exports, 'track', {
|
|
3366
|
+
enumerable: true,
|
|
3367
|
+
get: function () {
|
|
3368
|
+
return track;
|
|
3369
|
+
}
|
|
3370
|
+
});
|
|
3371
|
+
//# sourceMappingURL=init-C3RApHKJ.cjs.map
|