Skip to content

Commit 3e2da02

Browse files
committed
feat: update opentelemetry hook to latest version of semantic conventions
Signed-off-by: Federico Bond <[email protected]>
1 parent 97a5717 commit 3e2da02

File tree

4 files changed

+97
-20
lines changed

4 files changed

+97
-20
lines changed

hooks/openfeature-hooks-opentelemetry/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ keywords = []
1818
dependencies = [
1919
"openfeature-sdk>=0.6.0",
2020
"opentelemetry-api",
21+
"opentelemetry-semantic-conventions",
2122
]
2223
requires-python = ">=3.9"
2324

hooks/openfeature-hooks-opentelemetry/src/openfeature/contrib/hook/opentelemetry/__init__.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import json
22

3-
from openfeature.flag_evaluation import FlagEvaluationDetails
3+
from openfeature.exception import ErrorCode
4+
from openfeature.flag_evaluation import FlagEvaluationDetails, Reason
45
from openfeature.hook import Hook, HookContext, HookHints
56
from opentelemetry import trace
7+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
68

7-
OTEL_EVENT_NAME = "feature_flag"
9+
OTEL_EVENT_NAME = "feature_flag.evaluation"
810

911

1012
class EventAttributes:
11-
FLAG_KEY = f"{OTEL_EVENT_NAME}.key"
12-
FLAG_VARIANT = f"{OTEL_EVENT_NAME}.variant"
13-
PROVIDER_NAME = f"{OTEL_EVENT_NAME}.provider_name"
13+
KEY = "feature_flag.key"
14+
RESULT_VALUE = "feature_flag.result.value"
15+
RESULT_VARIANT = "feature_flag.result.variant"
16+
CONTEXT_ID = "feature_flag.context.id"
17+
PROVIDER_NAME = "feature_flag.provider.name"
18+
RESULT_REASON = "feature_flag.result.reason"
19+
SET_ID = "feature_flag.set.id"
20+
VERSION = "feature_flag.version"
1421

1522

1623
class TracingHook(Hook):
@@ -22,18 +29,28 @@ def after(
2229
) -> None:
2330
current_span = trace.get_current_span()
2431

25-
variant = details.variant
26-
if variant is None:
27-
if isinstance(details.value, str):
28-
variant = str(details.value)
29-
else:
30-
variant = json.dumps(details.value)
31-
3232
event_attributes = {
33-
EventAttributes.FLAG_KEY: details.flag_key,
34-
EventAttributes.FLAG_VARIANT: variant,
33+
EventAttributes.KEY: details.flag_key,
34+
EventAttributes.RESULT_VALUE: json.dumps(details.value),
35+
EventAttributes.RESULT_REASON: str(details.reason or Reason.UNKNOWN).lower()
3536
}
3637

38+
if details.variant:
39+
event_attributes[EventAttributes.RESULT_VARIANT] = details.variant
40+
41+
if details.reason:
42+
event_attributes[EventAttributes.RESULT_REASON] = str(details.reason).lower()
43+
44+
if details.reason == Reason.ERROR:
45+
error_type = (details.error_code or ErrorCode.GENERAL).lower()
46+
event_attributes[ERROR_TYPE] = error_type
47+
if details.error_message:
48+
event_attributes["error.message"] = details.error_message
49+
50+
context = hook_context.evaluation_context
51+
if context.targeting_key:
52+
event_attributes[EventAttributes.CONTEXT_ID] = context.targeting_key
53+
3754
if hook_context.provider_metadata:
3855
event_attributes[EventAttributes.PROVIDER_NAME] = (
3956
hook_context.provider_metadata.name

hooks/openfeature-hooks-opentelemetry/tests/test_otel.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,32 @@
66

77
from openfeature.contrib.hook.opentelemetry import TracingHook
88
from openfeature.evaluation_context import EvaluationContext
9-
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType
9+
from openfeature.exception import ErrorCode
10+
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType, Reason
1011
from openfeature.hook import HookContext
12+
from openfeature.provider.metadata import Metadata
1113

1214

1315
@pytest.fixture
1416
def mock_get_current_span(monkeypatch):
1517
monkeypatch.setattr(trace, "get_current_span", Mock())
1618

1719

18-
def test_before(mock_get_current_span):
20+
def test_after(mock_get_current_span):
1921
# Given
2022
hook = TracingHook()
2123
hook_context = HookContext(
2224
flag_key="flag_key",
2325
flag_type=FlagType.BOOLEAN,
2426
default_value=False,
25-
evaluation_context=EvaluationContext(),
27+
evaluation_context=EvaluationContext("123"),
28+
provider_metadata=Metadata(name="test-provider")
2629
)
2730
details = FlagEvaluationDetails(
2831
flag_key="flag_key",
2932
value=True,
3033
variant="enabled",
31-
reason=None,
34+
reason=Reason.TARGETING_MATCH,
3235
error_code=None,
3336
error_message=None,
3437
)
@@ -41,14 +44,55 @@ def test_before(mock_get_current_span):
4144

4245
# Then
4346
mock_span.add_event.assert_called_once_with(
44-
"feature_flag",
47+
"feature_flag.evaluation",
4548
{
4649
"feature_flag.key": "flag_key",
47-
"feature_flag.variant": "enabled",
50+
"feature_flag.result.value": "true",
51+
"feature_flag.result.variant": "enabled",
52+
"feature_flag.result.reason": "targeting_match",
53+
"feature_flag.context.id": "123",
54+
"feature_flag.provider.name": "test-provider",
4855
},
4956
)
5057

5158

59+
def test_after_evaluation_error(mock_get_current_span):
60+
# Given
61+
hook = TracingHook()
62+
hook_context = HookContext(
63+
flag_key="flag_key",
64+
flag_type=FlagType.BOOLEAN,
65+
default_value=False,
66+
evaluation_context=EvaluationContext(),
67+
provider_metadata=None,
68+
)
69+
details = FlagEvaluationDetails(
70+
flag_key="flag_key",
71+
value=False,
72+
variant=None,
73+
reason=Reason.ERROR,
74+
error_code=ErrorCode.FLAG_NOT_FOUND,
75+
error_message="Flag not found: flag_key",
76+
)
77+
78+
mock_span = Mock(spec=Span)
79+
trace.get_current_span.return_value = mock_span
80+
81+
# When
82+
hook.after(hook_context, details, hints={})
83+
84+
# Then
85+
mock_span.add_event.assert_called_once_with(
86+
"feature_flag.evaluation",
87+
{
88+
"feature_flag.key": "flag_key",
89+
"feature_flag.result.value": "false",
90+
"feature_flag.result.reason": "error",
91+
"error.type": "flag_not_found",
92+
"error.message": "Flag not found: flag_key",
93+
},
94+
)
95+
5296
def test_error(mock_get_current_span):
5397
# Given
5498
hook = TracingHook()

uv.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)