Skip to content

Commit 57fa91d

Browse files
authored
[OB-139] add new evaluation attributes reported by @launchdarkly/observability (#39)
## Summary Ensures the LD integration correctly reports additional `evaluation` span attributes. See [tech spec](https://docs.google.com/document/d/1X8L0nl0wfEXAXbuq1Onju_cGcOIEHb_mw_6LEKVkhlY/edit?tab=t.0#heading=h.y41mcmkl1t79) for more details. ## How did you test this change? Local app ![Screenshot 2025-05-15 at 16 25 59](https://github.com/user-attachments/assets/57bfece1-f3d8-44e6-a4e5-47361b815a5b) ## Are there any deployment considerations? changeset ## Does this work require review from our design team? no <!-- ld-jira-link --> --- Related Jira issue: [OB-139: Add feature_flag.set.id and inExperiment attributes to eval span events](https://launchdarkly.atlassian.net/browse/OB-139) <!-- end-ld-jira-link -->
1 parent 5b6d2e1 commit 57fa91d

File tree

3 files changed

+68
-42
lines changed

3 files changed

+68
-42
lines changed

.changeset/poor-trains-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'highlight.run': patch
3+
---
4+
5+
add new evaluation attributes reported by @launchdarkly/observability

sdk/highlight-run/src/integrations/launchdarkly/index.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@ export type { Hook, LDClient }
2020

2121
export const FEATURE_FLAG_SCOPE = 'feature_flag'
2222
export const LD_SCOPE = 'launchdarkly'
23+
export const FEATURE_FLAG_SPAN_NAME = 'evaluation'
24+
2325
export const FEATURE_FLAG_ENV_ATTR = `${FEATURE_FLAG_SCOPE}.set.id`
2426
export const FEATURE_FLAG_KEY_ATTR = `${FEATURE_FLAG_SCOPE}.key`
25-
export const FEATURE_FLAG_CONTEXT_ATTR = `${FEATURE_FLAG_SCOPE}.context`
26-
export const FEATURE_FLAG_CONTEXT_KEY_ATTR = `${FEATURE_FLAG_CONTEXT_ATTR}.key`
27+
export const FEATURE_FLAG_CONTEXT_ATTR = `${FEATURE_FLAG_SCOPE}.contextKeys`
28+
export const FEATURE_FLAG_CONTEXT_ID_ATTR = `${FEATURE_FLAG_SCOPE}.context.id`
29+
export const FEATURE_FLAG_VALUE_ATTR = `${FEATURE_FLAG_SCOPE}.result.value`
2730
export const FEATURE_FLAG_PROVIDER_ATTR = `${FEATURE_FLAG_SCOPE}.provider.name`
28-
export const FEATURE_FLAG_VARIANT_ATTR = `${FEATURE_FLAG_SCOPE}.variant`
29-
export const FEATURE_FLAG_RESULT_VARIANT_ATTR = `${FEATURE_FLAG_SCOPE}.result.variant`
30-
export const FEATURE_FLAG_PROVIDER_NAME_ATTR = `${FEATURE_FLAG_SCOPE}.provider_name`
3131
export const FEATURE_FLAG_IN_EXPERIMENT_ATTR = `${FEATURE_FLAG_SCOPE}.result.reason.inExperiment`
3232
export const FEATURE_FLAG_VARIATION_INDEX_ATTR = `${FEATURE_FLAG_SCOPE}.result.variationIndex`
3333
export const FEATURE_FLAG_APP_ID_ATTR = `${LD_SCOPE}.application.id`
3434
export const FEATURE_FLAG_APP_VERSION_ATTR = `${LD_SCOPE}.application.version`
35-
export const FEATURE_FLAG_SPAN_NAME = 'evaluation'
3635

3736
export const LD_INITIALIZE_EVENT = '$ld:telemetry:session:init'
3837
export const LD_ERROR_EVENT = '$ld:telemetry:error'
@@ -112,26 +111,34 @@ export function setupLaunchDarklyIntegration(
112111
},
113112
afterEvaluation: (hookContext, data, detail) => {
114113
const eventAttributes: Attributes = {
114+
[FEATURE_FLAG_PROVIDER_ATTR]: 'LaunchDarkly',
115115
[FEATURE_FLAG_KEY_ATTR]: hookContext.flagKey,
116-
[FEATURE_FLAG_PROVIDER_NAME_ATTR]: 'LaunchDarkly',
117-
[FEATURE_FLAG_VARIANT_ATTR]: JSON.stringify(detail.value),
118-
[FEATURE_FLAG_RESULT_VARIANT_ATTR]: JSON.stringify(
119-
detail.value,
120-
),
121-
[FEATURE_FLAG_IN_EXPERIMENT_ATTR]: detail.reason?.inExperiment,
122-
[FEATURE_FLAG_VARIATION_INDEX_ATTR]:
123-
detail.variationIndex ?? undefined,
116+
[FEATURE_FLAG_VALUE_ATTR]: JSON.stringify(detail.value),
117+
// only set the following keys when values are truthy
118+
...(detail.reason?.inExperiment
119+
? {
120+
[FEATURE_FLAG_IN_EXPERIMENT_ATTR]:
121+
detail.reason.inExperiment,
122+
}
123+
: {}),
124+
...(detail.variationIndex
125+
? {
126+
[FEATURE_FLAG_VARIATION_INDEX_ATTR]:
127+
detail.variationIndex,
128+
}
129+
: {}),
124130
}
125131

126132
if (hookContext.context) {
127133
eventAttributes[FEATURE_FLAG_CONTEXT_ATTR] = JSON.stringify(
128134
getCanonicalObj(hookContext.context),
129135
)
130-
eventAttributes[FEATURE_FLAG_CONTEXT_KEY_ATTR] =
131-
getCanonicalKey(hookContext.context)
136+
eventAttributes[FEATURE_FLAG_CONTEXT_ID_ATTR] = getCanonicalKey(
137+
hookContext.context,
138+
)
132139
}
133140

134-
hClient.startSpan(FEATURE_FLAG_SPAN_NAME, eventAttributes, (s) => {
141+
hClient.startSpan(FEATURE_FLAG_SPAN_NAME, (s) => {
135142
if (s) {
136143
s.addEvent(FEATURE_FLAG_SCOPE, eventAttributes)
137144
}

sdk/highlight-run/src/plugins/observe.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import type { LDPlugin, LDPluginEnvironmentMetadata } from './plugin'
2-
import type { Hook, LDClient } from '../integrations/launchdarkly'
3-
import { Observe as ObserveAPI } from '../api/observe'
4-
import { ObserveSDK } from '../sdk/observe'
5-
import { LDObserve } from '../sdk/LDObserve'
62
import {
73
FEATURE_FLAG_APP_ID_ATTR,
84
FEATURE_FLAG_APP_VERSION_ATTR,
95
FEATURE_FLAG_CONTEXT_ATTR,
10-
FEATURE_FLAG_CONTEXT_KEY_ATTR,
6+
FEATURE_FLAG_CONTEXT_ID_ATTR,
117
FEATURE_FLAG_ENV_ATTR,
8+
FEATURE_FLAG_IN_EXPERIMENT_ATTR,
129
FEATURE_FLAG_KEY_ATTR,
1310
FEATURE_FLAG_PROVIDER_ATTR,
1411
FEATURE_FLAG_SCOPE,
1512
FEATURE_FLAG_SPAN_NAME,
16-
FEATURE_FLAG_VARIANT_ATTR,
13+
FEATURE_FLAG_VALUE_ATTR,
14+
FEATURE_FLAG_VARIATION_INDEX_ATTR,
1715
getCanonicalKey,
1816
getCanonicalObj,
17+
Hook,
18+
LDClient,
1919
} from '../integrations/launchdarkly'
20+
import { Observe as ObserveAPI } from '../api/observe'
21+
import { ObserveSDK } from '../sdk/observe'
22+
import { LDObserve } from '../sdk/LDObserve'
2023
import type { ObserveOptions } from '../client/types/observe'
2124
import { Plugin } from './common'
2225
import {
@@ -80,9 +83,16 @@ export class Observe extends Plugin<ObserveOptions> implements LDPlugin {
8083
[ATTR_TELEMETRY_SDK_NAME]: metadata.sdk.name,
8184
[ATTR_TELEMETRY_SDK_VERSION]: metadata.sdk.version,
8285
[FEATURE_FLAG_ENV_ATTR]: metadata.clientSideId,
83-
[FEATURE_FLAG_APP_ID_ATTR]: metadata.application?.id,
84-
[FEATURE_FLAG_APP_VERSION_ATTR]: metadata.application?.version,
8586
[FEATURE_FLAG_PROVIDER_ATTR]: 'LaunchDarkly',
87+
...(metadata.application?.id
88+
? { [FEATURE_FLAG_APP_ID_ATTR]: metadata.application.id }
89+
: {}),
90+
...(metadata.application?.version
91+
? {
92+
[FEATURE_FLAG_APP_VERSION_ATTR]:
93+
metadata.application.version,
94+
}
95+
: {}),
8696
}
8797
return [
8898
{
@@ -115,30 +125,34 @@ export class Observe extends Plugin<ObserveOptions> implements LDPlugin {
115125

116126
const eventAttributes: Attributes = {
117127
[FEATURE_FLAG_KEY_ATTR]: hookContext.flagKey,
118-
[FEATURE_FLAG_VARIANT_ATTR]: JSON.stringify(
119-
detail.value,
120-
),
128+
[FEATURE_FLAG_VALUE_ATTR]: JSON.stringify(detail.value),
129+
// only set the following keys when values are truthy
130+
...(detail.reason?.inExperiment
131+
? {
132+
[FEATURE_FLAG_IN_EXPERIMENT_ATTR]:
133+
detail.reason.inExperiment,
134+
}
135+
: {}),
136+
...(detail.variationIndex
137+
? {
138+
[FEATURE_FLAG_VARIATION_INDEX_ATTR]:
139+
detail.variationIndex,
140+
}
141+
: {}),
121142
}
122143

123144
if (hookContext.context) {
124145
eventAttributes[FEATURE_FLAG_CONTEXT_ATTR] =
125146
JSON.stringify(getCanonicalObj(hookContext.context))
126-
eventAttributes[FEATURE_FLAG_CONTEXT_KEY_ATTR] =
147+
eventAttributes[FEATURE_FLAG_CONTEXT_ID_ATTR] =
127148
getCanonicalKey(hookContext.context)
128149
}
129-
130-
this.observe.startSpan(
131-
FEATURE_FLAG_SPAN_NAME,
132-
{
133-
...metaAttrs,
134-
attributes: eventAttributes,
135-
},
136-
(s) => {
137-
if (s) {
138-
s.addEvent(FEATURE_FLAG_SCOPE, eventAttributes)
139-
}
140-
},
141-
)
150+
const attributes = { ...metaAttrs, ...eventAttributes }
151+
this.observe.startSpan(FEATURE_FLAG_SPAN_NAME, (s) => {
152+
if (s) {
153+
s.addEvent(FEATURE_FLAG_SCOPE, attributes)
154+
}
155+
})
142156

143157
return data
144158
},

0 commit comments

Comments
 (0)