77if 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
6870def _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
100103def _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
111115def _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
167161def _status (status : Status ) -> dict [str , Any ]:
168- # FIXME
162+ # FIXME: need an example of bad status
169163 return {}
170164
171165
172166def _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
0 commit comments