@aws/durable-execution-sdk-js-otel 1.1.0 → 1.2.1
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 +338 -12
- package/dist/execution-plugin.mjs +227 -50
- package/dist/execution-plugin.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/invocation-plugin.mjs +209 -105
- package/dist/invocation-plugin.mjs.map +1 -1
- package/dist/otel-execution.mjs +1 -1
- package/dist/otel-execution.mjs.map +1 -1
- package/dist/otel-invocation.mjs +2 -2
- package/dist/otel-invocation.mjs.map +1 -1
- package/dist/{global-id-generator.mjs → plugin-registration.mjs} +147 -4
- package/dist/plugin-registration.mjs.map +1 -0
- package/dist-cjs/execution-plugin.js +240 -63
- package/dist-cjs/execution-plugin.js.map +1 -1
- package/dist-cjs/index.js +10 -10
- package/dist-cjs/invocation-plugin.js +221 -117
- package/dist-cjs/invocation-plugin.js.map +1 -1
- package/dist-cjs/otel-execution.js +1 -1
- package/dist-cjs/otel-execution.js.map +1 -1
- package/dist-cjs/otel-invocation.js +2 -2
- package/dist-cjs/otel-invocation.js.map +1 -1
- package/dist-cjs/{global-id-generator.js → plugin-registration.js} +152 -2
- package/dist-cjs/plugin-registration.js.map +1 -0
- package/dist-types/execution-plugin.d.ts +25 -4
- package/dist-types/execution-plugin.d.ts.map +1 -1
- package/dist-types/execution-trace-context.d.ts +4 -2
- package/dist-types/execution-trace-context.d.ts.map +1 -1
- package/dist-types/external-completions.d.ts +11 -0
- package/dist-types/external-completions.d.ts.map +1 -0
- package/dist-types/global-sampler.d.ts +2 -0
- package/dist-types/global-sampler.d.ts.map +1 -1
- package/dist-types/invocation-clock.d.ts +9 -0
- package/dist-types/invocation-clock.d.ts.map +1 -0
- package/dist-types/invocation-plugin.d.ts +18 -2
- package/dist-types/invocation-plugin.d.ts.map +1 -1
- package/dist-types/operation-span-status.d.ts +5 -0
- package/dist-types/operation-span-status.d.ts.map +1 -0
- package/dist-types/otel-plugin-config.d.ts +8 -0
- package/dist-types/otel-plugin-config.d.ts.map +1 -1
- package/dist-types/otel-plugin-provider.d.ts +7 -0
- package/dist-types/otel-plugin-provider.d.ts.map +1 -1
- package/dist-types/plugin-registration.d.ts +2 -0
- package/dist-types/plugin-registration.d.ts.map +1 -0
- package/dist-types/propagation-metadata.d.ts +12 -0
- package/dist-types/propagation-metadata.d.ts.map +1 -0
- package/package.json +13 -9
- package/dist/global-id-generator.mjs.map +0 -1
- package/dist-cjs/global-id-generator.js.map +0 -1
package/README.md
CHANGED
|
@@ -153,7 +153,25 @@ tracer from it. Register it globally when the application also needs its
|
|
|
153
153
|
context manager, propagator, or library instrumentation.
|
|
154
154
|
|
|
155
155
|
At the end of each enabled invocation, both plugins call `forceFlush()` when
|
|
156
|
-
the provider exposes it.
|
|
156
|
+
the provider exposes it. For the default global API provider, they also check
|
|
157
|
+
its public delegate, so an SDK `BatchSpanProcessor` is flushed when the API proxy
|
|
158
|
+
does not expose `forceFlush()` itself. Explicit factory providers are used as
|
|
159
|
+
returned and are not unwrapped. The plugins never call `shutdown()`.
|
|
160
|
+
|
|
161
|
+
Both views use one `flushTimeoutMillis` budget across the invocation-end
|
|
162
|
+
drain/flush rounds (default **1000 ms**, positive and at most **2147483647 ms**,
|
|
163
|
+
Node.js's maximum timer delay). This bounds
|
|
164
|
+
asynchronous provider waiting on both the global and explicit-provider paths.
|
|
165
|
+
Choose a value that leaves room in the Lambda invocation timeout and configure
|
|
166
|
+
the exporter's own transport timeouts too. The plugin has no remaining-runtime
|
|
167
|
+
deadline in its invocation metadata and cannot guarantee that this budget fits
|
|
168
|
+
the time left after the handler.
|
|
169
|
+
|
|
170
|
+
On expiry, the plugin drains one bounded snapshot of terminal notifications
|
|
171
|
+
already received, then returns without starting another provider flush. Export
|
|
172
|
+
delivery is best effort: spans may remain queued. Timing out does not cancel an
|
|
173
|
+
export, shut down the provider, or preempt synchronously blocking provider or
|
|
174
|
+
processor code.
|
|
157
175
|
|
|
158
176
|
## Dynamic Loading from a Lambda Layer
|
|
159
177
|
|
|
@@ -183,6 +201,13 @@ needed.
|
|
|
183
201
|
|
|
184
202
|
## Choosing a Plugin
|
|
185
203
|
|
|
204
|
+
Configure exactly one durable OTel view: `ExecutionOtelPlugin` for a workflow
|
|
205
|
+
view, or `InvocationOtelPlugin` for an invocation view. Registering both (or
|
|
206
|
+
registering one view twice) raises `PluginLoadError` before invocation hooks
|
|
207
|
+
run. This applies to explicit `plugins`, `DURABLE_EXECUTION_PLUGINS`, and any
|
|
208
|
+
combination of the two. Other instrumentation plugins can run alongside the
|
|
209
|
+
selected view.
|
|
210
|
+
|
|
186
211
|
### `ExecutionOtelPlugin`
|
|
187
212
|
|
|
188
213
|
Use this plugin for a workflow-centered view. Operations and attempts are
|
|
@@ -238,6 +263,33 @@ Open operation spans are ended at the invocation boundary and retain
|
|
|
238
263
|
`durable.operation.status=STARTED`. When an operation completes in a later
|
|
239
264
|
invocation, the plugin emits a continuation span in that invocation.
|
|
240
265
|
|
|
266
|
+
Live invocation-view span boundaries and exception events use a wall-clock
|
|
267
|
+
origin captured with `Date.now()` at invocation start, advanced by monotonic
|
|
268
|
+
elapsed time. This matches the clock model of ordinary `tracer.startSpan()`
|
|
269
|
+
spans instead of using the process-wide `performance.timeOrigin`. Absolute
|
|
270
|
+
`HrTime` tuples keep epoch milliseconds distinct from relative performance time.
|
|
271
|
+
Each resumed invocation captures its own anchor. Supplied execution-start
|
|
272
|
+
Dates used to backdate Workflow and synthetic roots are preserved; execution-view
|
|
273
|
+
provided timestamps are retained without adding cross-clock boundary compensation.
|
|
274
|
+
|
|
275
|
+
The wall and monotonic reads are not atomic. Sampling delay, millisecond wall
|
|
276
|
+
precision, later wall-clock adjustments, and clocks on different machines can
|
|
277
|
+
still disagree. The plugin does not rewrite backend Dates or user spans, retain
|
|
278
|
+
a clock high-water mark across invocations, or promise simultaneous strict
|
|
279
|
+
ordering and complete containment across arbitrary clock skew.
|
|
280
|
+
|
|
281
|
+
Independently of the invocation clock origin, execution-view operation and attempt
|
|
282
|
+
exception events use the supplied backend end timestamp when one is available,
|
|
283
|
+
just as their span end does. Recording the event at local receipt time can place
|
|
284
|
+
it after the backend-ended span. Missing end timestamps retain the normal tracer
|
|
285
|
+
default; this does not add rounding or cross-clock ordering policies.
|
|
286
|
+
|
|
287
|
+
When an execution-view completion has no recorded start timestamp, an available
|
|
288
|
+
backend end timestamp supplies a zero-duration fallback. This keeps its exception
|
|
289
|
+
at that known completion time instead of before a later local default start.
|
|
290
|
+
Recorded starts and missing-end defaults retain their existing behavior; this
|
|
291
|
+
does not reconcile a captured local start with an earlier backend end.
|
|
292
|
+
|
|
241
293
|
Because the original span context is not checkpointed, replayed `STEP` and
|
|
242
294
|
`CONTEXT` spans and cross-invocation continuation spans use new provider IDs.
|
|
243
295
|
They correlate through two links: the real exported `Workflow` span, and the
|
|
@@ -298,6 +350,66 @@ The `Invocation` span may still nest under a same-trace ambient handler span —
|
|
|
298
350
|
see [Invocation parent](#invocation-parent) — without changing the execution
|
|
299
351
|
ancestor the `Workflow` span joins.
|
|
300
352
|
|
|
353
|
+
### Synthetic root export lifecycle
|
|
354
|
+
|
|
355
|
+
When fallback tracing is sampled, the backend supplies `executionStartTimestamp`,
|
|
356
|
+
and the configured SDK tracer allows the durable sampler wrapper to be installed,
|
|
357
|
+
both views create and end `DurableExecutionRoot` during each invocation's start
|
|
358
|
+
hook. Its start and end are both that backend timestamp. The normal
|
|
359
|
+
invocation-boundary flush can export it before a `PENDING` or `RETRYING` invocation
|
|
360
|
+
returns. A retry with checkpoint history or an intermediate resume can therefore
|
|
361
|
+
recover an earlier missing export, including when tracing became available only
|
|
362
|
+
after the first invocation. Recovery requires a later sampled invocation to
|
|
363
|
+
actually deliver its copy; stopping or timing out before any successful delivery
|
|
364
|
+
can still leave the execution without an exported root. This policy does not rely
|
|
365
|
+
on `isFirstInvocation`: a retry after checkpointed work runs in replay mode, while
|
|
366
|
+
an attempt without that history can still be identified as the first invocation.
|
|
367
|
+
|
|
368
|
+
Each sampled invocation emits at most one anchor: a terminal invocation does not
|
|
369
|
+
emit a second copy if its start hook already created one. Additional copies on
|
|
370
|
+
resumes are intentional, allowing consumers to deduplicate by trace ID and span ID.
|
|
371
|
+
The SDK-owned span fields are identical when the execution's trace identity,
|
|
372
|
+
backend start timestamp and instrumentation configuration remain stable. The
|
|
373
|
+
anchor carries `durable.execution.arn`, but no terminal status, request ID or
|
|
374
|
+
execution duration; `Workflow` continues to carry the duration and outcome.
|
|
375
|
+
`DurableExecutionRoot` is a zero-duration identity span: its descendants end after
|
|
376
|
+
it, which some trace viewers or containment validators may flag.
|
|
377
|
+
|
|
378
|
+
A forwarding tracer that hides the configured sampler retains the existing
|
|
379
|
+
terminal-only root creation path. Creating an early root through that tracer would
|
|
380
|
+
query the hidden sampler independently and could sample a root after dropping its
|
|
381
|
+
Invocation child. The plugin does not add that early sampler call; the underlying
|
|
382
|
+
provider still controls sampling, and these tracers do not receive early-anchor
|
|
383
|
+
recovery. A terminal backup can recover a missing first export only if a sampled
|
|
384
|
+
terminal invocation actually runs and delivers it.
|
|
385
|
+
|
|
386
|
+
Resources belong to the configured provider. Resource attributes such as
|
|
387
|
+
`faas.instance` can differ across execution environments even for identical
|
|
388
|
+
anchor span content; backends may retain whichever resource accompanies their
|
|
389
|
+
chosen copy. Span processors that enrich these spans must likewise avoid
|
|
390
|
+
invocation-varying span fields when relying on identity-based deduplication.
|
|
391
|
+
Export delivery and flushing remain the provider/layer's responsibility. The
|
|
392
|
+
plugin awaits an available provider or global-delegate `forceFlush()`, but a killed invocation
|
|
393
|
+
or a failed export cannot guarantee delivery; this is not exactly-once export.
|
|
394
|
+
|
|
395
|
+
If a caller omits `executionStartTimestamp`, the plugin retains terminal-only
|
|
396
|
+
synthetic-root materialization with its existing timestamp fallback. It does not
|
|
397
|
+
invent a wall-clock-based early anchor. These callers do not get the early-anchor
|
|
398
|
+
or identical-timestamp guarantee; the backend timestamp must be consistently
|
|
399
|
+
supplied to enable that contract. Complete propagated remote parents are never
|
|
400
|
+
materialized by the SDK. With the durable sampler wrapper installed, explicit
|
|
401
|
+
`NOT_SAMPLED` executions emit no root.
|
|
402
|
+
|
|
403
|
+
With the durable sampler wrapper installed, sampling is resolved once per
|
|
404
|
+
invocation and the anchor reuses that decision without a second sampler query.
|
|
405
|
+
Explicit upstream decisions or a deterministic trace-based policy (for example a
|
|
406
|
+
ratio sampler) keep anchor and terminal `Workflow` decisions consistent across
|
|
407
|
+
resumes. A non-deterministic custom sampler can select different invocations,
|
|
408
|
+
leaving an anchor without a sampled terminal `Workflow`. Decisions are not
|
|
409
|
+
persisted. These once-per-invocation guarantees do not apply to an opaque tracer:
|
|
410
|
+
its provider can query its sampler independently for each span, including the
|
|
411
|
+
existing terminal root; the early-anchor path is skipped to preserve that baseline.
|
|
412
|
+
|
|
301
413
|
### Workflow identity and lifecycle
|
|
302
414
|
|
|
303
415
|
`Workflow` is an `INTERNAL` span that **joins the execution trace** by parenting
|
|
@@ -317,7 +429,7 @@ The plugins carry the same `Workflow` span identity on each invocation as a
|
|
|
317
429
|
non-recording span context, and create the single recording `Workflow` span only
|
|
318
430
|
when the execution reaches `SUCCEEDED` or `FAILED`, backdated to the execution
|
|
319
431
|
start. `PENDING` and `RETRYING` invocations create no recording `Workflow` span,
|
|
320
|
-
so none is ever left unended, and
|
|
432
|
+
so none is ever left unended, and one terminal `Workflow` span is exported for the
|
|
321
433
|
durable execution while intermediate invocation spans export normally. The
|
|
322
434
|
non-recording context carries the execution's sampling decision, so operations
|
|
323
435
|
under it are sampled consistently with the eventual root. In-flight attempt spans
|
|
@@ -415,6 +527,9 @@ interface OtelPluginConfig {
|
|
|
415
527
|
|
|
416
528
|
/** Add active OTel IDs to durable log records. Defaults to true. */
|
|
417
529
|
enrichLogger?: boolean;
|
|
530
|
+
|
|
531
|
+
/** Total invocation-end provider wait budget; defaults to 1000 ms. */
|
|
532
|
+
flushTimeoutMillis?: number;
|
|
418
533
|
}
|
|
419
534
|
```
|
|
420
535
|
|
|
@@ -455,16 +570,28 @@ environment values are available.
|
|
|
455
570
|
|
|
456
571
|
OpenTelemetry status mapping is:
|
|
457
572
|
|
|
458
|
-
| Span | Durable result
|
|
459
|
-
| ---------- |
|
|
460
|
-
| Workflow | `SUCCEEDED` / `FAILED`
|
|
461
|
-
| Invocation | `SUCCEEDED` or `PENDING` / `FAILED` / `RETRYING`
|
|
462
|
-
| Operation | `SUCCEEDED` /
|
|
463
|
-
| Attempt | `FAILED` / any other outcome
|
|
464
|
-
|
|
465
|
-
Operation
|
|
466
|
-
|
|
467
|
-
|
|
573
|
+
| Span | Durable result | OTel status |
|
|
574
|
+
| ---------- | -------------- | ----------- |
|
|
575
|
+
| Workflow | `SUCCEEDED` / `FAILED` | `OK` / `ERROR` |
|
|
576
|
+
| Invocation | `SUCCEEDED` or `PENDING` / `FAILED` / `RETRYING` | `OK` / `ERROR` / `UNSET` |
|
|
577
|
+
| Operation | `SUCCEEDED` / `FAILED`, `TIMED_OUT`, `STOPPED` or an error object / otherwise | `OK` / `ERROR` / `UNSET` |
|
|
578
|
+
| Attempt | `FAILED` / any other outcome | `ERROR` / `OK` |
|
|
579
|
+
|
|
580
|
+
Operation failure status and exception details are independent. `FAILED`,
|
|
581
|
+
`TIMED_OUT`, and `STOPPED` remain `ERROR` when error details are absent, including when
|
|
582
|
+
the handler catches its failure and returns successfully. The status message uses
|
|
583
|
+
the supplied error message, or the failed status name when no error exists.
|
|
584
|
+
|
|
585
|
+
`CANCELLED` without error details remains `UNSET`. A supplied error still makes
|
|
586
|
+
a cancelled operation `ERROR`; this mapping does not introduce a general
|
|
587
|
+
cancellation policy.
|
|
588
|
+
|
|
589
|
+
Operation and attempt exceptions are recorded only when an error object is
|
|
590
|
+
available. Wire errors with no defined values do not synthesize exception events,
|
|
591
|
+
including when an unrecognized property is `undefined`. Explicit partial
|
|
592
|
+
details, including empty strings or an empty stack trace, remain error information.
|
|
593
|
+
Workflow and Invocation failures set `ERROR` without recording an exception event.
|
|
594
|
+
Caller exceptions, checkpoint payloads and replay behavior are unchanged.
|
|
468
595
|
|
|
469
596
|
## Log Correlation
|
|
470
597
|
|
|
@@ -591,6 +718,102 @@ The package also exports the `ContextExtractor`, `ContextExtractorResult`,
|
|
|
591
718
|
`ExecutionTraceEnvironment`, `IdGeneratorFactory`, `TracerProviderFactory`, and
|
|
592
719
|
`OtelPluginConfig` types.
|
|
593
720
|
|
|
721
|
+
### External completions and replay
|
|
722
|
+
|
|
723
|
+
Both views retain terminal wait, invoke, and callback notifications received at
|
|
724
|
+
invocation start or in checkpoint responses. If workflow traversal does not
|
|
725
|
+
reach a completed operation before suspending or returning, its completion is
|
|
726
|
+
exported at invocation end. Supplied operation timestamps, parent identity,
|
|
727
|
+
status, and error details are preserved in the execution view; the invocation
|
|
728
|
+
view completes the live segment or emits a linked continuation.
|
|
729
|
+
|
|
730
|
+
A failed invoke still rejects with `InvokeError` when backend error details are
|
|
731
|
+
absent. Its telemetry retains the absent error metadata in live, resumed, and
|
|
732
|
+
deferred delivery, rather than inventing an exception. Provided error details
|
|
733
|
+
continue through the existing metadata converter.
|
|
734
|
+
|
|
735
|
+
For a named `waitForCallback`, a deferred inner callback recovers its derived
|
|
736
|
+
`<name>-callback` display name from a known `WaitForCallback` parent when replay
|
|
737
|
+
skips that branch. This does not change checkpointed names, operation identity,
|
|
738
|
+
parent identity, or the source notification; explicit names remain unchanged.
|
|
739
|
+
|
|
740
|
+
At the final cleanup of a terminal invocation, the execution view parents a
|
|
741
|
+
deferred completion to `Workflow` if its direct parent is still an open local
|
|
742
|
+
placeholder that will be discarded. During the open drain/flush phase, a live
|
|
743
|
+
parent can still finish and its normal end hook exports the queued child with
|
|
744
|
+
the real parent identity. An open parent alone does not trigger repeated flushes.
|
|
745
|
+
The plugin does not invent a completion or span for an abandoned context. Pending
|
|
746
|
+
or retrying invocations retain the parent identity
|
|
747
|
+
because a later invocation can still complete and export that context. An
|
|
748
|
+
exception is an unfinished parent below a known completed context in checkpoint
|
|
749
|
+
history: replay cannot restart that unfinished branch, so its deferred completion
|
|
750
|
+
also uses `Workflow`. `ReplayChildren` can traverse completed children to rebuild
|
|
751
|
+
a result, but `ReplaySucceededContext` still blocks unfinished child operations.
|
|
752
|
+
Missing or inconclusive ancestry is not treated as proof of abandonment.
|
|
753
|
+
|
|
754
|
+
In both views, a completion notification already queued when its live context
|
|
755
|
+
ends is exported before that context closes, preserving its OTel parent. Queued
|
|
756
|
+
child draining and parent finalization run synchronously within the End hook;
|
|
757
|
+
flush completion cannot reset invocation state between them. Invocation-end
|
|
758
|
+
drains likewise finish their current snapshot before yielding. This
|
|
759
|
+
includes an unawaited callback from a prior invocation: the invocation view emits
|
|
760
|
+
its linked continuation while the resumed parent segment is still open.
|
|
761
|
+
|
|
762
|
+
In the execution view, a completed context in checkpoint history remains a valid
|
|
763
|
+
parent for a later completion. A context span actually recorded during the current
|
|
764
|
+
invocation also remains a valid parent when an enclosing context suppressed its
|
|
765
|
+
terminal checkpoint and history still shows it as unfinished. This recording takes
|
|
766
|
+
precedence over abandonment inference within that invocation; knowledge of an
|
|
767
|
+
uncheckpointed recording is not persisted into later invocations.
|
|
768
|
+
|
|
769
|
+
A notification delivered after the parent's end hook may carry a
|
|
770
|
+
completion that occurred earlier in the backend. Delivery order does not establish
|
|
771
|
+
that chronology. Reusing the recorded parent identity does not recreate a parent
|
|
772
|
+
span, compare timestamps, retime supplied dates, or promise temporal containment.
|
|
773
|
+
The unfinished-parent safeguards still apply when no such recorded parent is known.
|
|
774
|
+
|
|
775
|
+
This requires the SDK to receive the operation metadata. Lambda normally removes
|
|
776
|
+
a completed context's children from subsequent execution state unless
|
|
777
|
+
`ReplayChildren` preserves them. For completion visibility after a child context
|
|
778
|
+
has finished, enable the appropriate `pluginsConfig.childOperationsDepth` (for
|
|
779
|
+
example, `1` for direct children of a top-level context). Preserved contexts
|
|
780
|
+
replay their orchestration; completed durable work still uses stored results.
|
|
781
|
+
The plugin cannot reconstruct an operation that was pruned and whose lifecycle
|
|
782
|
+
hooks do not run. The local runner now applies the service's pruning rule to
|
|
783
|
+
invocation input and `GetDurableExecutionState`; its inspection results and
|
|
784
|
+
checkpoint responses still retain their existing operation data.
|
|
785
|
+
|
|
786
|
+
Within the configured flush budget, the invocation-end hook drains notifications
|
|
787
|
+
received while it awaits a provider flush, then flushes the newly exported
|
|
788
|
+
completions. On expiry it drains one snapshot of already-received notifications
|
|
789
|
+
without another provider wait before clearing its state. In the execution view,
|
|
790
|
+
completions held for a still-open parent are finalized after the open drain/flush
|
|
791
|
+
phase. Both views close their invocation state before a final provider flush for
|
|
792
|
+
operation or attempt spans ended during the previous wait or final cleanup,
|
|
793
|
+
using only the original budget remaining. New operation-end hooks after
|
|
794
|
+
finalization do not record additional spans during that final wait. At deadline
|
|
795
|
+
expiry these final exports remain best effort. Change hooks enqueue notifications;
|
|
796
|
+
the end hook owns this loop. In the execution view, these drains use the latest operation ancestry received during
|
|
797
|
+
the end hook to detect an unfinished parent abandoned by a completed ancestor.
|
|
798
|
+
Missing ancestry alone does not prove abandonment. In the
|
|
799
|
+
invocation view, a completion after its individual segment has ended uses a linked
|
|
800
|
+
continuation. A segment first created while a provider flush is waiting remains
|
|
801
|
+
eligible for normal completion. Any such segment still open at final cleanup is
|
|
802
|
+
ended before state is cleared and included in the final bounded flush. The
|
|
803
|
+
execution view likewise closes recording attempt spans that started during the
|
|
804
|
+
wait, without inventing a completed operation span for unfinished work.
|
|
805
|
+
Enclosing spans may already have ended, so this does not guarantee
|
|
806
|
+
late-child timestamp containment. Updates after the final drain/reset, a frozen
|
|
807
|
+
runtime, and concurrent executions sharing a plugin instance remain outside this
|
|
808
|
+
shutdown guarantee; the shared-instance limitation is tracked in [#950](https://github.com/aws/aws-durable-execution-sdk-js/issues/950).
|
|
809
|
+
|
|
810
|
+
Notifications and operation-end hooks are deduplicated within each invocation.
|
|
811
|
+
Plain replay without a fresh update does not re-export stored external
|
|
812
|
+
completions. Deduplication is not persisted: a retry carrying the same updated
|
|
813
|
+
operation IDs can export the completion again, preserving recovery eligibility.
|
|
814
|
+
This is a conditional redelivery behavior, not a claim that Lambda always sends
|
|
815
|
+
those IDs again or that the exporter has a durable delivery acknowledgement.
|
|
816
|
+
|
|
594
817
|
## Verification and Troubleshooting
|
|
595
818
|
|
|
596
819
|
After deployment:
|
|
@@ -623,3 +846,106 @@ remote parent, or a synthetic execution root).
|
|
|
623
846
|
## License
|
|
624
847
|
|
|
625
848
|
Apache-2.0
|
|
849
|
+
|
|
850
|
+
## Chained-invoke propagation
|
|
851
|
+
|
|
852
|
+
When preparing a new `context.invoke()` START, the SDK calls the optional
|
|
853
|
+
`providePropagationMetadata(input)` hook after assigning the operation identity
|
|
854
|
+
and before serializing the checkpoint. The readonly input contains
|
|
855
|
+
`executionArn`, `operationId`, optional `parentOperationId`, and
|
|
856
|
+
`targetFunctionName`. The SDK places the collected `xAmznTraceId` in the flat
|
|
857
|
+
`ChainedInvokeOptions.XAmznTraceId` wire member, separately for each operation
|
|
858
|
+
in a batched checkpoint. Function name, tenant, payload and operation identity
|
|
859
|
+
are unchanged; the SDK does not inject tracing fields into user payloads.
|
|
860
|
+
|
|
861
|
+
With the durable sampler bridge still installed on a compatible SDK tracer,
|
|
862
|
+
both OTel views encode the resolved execution trace ID as `Root`, the calling
|
|
863
|
+
CHAINED_INVOKE operation span ID as `Parent`, and the resolved sampling decision
|
|
864
|
+
as `Sampled`, including `Sampled=0`. Producing metadata does not create an extra
|
|
865
|
+
span or use an unrelated active span. For example, existing plugin registration
|
|
866
|
+
also covers both calls here:
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
const first = context.invoke("first", "first-function:1", { item: 1 });
|
|
870
|
+
const second = context.invoke("second", "second-function:1", { item: 2 });
|
|
871
|
+
return context.promise.all([first, second]);
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
An opaque tracer or a tracer whose sampler was replaced may make an independent
|
|
875
|
+
decision for the invoke span. The plugins omit their optional propagation metadata
|
|
876
|
+
when they cannot verify that their sampler bridge still controls that decision.
|
|
877
|
+
They do not probe the sampler, create a span to obtain a decision, reinstall a
|
|
878
|
+
customer's sampler, or change ordinary span behavior. The check occurs when the
|
|
879
|
+
START is prepared; later provider reconfiguration cannot retroactively change a
|
|
880
|
+
checkpointed header.
|
|
881
|
+
|
|
882
|
+
The dispatcher calls providers synchronously with a frozen input snapshot.
|
|
883
|
+
The first non-null supported value wins; identical values do not conflict.
|
|
884
|
+
Later different values generate a warning with both plugin identities and a
|
|
885
|
+
conflict count. Invalid results and ordinary plugin failures are isolated.
|
|
886
|
+
Blank or whitespace-only headers are ignored so later providers can contribute;
|
|
887
|
+
nonblank values remain opaque and are not trimmed or parsed by the core SDK.
|
|
888
|
+
With no plugin, an absent hook or no returned value, the wire member is omitted
|
|
889
|
+
and the backend's existing tracing fallback applies.
|
|
890
|
+
|
|
891
|
+
Replaying a persisted START, including a pending or completed invoke, does not
|
|
892
|
+
call the hook or send another START. A failed, uncommitted START can collect
|
|
893
|
+
metadata again when the invocation is retried. Providers must therefore use the
|
|
894
|
+
stable identity, avoid side effects, and not assume exactly-once callbacks.
|
|
895
|
+
|
|
896
|
+
The runtime dependency requires `@aws-sdk/client-lambda` 3.1152.0 or newer.
|
|
897
|
+
That published release declares the optional `XAmznTraceId` field and includes
|
|
898
|
+
it in the generated serializer. The strict wire-model parity guard and actual
|
|
899
|
+
serializer regression pass with that release.
|
|
900
|
+
|
|
901
|
+
**Deployed backend trace-topology qualification remains separate.** Local
|
|
902
|
+
serialization and package compatibility do not establish
|
|
903
|
+
that a deployed chained invocation receives the intended Root/Parent/Sampled
|
|
904
|
+
or exports the expected caller/child topology. Existing sampled cloud traces
|
|
905
|
+
do not qualify unsampled delivery.
|
|
906
|
+
|
|
907
|
+
The design also adds `DistributedMapOptions.XAmznTraceId` to a future service
|
|
908
|
+
model. This SDK currently has no distributed-map options or START API; its
|
|
909
|
+
`map` and `parallel` APIs use CONTEXT operations. They are not distributed-map
|
|
910
|
+
transport paths, and this change does not introduce a new operation API or
|
|
911
|
+
invent the future Source/Processor/Destination structures.
|
|
912
|
+
## Core compatibility
|
|
913
|
+
|
|
914
|
+
OTel 1.2 preserves valid core 2.4–2.x registrations, including separate
|
|
915
|
+
OTel-only Lambda layers. Its core peer is optional (`>=2.4.0 <3.0.0`), and the
|
|
916
|
+
dynamic provider contract remains version 1. Existing on-demand environment
|
|
917
|
+
carrier behavior remains available on older cores; upgrading the plugin alone
|
|
918
|
+
does not add fields to the invocation metadata those cores supply.
|
|
919
|
+
|
|
920
|
+
The new invocation-local LMI carrier and core registration exclusivity require
|
|
921
|
+
the coordinated core 2.7 / OTel 1.2 pair. Upgrade both for those capabilities.
|
|
922
|
+
Older core/plugin combinations do not gain those guarantees. The two OTel views
|
|
923
|
+
remain an unsupported combination, and the updated core rejects it with updated
|
|
924
|
+
plugin metadata. The later core 3 factory migration is a separate major release.
|
|
925
|
+
|
|
926
|
+
Instrumentation error details are optional even for failed operations. The core
|
|
927
|
+
metadata correction for an empty callback error payload no longer invents an
|
|
928
|
+
error object. API-v1 plugins that previously used only `OperationInfo.error`
|
|
929
|
+
presence as a failure signal should use `OperationInfo.status` instead (and
|
|
930
|
+
`AttemptEndInfo.outcome` for an attempt). Missing error details do not imply a
|
|
931
|
+
successful operation; supplied partial or explicitly empty detail fields are
|
|
932
|
+
retained. This does not change the provider/hook shape, callback rejection,
|
|
933
|
+
persisted error payload, or replay identity.
|
|
934
|
+
|
|
935
|
+
Registration metadata uses the namespaced symbol
|
|
936
|
+
`Symbol.for("aws.lambda.durable.instrumentation.plugin-registration")`.
|
|
937
|
+
The bundled views declare it on their constructors, so the core can validate
|
|
938
|
+
all selected provider types before calling any environment-selected factory.
|
|
939
|
+
Conflicting dynamic views therefore do not install sampler or ID-generator
|
|
940
|
+
wrappers on the global tracer. Subclasses retain all inherited static exclusive
|
|
941
|
+
groups; declaring their own static metadata adds a constraint and cannot replace
|
|
942
|
+
an inherited one. Repeating a group within one constructor chain does not count
|
|
943
|
+
as a second plugin registration. Subclasses are identified by their actual
|
|
944
|
+
constructor names in diagnostics. Explicit instances
|
|
945
|
+
have already been constructed by the application before registration; validation
|
|
946
|
+
cannot undo that construction. Existing instance metadata remains supported and
|
|
947
|
+
returned instances are checked too, including subclasses returned by providers
|
|
948
|
+
that declare a broader base type.
|
|
949
|
+
The base hook interface stays unchanged, and ordinary properties such as a
|
|
950
|
+
custom plugin's `registration` field are not interpreted by the SDK. This keeps
|
|
951
|
+
existing valid plugin classes and generic hook dispatchers source-compatible.
|