Skip to content

Commit 4f71dd4

Browse files
fix: make OTEL evaluation event attribute types compliant to OTEL types (#1237)
Signed-off-by: Lukas Reining <[email protected]>
1 parent ee23639 commit 4f71dd4

File tree

3 files changed

+48
-9
lines changed

3 files changed

+48
-9
lines changed

packages/shared/src/telemetry/attributes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export const TelemetryAttribute = {
4242
*
4343
* - type: `undefined`
4444
* - requirement level: `conditionally required`
45-
* - condition: variant is not defined on the evaluation details
4645
* - example: `#ff0000`; `1`; `true`
4746
*/
4847
VALUE: 'feature_flag.result.value',

packages/shared/src/telemetry/evaluation-event.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import { ErrorCode, StandardResolutionReasons, type EvaluationDetails, type FlagValue } from '../evaluation/evaluation';
2-
import type { HookContext } from '../hooks/hooks';
1+
import { ErrorCode, StandardResolutionReasons, type EvaluationDetails, type FlagValue } from '../evaluation';
2+
import type { HookContext } from '../hooks';
33
import { TelemetryAttribute } from './attributes';
44
import { TelemetryFlagMetadata } from './flag-metadata';
55

6+
/**
7+
* Attribute types for OpenTelemetry.
8+
* @see https://github.com/open-telemetry/opentelemetry-js/blob/fbbce6e1c0de86e4c504b5788d876fae4d3bc254/api/src/common/Attributes.ts#L35
9+
*/
10+
export declare type AttributeValue =
11+
| string
12+
| number
13+
| boolean
14+
| string[]
15+
| number[]
16+
| boolean[];
17+
618
type EvaluationEvent = {
719
/**
820
* The name of the feature flag evaluation event.
@@ -13,7 +25,7 @@ type EvaluationEvent = {
1325
* @experimental The attributes are subject to change.
1426
* @see https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
1527
*/
16-
attributes: Record<string, string | number | boolean | FlagValue>;
28+
attributes: Record<string, AttributeValue | undefined>;
1729
};
1830

1931
const FLAG_EVALUATION_EVENT_NAME = 'feature_flag.evaluation';
@@ -36,12 +48,23 @@ export function createEvaluationEvent(
3648

3749
if (evaluationDetails.variant) {
3850
attributes[TelemetryAttribute.VARIANT] = evaluationDetails.variant;
39-
} else {
40-
attributes[TelemetryAttribute.VALUE] = evaluationDetails.value;
51+
}
52+
53+
if (evaluationDetails.value !== null) {
54+
if (typeof evaluationDetails.value !== 'object') {
55+
attributes[TelemetryAttribute.VALUE] = evaluationDetails.value;
56+
} else {
57+
try {
58+
// Objects are not valid attribute values, so we convert them to a JSON string
59+
attributes[TelemetryAttribute.VALUE] = JSON.stringify(evaluationDetails.value);
60+
} catch {
61+
// We ignore non serializable values
62+
}
63+
}
4164
}
4265

4366
const contextId =
44-
evaluationDetails.flagMetadata[TelemetryFlagMetadata.CONTEXT_ID] || hookContext.context.targetingKey;
67+
evaluationDetails.flagMetadata[TelemetryFlagMetadata.CONTEXT_ID] ?? hookContext.context.targetingKey;
4568
if (contextId) {
4669
attributes[TelemetryAttribute.CONTEXT_ID] = contextId;
4770
}

packages/shared/test/telemetry.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('evaluationEvent', () => {
4949
});
5050
});
5151

52-
it('should include variant when provided', () => {
52+
it('should include variant and value when provided', () => {
5353
const details: EvaluationDetails<boolean> = {
5454
flagKey,
5555
value: true,
@@ -61,7 +61,24 @@ describe('evaluationEvent', () => {
6161
const result = createEvaluationEvent(mockHookContext, details);
6262

6363
expect(result.attributes[TelemetryAttribute.VARIANT]).toBe('test-variant');
64-
expect(result.attributes[TelemetryAttribute.VALUE]).toBeUndefined();
64+
expect(result.attributes[TelemetryAttribute.VALUE]).toEqual(true);
65+
});
66+
67+
it('should include object values as strings', () => {
68+
const flagValue = { key: 'value' };
69+
70+
const details: EvaluationDetails<typeof flagValue> = {
71+
flagKey,
72+
value: flagValue,
73+
variant: 'test-variant',
74+
reason: StandardResolutionReasons.STATIC,
75+
flagMetadata: {},
76+
};
77+
78+
const result = createEvaluationEvent(mockHookContext, details);
79+
80+
expect(result.attributes[TelemetryAttribute.VARIANT]).toBe('test-variant');
81+
expect(result.attributes[TelemetryAttribute.VALUE]).toEqual(JSON.stringify(flagValue));
6582
});
6683

6784
it('should include flag metadata when provided', () => {

0 commit comments

Comments
 (0)