From d42142166ca23e89f7f1f1c13507c4c4a351a5a1 Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 12 Sep 2025 17:49:38 +0200 Subject: [PATCH 1/5] fix: make OTEL evaluation event attribute types compliant to OTEL types Signed-off-by: Lukas Reining --- .../shared/src/telemetry/evaluation-event.ts | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/telemetry/evaluation-event.ts b/packages/shared/src/telemetry/evaluation-event.ts index 81a85aad2..603017f00 100644 --- a/packages/shared/src/telemetry/evaluation-event.ts +++ b/packages/shared/src/telemetry/evaluation-event.ts @@ -1,8 +1,20 @@ -import { ErrorCode, StandardResolutionReasons, type EvaluationDetails, type FlagValue } from '../evaluation/evaluation'; -import type { HookContext } from '../hooks/hooks'; +import { ErrorCode, StandardResolutionReasons, type EvaluationDetails, type FlagValue } from '../evaluation'; +import type { HookContext } from '../hooks'; import { TelemetryAttribute } from './attributes'; import { TelemetryFlagMetadata } from './flag-metadata'; +/** + * Attribute types for OpenTelemetry. + * @see https://github.com/open-telemetry/opentelemetry-js/blob/fbbce6e1c0de86e4c504b5788d876fae4d3bc254/api/src/common/Attributes.ts#L35 + */ +export declare type AttributeValue = + | string + | number + | boolean + | Array + | Array + | Array; + type EvaluationEvent = { /** * The name of the feature flag evaluation event. @@ -13,7 +25,7 @@ type EvaluationEvent = { * @experimental The attributes are subject to change. * @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/ */ - attributes: Record; + attributes: Record; }; const FLAG_EVALUATION_EVENT_NAME = 'feature_flag.evaluation'; @@ -36,12 +48,23 @@ export function createEvaluationEvent( if (evaluationDetails.variant) { attributes[TelemetryAttribute.VARIANT] = evaluationDetails.variant; - } else { - attributes[TelemetryAttribute.VALUE] = evaluationDetails.value; + } + + if (evaluationDetails.value !== null) { + if (typeof evaluationDetails.value !== 'object') { + attributes[TelemetryAttribute.VALUE] = evaluationDetails.value; + } else { + try { + // Objects are not valid attribute values, so we convert them to a JSON string + attributes[TelemetryAttribute.VALUE] = JSON.stringify(evaluationDetails.value); + } catch { + // We ignore non serializable values + } + } } const contextId = - evaluationDetails.flagMetadata[TelemetryFlagMetadata.CONTEXT_ID] || hookContext.context.targetingKey; + evaluationDetails.flagMetadata[TelemetryFlagMetadata.CONTEXT_ID] ?? hookContext.context.targetingKey; if (contextId) { attributes[TelemetryAttribute.CONTEXT_ID] = contextId; } From bdf3e68bd0770cb388cfd094489edccae53742d8 Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 12 Sep 2025 17:57:13 +0200 Subject: [PATCH 2/5] fix: remove condition for value Signed-off-by: Lukas Reining --- packages/shared/src/telemetry/attributes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared/src/telemetry/attributes.ts b/packages/shared/src/telemetry/attributes.ts index c61808456..4ec6b214b 100644 --- a/packages/shared/src/telemetry/attributes.ts +++ b/packages/shared/src/telemetry/attributes.ts @@ -42,7 +42,6 @@ export const TelemetryAttribute = { * * - type: `undefined` * - requirement level: `conditionally required` - * - condition: variant is not defined on the evaluation details * - example: `#ff0000`; `1`; `true` */ VALUE: 'feature_flag.result.value', From 24ac3380a3580294f61573d9fa45b60674c82c66 Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 12 Sep 2025 18:00:24 +0200 Subject: [PATCH 3/5] fix: test Signed-off-by: Lukas Reining --- packages/shared/test/telemetry.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/test/telemetry.spec.ts b/packages/shared/test/telemetry.spec.ts index c8ca5f48d..1c78ec5e3 100644 --- a/packages/shared/test/telemetry.spec.ts +++ b/packages/shared/test/telemetry.spec.ts @@ -49,7 +49,7 @@ describe('evaluationEvent', () => { }); }); - it('should include variant when provided', () => { + it('should include variant and value when provided', () => { const details: EvaluationDetails = { flagKey, value: true, @@ -61,7 +61,7 @@ describe('evaluationEvent', () => { const result = createEvaluationEvent(mockHookContext, details); expect(result.attributes[TelemetryAttribute.VARIANT]).toBe('test-variant'); - expect(result.attributes[TelemetryAttribute.VALUE]).toBeUndefined(); + expect(result.attributes[TelemetryAttribute.VALUE]).toEqual(true); }); it('should include flag metadata when provided', () => { From 6d01a062cca3cf96c8a1d70602f456858ec7b78f Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 12 Sep 2025 18:02:18 +0200 Subject: [PATCH 4/5] fix: add test for object serialization Signed-off-by: Lukas Reining --- packages/shared/test/telemetry.spec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/shared/test/telemetry.spec.ts b/packages/shared/test/telemetry.spec.ts index 1c78ec5e3..5d15e0a4f 100644 --- a/packages/shared/test/telemetry.spec.ts +++ b/packages/shared/test/telemetry.spec.ts @@ -64,6 +64,23 @@ describe('evaluationEvent', () => { expect(result.attributes[TelemetryAttribute.VALUE]).toEqual(true); }); + it('should include object values as strings', () => { + const flagValue = { key: 'value' }; + + const details: EvaluationDetails = { + flagKey, + value: flagValue, + variant: 'test-variant', + reason: StandardResolutionReasons.STATIC, + flagMetadata: {}, + }; + + const result = createEvaluationEvent(mockHookContext, details); + + expect(result.attributes[TelemetryAttribute.VARIANT]).toBe('test-variant'); + expect(result.attributes[TelemetryAttribute.VALUE]).toEqual(JSON.stringify(flagValue)); + }); + it('should include flag metadata when provided', () => { const details: EvaluationDetails = { flagKey, From 81c480fe5458038a245260075bbbb77c879bd9d4 Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 12 Sep 2025 19:22:07 +0200 Subject: [PATCH 5/5] Update evaluation-event.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Lukas Reining --- packages/shared/src/telemetry/evaluation-event.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/telemetry/evaluation-event.ts b/packages/shared/src/telemetry/evaluation-event.ts index 603017f00..d23e024fc 100644 --- a/packages/shared/src/telemetry/evaluation-event.ts +++ b/packages/shared/src/telemetry/evaluation-event.ts @@ -11,9 +11,9 @@ export declare type AttributeValue = | string | number | boolean - | Array - | Array - | Array; + | string[] + | number[] + | boolean[]; type EvaluationEvent = { /**