@odigos/aws-cdk 0.0.11 → 0.0.13

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/.jsii CHANGED
@@ -3826,7 +3826,7 @@
3826
3826
  },
3827
3827
  "name": "@odigos/aws-cdk",
3828
3828
  "readme": {
3829
- "markdown": "# Odigos CDK constructs for AWS Lambda\n\nInstrument AWS Lambda functions with the Odigos OpenTelemetry layer, from your own CDK app.\n\n```bash\nnpm install @odigos/aws-cdk # TypeScript / JavaScript\npip install odigos-aws-cdk # Python\n```\n\n```ts\nimport { OdigosLambdaInstrumentation } from '@odigos/aws-cdk';\n\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [apiFn, workerFn],\n});\n```\n\n```python\nfrom odigos_aws_cdk import OdigosLambdaInstrumentation\n\nOdigosLambdaInstrumentation(self, \"Odigos\",\n destination={\"endpoint\": \"otlp.example.com:4317\"},\n functions=[api_fn, worker_fn],\n)\n```\n\nThat attaches the layer for each function's language and architecture, sets the exec wrapper that loads\nthe OpenTelemetry SDK, and hands the in-Lambda collector its whole configuration.\n\n## How the telemetry actually leaves the function\n\nYour function's SDK **does not** export to your destination. It exports to an OpenTelemetry collector\nrunning inside the same Lambda sandbox, on localhost. The collector forwards to your destination *after*\nthe handler has already returned its response.\n\nThat indirection is the whole point. A slow or unreachable destination costs billed duration, not caller\nlatency — and the collector's `decouple` processor is what holds the sandbox open until the export queue\ndrains, so a low-traffic function does not lose its telemetry to the freeze.\n\nConsequently `OTEL_EXPORTER_OTLP_ENDPOINT` is deliberately never set. If you set it yourself you put the\nexport back on the invocation's critical path.\n\n## Endpoint grammar\n\n`endpoint` is OTLP/**gRPC** as bare `host:port` (conventionally 4317). `httpEndpoint` is OTLP/**HTTP** as a\nscheme-qualified URL (conventionally 4318). Exactly one. They select different collector exporters with\ndifferent endpoint grammars, so a scheme in `endpoint` — or its absence in `httpEndpoint` — throws at synth\nrather than producing a collector that starts and silently delivers nothing. A gRPC endpoint on port 4318\n(or an HTTP one on 4317) is the likeliest version of that mistake, and is reported as a synth warning.\n\n## Choosing a layer source\n\n```ts\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [fn],\n layerSource: OdigosLayerSource.IN_ACCOUNT,\n});\n```\n\n| | `ODIGOS_PUBLIC` (default) | `IN_ACCOUNT` |\n|---|---|---|\n| What it synthesizes | a layer ARN | an `AWS::Serverless::Application` (nested stack) per language/arch |\n| Layer owner | the Odigos AWS account | **your** account, created by your stack |\n| Resolution | synth time, from a table baked into this package | deploy time, from the Serverless Application Repository |\n| Environment-agnostic stacks | not supported — needs a concrete region | works |\n| Deploy speed | unchanged | slower on first deploy |\n\nBoth install identical bytes. `IN_ACCOUNT` exists for organisations whose policy forbids attaching a\nLambda layer owned by another AWS account. It needs the deploying principal to be allowed\n`serverlessrepo:CreateCloudFormationTemplate` and `serverlessrepo:GetApplication`.\n\nIf you mirror the layer somewhere yourself, pass `layerVersion` and neither path is used.\n\n## Versioning\n\n**The package version is the layer version.** `@odigos/aws-cdk@0.2.0` resolves to the layer built and\npublished by Odigos release `v0.2.0`, and pins the SAR application to semantic version `0.2.0` — which is\nimmutable, so it means the same bytes forever.\n\nNothing resolves at deploy time in the default path, so upgrading the layer is `npm update` and a\n`cdk diff` that shows the change. An instrumentation layer moving on its own, under a deployment that\nchanged nothing, is not a property you want.\n\n## Handler mode vs. web-server mode\n\n`OdigosInstrumentationMode.HANDLER` (the default) is for a normal Lambda handler. It sets\n`AWS_LAMBDA_EXEC_WRAPPER`.\n\n`OdigosInstrumentationMode.WEB_SERVER` is for a function running a web server behind the\n[AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter). The adapter owns the exec\nwrapper, so this mode does not touch it and loads instrumentation through the language's own loader\nvariable instead.\n\n```ts\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [webFn],\n mode: OdigosInstrumentationMode.WEB_SERVER,\n // NODE_OPTIONS the function already needs — ours is appended, not substituted.\n existingLoaderValue: '--max-old-space-size=2048',\n});\n```\n\nThere is no auto-detection, deliberately. Detecting the adapter means inspecting the function's other\nlayers, which at synth time are frequently unresolved tokens; a detector that was right most of the time\nwould silently break the functions it got wrong. **Applying `HANDLER` to a web-adapter function\noverwrites the adapter's wrapper and the function stops serving.**\n\n`existingLoaderValue` has to be passed explicitly because CDK's `lambda.Function` exposes no way to read\nback an environment variable set elsewhere in your app. Omit it and the loader variable is set to the\nOdigos value alone — for `NODE_OPTIONS` that can mean losing a flag your app needs to boot.\n\nRuby has no verified web-server loader and is rejected at synth in this mode.\n\n## Java handler interfaces\n\nThe Java layer ships four wrapper scripts, one per handler interface, and this is a correctness switch\nrather than a detail level:\n\n```ts\njavaHandlerDistro: OdigosJavaHandlerDistro.OTEL_STREAM_HANDLER\n```\n\n| Distro | For |\n|---|---|\n| `OTEL_HANDLER` (default) | `RequestHandler` |\n| `OTEL_PROXY_HANDLER` | API Gateway proxy events (enriched) |\n| `OTEL_SQS_HANDLER` | SQS events (enriched) |\n| `OTEL_STREAM_HANDLER` | `RequestStreamHandler` |\n\n`OTEL_HANDLER` installs a `RequestHandler` implementation. A `RequestStreamHandler` function has a\ndifferent signature entirely, so wrapping it with the default **breaks the invocation** — it does not\nmerely produce thinner spans.\n\n## Signals\n\n```ts\ndestination: {\n endpoint: 'otlp.example.com:4317',\n metrics: false, // traces and logs stay on\n}\n```\n\nA disabled signal gets no collector pipeline **and** the SDK is told not to produce it. Both halves\nmatter: a signal with no pipeline is refused at the receiver on every batch, so suppressing it at the\nsource is what stops the error loop.\n\nTurning all three off throws at synth — a collector with no pipeline fails to start, which would leave\nthe function instrumented and silent.\n\n## Instrumenting a whole stack\n\n```ts\nAspects.of(stack).add(new OdigosInstrumentationAspect(stack, {\n destination: { endpoint: 'otlp.example.com:4317' },\n}));\n```\n\n`skipUnsupportedRuntimes` defaults to `true` for the aspect: it visits every function in the scope,\nincluding ones whose runtime has no OpenTelemetry Lambda layer (`dotnet*`, `provided.*` for Go and custom\nruntimes). Each skip is a synth warning rather than a silent no-op. The construct defaults it to `false`,\nbecause explicitly asking to instrument a Go function is a mistake worth failing on.\n\n## Credentials in `headers`\n\n`destination.headers` is rendered into `OPENTELEMETRY_COLLECTOR_CONFIG_URI`, which is a plain Lambda\nenvironment variable — readable by anyone with `lambda:GetFunctionConfiguration` and visible in the\nconsole. For a long-lived credential, prefer a destination that authenticates by network position, or\nterminate at a collector you control.\n\n## Making Odigos report the function as instrumented\n\nBy default these constructs do **not** write `ODIGOS_MANAGED`, and the consequence is easy to hit: Odigos\nreports the function as `Absent` — \"awaiting your deployment\" — even though the layer is attached, the\nwrapper is set and the collector is configured. The connector derives ownership from that one variable, and\nchecks for it *before* comparing anything else, so nothing about a correctly instrumented function\nsubstitutes for it.\n\nFor a function Odigos discovers in **manual (IaC) mode** — which is the mode a workload is in unless you\nhave explicitly handed instrumentation to the connector — turn it on:\n\n```ts\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [fn],\n recordOwnershipMarker: true,\n});\n```\n\nThe marker records the exact layer ARN the construct attached, which is also what the connector reads back\nto decide which layer version the function is *supposed* to carry — so a version you pinned is respected\ninstead of being reported as drift. It works in `IN_ACCOUNT` mode too, where the ARN is a deploy-time\nreference your own code has no way to obtain.\n\nIt also records a rollback snapshot (`prevEnv`) naming every variable the construct set. That matters: a\nmarker without one makes an uninstrument remove the **layer** while leaving `AWS_LAMBDA_EXEC_WRAPPER`\npointing into it, which breaks the function at its next cold start. In web-server mode the snapshot holds\nyour `existingLoaderValue`, so a rollback restores your own `NODE_OPTIONS` rather than deleting it.\n\nLeave it `false` for a function you have enabled as an **automatic-mode** connector source: there the\nconnector owns the marker, including a snapshot it wrote itself, and two writers of one ownership record is\nthe combination that genuinely misbehaves.\n\n## Service names\n\nLeft unset, `OTEL_SERVICE_NAME` is the function's physical name when your stack names it explicitly, and\nthe construct id otherwise. The fallback exists because a name CloudFormation generates is a `Ref` to the\nfunction, and a function referencing itself in its own environment is a dependency cycle CloudFormation\nrejects at deploy time.\n\nThe connector compares `OTEL_SERVICE_NAME` against the workload's name in Odigos, which is the real AWS\nfunction name — so for a function whose name is generated, pass `serviceName` explicitly or it is reported\nas drifted. `serviceName` applies to **every** function a construct instruments, so use one construct per\nfunction when you want per-function names.\n\n## Do not also instrument these functions with the Odigos connector\n\nThe Odigos AWS connector instruments **live** functions by mutating them, and reconciles them back to its\nown desired state on every pass. A function instrumented here *and* enabled as an automatic-mode connector\nsource ends up owned by both: each `cdk deploy` reasserts the CDK's environment, each reconcile reasserts\nthe connector's, and every round trip publishes a new function version.\n\nPick one per function. Discovery-only (manual) mode is the combination that works: Odigos watches and\nreports, your stack owns the configuration — that is what `recordOwnershipMarker` is for.\n\n## API\n\n| Export | |\n|---|---|\n| `OdigosLambdaInstrumentation` | the construct; `instrument(fn)`, `instrumentAll(fns)` |\n| `OdigosInstrumentationAspect` | applies it to every function in a scope |\n| `OdigosDestination` | where the collector exports; exactly one of `endpoint` / `httpEndpoint` |\n| `OdigosLayerSource` | `ODIGOS_PUBLIC` \\| `IN_ACCOUNT` |\n| `OdigosInstrumentationMode` | `HANDLER` \\| `WEB_SERVER` |\n| `OdigosJavaHandlerDistro` | the four Java wrappers |\n| `recordOwnershipMarker` | the option that makes Odigos recognise the function as instrumented |\n| `renderCollectorConfig` | the collector config renderer, if you want to inspect what gets set |\n| `LAYER_ARNS`, `ODIGOS_VERSION` | the baked layer table and the release it came from |\n\n## License\n\nApache-2.0.\n"
3829
+ "markdown": "# Odigos CDK constructs for AWS Lambda\n\nInstrument AWS Lambda functions with the Odigos OpenTelemetry layer, from your own CDK app.\n\n```bash\nnpm install @odigos/aws-cdk # TypeScript / JavaScript\npip install odigos-aws-cdk # Python\n```\n\n```ts\nimport { OdigosLambdaInstrumentation } from '@odigos/aws-cdk';\n\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [apiFn, workerFn],\n});\n```\n\n```python\nfrom odigos_aws_cdk import OdigosLambdaInstrumentation\n\nOdigosLambdaInstrumentation(self, \"Odigos\",\n destination={\"endpoint\": \"otlp.example.com:4317\"},\n functions=[api_fn, worker_fn],\n)\n```\n\nThat attaches the layer for each function's language and architecture, sets the exec wrapper that loads\nthe OpenTelemetry SDK, and hands the in-Lambda collector its whole configuration.\n\n## How the telemetry actually leaves the function\n\nYour function's SDK **does not** export to your destination. It exports to an OpenTelemetry collector\nrunning inside the same Lambda sandbox, on localhost. The collector forwards to your destination *after*\nthe handler has already returned its response.\n\nThat indirection is the whole point. A slow or unreachable destination costs billed duration, not caller\nlatency — and the collector's `decouple` processor is what holds the sandbox open until the export queue\ndrains, so a low-traffic function does not lose its telemetry to the freeze.\n\nConsequently `OTEL_EXPORTER_OTLP_ENDPOINT` is deliberately never set. If you set it yourself you put the\nexport back on the invocation's critical path.\n\n## Endpoint grammar\n\n`endpoint` is OTLP/**gRPC** as bare `host:port` (conventionally 4317). `httpEndpoint` is OTLP/**HTTP** as a\nscheme-qualified URL (conventionally 4318). Exactly one. They select different collector exporters with\ndifferent endpoint grammars, so a scheme in `endpoint` — or its absence in `httpEndpoint` — throws at synth\nrather than producing a collector that starts and silently delivers nothing. A gRPC endpoint on port 4318\n(or an HTTP one on 4317) is the likeliest version of that mistake, and is reported as a synth warning.\n\n## Choosing a layer source\n\n```ts\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [fn],\n layerSource: OdigosLayerSource.IN_ACCOUNT,\n});\n```\n\n| | `ODIGOS_PUBLIC` (default) | `IN_ACCOUNT` |\n|---|---|---|\n| What it synthesizes | a layer ARN | an `AWS::Serverless::Application` (nested stack) per language/arch |\n| Layer owner | the Odigos AWS account | **your** account, created by your stack |\n| Resolution | synth time, from a table baked into this package | deploy time, from the Serverless Application Repository |\n| Environment-agnostic stacks | not supported — needs a concrete region | works |\n| Deploy speed | unchanged | slower on first deploy |\n\nBoth install identical bytes. `IN_ACCOUNT` exists for organisations whose policy forbids attaching a\nLambda layer owned by another AWS account. It needs the deploying principal to be allowed\n`serverlessrepo:CreateCloudFormationTemplate` and `serverlessrepo:GetApplication`.\n\nIf you mirror the layer somewhere yourself, pass `layerVersion` and neither path is used.\n\n## Versioning\n\n**The package version is the layer version.** `@odigos/aws-cdk@0.2.0` resolves to the layer built and\npublished by Odigos release `v0.2.0`, and pins the SAR application to semantic version `0.2.0` — which is\nimmutable, so it means the same bytes forever.\n\nNothing resolves at deploy time in the default path, so upgrading the layer is `npm update` and a\n`cdk diff` that shows the change. An instrumentation layer moving on its own, under a deployment that\nchanged nothing, is not a property you want.\n\n## Handler mode vs. web-server mode\n\n`OdigosInstrumentationMode.HANDLER` (the default) is for a normal Lambda handler. It sets\n`AWS_LAMBDA_EXEC_WRAPPER`.\n\n`OdigosInstrumentationMode.WEB_SERVER` is for a function running a web server behind the\n[AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter). The adapter owns the exec\nwrapper, so this mode does not touch it and loads instrumentation through the language's own loader\nvariable instead.\n\n```ts\nnew OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [webFn],\n mode: OdigosInstrumentationMode.WEB_SERVER,\n // NODE_OPTIONS the function already needs — ours is appended, not substituted.\n existingLoaderValue: '--max-old-space-size=2048',\n});\n```\n\nThere is no auto-detection, deliberately. Detecting the adapter means inspecting the function's other\nlayers, which at synth time are frequently unresolved tokens; a detector that was right most of the time\nwould silently break the functions it got wrong. **Applying `HANDLER` to a web-adapter function\noverwrites the adapter's wrapper and the function stops serving.**\n\n`existingLoaderValue` has to be passed explicitly because CDK's `lambda.Function` exposes no way to read\nback an environment variable set elsewhere in your app. Omit it and the loader variable is set to the\nOdigos value alone — for `NODE_OPTIONS` that can mean losing a flag your app needs to boot.\n\nRuby has no verified web-server loader and is rejected at synth in this mode.\n\n## Java handler interfaces\n\nThe Java layer ships four wrapper scripts, one per handler interface, and this is a correctness switch\nrather than a detail level:\n\n```ts\njavaHandlerDistro: OdigosJavaHandlerDistro.OTEL_STREAM_HANDLER\n```\n\n| Distro | For |\n|---|---|\n| `OTEL_HANDLER` (default) | `RequestHandler` |\n| `OTEL_PROXY_HANDLER` | API Gateway proxy events (enriched) |\n| `OTEL_SQS_HANDLER` | SQS events (enriched) |\n| `OTEL_STREAM_HANDLER` | `RequestStreamHandler` |\n\n`OTEL_HANDLER` installs a `RequestHandler` implementation. A `RequestStreamHandler` function has a\ndifferent signature entirely, so wrapping it with the default **breaks the invocation** — it does not\nmerely produce thinner spans.\n\n## Signals\n\n**Traces only, by default.** Metrics and logs are opt-in:\n\n```ts\ndestination: {\n endpoint: 'otlp.example.com:4317',\n metrics: true, // off unless you ask\n logs: true, // off unless you ask\n}\n```\n\nThey are opt-in because the cost of a signal the destination does not implement falls on the function:\nevery batch is refused and the in-Lambda collector retries it on an exponential backoff, inside billed\nduration. Traces are what nearly every destination accepts; the other two are a decision about your\ndestination, so they are made deliberately.\n\nA disabled signal gets no collector pipeline **and** the SDK is told not to produce it\n(`OTEL_METRICS_EXPORTER=none`). Both halves matter: a signal with no pipeline is refused at the receiver on\nevery batch, so suppressing it at the source is what stops the error loop. An enabled signal's variable\nstays unset, because `otlp` is already the SDK default.\n\n`traces: false` with neither of the others on throws at synth — a collector with no pipeline fails to\nstart, which would leave the function instrumented and silent.\n\n**If Odigos also manages this function in manual mode, the signals have to match.** The connector renders\nthe collector configuration from the destination configured in Odigos and compares the whole string, so a\ntraces-only function judged against an all-three destination is reported as drifted on the\ncollector-export component. Enable the same set on both sides.\n\n## Instrumenting a whole stack\n\n```ts\nAspects.of(stack).add(new OdigosInstrumentationAspect(stack, {\n destination: { endpoint: 'otlp.example.com:4317' },\n}));\n```\n\n`skipUnsupportedRuntimes` defaults to `true` for the aspect: it visits every function in the scope,\nincluding ones whose runtime has no OpenTelemetry Lambda layer (`dotnet*`, `provided.*` for Go and custom\nruntimes). Each skip is a synth warning rather than a silent no-op. The construct defaults it to `false`,\nbecause explicitly asking to instrument a Go function is a mistake worth failing on.\n\n## Credentials in `headers`\n\n`destination.headers` is rendered into `OPENTELEMETRY_COLLECTOR_CONFIG_URI`, which is a plain Lambda\nenvironment variable — readable by anyone with `lambda:GetFunctionConfiguration` and visible in the\nconsole. For a long-lived credential, prefer a destination that authenticates by network position, or\nterminate at a collector you control.\n\n## How Odigos recognises the function\n\nThese constructs write `ODIGOS_IAC` on every function they instrument, recording the layer ARN they attached\nand the package version that attached it. That variable is what Odigos reads to attribute the function to\nitself; without it a function reports as `Absent` — \"awaiting your deployment\" — however completely the layer,\nwrapper and collector configuration are applied. There is nothing to switch on.\n\nThe recorded layer ARN matters beyond attribution: it is what the connector checks the function against, so a\nversion you pinned is respected instead of being reported as drift the day Odigos publishes a newer one. It\nworks in `IN_ACCOUNT` mode too, where the ARN is a deploy-time reference your own code has no way to obtain.\n\nWhat these constructs never write is `ODIGOS_MANAGED`. That is the connector's own record of a mutation *it*\nperformed, including the `prevEnv` snapshot it restores from on rollback, and it is written only when you hand\na function to the connector by enabling it as an automatic-mode source. A CDK stack has no such snapshot to\noffer — nothing was mutated, your stack *is* the function's definition — so writing it would claim a rollback\nthat was never recorded. Do not add it by hand.\n\n## Service names\n\nLeft unset, `OTEL_SERVICE_NAME` is the function's physical name when your stack names it explicitly, and\nthe construct id otherwise. The fallback exists because a name CloudFormation generates is a `Ref` to the\nfunction, and a function referencing itself in its own environment is a dependency cycle CloudFormation\nrejects at deploy time.\n\nThe connector compares `OTEL_SERVICE_NAME` against the workload's name in Odigos, which is the real AWS\nfunction name — so for a function whose name is generated, pass `serviceName` explicitly or it is reported\nas drifted. `serviceName` applies to **every** function a construct instruments, so use one construct per\nfunction when you want per-function names.\n\n## Do not also instrument these functions with the Odigos connector\n\nThe Odigos AWS connector instruments **live** functions by mutating them, and reconciles them back to its\nown desired state on every pass. A function instrumented here *and* enabled as an automatic-mode connector\nsource ends up owned by both: each `cdk deploy` reasserts the CDK's environment, each reconcile reasserts\nthe connector's, and every round trip publishes a new function version.\n\nPick one per function. Discovery-only (manual) mode is the combination that works: Odigos watches and\nreports, your stack owns the configuration. That is what `ODIGOS_IAC` expresses — instrumented by Odigos,\nowned by you — and it is why these constructs never write the connector's ownership marker.\n\n## API\n\n| Export | |\n|---|---|\n| `OdigosLambdaInstrumentation` | the construct; `instrument(fn)`, `instrumentAll(fns)` |\n| `OdigosInstrumentationAspect` | applies it to every function in a scope |\n| `OdigosDestination` | where the collector exports; exactly one of `endpoint` / `httpEndpoint` |\n| `OdigosLayerSource` | `ODIGOS_PUBLIC` \\| `IN_ACCOUNT` |\n| `OdigosInstrumentationMode` | `HANDLER` \\| `WEB_SERVER` |\n| `OdigosJavaHandlerDistro` | the four Java wrappers |\n| `renderCollectorConfig` | the collector config renderer, if you want to inspect what gets set |\n| `LAYER_ARNS`, `ODIGOS_VERSION` | the baked layer table and the release it came from |\n\n## License\n\nApache-2.0.\n"
3830
3830
  },
