Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/shared/src/telemetry/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
35 changes: 29 additions & 6 deletions packages/shared/src/telemetry/evaluation-event.ts
Original file line number Diff line number Diff line change
@@ -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<null | undefined | string>
| Array<null | undefined | number>
| Array<null | undefined | boolean>;

type EvaluationEvent = {
/**
* The name of the feature flag evaluation event.
Expand All @@ -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<string, string | number | boolean | FlagValue>;
attributes: Record<string, AttributeValue | undefined>;
};

const FLAG_EVALUATION_EVENT_NAME = 'feature_flag.evaluation';
Expand All @@ -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;
}
Expand Down
21 changes: 19 additions & 2 deletions packages/shared/test/telemetry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('evaluationEvent', () => {
});
});

it('should include variant when provided', () => {
it('should include variant and value when provided', () => {
const details: EvaluationDetails<boolean> = {
flagKey,
value: true,
Expand All @@ -61,7 +61,24 @@ 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 object values as strings', () => {
const flagValue = { key: 'value' };

const details: EvaluationDetails<typeof flagValue> = {
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', () => {
Expand Down