otlp-json 0.9.3__py3-none-any.whl → 0.9.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
otlp_json/__init__.py
CHANGED
|
@@ -7,6 +7,8 @@ from typing import Any, TYPE_CHECKING
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from typing_extensions import TypeAlias
|
|
9
9
|
|
|
10
|
+
from opentelemetry.trace import Link
|
|
11
|
+
from opentelemetry._logs import LogRecord
|
|
10
12
|
from opentelemetry.sdk.trace import ReadableSpan, Event
|
|
11
13
|
from opentelemetry.sdk.resources import Resource
|
|
12
14
|
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
|
|
@@ -16,45 +18,51 @@ if TYPE_CHECKING:
|
|
|
16
18
|
_VALUE: TypeAlias = "_LEAF_VALUE | Sequence[_LEAF_VALUE]"
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
__all__ = [
|
|
22
|
+
"CONTENT_TYPE",
|
|
23
|
+
"encode_spans",
|
|
24
|
+
]
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
# NOTE: order matters, for isinstance(True, int).
|
|
24
|
-
bool: ("boolValue", bool),
|
|
25
|
-
int: ("intValue", str),
|
|
26
|
-
float: ("doubleValue", float),
|
|
27
|
-
bytes: ("bytesValue", bytes),
|
|
28
|
-
str: ("stringValue", str),
|
|
29
|
-
Sequence: (
|
|
30
|
-
"arrayValue",
|
|
31
|
-
lambda value: {"values": [_value(e) for e in _homogeneous_array(value)]},
|
|
32
|
-
),
|
|
33
|
-
Mapping: (
|
|
34
|
-
"kvlistValue",
|
|
35
|
-
lambda value: {"values": [{k: _value(v) for k, v in value.items()}]},
|
|
36
|
-
),
|
|
37
|
-
}
|
|
26
|
+
CONTENT_TYPE = "application/json"
|
|
38
27
|
|
|
39
28
|
|
|
40
29
|
def encode_spans(spans: Sequence[ReadableSpan]) -> bytes:
|
|
41
|
-
|
|
30
|
+
resource_cache: dict[Resource, tuple] = {}
|
|
31
|
+
scope_cache: dict[InstrumentationScope, tuple] = {}
|
|
32
|
+
|
|
33
|
+
def linearise(span: ReadableSpan):
|
|
34
|
+
r = span.resource
|
|
35
|
+
if r not in resource_cache:
|
|
36
|
+
resource_cache[r] = (r.schema_url, tuple(r.attributes.items()))
|
|
37
|
+
s = span.instrumentation_scope
|
|
38
|
+
assert s
|
|
39
|
+
assert s.attributes is not None
|
|
40
|
+
if s not in scope_cache:
|
|
41
|
+
scope_cache[s] = (
|
|
42
|
+
s.schema_url,
|
|
43
|
+
s.name,
|
|
44
|
+
s.version,
|
|
45
|
+
tuple(s.attributes.items()),
|
|
46
|
+
)
|
|
47
|
+
return (resource_cache[r], scope_cache[s])
|
|
48
|
+
|
|
49
|
+
spans = sorted(spans, key=linearise)
|
|
42
50
|
rv = {"resourceSpans": []}
|
|
43
|
-
|
|
51
|
+
last_resource = last_scope = None
|
|
44
52
|
for span in spans:
|
|
45
53
|
assert span.resource
|
|
46
54
|
assert span.instrumentation_scope
|
|
47
|
-
if span.resource
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
if span.resource != last_resource:
|
|
56
|
+
last_resource = span.resource
|
|
57
|
+
last_scope = None
|
|
50
58
|
rv["resourceSpans"].append(
|
|
51
59
|
{
|
|
52
60
|
"resource": _resource(span.resource),
|
|
53
61
|
"scopeSpans": [],
|
|
54
62
|
}
|
|
55
63
|
)
|
|
56
|
-
if span.instrumentation_scope
|
|
57
|
-
|
|
64
|
+
if span.instrumentation_scope != last_scope:
|
|
65
|
+
last_scope = span.instrumentation_scope
|
|
58
66
|
rv["resourceSpans"][-1]["scopeSpans"].append(
|
|
59
67
|
{
|
|
60
68
|
"scope": _scope(span.instrumentation_scope),
|
|
@@ -66,55 +74,76 @@ def encode_spans(spans: Sequence[ReadableSpan]) -> bytes:
|
|
|
66
74
|
|
|
67
75
|
|
|
68
76
|
def _resource(resource: Resource):
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
# TODO: add schema_url once that lands in opentelemetry-sdk
|
|
78
|
+
return _attributes(resource)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _attributes(
|
|
82
|
+
thing: Resource | InstrumentationScope | ReadableSpan | Event | Link | LogRecord,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
rv = {"attributes": [], "dropped_attributes_count": 0}
|
|
85
|
+
|
|
86
|
+
assert thing.attributes is not None
|
|
87
|
+
for k, v in thing.attributes.items():
|
|
71
88
|
try:
|
|
72
89
|
rv["attributes"].append({"key": k, "value": _value(v)})
|
|
73
90
|
except ValueError:
|
|
74
91
|
pass
|
|
75
92
|
|
|
76
|
-
|
|
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
|
|
93
|
+
rv["dropped_attributes_count"] = len(thing.attributes) - len(rv["attributes"]) # type: ignore
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if not rv["dropped_attributes_count"]:
|
|
88
|
-
del rv["dropped_attributes_count"]
|
|
95
|
+
for k in ("attributes", "dropped_attributes_count"):
|
|
96
|
+
if not rv[k]:
|
|
97
|
+
del rv[k]
|
|
89
98
|
|
|
90
99
|
return rv
|
|
91
100
|
|
|
92
101
|
|
|
93
|
-
def
|
|
102
|
+
def _ensure_homogeneous(value: Sequence[_LEAF_VALUE]) -> Sequence[_LEAF_VALUE]:
|
|
94
103
|
# TODO: empty lists are allowed, aren't they?
|
|
95
104
|
if len(types := {type(v) for v in value}) > 1:
|
|
96
105
|
raise ValueError(f"Attribute value arrays must be homogeneous, got {types=}")
|
|
97
106
|
return value
|
|
98
107
|
|
|
99
108
|
|
|
100
|
-
def _value(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
def _value(v: _VALUE) -> dict[str, Any]:
|
|
110
|
+
if isinstance(v, bool):
|
|
111
|
+
return {"boolValue": bool(v)}
|
|
112
|
+
if isinstance(v, int):
|
|
113
|
+
return {"intValue": str(int(v))}
|
|
114
|
+
if isinstance(v, float):
|
|
115
|
+
return {"doubleValue": float(v)}
|
|
116
|
+
if isinstance(v, bytes):
|
|
117
|
+
return {
|
|
118
|
+
"bytesValue": bytes(v)
|
|
119
|
+
} # FIXME this can't be right; gotta encode this somehow
|
|
120
|
+
if isinstance(v, str):
|
|
121
|
+
return {"stringValue": str(v)}
|
|
122
|
+
if isinstance(v, Sequence):
|
|
123
|
+
return {"arrayValue": {"values": [_value(e) for e in _ensure_homogeneous(v)]}}
|
|
124
|
+
if isinstance(v, Mapping):
|
|
125
|
+
return {"kvlistValue": {"values": [{k: _value(vv) for k, vv in v.items()}]}}
|
|
107
126
|
|
|
108
|
-
raise ValueError(f"Cannot convert attribute of {type(
|
|
127
|
+
raise ValueError(f"Cannot convert attribute value of {type(v)=}")
|
|
109
128
|
|
|
110
129
|
|
|
111
130
|
def _scope(scope: InstrumentationScope):
|
|
112
|
-
rv = {
|
|
131
|
+
rv = {
|
|
132
|
+
"name": scope.name,
|
|
133
|
+
# Upstream code for attrs and schema has landed, but wasn't released yet
|
|
134
|
+
# https://github.com/open-telemetry/opentelemetry-python/pull/4359
|
|
135
|
+
# "schema_url": scope.schema_url, # check if it may be null
|
|
136
|
+
# **_attributes(scope),
|
|
137
|
+
}
|
|
113
138
|
if scope.version:
|
|
114
139
|
rv["version"] = scope.version
|
|
115
140
|
return rv
|
|
116
141
|
|
|
117
142
|
|
|
143
|
+
_REMOTE = 0x300
|
|
144
|
+
_LOCAL = 0x100
|
|
145
|
+
|
|
146
|
+
|
|
118
147
|
def _span(span: ReadableSpan):
|
|
119
148
|
assert span.context
|
|
120
149
|
rv = {
|
|
@@ -122,30 +151,17 @@ def _span(span: ReadableSpan):
|
|
|
122
151
|
"kind": span.kind.value or 1, # unspecified -> internal
|
|
123
152
|
"traceId": _trace_id(span.context.trace_id),
|
|
124
153
|
"spanId": _span_id(span.context.span_id),
|
|
125
|
-
"flags":
|
|
126
|
-
"startTimeUnixNano": str(span.start_time),
|
|
127
|
-
"endTimeUnixNano": str(span.end_time), #
|
|
154
|
+
"flags": _REMOTE if span.parent and span.parent.is_remote else _LOCAL,
|
|
155
|
+
"startTimeUnixNano": str(span.start_time),
|
|
156
|
+
"endTimeUnixNano": str(span.end_time), # can this be unset?
|
|
128
157
|
"status": _status(span.status),
|
|
129
|
-
|
|
158
|
+
**_attributes(span),
|
|
130
159
|
}
|
|
131
160
|
|
|
132
161
|
if span.parent:
|
|
133
162
|
rv["parentSpanId"] = _span_id(span.parent.span_id)
|
|
134
163
|
|
|
135
|
-
|
|
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
|
-
|
|
164
|
+
# TODO: is this field really nullable?
|
|
149
165
|
if span.events:
|
|
150
166
|
rv["events"] = [_event(e) for e in span.events]
|
|
151
167
|
|
|
@@ -165,29 +181,19 @@ def _span_id(span_id: int) -> str:
|
|
|
165
181
|
|
|
166
182
|
|
|
167
183
|
def _status(status: Status) -> dict[str, Any]:
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
rv = {}
|
|
185
|
+
if status.status_code.value:
|
|
186
|
+
rv["code"] = status.status_code.value
|
|
187
|
+
if status.description:
|
|
188
|
+
rv["message"] = status.description
|
|
189
|
+
return rv
|
|
170
190
|
|
|
171
191
|
|
|
172
192
|
def _event(event: Event) -> dict[str, Any]:
|
|
173
193
|
rv = {
|
|
174
194
|
"name": event.name,
|
|
175
195
|
"timeUnixNano": str(event.timestamp),
|
|
176
|
-
|
|
196
|
+
**_attributes(event),
|
|
177
197
|
}
|
|
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
|
-
|
|
198
|
+
# TODO: any optional attributes?
|
|
193
199
|
return rv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: otlp-json
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
4
4
|
Summary: 🐍Lightweight OTEL span to JSON converter, no dependencies, pure Python🐍
|
|
5
5
|
Project-URL: Repository, https://github.com/dimaqq/otlp-json
|
|
6
6
|
Project-URL: Issues, https://github.com/dimaqq/otlp-json/issues
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
otlp_json/__init__.py,sha256=sUClLoVbl5pXQtY3qBpKHx8O7ahZPPTcbRdv6enmC4Q,6198
|
|
2
|
+
otlp_json/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
otlp_json-0.9.5.dist-info/METADATA,sha256=3g016j1QprMogf3He-BHvBK1meNoOLGWOf6pUcjQLJM,2810
|
|
4
|
+
otlp_json-0.9.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
otlp_json-0.9.5.dist-info/RECORD,,
|
otlp_json-0.9.3.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
otlp_json/__init__.py,sha256=KFI4PCFiyTOw3wTUClS1hIeUyAMCCZp1F9F6K6Qi_IQ,5862
|
|
2
|
-
otlp_json/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
otlp_json-0.9.3.dist-info/METADATA,sha256=7RMTwaC70Vni4nVgZt-VB1-yCv-qlb7908cUTEWPQRs,2810
|
|
4
|
-
otlp_json-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
-
otlp_json-0.9.3.dist-info/RECORD,,
|
|
File without changes
|