3831
3831
  "repository": {
3832
3832
  "directory": "cdk",
@@ -3852,7 +3852,7 @@
3852
3852
  "assembly": "@odigos/aws-cdk",
3853
3853
  "datatype": true,
3854
3854
  "docs": {
3855
- "remarks": "Exactly one of `endpoint` (OTLP/gRPC) and `httpEndpoint` (OTLP/HTTP) must be set. They are different\ncollector components with different endpoint grammars — `host:port` versus a scheme-qualified URL — so\nthis is a genuine branch and not a formatting difference. Setting neither, or both, throws at synth.\n\nNOTE ON THE DATA PATH. The function's SDK does not export here. It exports to the collector running\ninside the same Lambda sandbox on localhost, and the collector forwards to this destination after the\nhandler has already returned its response. That indirection is the point: a slow or unreachable\ndestination costs billed duration, not caller latency.",
3855
+ "remarks": "Exactly one of `endpoint` (OTLP/gRPC) and `httpEndpoint` (OTLP/HTTP) must be set. They are different\ncollector components with different endpoint grammars — `host:port` versus a scheme-qualified URL — so\nthis is a genuine branch and not a formatting difference. Setting neither, or both, throws at synth.\n\nNOTE ON THE DATA PATH. The function's SDK does not export here. It exports to the collector running\ninside the same Lambda sandbox on localhost, and the collector forwards to this destination after the\nhandler has already returned its response. That indirection is the point: a slow or unreachable\ndestination costs billed duration, not caller latency.\n\nTHIS MUST DESCRIBE THE SAME DESTINATION ODIGOS HAS CONFIGURED, SIGNAL FOR SIGNAL. The connector renders\nthis same configuration from its own destination record and compares the whole rendered document against\nwhat the function carries, so any difference — the endpoint, the credential, or which of the three signals\nis enabled — is reported as instrumentation drift on the workload rather than being reconciled. The\nasymmetry to watch is the signals: `traces` is on here and `metrics`/`logs` are off, while a destination in\nOdigos commonly has more than one enabled. A stack that leaves the defaults against such a destination\nreports as drifted for a reason nothing in the stack makes visible, so read the enabled signals off the\ndestination in Odigos and state them here.",
3856
3856
  "stability": "stable",
3857
3857
  "summary": "Where the in-Lambda collector exports telemetry to."
3858
3858
  },
@@ -3860,7 +3860,7 @@
3860
3860
  "kind": "interface",
3861
3861
  "locationInModule": {
3862
3862
  "filename": "src/types.ts",
3863
- "line": 102
3863
+ "line": 111
3864
3864
  },
3865
3865
  "name": "OdigosDestination",
3866
3866
  "properties": [
@@ -3875,7 +3875,7 @@
3875
3875
  "immutable": true,
3876
3876
  "locationInModule": {
3877
3877
  "filename": "src/types.ts",
3878
- "line": 111
3878
+ "line": 120
3879
3879
  },
3880
3880
  "name": "endpoint",
3881
3881
  "optional": true,
@@ -3894,7 +3894,7 @@
3894
3894
  "immutable": true,
3895
3895
  "locationInModule": {
3896
3896
  "filename": "src/types.ts",
3897
- "line": 139
3897
+ "line": 148
3898
3898
  },
3899
3899
  "name": "headers",
3900
3900
  "optional": true,
@@ -3917,7 +3917,7 @@
3917
3917
  "immutable": true,
3918
3918
  "locationInModule": {
3919
3919
  "filename": "src/types.ts",
3920
- "line": 118
3920
+ "line": 127
3921
3921
  },
3922
3922
  "name": "httpEndpoint",
3923
3923
  "optional": true,
@@ -3936,7 +3936,7 @@
3936
3936
  "immutable": true,
3937
3937
  "locationInModule": {
3938
3938
  "filename": "src/types.ts",
3939
- "line": 127
3939
+ "line": 136
3940
3940
  },
3941
3941
  "name": "insecure",
3942
3942
  "optional": true,
@@ -3947,14 +3947,15 @@
3947
3947
  {
3948
3948
  "abstract": true,
3949
3949
  "docs": {
3950
- "default": "true",
3950
+ "default": "false",
3951
+ "remarks": "Opt-in for the same reason as `metrics`. Note that a Lambda function's stdout already reaches\nCloudWatch Logs regardless of this setting; this controls only what the OpenTelemetry SDK emits to\nthe in-Lambda collector and the collector forwards to your destination.",
3951
3952
  "stability": "stable",
3952
- "summary": "Send logs."
3953
+ "summary": "Send logs. OFF unless you turn it on."
3953
3954
  },
3954
3955
  "immutable": true,
3955
3956
  "locationInModule": {
3956
3957
  "filename": "src/types.ts",
3957
- "line": 164
3958
+ "line": 185
3958
3959
  },
3959
3960
  "name": "logs",
3960
3961
  "optional": true,
@@ -3965,14 +3966,15 @@
3965
3966
  {
3966
3967
  "abstract": true,
3967
3968
  "docs": {
3968
- "default": "true",
3969
+ "default": "false",
3970
+ "remarks": "Opt-in rather than on-by-default because the cost of a signal the destination does not implement is\npaid by the function: every batch is refused and the in-Lambda collector retries it on an exponential\nbackoff, inside billed duration. Traces are what nearly every destination accepts; metrics are a\nchoice about the destination, so they are made explicitly.",
3969
3971
  "stability": "stable",
3970
- "summary": "Send metrics."
3972
+ "summary": "Send metrics. OFF unless you turn it on."
3971
3973
  },
3972
3974
  "immutable": true,
3973
3975
  "locationInModule": {
3974
3976
  "filename": "src/types.ts",
3975
- "line": 157
3977
+ "line": 174
3976
3978
  },
3977
3979
  "name": "metrics",
3978
3980
  "optional": true,
@@ -3984,14 +3986,14 @@
3984
3986
  "abstract": true,
3985
3987
  "docs": {
3986
3988
  "default": "true",
3987
- "remarks": "A signal that is off gets no collector pipeline AND the SDK is told not to produce it\n(`OTEL_TRACES_EXPORTER=none`). Both halves matter: a signal with no pipeline is refused at the\nreceiver on every batch, so suppressing it at the source is what stops the error loop.",
3989
+ "remarks": "A signal that is off gets no collector pipeline AND the SDK is told not to produce it\n(`OTEL_TRACES_EXPORTER=none`). Both halves matter: a signal with no pipeline is refused at the\nreceiver on every batch, so suppressing it at the source is what stops the error loop.\n\nTurning this off without turning on `metrics` or `logs` leaves the destination with no signal at all,\nwhich throws at synth — a collector with no pipeline fails to start.",
3988
3990
  "stability": "stable",
3989
3991
  "summary": "Send traces."
3990
3992
  },
3991
3993
  "immutable": true,
3992
3994
  "locationInModule": {
3993
3995
  "filename": "src/types.ts",
3994
- "line": 150
3996
+ "line": 162
3995
3997
  },
3996
3998
  "name": "traces",
3997
3999
  "optional": true,
@@ -4016,7 +4018,7 @@
4016
4018
  },
4017
4019
  "locationInModule": {
4018
4020
  "filename": "src/instrumentation.ts",
4019
- "line": 447
4021
+ "line": 386
4020
4022
  },
4021
4023
  "parameters": [
4022
4024
  {
@@ -4043,7 +4045,7 @@
4043
4045
  "kind": "class",
4044
4046
  "locationInModule": {
4045
4047
  "filename": "src/instrumentation.ts",
4046
- "line": 439
4048
+ "line": 378
4047
4049
  },
4048
4050
  "methods": [
4049
4051
  {
@@ -4053,7 +4055,7 @@
4053
4055
  },
4054
4056
  "locationInModule": {
4055
4057
  "filename": "src/instrumentation.ts",
4056
- "line": 454
4058
+ "line": 393
4057
4059
  },
4058
4060
  "name": "visit",
4059
4061
  "overrides": "aws-cdk-lib.IAspect",
@@ -4084,7 +4086,7 @@
4084
4086
  "kind": "interface",
4085
4087
  "locationInModule": {
4086
4088
  "filename": "src/instrumentation.ts",
4087
- "line": 420
4089
+ "line": 359
4088
4090
  },
4089
4091
  "name": "OdigosInstrumentationAspectProps",
4090
4092
  "symbolId": "src/instrumentation:OdigosInstrumentationAspectProps"
@@ -4267,25 +4269,6 @@
4267
4269
  "fqn": "@odigos/aws-cdk.OdigosInstrumentationMode"
4268
4270
  }
4269
4271
  },
4270
- {
4271
- "abstract": true,
4272
- "docs": {
4273
- "default": "false",
4274
- "remarks": "WITHOUT IT THE FUNCTION IS REPORTED AS UNINSTRUMENTED. The connector derives ownership from this\nvariable alone: no marker means `Absent` — \"awaiting your deployment\" — even though the layer is\nattached, the wrapper is set and the collector is configured. Set this to `true` for a function that\nOdigos discovers in MANUAL (IaC) mode, which is the mode a workload is in unless you explicitly hand\ninstrumentation to the connector.\n\nLeave it `false` (the default) for a function you have enabled as an AUTOMATIC-mode connector source.\nThere, the connector instruments the function itself and owns this variable, including the `prevEnv`\nsnapshot it restores on rollback; two writers of one ownership record is the one combination that\ngenuinely misbehaves.\n\nThe marker records the exact layer ARN this construct attached, which is also what the connector reads\nback to decide which layer version the function is supposed to carry — so a version you pinned is\nrespected rather than reported as drift. It works in every layer mode, including `IN_ACCOUNT`, where the\nARN is a deploy-time reference your own code cannot obtain.",
4275
- "stability": "stable",
4276
- "summary": "Write `ODIGOS_MANAGED`, the marker that makes the Odigos connector recognise this function as instrumented."
4277
- },
4278
- "immutable": true,
4279
- "locationInModule": {
4280
- "filename": "src/instrumentation.ts",
4281
- "line": 187
4282
- },
4283
- "name": "recordOwnershipMarker",
4284
- "optional": true,
4285
- "type": {
4286
- "primitive": "boolean"
4287
- }
4288
- },
4289
4272
  {
4290
4273
  "abstract": true,
4291
4274
  "docs": {
@@ -4316,7 +4299,7 @@
4316
4299
  "immutable": true,
4317
4300
  "locationInModule": {
4318
4301
  "filename": "src/instrumentation.ts",
4319
- "line": 198
4302
+ "line": 174
4320
4303
  },
4321
4304
  "name": "skipUnsupportedRuntimes",
4322
4305
  "optional": true,
@@ -4378,7 +4361,7 @@
4378
4361
  "assembly": "@odigos/aws-cdk",
4379
4362
  "base": "constructs.Construct",
4380
4363
  "docs": {
4381
- "remarks": "```ts\nconst odigos = new OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [apiFn, workerFn],\n});\nodigos.instrument(anotherFn);\n```\n\nWhat it does to each function: attaches the layer for the function's language and architecture, sets\n`AWS_LAMBDA_EXEC_WRAPPER` so the layer loads the SDK, sets `OTEL_SERVICE_NAME`, and hands the in-Lambda\ncollector its entire configuration inline through `OPENTELEMETRY_COLLECTOR_CONFIG_URI`.\n\nThe function's SDK exports to the collector on localhost, not to your destination. The collector\nforwards after the handler has already returned, so a slow destination costs billed duration rather\nthan caller latency.\n\n## Do not also instrument these functions with the connector\n\nThe Odigos AWS connector instruments live functions by mutating them, and reconciles them back to its\nown desired state on every pass. A function instrumented here and also enabled as a connector source\nends up owned by both: each `cdk deploy` reasserts the CDK's environment, each reconcile reasserts the\nconnector's, and every round trip publishes a new function version.\n\nChoose one per function. This construct never sets the connector's `ODIGOS_MANAGED` ownership marker,\nso an IaC-instrumented function is not one the connector will roll back — but it will happily take it\nover if you enable it as a source.",
4364
+ "remarks": "```ts\nconst odigos = new OdigosLambdaInstrumentation(this, 'Odigos', {\n destination: { endpoint: 'otlp.example.com:4317' },\n functions: [apiFn, workerFn],\n});\nodigos.instrument(anotherFn);\n```\n\nWhat it does to each function: attaches the layer for the function's language and architecture, sets\n`AWS_LAMBDA_EXEC_WRAPPER` so the layer loads the SDK, sets `OTEL_SERVICE_NAME`, and hands the in-Lambda\ncollector its entire configuration inline through `OPENTELEMETRY_COLLECTOR_CONFIG_URI`.\n\nThe function's SDK exports to the collector on localhost, not to your destination. The collector\nforwards after the handler has already returned, so a slow destination costs billed duration rather\nthan caller latency.\n\n## Do not also instrument these functions with the connector\n\nThe Odigos AWS connector instruments live functions by mutating them, and reconciles them back to its\nown desired state on every pass. A function instrumented here and also enabled as a connector source\nends up owned by both: each `cdk deploy` reasserts the CDK's environment, each reconcile reasserts the\nconnector's, and every round trip publishes a new function version.\n\nChoose one per function. This construct writes `ODIGOS_IAC` and never the connector's `ODIGOS_MANAGED`\nownership marker, so an IaC-instrumented function reports as instrumented but is not one the connector\nwill roll back — though it will happily take it over if you enable it as a source.",
4382
4365
  "stability": "stable",
4383
4366
  "summary": "Instruments AWS Lambda functions with the Odigos OpenTelemetry layer."
4384
4367
  },
@@ -4389,7 +4372,7 @@
4389
4372
  },
4390
4373
  "locationInModule": {
4391
4374
  "filename": "src/instrumentation.ts",
4392
- "line": 249
4375
+ "line": 225
4393
4376
  },
4394
4377
  "parameters": [
4395
4378
  {
@@ -4415,7 +4398,7 @@
4415
4398
  "kind": "class",
4416
4399
  "locationInModule": {
4417
4400
  "filename": "src/instrumentation.ts",
4418
- "line": 243
4401
+ "line": 219
4419
4402
  },
4420
4403
  "methods": [
4421
4404
  {
@@ -4426,7 +4409,7 @@
4426
4409
  },
4427
4410
  "locationInModule": {
4428
4411
  "filename": "src/instrumentation.ts",
4429
- "line": 289
4412
+ "line": 265
4430
4413
  },
4431
4414
  "name": "instrument",
4432
4415
  "parameters": [
@@ -4445,7 +4428,7 @@
4445
4428
  },
4446
4429
  "locationInModule": {
4447
4430
  "filename": "src/instrumentation.ts",
4448
- "line": 322
4431
+ "line": 298
4449
4432
  },
4450
4433
  "name": "instrumentAll",
4451
4434
  "parameters": [
@@ -4480,7 +4463,7 @@
4480
4463
  "kind": "interface",
4481
4464
  "locationInModule": {
4482
4465
  "filename": "src/instrumentation.ts",
4483
- "line": 204
4466
+ "line": 180
4484
4467
  },
4485
4468
  "name": "OdigosLambdaInstrumentationProps",
4486
4469
  "properties": [
@@ -4495,7 +4478,7 @@
4495
4478
  "immutable": true,
4496
4479
  "locationInModule": {
4497
4480
  "filename": "src/instrumentation.ts",
4498
- "line": 210
4481
+ "line": 186
4499
4482
  },
4500
4483
  "name": "functions",
4501
4484
  "optional": true,
@@ -4545,6 +4528,6 @@
4545
4528
  "symbolId": "src/types:OdigosLayerSource"
4546
4529
  }
4547
4530
  },
4548
- "version": "0.0.11",
4549
- "fingerprint": "GhtDOF4Uik3gZvcWZlYji/v3oOcE8BiYdiH6HobYKlc="
4531
+ "version": "0.0.13",
4532
+ "fingerprint": "62LewgfsWUxqzy3tCKsO8Zo2MuyMvKF8j7uqckYFRoc="
4550
4533
  }
package/README.md CHANGED
@@ -136,19 +136,33 @@ merely produce thinner spans.
136
136
 
137
137
  ## Signals
138
138
 
139
+ **Traces only, by default.** Metrics and logs are opt-in:
140
+
139
141
  ```ts
140
142
  destination: {
141
143
  endpoint: 'otlp.example.com:4317',
142
- metrics: false, // traces and logs stay on
144
+ metrics: true, // off unless you ask
145
+ logs: true, // off unless you ask
143
146
  }
144
147
  ```
145
148
 
146
- A disabled signal gets no collector pipeline **and** the SDK is told not to produce it. Both halves
147
- matter: a signal with no pipeline is refused at the receiver on every batch, so suppressing it at the
148
- source is what stops the error loop.
149
+ They are opt-in because the cost of a signal the destination does not implement falls on the function:
150
+ every batch is refused and the in-Lambda collector retries it on an exponential backoff, inside billed
151
+ duration. Traces are what nearly every destination accepts; the other two are a decision about your
152
+ destination, so they are made deliberately.
153
+
154
+ A disabled signal gets no collector pipeline **and** the SDK is told not to produce it
155
+ (`OTEL_METRICS_EXPORTER=none`). Both halves matter: a signal with no pipeline is refused at the receiver on
156
+ every batch, so suppressing it at the source is what stops the error loop. An enabled signal's variable
157
+ stays unset, because `otlp` is already the SDK default.
158
+
159
+ `traces: false` with neither of the others on throws at synth — a collector with no pipeline fails to
160
+ start, which would leave the function instrumented and silent.
149
161
 
150
- Turning all three off throws at synth — a collector with no pipeline fails to start, which would leave
151
- the function instrumented and silent.
162
+ **If Odigos also manages this function in manual mode, the signals have to match.** The connector renders
163
+ the collector configuration from the destination configured in Odigos and compares the whole string, so a
164
+ traces-only function judged against an all-three destination is reported as drifted on the
165
+ collector-export component. Enable the same set on both sides.
152
166
 
153
167
  ## Instrumenting a whole stack
154
168
 
@@ -170,38 +184,22 @@ environment variable — readable by anyone with `lambda:GetFunctionConfiguratio
170
184
  console. For a long-lived credential, prefer a destination that authenticates by network position, or
171
185
  terminate at a collector you control.
172
186
 
173
- ## Making Odigos report the function as instrumented
174
-
175
- By default these constructs do **not** write `ODIGOS_MANAGED`, and the consequence is easy to hit: Odigos
176
- reports the function as `Absent` — "awaiting your deployment" — even though the layer is attached, the
177
- wrapper is set and the collector is configured. The connector derives ownership from that one variable, and
178
- checks for it *before* comparing anything else, so nothing about a correctly instrumented function
179
- substitutes for it.
180
-
181
- For a function Odigos discovers in **manual (IaC) mode** — which is the mode a workload is in unless you
182
- have explicitly handed instrumentation to the connector — turn it on:
183
-
184
- ```ts
185
- new OdigosLambdaInstrumentation(this, 'Odigos', {
186
- destination: { endpoint: 'otlp.example.com:4317' },
187
- functions: [fn],
188
- recordOwnershipMarker: true,
189
- });
190
- ```
187
+ ## How Odigos recognises the function
191
188
 
192
- The marker records the exact layer ARN the construct attached, which is also what the connector reads back
193
- to decide which layer version the function is *supposed* to carry — so a version you pinned is respected
194
- instead of being reported as drift. It works in `IN_ACCOUNT` mode too, where the ARN is a deploy-time
195
- reference your own code has no way to obtain.
189
+ These constructs write `ODIGOS_IAC` on every function they instrument, recording the layer ARN they attached
190
+ and the package version that attached it. That variable is what Odigos reads to attribute the function to
191
+ itself; without it a function reports as `Absent` — "awaiting your deployment" — however completely the layer,
192
+ wrapper and collector configuration are applied. There is nothing to switch on.
196
193
 
197
- It also records a rollback snapshot (`prevEnv`) naming every variable the construct set. That matters: a
198
- marker without one makes an uninstrument remove the **layer** while leaving `AWS_LAMBDA_EXEC_WRAPPER`
199
- pointing into it, which breaks the function at its next cold start. In web-server mode the snapshot holds
200
- your `existingLoaderValue`, so a rollback restores your own `NODE_OPTIONS` rather than deleting it.
194
+ The recorded layer ARN matters beyond attribution: it is what the connector checks the function against, so a
195
+ version you pinned is respected instead of being reported as drift the day Odigos publishes a newer one. It
196
+ works in `IN_ACCOUNT` mode too, where the ARN is a deploy-time reference your own code has no way to obtain.
201
197
 
202
- Leave it `false` for a function you have enabled as an **automatic-mode** connector source: there the
203
- connector owns the marker, including a snapshot it wrote itself, and two writers of one ownership record is
204
- the combination that genuinely misbehaves.
198
+ What these constructs never write is `ODIGOS_MANAGED`. That is the connector's own record of a mutation *it*
199
+ performed, including the `prevEnv` snapshot it restores from on rollback, and it is written only when you hand
200
+ a function to the connector by enabling it as an automatic-mode source. A CDK stack has no such snapshot to
201
+ offer — nothing was mutated, your stack *is* the function's definition — so writing it would claim a rollback
202
+ that was never recorded. Do not add it by hand.
205
203
 
206
204
  ## Service names
207
205
 
@@ -223,7 +221,8 @@ source ends up owned by both: each `cdk deploy` reasserts the CDK's environment,
223
221
  the connector's, and every round trip publishes a new function version.
224
222
 
225
223
  Pick one per function. Discovery-only (manual) mode is the combination that works: Odigos watches and
226
- reports, your stack owns the configuration — that is what `recordOwnershipMarker` is for.
224
+ reports, your stack owns the configuration. That is what `ODIGOS_IAC` expresses — instrumented by Odigos,
225
+ owned by you — and it is why these constructs never write the connector's ownership marker.
227
226
 
228
227
  ## API
229
228
 
@@ -235,7 +234,6 @@ reports, your stack owns the configuration — that is what `recordOwnershipMark
235
234
  | `OdigosLayerSource` | `ODIGOS_PUBLIC` \| `IN_ACCOUNT` |
236
235
  | `OdigosInstrumentationMode` | `HANDLER` \| `WEB_SERVER` |
237
236
  | `OdigosJavaHandlerDistro` | the four Java wrappers |
238
- | `recordOwnershipMarker` | the option that makes Odigos recognise the function as instrumented |
239
237
  | `renderCollectorConfig` | the collector config renderer, if you want to inspect what gets set |
240
238
  | `LAYER_ARNS`, `ODIGOS_VERSION` | the baked layer table and the release it came from |
241
239
 
@@ -43,8 +43,36 @@ export declare const EXPORTER_NONE = "none";
43
43
  export declare const ENV_TRACES_EXPORTER = "OTEL_TRACES_EXPORTER";
44
44
  export declare const ENV_METRICS_EXPORTER = "OTEL_METRICS_EXPORTER";
45
45
  export declare const ENV_LOGS_EXPORTER = "OTEL_LOGS_EXPORTER";
46
- /** Whether a signal is enabled, defaulting to on. */
47
- export declare function signalEnabled(value: boolean | undefined): boolean;
46
+ /**
47
+ * Whether a signal is enabled, given the caller's value and the signal's own default.
48
+ *
49
+ * The defaults are NOT uniform: traces are on, metrics and logs are off (see `SIGNAL_DEFAULTS`). So the
50
+ * default has to travel with the signal rather than being folded into one "unset means on" rule.
51
+ */
52
+ export declare function signalEnabled(value: boolean | undefined, defaultEnabled: boolean): boolean;
53
+ /**
54
+ * Which signals a destination carries when the caller says nothing.
55
+ *
56
+ * TRACES ONLY. Metrics and logs are opt-in because the cost of having them on by default falls on people
57
+ * who never asked for them: a destination that does not implement a signal refuses every batch of it, and
58
+ * the in-Lambda collector then retries on an exponential backoff inside billed duration. Traces are what
59
+ * almost every destination accepts and what almost everyone instruments for; the other two are a
60
+ * deliberate choice about the destination, so they are made deliberately.
61
+ *
62
+ * Turning one on is `metrics: true`. Enabling a signal also drops the `OTEL_*_EXPORTER=none` override, so
63
+ * the SDK starts producing it again.
64
+ */
65
+ export declare const SIGNAL_DEFAULTS: {
66
+ traces: boolean;
67
+ metrics: boolean;
68
+ logs: boolean;
69
+ };
70
+ /** The three signals resolved against their defaults — the single place that pairing is decided. */
71
+ export declare function resolvedSignals(dest: OdigosDestination): {
72
+ traces: boolean;
73
+ metrics: boolean;
74
+ logs: boolean;
75
+ };
48
76
  /**
49
77
  * Picks the exporter by the transport the destination configured. A destination carries a gRPC endpoint
50
78
  * (type `otlp`) or an OTLP/HTTP one (type `otlphttp`); the two are different collector components with