Skip to content

Commit 46f8c91

Browse files
committed
refactor: common function to serialise attributes and fix #1
1 parent 2800044 commit 46f8c91

File tree

3 files changed

+39
-59
lines changed

3 files changed

+39
-59
lines changed

otlp_json/__init__.py

Lines changed: 31 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
if TYPE_CHECKING:
88
from typing_extensions import TypeAlias
99

10+
from opentelemetry.trace import Link
11+
from opentelemetry._logs import LogRecord
1012
from opentelemetry.sdk.trace import ReadableSpan, Event
1113
from opentelemetry.sdk.resources import Resource
1214
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
@@ -66,26 +68,27 @@ def encode_spans(spans: Sequence[ReadableSpan]) -> bytes:
6668

6769

6870
def _resource(resource: Resource):
69-
rv = {"attributes": []}
70-
for k, v in resource.attributes.items():
71+
# TODO: add schema_url once that lands in opentelemetry-sdk
72+
return _attributes(resource)
73+
74+
75+
def _attributes(
76+
thing: Resource | InstrumentationScope | ReadableSpan | Event | Link | LogRecord,
77+
) -> dict[str, Any]:
78+
rv = {"attributes": [], "dropped_attributes_count": 0}
79+
80+
assert thing.attributes is not None
81+
for k, v in thing.attributes.items():
7182
try:
7283
rv["attributes"].append({"key": k, "value": _value(v)})
7384
except ValueError:
7485
pass
7586

76-
# NOTE: blocks that contain droppedAttributesCount:
77-
# - Event
78-
# - Link
79-
# - InstrumentationScope
80-
# - LogRecord (out of scope for this library)
81-
# - Resource
82-
rv["dropped_attributes_count"] = len(resource.attributes) - len(rv["attributes"]) # type: ignore
87+
rv["dropped_attributes_count"] = len(thing.attributes) - len(rv["attributes"]) # type: ignore
8388

84-
if not rv["attributes"]:
85-
del rv["attributes"]
86-
87-
if not rv["dropped_attributes_count"]:
88-
del rv["dropped_attributes_count"]
89+
for k in ("attributes", "dropped_attributes_count"):
90+
if not rv[k]:
91+
del rv[k]
8992

9093
return rv
9194

@@ -99,8 +102,9 @@ def _homogeneous_array(value: list[_LEAF_VALUE]) -> list[_LEAF_VALUE]:
99102

100103
def _value(value: _VALUE) -> dict[str, Any]:
101104
# Attribute value can be a primitive type, excluging None...
102-
# TODO: protobuf allows bytes, but I think OTLP doesn't.
103-
# TODO: protobuf allows k:v pairs, but I think OTLP doesn't.
105+
# protobuf allows bytes, but I think OTLP spec does not?
106+
# protobuf allows k:v pairs, but I think OTLP doesn't.
107+
# TODO: read up the spec and validate the allowed type range.
104108
for klass, (key, post) in _VALUE_TYPES.items():
105109
if isinstance(value, klass):
106110
return {key: post(value)}
@@ -109,7 +113,10 @@ def _value(value: _VALUE) -> dict[str, Any]:
109113

110114

111115
def _scope(scope: InstrumentationScope):
112-
rv = {"name": scope.name}
116+
rv = {
117+
"name": scope.name,
118+
**_attributes(scope),
119+
}
113120
if scope.version:
114121
rv["version"] = scope.version
115122
return rv
@@ -123,29 +130,16 @@ def _span(span: ReadableSpan):
123130
"traceId": _trace_id(span.context.trace_id),
124131
"spanId": _span_id(span.context.span_id),
125132
"flags": 0x100 | ([0, 0x200][bool(span.parent and span.parent.is_remote)]),
126-
"startTimeUnixNano": str(span.start_time), # TODO: is it ever optional?
127-
"endTimeUnixNano": str(span.end_time), # -"-
133+
"startTimeUnixNano": str(span.start_time),
134+
"endTimeUnixNano": str(span.end_time), # can this be unset?
128135
"status": _status(span.status),
129-
"attributes": [],
136+
**_attributes(span),
130137
}
131138

132139
if span.parent:
133140
rv["parentSpanId"] = _span_id(span.parent.span_id)
134141

135-
for k, v in span.attributes.items(): # type: ignore
136-
try:
137-
rv["attributes"].append({"key": k, "value": _value(v)})
138-
except ValueError:
139-
pass
140-
141-
rv["dropped_attributes_count"] = len(span.attributes) - len(rv["attributes"]) # type: ignore
142-
143-
if not rv["attributes"]:
144-
del rv["attributes"]
145-
146-
if not rv["dropped_attributes_count"]:
147-
del rv["dropped_attributes_count"]
148-
142+
# TODO: is this field really nullable?
149143
if span.events:
150144
rv["events"] = [_event(e) for e in span.events]
151145

@@ -165,29 +159,15 @@ def _span_id(span_id: int) -> str:
165159

166160

167161
def _status(status: Status) -> dict[str, Any]:
168-
# FIXME
162+
# FIXME: need an example of bad status
169163
return {}
170164

171165

172166
def _event(event: Event) -> dict[str, Any]:
173167
rv = {
174168
"name": event.name,
175169
"timeUnixNano": str(event.timestamp),
176-
"attributes": [],
170+
**_attributes(event),
177171
}
178-
179-
for k, v in event.attributes.items(): # type: ignore
180-
try:
181-
rv["attributes"].append({"key": k, "value": _value(v)})
182-
except ValueError:
183-
pass
184-
185-
rv["dropped_attributes_count"] = len(event.attributes) - len(rv["attributes"]) # type: ignore
186-
187-
if not rv["attributes"]:
188-
del rv["attributes"]
189-
190-
if not rv["dropped_attributes_count"]:
191-
del rv["dropped_attributes_count"]
192-
172+
# TODO: any optional attributes?
193173
return rv

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "otlp-json"
3-
version = "0.9.3"
3+
version = "0.9.4"
44
description = "🐍Lightweight OTEL span to JSON converter, no dependencies, pure Python🐍"
55
requires-python = ">=3.8"
66
# https://github.com/astral-sh/uv/issues/4204

uv.lock

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

0 commit comments

Comments
 (0)