Skip to content

Commit a70dae1

Browse files
committed
add otel compatible telemetry signal
1 parent 06b6f6b commit a70dae1

File tree

3 files changed

+470
-0
lines changed

3 files changed

+470
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.openfeature.sdk
2+
3+
/**
4+
* OpenTelemetry compatible telemetry signal for flag evaluations. Can be created by calling [createEvaluationEvent].
5+
*
6+
* See
7+
* [TELEMETRY_KEY],
8+
* [TELEMETRY_ERROR_CODE],
9+
* [TELEMETRY_VARIANT],
10+
* [TELEMETRY_CONTEXT_ID],
11+
* [TELEMETRY_ERROR_MSG],
12+
* [TELEMETRY_REASON],
13+
* [TELEMETRY_PROVIDER],
14+
* [TELEMETRY_FLAG_SET_ID],
15+
* [TELEMETRY_VERSION],
16+
* [TELEMETRY_FLAG_META_CONTEXT_ID],
17+
* [TELEMETRY_FLAG_META_FLAG_SET_ID],
18+
* [TELEMETRY_FLAG_META_VERSION],
19+
* [TELEMETRY_BODY] and
20+
* [FLAG_EVALUATION_EVENT_NAME]
21+
* for attribute and body keys.
22+
*/
23+
data class EvaluationEvent(
24+
val name: String,
25+
val attributes: Map<String, Any?>,
26+
val body: Map<String, Any?>,
27+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package dev.openfeature.sdk
2+
3+
import dev.openfeature.sdk.exceptions.ErrorCode
4+
5+
const val TELEMETRY_KEY = "feature_flag.key"
6+
const val TELEMETRY_ERROR_CODE = "error.type"
7+
const val TELEMETRY_VARIANT = "feature_flag.variant"
8+
const val TELEMETRY_CONTEXT_ID = "feature_flag.context.id"
9+
const val TELEMETRY_ERROR_MSG = "feature_flag.evaluation.error.message"
10+
const val TELEMETRY_REASON = "feature_flag.evaluation.reason"
11+
const val TELEMETRY_PROVIDER = "feature_flag.provider_name"
12+
const val TELEMETRY_FLAG_SET_ID = "feature_flag.set.id"
13+
const val TELEMETRY_VERSION = "feature_flag.version"
14+
15+
// Well-known flag metadata attributes for telemetry events.
16+
// Specification: https://openfeature.dev/specification/appendix-d#flag-metadata
17+
const val TELEMETRY_FLAG_META_CONTEXT_ID = "contextId"
18+
const val TELEMETRY_FLAG_META_FLAG_SET_ID = "flagSetId"
19+
const val TELEMETRY_FLAG_META_VERSION = "version"
20+
21+
// OpenTelemetry event body.
22+
// Specification: https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/
23+
const val TELEMETRY_BODY = "value"
24+
25+
const val FLAG_EVALUATION_EVENT_NAME = "feature_flag.evaluation"
26+
27+
/**
28+
* Creates an [EvaluationEvent] from a flag evaluation. To be used inside a finally hook to provide an OpenTelemetry
29+
* compatible telemetry signal.
30+
*/
31+
fun <T> createEvaluationEvent(
32+
hookContext: HookContext<T>,
33+
providerEvaluation: ProviderEvaluation<T>
34+
): EvaluationEvent {
35+
val attributes = mutableMapOf<String, Any?>()
36+
val body = mutableMapOf<String, Any?>()
37+
attributes[TELEMETRY_KEY] = hookContext.flagKey
38+
attributes[TELEMETRY_PROVIDER] = hookContext.providerMetadata.name ?: ""
39+
attributes[TELEMETRY_REASON] = providerEvaluation.reason?.lowercase() ?: Reason.UNKNOWN.name.lowercase()
40+
attributes[TELEMETRY_CONTEXT_ID] =
41+
providerEvaluation.metadata.getString(TELEMETRY_FLAG_META_CONTEXT_ID) ?: hookContext.ctx?.getTargetingKey()
42+
providerEvaluation.metadata.getString(TELEMETRY_FLAG_META_FLAG_SET_ID)?.let { attributes[TELEMETRY_FLAG_SET_ID] = it }
43+
providerEvaluation.metadata.getString(TELEMETRY_FLAG_META_VERSION)?.let { attributes[TELEMETRY_VERSION] = it }
44+
45+
val variant = providerEvaluation.variant
46+
if (variant == null) {
47+
body[TELEMETRY_BODY] = providerEvaluation.value
48+
} else {
49+
attributes[TELEMETRY_VARIANT] = variant
50+
}
51+
52+
if (providerEvaluation.reason == Reason.ERROR.name) {
53+
attributes[TELEMETRY_ERROR_CODE] = providerEvaluation.errorCode ?: ErrorCode.GENERAL
54+
providerEvaluation.errorMessage?.let { attributes[TELEMETRY_ERROR_MSG] = it }
55+
}
56+
57+
return EvaluationEvent(
58+
FLAG_EVALUATION_EVENT_NAME,
59+
attributes,
60+
body
61+
)
62+
}

0 commit comments

Comments
 (0)