Skip to content

Commit 6fb3032

Browse files
authored
CEXT-5395: Automatic Error Status on Root Spans (#47)
* feat: make root span be marked as errored by default according to app builder standard * test: add tests * chore: add changeset * docs: update docs * refactor: simplify successful condition check
1 parent 3195354 commit 6fb3032

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

.changeset/olive-boats-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@adobe/aio-lib-telemetry": minor
3+
---
4+
5+
Automatically mark root spans with an error status if they return a response that matches an unsuccessful response shape (according to [App Builder documentation](https://developer.adobe.com/app-builder/docs/guides/runtime_guides/creating-actions#unsuccessful-response))

docs/usage.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,12 +481,21 @@ Example use cases on when you might want to use these options are:
481481
- **Customizing Span Names**: If you want to use a custom span name for a function, you can set the [`spanConfig.spanName`](./api-reference/interfaces/InstrumentationConfig.md#spanname) option. There are other span configuration options available, see the API reference for [`SpanConfig`](./api-reference/interfaces/InstrumentationConfig.md#spanconfig) for more details.
482482
- **Reacting to the Result**: If you want to react to the result of a function, you can set the [`onResult`](./api-reference/interfaces/InstrumentationConfig.md#onresult) option.
483483
- **Handling Errors**: If you want to handle errors of a function, you can set the [`onError`](./api-reference/interfaces/InstrumentationConfig.md#onerror) option.
484-
- **Handling Success/Failure**: By default, the library considers a function successful if it doesn't throw an error. You can customize this behavior by setting the [`isSuccessful`](./api-reference/interfaces/InstrumentationConfig.md#issuccessful) option.
485-
- This option takes a function that receives the result and returns a boolean indicating whether the operation was successful.
486-
- The success/failure state may not matter for your use case. Internally, it determines when to trigger the [`onError`](./api-reference/interfaces/InstrumentationConfig.md#onerror) and [`onResult`](./api-reference/interfaces/InstrumentationConfig.md#onresult) hooks, and whether to [set the span status](https://opentelemetry.io/docs/concepts/signals/traces/#span-status) to `OK` or `ERROR`. Different observability backends might interpret these statuses in their own way.
484+
- **Handling Success/Failure**: See the [Span Status](#span-status) section below for more details.
487485

488486
See the API reference for the configuration options available: [`InstrumentationConfig`](./api-reference/interfaces/InstrumentationConfig.md).
489487

488+
### Span Status
489+
490+
By default, the library considers a function successful if it doesn't throw an error. You can customize this behavior using the [`isSuccessful`](./api-reference/interfaces/InstrumentationConfig.md#issuccessful) option.
491+
492+
- This option accepts a function that receives the result and returns a boolean indicating whether the operation succeeded.
493+
- The success/failure state may not be relevant to your use case. Internally, it determines when to trigger the [`onError`](./api-reference/interfaces/InstrumentationConfig.md#onerror) and [`onResult`](./api-reference/interfaces/InstrumentationConfig.md#onresult) hooks, and whether to [set the span status](https://opentelemetry.io/docs/concepts/signals/traces/#span-status) to `OK` or `ERROR`. Note that different observability backends may interpret these statuses differently.
494+
495+
#### Runtime Action Success/Failure
496+
497+
App Builder determines action failure by looking for an `error` property in the result (see [this section](https://developer.adobe.com/app-builder/docs/guides/runtime_guides/creating-actions#unsuccessful-response) of the App Builder documentation for more details). When using the `instrumentEntrypoint` helper (the one applied to the `main` function), this behavior is replicated to evaluate the success/failure state of the root span. The helper reads the response object and sets the span status accordingly by providing a default implementation for the [`isSuccessful`](./api-reference/interfaces/InstrumentationConfig.md#issuccessful) option that performs this `error` property check. You can override this behavior if needed by setting a custom `isSuccessful` function.
498+
490499
## Additional Resources
491500

492501
### API Reference

source/core/instrumentation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,16 @@ export function instrumentEntrypoint<
376376
) {
377377
const { actionName } = getRuntimeActionMetadata();
378378
return instrument(handler, {
379+
// App Builder expects an "error" property in the result to determine if the action failed.
380+
// See: https://developer.adobe.com/app-builder/docs/guides/runtime_guides/creating-actions#unsuccessful-response
381+
isSuccessful: (result) => {
382+
// Only do the check if the result is an object (should always be the case)
383+
// Otherwise default to true (non-intrusive)
384+
return typeof result === "object"
385+
? result && !("error" in result)
386+
: true;
387+
},
388+
379389
...instrumentationConfig,
380390
spanConfig: {
381391
spanName: `${actionName}/${fn.name || "entrypoint"}`,

tests/unit/core/instrumentation.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,5 +762,44 @@ describe("core/instrumentation", () => {
762762
namedEntrypoint({});
763763
expect(mockSpan.registerName).toHaveBeenCalledWith("test-action/main");
764764
});
765+
766+
test("should mark root span as error if the runtime action fails", () => {
767+
const error = {
768+
statusCode: 500,
769+
message: "Runtime action failed",
770+
};
771+
772+
const instrumentedMain = instrumentation.instrumentEntrypoint(
773+
function main() {
774+
return { error };
775+
},
776+
{
777+
initializeTelemetry: mockInitializeTelemetry,
778+
},
779+
);
780+
781+
const result = instrumentedMain({});
782+
expect(result).toEqual({ error });
783+
expect(mockSpan.setStatus).toHaveBeenCalledWith({
784+
code: SpanStatusCode.ERROR,
785+
});
786+
});
787+
788+
test("should not instrusively mark root span if the runtime action returns a non-object", () => {
789+
const instrumentedMain = instrumentation.instrumentEntrypoint(
790+
function main() {
791+
return "test";
792+
},
793+
{
794+
initializeTelemetry: mockInitializeTelemetry,
795+
},
796+
);
797+
798+
const result = instrumentedMain({});
799+
expect(result).toEqual("test");
800+
expect(mockSpan.setStatus).not.toHaveBeenCalledWith({
801+
code: SpanStatusCode.ERROR,
802+
});
803+
});
765804
});
766805
});

0 commit comments

Comments
 (0)