From d70a5a5d22f546e7cf095aeca0b8e47022ccbf86 Mon Sep 17 00:00:00 2001 From: Amir Blum Date: Mon, 6 May 2024 13:57:55 +0300 Subject: [PATCH] feat(instrumentation): add util to execute span customization hook in base class (#4663) * feat(instrumentation): hoist span event hook execution to base class * test: add test for new hook runner * chore: changelog * fix: use event name from arguments * fix: remove unused import * fix: make diag message structual * make the private function start with underscore * chore: rename insetrumentation event to span customization hook * chore: update changelog * chore: lint fix * Update experimental/packages/opentelemetry-instrumentation/src/instrumentation.ts Co-authored-by: Marc Pichler * chore: move CHANGELOG to experimental * fix: changelog --------- Co-authored-by: Marc Pichler --- CHANGELOG.md | 1 - experimental/CHANGELOG.md | 2 ++ .../src/instrumentation.ts | 31 ++++++++++++++++++ .../src/types.ts | 23 ++++++++++++- .../test/common/Instrumentation.test.ts | 32 +++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c712f777419..67c447ab70c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) -* feat(instrumentation): generic config type in instrumentation base [#4659](https://github.com/open-telemetry/opentelemetry-js/pull/4659) @blumamir * feat: support node 22 [#4666](https://github.com/open-telemetry/opentelemetry-js/pull/4666) @dyladan * feat(sdk-trace-node): support `xray` Propagator via `OTEL_PROPAGATORS` environment variable [#4602](https://github.com/open-telemetry/opentelemetry-js/pull/4602) @anuraags diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index f336cb0de9f..20e3fbfdab3 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -20,6 +20,8 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) +* feat(instrumentation): add util to execute span customization hook in base class [#4663](https://github.com/open-telemetry/opentelemetry-js/pull/4663) @blumamir +* feat(instrumentation): generic config type in instrumentation base [#4659](https://github.com/open-telemetry/opentelemetry-js/pull/4659) @blumamir * feat: support node 22 [#4666](https://github.com/open-telemetry/opentelemetry-js/pull/4666) @dyladan * feat(propagator-aws-xray-lambda): add AWS Xray Lambda propagator [4554](https://github.com/open-telemetry/opentelemetry-js/pull/4554) diff --git a/experimental/packages/opentelemetry-instrumentation/src/instrumentation.ts b/experimental/packages/opentelemetry-instrumentation/src/instrumentation.ts index bd4a280fc8e..551b1df11e9 100644 --- a/experimental/packages/opentelemetry-instrumentation/src/instrumentation.ts +++ b/experimental/packages/opentelemetry-instrumentation/src/instrumentation.ts @@ -23,6 +23,7 @@ import { trace, Tracer, TracerProvider, + Span, } from '@opentelemetry/api'; import { Logger, LoggerProvider, logs } from '@opentelemetry/api-logs'; import * as shimmer from 'shimmer'; @@ -30,6 +31,7 @@ import { InstrumentationModuleDefinition, Instrumentation, InstrumentationConfig, + SpanCustomizationHook, } from './types'; /** @@ -178,4 +180,33 @@ export abstract class InstrumentationAbstract< | InstrumentationModuleDefinition | InstrumentationModuleDefinition[] | void; + + /** + * Execute span customization hook, if configured, and log any errors. + * Any semantics of the trigger and info are defined by the specific instrumentation. + * @param hookHandler The optional hook handler which the user has configured via instrumentation config + * @param triggerName The name of the trigger for executing the hook for logging purposes + * @param span The span to which the hook should be applied + * @param info The info object to be passed to the hook, with useful data the hook may use + */ + protected _runSpanCustomizationHook( + hookHandler: SpanCustomizationHook | undefined, + triggerName: string, + span: Span, + info: SpanCustomizationInfoType + ) { + if (!hookHandler) { + return; + } + + try { + hookHandler(span, info); + } catch (e) { + this._diag.error( + `Error running span customization hook due to exception in handler`, + { triggerName }, + e + ); + } + } } diff --git a/experimental/packages/opentelemetry-instrumentation/src/types.ts b/experimental/packages/opentelemetry-instrumentation/src/types.ts index 5e74b06b5a8..3adc03921df 100644 --- a/experimental/packages/opentelemetry-instrumentation/src/types.ts +++ b/experimental/packages/opentelemetry-instrumentation/src/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TracerProvider, MeterProvider } from '@opentelemetry/api'; +import { TracerProvider, MeterProvider, Span } from '@opentelemetry/api'; import { LoggerProvider } from '@opentelemetry/api-logs'; /** Interface Instrumentation to apply patch. */ @@ -135,3 +135,24 @@ export interface InstrumentationModuleDefinition { // eslint-disable-next-line @typescript-eslint/no-explicit-any unpatch?: (moduleExports: any, moduleVersion?: string) => void; } + +/** + * SpanCustomizationHook is a common way for instrumentations to expose extension points + * where users can add custom behavior to a span based on info object passed to the hook at different times of the span lifecycle. + * This is an advanced feature, commonly used to add additional or non-spec-compliant attributes to the span, + * capture payloads, modify the span in some way, or carry some other side effect. + * + * The hook is registered with the instrumentation specific config by implementing an handler function with this signature, + * and if the hook is present, it will be called with the span and the event information + * when the event is emitted. + * + * When and under what conditions the hook is called and what data is passed + * in the info argument, is specific to each instrumentation and life-cycle event + * and should be documented where it is used. + * + * Instrumentation may define multiple hooks, for different spans, or different span life-cycle events. + */ +export type SpanCustomizationHook = ( + span: Span, + info: SpanCustomizationInfoType +) => void; diff --git a/experimental/packages/opentelemetry-instrumentation/test/common/Instrumentation.test.ts b/experimental/packages/opentelemetry-instrumentation/test/common/Instrumentation.test.ts index b35ccf504f7..9b0e916762a 100644 --- a/experimental/packages/opentelemetry-instrumentation/test/common/Instrumentation.test.ts +++ b/experimental/packages/opentelemetry-instrumentation/test/common/Instrumentation.test.ts @@ -20,6 +20,7 @@ import { InstrumentationBase, InstrumentationConfig, InstrumentationModuleDefinition, + SpanCustomizationHook, } from '../../src'; import { MeterProvider } from '@opentelemetry/sdk-metrics'; @@ -36,6 +37,12 @@ class TestInstrumentation extends InstrumentationBase { override enable() {} override disable() {} init() {} + + // the runInstrumentationEventHook, so we have to invoke it from the class for testing + testRunHook(hookHandler?: SpanCustomizationHook) { + const span = this.tracer.startSpan('test'); + this._runSpanCustomizationHook(hookHandler, 'test', span, {}); + } } describe('BaseInstrumentation', () => { @@ -182,5 +189,30 @@ describe('BaseInstrumentation', () => { assert.deepStrictEqual(instrumentation.getModuleDefinitions(), []); }); + + describe('runInstrumentationEventHook', () => { + it('should call the hook', () => { + const instrumentation = new TestInstrumentation({}); + let called = false; + const hook = () => { + called = true; + }; + instrumentation.testRunHook(hook); + assert.strictEqual(called, true); + }); + + it('empty hook should work', () => { + const instrumentation = new TestInstrumentation({}); + instrumentation.testRunHook(undefined); + }); + + it('exception in hook should not crash', () => { + const instrumentation = new TestInstrumentation({}); + const hook = () => { + throw new Error('test'); + }; + instrumentation.testRunHook(hook); + }); + }); }); });