Skip to content

Commit fc0ff3d

Browse files
chrfwowbeeme1mrnicklasl
authored
feat: add otel compatible telemetry signal (#129)
Signed-off-by: christian.lutnik <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: Nicklas Lundin <[email protected]>
1 parent d6ac0a4 commit fc0ff3d

File tree

4 files changed

+564
-0
lines changed

4 files changed

+564
-0
lines changed

android/api/android.api

+33
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ public abstract interface class dev/openfeature/sdk/EvaluationContext : dev/open
3939
public abstract fun withTargetingKey (Ljava/lang/String;)Ldev/openfeature/sdk/EvaluationContext;
4040
}
4141

42+
public final class dev/openfeature/sdk/EvaluationEvent {
43+
public fun <init> (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V
44+
public final fun component1 ()Ljava/lang/String;
45+
public final fun component2 ()Ljava/util/Map;
46+
public final fun component3 ()Ljava/util/Map;
47+
public final fun copy (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)Ldev/openfeature/sdk/EvaluationEvent;
48+
public static synthetic fun copy$default (Ldev/openfeature/sdk/EvaluationEvent;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ldev/openfeature/sdk/EvaluationEvent;
49+
public fun equals (Ljava/lang/Object;)Z
50+
public final fun getAttributes ()Ljava/util/Map;
51+
public final fun getBody ()Ljava/util/Map;
52+
public final fun getName ()Ljava/lang/String;
53+
public fun hashCode ()I
54+
public fun toString ()Ljava/lang/String;
55+
}
56+
4257
public final class dev/openfeature/sdk/EvaluationMetadata {
4358
public static final field Companion Ldev/openfeature/sdk/EvaluationMetadata$Companion;
4459
public fun equals (Ljava/lang/Object;)Z
@@ -398,6 +413,24 @@ public abstract interface class dev/openfeature/sdk/Structure {
398413
public abstract fun keySet ()Ljava/util/Set;
399414
}
400415

416+
public final class dev/openfeature/sdk/TelemetryKt {
417+
public static final field FLAG_EVALUATION_EVENT_NAME Ljava/lang/String;
418+
public static final field TELEMETRY_BODY Ljava/lang/String;
419+
public static final field TELEMETRY_CONTEXT_ID Ljava/lang/String;
420+
public static final field TELEMETRY_ERROR_CODE Ljava/lang/String;
421+
public static final field TELEMETRY_ERROR_MSG Ljava/lang/String;
422+
public static final field TELEMETRY_FLAG_META_CONTEXT_ID Ljava/lang/String;
423+
public static final field TELEMETRY_FLAG_META_FLAG_SET_ID Ljava/lang/String;
424+
public static final field TELEMETRY_FLAG_META_VERSION Ljava/lang/String;
425+
public static final field TELEMETRY_FLAG_SET_ID Ljava/lang/String;
426+
public static final field TELEMETRY_KEY Ljava/lang/String;
427+
public static final field TELEMETRY_PROVIDER Ljava/lang/String;
428+
public static final field TELEMETRY_REASON Ljava/lang/String;
429+
public static final field TELEMETRY_VARIANT Ljava/lang/String;
430+
public static final field TELEMETRY_VERSION Ljava/lang/String;
431+
public static final fun createEvaluationEvent (Ldev/openfeature/sdk/HookContext;Ldev/openfeature/sdk/FlagEvaluationDetails;)Ldev/openfeature/sdk/EvaluationEvent;
432+
}
433+
401434
public abstract interface class dev/openfeature/sdk/Tracking {
402435
public abstract fun track (Ljava/lang/String;Ldev/openfeature/sdk/TrackingEventDetails;)V
403436
}
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,64 @@
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+
flagEvaluationDetails: FlagEvaluationDetails<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] = flagEvaluationDetails.reason?.lowercase() ?: Reason.UNKNOWN.name.lowercase()
40+
attributes[TELEMETRY_CONTEXT_ID] =
41+
flagEvaluationDetails.metadata.getString(TELEMETRY_FLAG_META_CONTEXT_ID) ?: hookContext.ctx?.getTargetingKey()
42+
flagEvaluationDetails.metadata.getString(TELEMETRY_FLAG_META_FLAG_SET_ID)?.let {
43+
attributes[TELEMETRY_FLAG_SET_ID] = it
44+
}
45+
flagEvaluationDetails.metadata.getString(TELEMETRY_FLAG_META_VERSION)?.let { attributes[TELEMETRY_VERSION] = it }
46+
47+
val variant = flagEvaluationDetails.variant
48+
if (variant == null) {
49+
body[TELEMETRY_BODY] = flagEvaluationDetails.value
50+
} else {
51+
attributes[TELEMETRY_VARIANT] = variant
52+
}
53+
54+
if (flagEvaluationDetails.reason == Reason.ERROR.name) {
55+
attributes[TELEMETRY_ERROR_CODE] = flagEvaluationDetails.errorCode ?: ErrorCode.GENERAL
56+
flagEvaluationDetails.errorMessage?.let { attributes[TELEMETRY_ERROR_MSG] = it }
57+
}
58+
59+
return EvaluationEvent(
60+
FLAG_EVALUATION_EVENT_NAME,
61+
attributes,
62+
body
63+
)
64+
}

0 commit comments

Comments
 (0)