Skip to content

Commit 526c85b

Browse files
fix: flatten the map for span attributes
Signed-off-by: Shivanshu Raj Shrivastava <[email protected]>
1 parent 5342856 commit 526c85b

File tree

2 files changed

+83
-3
lines changed
  • instrumentation/opentelemetry-instrumentation-celery

2 files changed

+83
-3
lines changed

instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@
5555
)
5656

5757

58+
def flatten_dict(d, parent_key="", sep="."):
59+
"""Flatten a dictionary using dot notation."""
60+
items = []
61+
for k, v in d.items():
62+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
63+
if isinstance(v, dict):
64+
items.extend(flatten_dict(v, new_key, sep=sep).items())
65+
else:
66+
items.append((new_key, v))
67+
return dict(items)
68+
69+
5870
# pylint:disable=too-many-branches
5971
def set_attributes_from_context(span, context):
6072
"""Helper to extract meta values from a Celery Context"""
@@ -95,6 +107,14 @@ def set_attributes_from_context(span, context):
95107
SpanAttributes.MESSAGING_DESTINATION, routing_key
96108
)
97109

110+
# Flatten the dictionary
111+
if value:
112+
flattened = flatten_dict(value)
113+
for k, v in flattened.items():
114+
if v is not None and v != "":
115+
span.set_attribute(f"celery.delivery_info.{k}", v)
116+
continue
117+
98118
elif key == "id":
99119
attribute_name = SpanAttributes.MESSAGING_MESSAGE_ID
100120

@@ -115,6 +135,15 @@ def set_attributes_from_context(span, context):
115135
value = "topic"
116136
break
117137

138+
# If the value is a dictionary, flatten it
139+
if isinstance(value, dict):
140+
flattened = flatten_dict(value)
141+
for nested_key, nested_value in flattened.items():
142+
if nested_value is not None and nested_value != "":
143+
nested_attr_name = f"celery.{key}.{nested_key}"
144+
span.set_attribute(nested_attr_name, nested_value)
145+
continue # Skip the default attribute setting since we've handled it
146+
118147
# set attribute name if not set specially for a key
119148
if attribute_name is None:
120149
attribute_name = f"celery.{key}"

instrumentation/opentelemetry-instrumentation-celery/tests/test_utils.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ def test_set_attributes_from_context(self):
5858
span.attributes.get(SpanAttributes.MESSAGING_DESTINATION), "celery"
5959
)
6060

61-
self.assertEqual(
62-
span.attributes["celery.delivery_info"], str({"eager": True})
63-
)
61+
self.assertEqual(span.attributes["celery.delivery_info.eager"], True)
6462
self.assertEqual(span.attributes.get("celery.eta"), "soon")
6563
self.assertEqual(span.attributes.get("celery.expires"), "later")
6664
self.assertEqual(span.attributes.get("celery.hostname"), "localhost")
@@ -72,6 +70,59 @@ def test_set_attributes_from_context(self):
7270
)
7371
self.assertNotIn("custom_meta", span.attributes)
7472

73+
def test_set_nested_attributes_from_context(self):
74+
context = {
75+
"correlation_id": "44b7f305",
76+
"delivery_info": {
77+
"eager": True,
78+
"routing_key": "api_gateway",
79+
"priority": 0,
80+
"redelivered": False,
81+
},
82+
"eta": {"time": "soon", "date": "today"},
83+
"expires": "later",
84+
"hostname": "localhost",
85+
"id": "44b7f305",
86+
"reply_to": "44b7f305",
87+
"retries": 4,
88+
"timelimit": ("now", "later"),
89+
"custom_meta": "custom_value",
90+
"routing_key": "celery",
91+
}
92+
93+
span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
94+
utils.set_attributes_from_context(span, context)
95+
96+
self.assertEqual(
97+
span.attributes.get(SpanAttributes.MESSAGING_MESSAGE_ID),
98+
"44b7f305",
99+
)
100+
self.assertEqual(
101+
span.attributes.get(SpanAttributes.MESSAGING_CONVERSATION_ID),
102+
"44b7f305",
103+
)
104+
self.assertEqual(
105+
span.attributes.get(SpanAttributes.MESSAGING_DESTINATION), "celery"
106+
)
107+
108+
self.assertEqual(span.attributes["celery.delivery_info.eager"], True)
109+
self.assertEqual(
110+
span.attributes["celery.delivery_info.routing_key"], "api_gateway"
111+
)
112+
self.assertEqual(span.attributes["celery.delivery_info.priority"], 0)
113+
self.assertEqual(
114+
span.attributes["celery.delivery_info.redelivered"], False
115+
)
116+
self.assertEqual(span.attributes.get("celery.eta.time"), "soon")
117+
self.assertEqual(span.attributes.get("celery.eta.date"), "today")
118+
119+
self.assertEqual(span.attributes.get("celery.reply_to"), "44b7f305")
120+
self.assertEqual(span.attributes.get("celery.retries"), 4)
121+
self.assertEqual(
122+
span.attributes.get("celery.timelimit"), ("now", "later")
123+
)
124+
self.assertNotIn("custom_meta", span.attributes)
125+
75126
def test_set_attributes_not_recording(self):
76127
# it should extract only relevant keys
77128
context = {

0 commit comments

Comments
 (0)