ko-observability 0.2.0__tar.gz

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.
@@ -0,0 +1,34 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
8
+ .venv/
9
+ venv/
10
+
11
+ node_modules/
12
+ package-lock.json
13
+
14
+ .env
15
+ .env.otel
16
+ .env.otel-platform
17
+ deploy/tls/*.pem
18
+ deploy/tls/*.key
19
+ !deploy/tls/.gitignore
20
+
21
+ .DS_Store
22
+ .idea/
23
+ .vscode/
24
+
25
+ .claude/settings.local.json
26
+ .codegraph/
27
+
28
+ # TELEMETRY_DESTINATION=file default output + parity/Go-in-Docker caches
29
+ telemetry/
30
+ parity/file_mode/.out/
31
+ .gocache/
32
+
33
+ # /graphify skill output (knowledge-graph build artifacts, session-local)
34
+ graphify-out/
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0](https://github.com/Komus-ai/nexora-observability/compare/python-v0.1.0...python-v0.2.0) (2026-09-18)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * telemetry is written to ./telemetry/*.jsonl by default instead of being exported to a Collector over OTLP/gRPC. Set TELEMETRY_DESTINATION=otlp to restore the previous behavior.
9
+
10
+ ### Features
11
+
12
+ * default telemetry destination to local OTLP/JSON files ([223b5cf](https://github.com/Komus-ai/nexora-observability/commit/223b5cfc92bb31d9a3b255a4eb4c6e2dd69f1af2))
13
+ * **go:** add Go SDK core + full-repo audit fixes (Python/JS/deploy/CI/docs) ([#5](https://github.com/Komus-ai/nexora-observability/issues/5)) ([61433d9](https://github.com/Komus-ai/nexora-observability/commit/61433d904dada98757e900b13049d801f5188085))
14
+ * initial nexora-observability package (Python + JS/TS OTel bootstrap) ([129c2fa](https://github.com/Komus-ai/nexora-observability/commit/129c2fa69657b7de61cf652a7f120ce80af7f478))
15
+ * **python:** add optional read-only query client for own telemetry ([b92a4bb](https://github.com/Komus-ai/nexora-observability/commit/b92a4bb23568cab30e4ee7529549d7fdcfc51104))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **python:** fix mypy type errors, add PEP 561 marker, cover init_otel ([85e2a19](https://github.com/Komus-ai/nexora-observability/commit/85e2a19b3d1697d0a25b853260994c26560072d2))
21
+ * **query:** correct status field path/value from live-cluster verification ([8e5861c](https://github.com/Komus-ai/nexora-observability/commit/8e5861c9d874f97caf6c49d4317fc4e978dfede9))
22
+ * release-readiness fixes for first 0.2.0 publish ([#8](https://github.com/Komus-ai/nexora-observability/issues/8)) ([d7dda1f](https://github.com/Komus-ai/nexora-observability/commit/d7dda1f79ab50edc3fd29b2b71348c1956c06634))
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.5
2
+ Name: ko-observability
3
+ Version: 0.2.0
4
+ Summary: Org-wide OpenTelemetry bootstrap for Nexora Python services
5
+ Project-URL: Repository, https://github.com/Komus-ai/nexora-observability
6
+ Project-URL: Documentation, https://github.com/Komus-ai/nexora-observability/tree/main/docs
7
+ Author-email: Komus-ai <dev@cinco.ai>
8
+ License: MIT
9
+ Keywords: observability,opentelemetry,otlp,structlog
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: opentelemetry-api>=1.27.0
17
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common>=1.27.0
18
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.27.0
19
+ Requires-Dist: opentelemetry-sdk>=1.27.0
20
+ Requires-Dist: protobuf<8.0,>=5.0
21
+ Requires-Dist: structlog>=24.4.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
24
+ Requires-Dist: mypy>=1.13.0; extra == 'dev'
25
+ Requires-Dist: opentelemetry-sdk>=1.27.0; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
27
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
28
+ Provides-Extra: elasticsearch
29
+ Requires-Dist: opentelemetry-instrumentation-elasticsearch>=0.48b0; extra == 'elasticsearch'
30
+ Provides-Extra: fastapi
31
+ Requires-Dist: fastapi>=0.115.0; extra == 'fastapi'
32
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.48b0; extra == 'fastapi'
33
+ Provides-Extra: httpx
34
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.48b0; extra == 'httpx'
35
+ Provides-Extra: query
36
+ Requires-Dist: httpx>=0.27.0; extra == 'query'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # ko-observability (Python)
40
+
41
+ Org-wide OpenTelemetry bootstrap for Nexora Python services: one-call, idempotent
42
+ `init_otel` for process startup, lazy `get_tracer` / `get_meter` accessors for library code
43
+ that doesn't own process lifecycle, and a structlog processor (`otel_bridge` /
44
+ `make_otel_bridge`) that stamps each log line with the active span's `trace_id`/`span_id`.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install ko-observability
50
+ ```
51
+
52
+ Optional extras, install alongside the base package as needed:
53
+
54
+ ```bash
55
+ # Auto-instrument FastAPI request/response spans
56
+ pip install "ko-observability[fastapi]"
57
+
58
+ # Auto-instrument outbound httpx calls
59
+ pip install "ko-observability[httpx]"
60
+
61
+ # Auto-instrument the Elasticsearch client
62
+ pip install "ko-observability[elasticsearch]"
63
+
64
+ # Read your own service's telemetry back out (admin-panel style queries)
65
+ pip install "ko-observability[query]"
66
+ ```
67
+
68
+ ## Quick start
69
+
70
+ ```python
71
+ from ko_observability import init_otel, get_tracer
72
+
73
+ init_otel(service_name="my-service", service_namespace="nexora")
74
+
75
+ tracer = get_tracer(__name__)
76
+ with tracer.start_as_current_span("do-work"):
77
+ ...
78
+ ```
79
+
80
+ Call `init_otel` exactly once at process startup. It's safe to call again (or to let library
81
+ code fall back to `get_tracer`/`get_meter` before it runs) — both paths are no-ops on top of an
82
+ already-configured provider. `OTEL_SDK_DISABLED=true` turns the whole thing into a no-op, and a
83
+ misconfigured exporter degrades to "telemetry doesn't work" rather than crashing the host process.
84
+
85
+ ### Where does it go?
86
+
87
+ | `TELEMETRY_DESTINATION` | Output |
88
+ |---|---|
89
+ | `file` (default) | `$TELEMETRY_FILE_DIR/{traces,metrics,logs}.jsonl` (default `./telemetry`), one OTLP/JSON request per line |
90
+ | `otlp` | OTLP/gRPC to `OTEL_EXPORTER_OTLP_ENDPOINT` (default `http://localhost:4317`) |
91
+
92
+ Full contract: [docs/otel.md → Local file mode](../../docs/otel.md#local-file-mode-the-default).
93
+
94
+ ## Reading telemetry back (`query` extra)
95
+
96
+ ```python
97
+ from ko_observability.query import get_query_client
98
+
99
+ client = get_query_client()
100
+ traces = client.list_traces(limit=20)
101
+ ```
102
+
103
+ Scoped to the caller's own `service_name` by default. See [`docs/query.md`](../../docs/query.md).
104
+
105
+ ## Docs
106
+
107
+ Full documentation — env vars, the logs/metrics pipelines, semantic conventions, and the
108
+ Collector deployment this SDK ships telemetry to — lives in the repository:
109
+
110
+ https://github.com/Komus-ai/nexora-observability/tree/main/docs
@@ -0,0 +1,72 @@
1
+ # ko-observability (Python)
2
+
3
+ Org-wide OpenTelemetry bootstrap for Nexora Python services: one-call, idempotent
4
+ `init_otel` for process startup, lazy `get_tracer` / `get_meter` accessors for library code
5
+ that doesn't own process lifecycle, and a structlog processor (`otel_bridge` /
6
+ `make_otel_bridge`) that stamps each log line with the active span's `trace_id`/`span_id`.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install ko-observability
12
+ ```
13
+
14
+ Optional extras, install alongside the base package as needed:
15
+
16
+ ```bash
17
+ # Auto-instrument FastAPI request/response spans
18
+ pip install "ko-observability[fastapi]"
19
+
20
+ # Auto-instrument outbound httpx calls
21
+ pip install "ko-observability[httpx]"
22
+
23
+ # Auto-instrument the Elasticsearch client
24
+ pip install "ko-observability[elasticsearch]"
25
+
26
+ # Read your own service's telemetry back out (admin-panel style queries)
27
+ pip install "ko-observability[query]"
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ from ko_observability import init_otel, get_tracer
34
+
35
+ init_otel(service_name="my-service", service_namespace="nexora")
36
+
37
+ tracer = get_tracer(__name__)
38
+ with tracer.start_as_current_span("do-work"):
39
+ ...
40
+ ```
41
+
42
+ Call `init_otel` exactly once at process startup. It's safe to call again (or to let library
43
+ code fall back to `get_tracer`/`get_meter` before it runs) — both paths are no-ops on top of an
44
+ already-configured provider. `OTEL_SDK_DISABLED=true` turns the whole thing into a no-op, and a
45
+ misconfigured exporter degrades to "telemetry doesn't work" rather than crashing the host process.
46
+
47
+ ### Where does it go?
48
+
49
+ | `TELEMETRY_DESTINATION` | Output |
50
+ |---|---|
51
+ | `file` (default) | `$TELEMETRY_FILE_DIR/{traces,metrics,logs}.jsonl` (default `./telemetry`), one OTLP/JSON request per line |
52
+ | `otlp` | OTLP/gRPC to `OTEL_EXPORTER_OTLP_ENDPOINT` (default `http://localhost:4317`) |
53
+
54
+ Full contract: [docs/otel.md → Local file mode](../../docs/otel.md#local-file-mode-the-default).
55
+
56
+ ## Reading telemetry back (`query` extra)
57
+
58
+ ```python
59
+ from ko_observability.query import get_query_client
60
+
61
+ client = get_query_client()
62
+ traces = client.list_traces(limit=20)
63
+ ```
64
+
65
+ Scoped to the caller's own `service_name` by default. See [`docs/query.md`](../../docs/query.md).
66
+
67
+ ## Docs
68
+
69
+ Full documentation — env vars, the logs/metrics pipelines, semantic conventions, and the
70
+ Collector deployment this SDK ships telemetry to — lives in the repository:
71
+
72
+ https://github.com/Komus-ai/nexora-observability/tree/main/docs
@@ -0,0 +1,69 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ko-observability"
7
+ version = "0.2.0"
8
+ description = "Org-wide OpenTelemetry bootstrap for Nexora Python services"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Komus-ai", email = "dev@cinco.ai" }]
13
+ keywords = ["opentelemetry", "observability", "otlp", "structlog"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ dependencies = [
22
+ "opentelemetry-api>=1.27.0",
23
+ "opentelemetry-sdk>=1.27.0",
24
+ "opentelemetry-exporter-otlp-proto-grpc>=1.27.0",
25
+ "opentelemetry-exporter-otlp-proto-common>=1.27.0",
26
+ "protobuf>=5.0,<8.0",
27
+ "structlog>=24.4.0",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ fastapi = [
32
+ "opentelemetry-instrumentation-fastapi>=0.48b0",
33
+ "fastapi>=0.115.0",
34
+ ]
35
+ httpx = ["opentelemetry-instrumentation-httpx>=0.48b0"]
36
+ elasticsearch = ["opentelemetry-instrumentation-elasticsearch>=0.48b0"]
37
+ query = ["httpx>=0.27.0"]
38
+ dev = [
39
+ "pytest>=8.3.0",
40
+ "opentelemetry-sdk>=1.27.0",
41
+ "httpx>=0.27.0",
42
+ "ruff>=0.8.0",
43
+ "mypy>=1.13.0",
44
+ ]
45
+
46
+ [project.urls]
47
+ Repository = "https://github.com/Komus-ai/nexora-observability"
48
+ Documentation = "https://github.com/Komus-ai/nexora-observability/tree/main/docs"
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/ko_observability"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ pythonpath = ["src"]
56
+
57
+ [tool.ruff]
58
+ target-version = "py312"
59
+ line-length = 100
60
+ src = ["src", "tests"]
61
+
62
+ [tool.ruff.lint]
63
+ select = ["E", "F", "I", "UP", "B"]
64
+
65
+ [tool.mypy]
66
+ python_version = "3.12"
67
+ packages = ["ko_observability"]
68
+ mypy_path = "src"
69
+ ignore_missing_imports = true
@@ -0,0 +1,15 @@
1
+ """Nexora org-wide OpenTelemetry libraries."""
2
+
3
+ from ko_observability.client import get_meter, get_tracer
4
+ from ko_observability.otel import init_otel
5
+ from ko_observability.structlog_bridge import make_otel_bridge, otel_bridge
6
+
7
+ __all__ = [
8
+ "init_otel",
9
+ "get_tracer",
10
+ "get_meter",
11
+ "otel_bridge",
12
+ "make_otel_bridge",
13
+ ]
14
+
15
+ __version__ = "0.2.0"
@@ -0,0 +1,67 @@
1
+ """Lazy OTel client helpers for library and application code.
2
+
3
+ Use these when you want a Tracer/Meter without requiring the host process to
4
+ have called ``init_otel`` first (e.g. inside a reusable library). Each
5
+ function bootstraps a minimal provider on first use if -- and only if -- no
6
+ provider has been registered yet by the host process.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import threading
13
+
14
+ from opentelemetry import metrics as _otel_metrics
15
+ from opentelemetry import trace
16
+
17
+ from ko_observability.exporters import make_span_exporter
18
+ from ko_observability.otlp_config import sdk_disabled
19
+
20
+ _init_lock = threading.Lock()
21
+ _initialized = False
22
+
23
+
24
+ def get_tracer(name: str, version: str | None = None):
25
+ """Return a Tracer, lazily bootstrapping a minimal provider if needed."""
26
+ _ensure_trace_provider()
27
+ return trace.get_tracer(name, version)
28
+
29
+
30
+ def get_meter(name: str, version: str | None = None) -> _otel_metrics.Meter:
31
+ """Return a Meter (no-op if metrics provider is not configured)."""
32
+ return _otel_metrics.get_meter(name, version or "")
33
+
34
+
35
+ def _ensure_trace_provider() -> None:
36
+ global _initialized
37
+ if _initialized:
38
+ return
39
+ with _init_lock:
40
+ if _initialized:
41
+ return
42
+ if sdk_disabled():
43
+ _initialized = True
44
+ return
45
+
46
+ provider = trace.get_tracer_provider()
47
+ if "NoOp" not in type(provider).__name__ and "Proxy" not in type(provider).__name__:
48
+ _initialized = True
49
+ return
50
+
51
+ try:
52
+ from opentelemetry.sdk.resources import Resource
53
+ from opentelemetry.sdk.trace import TracerProvider
54
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
55
+
56
+ resource = Resource.create(
57
+ {
58
+ "service.name": os.getenv("OTEL_SERVICE_NAME", "nexora-service"),
59
+ "service.namespace": os.getenv("OTEL_SERVICE_NAMESPACE", "nexora"),
60
+ }
61
+ )
62
+ tp = TracerProvider(resource=resource)
63
+ tp.add_span_processor(BatchSpanProcessor(make_span_exporter()))
64
+ trace.set_tracer_provider(tp)
65
+ except Exception:
66
+ pass
67
+ _initialized = True
@@ -0,0 +1,208 @@
1
+ """Exporter factories: local JSONL files (default) or OTLP/gRPC, per TELEMETRY_DESTINATION.
2
+
3
+ File exporters write one OTLP/JSON ``Export*ServiceRequest`` per line -- the
4
+ same encoding the Collector's ``otlpjsonfile`` receiver replays -- with
5
+ ``traceId``/``spanId``/``parentSpanId`` rewritten from protobuf-JSON base64
6
+ to the spec's hex form. Append-only, one ``write()`` per batch, mutex-guarded.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import binascii
13
+ import json
14
+ import logging
15
+ import os
16
+ import threading
17
+ from collections.abc import Sequence
18
+ from typing import Any
19
+
20
+ from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult
21
+ from opentelemetry.sdk.metrics.export import MetricExporter, MetricExportResult, MetricsData
22
+ from opentelemetry.sdk.trace import ReadableSpan
23
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
24
+
25
+ from ko_observability.otlp_config import (
26
+ otlp_endpoint,
27
+ otlp_exporter_insecure,
28
+ telemetry_destination,
29
+ telemetry_file_dir,
30
+ )
31
+
32
+ _logger = logging.getLogger(__name__)
33
+
34
+ TRACES_FILE = "traces.jsonl"
35
+ METRICS_FILE = "metrics.jsonl"
36
+ LOGS_FILE = "logs.jsonl"
37
+
38
+ _ID_KEYS = frozenset({"traceId", "spanId", "parentSpanId"})
39
+
40
+
41
+ def _b64_to_hex(value: str) -> str:
42
+ try:
43
+ return base64.b64decode(value, validate=True).hex()
44
+ except (binascii.Error, ValueError):
45
+ return value
46
+
47
+
48
+ def _hex_ids(obj: Any) -> Any:
49
+ if isinstance(obj, dict):
50
+ return {
51
+ k: (_b64_to_hex(v) if k in _ID_KEYS and isinstance(v, str) else _hex_ids(v))
52
+ for k, v in obj.items()
53
+ }
54
+ if isinstance(obj, list):
55
+ return [_hex_ids(v) for v in obj]
56
+ return obj
57
+
58
+
59
+ def _to_otlp_json(message: Any) -> dict[str, Any]:
60
+ from google.protobuf.json_format import MessageToDict
61
+
62
+ return _hex_ids(MessageToDict(message, use_integers_for_enums=True))
63
+
64
+
65
+ class _JsonlWriter:
66
+ def __init__(self, path: str) -> None:
67
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
68
+ self._fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
69
+ self._lock = threading.Lock()
70
+
71
+ def write_line(self, obj: dict[str, Any]) -> None:
72
+ data = (json.dumps(obj, separators=(",", ":")) + "\n").encode("utf-8")
73
+ with self._lock:
74
+ if self._fd < 0:
75
+ raise OSError("telemetry file closed")
76
+ view = memoryview(data)
77
+ written = 0
78
+ try:
79
+ while view:
80
+ n = os.write(self._fd, view)
81
+ written += n
82
+ view = view[n:]
83
+ except OSError:
84
+ # A partial write before the failure leaves a fragment (no
85
+ # trailing newline) on disk -- but only if this call made any
86
+ # progress at all; a failure on the very first byte leaves the
87
+ # file exactly as the last successful write_line left it (already
88
+ # newline-terminated), so no recovery byte is needed there.
89
+ # Left alone, the next successful export would append onto the
90
+ # fragment and produce one unparseable JSONL line, corrupting
91
+ # previously-good data rather than just the failed batch.
92
+ # Best-effort: terminate the fragment with a newline so line
93
+ # framing survives, then propagate the original error (never
94
+ # crash the host).
95
+ if written > 0:
96
+ try:
97
+ os.write(self._fd, b"\n")
98
+ except OSError:
99
+ pass
100
+ raise
101
+
102
+ def close(self) -> None:
103
+ with self._lock:
104
+ if self._fd >= 0:
105
+ os.close(self._fd)
106
+ self._fd = -1
107
+
108
+
109
+ def _default_path(name: str) -> str:
110
+ return os.path.join(telemetry_file_dir(), name)
111
+
112
+
113
+ class FileSpanExporter(SpanExporter):
114
+ def __init__(self, path: str | None = None) -> None:
115
+ self._writer = _JsonlWriter(path or _default_path(TRACES_FILE))
116
+
117
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
118
+ try:
119
+ from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans
120
+
121
+ self._writer.write_line(_to_otlp_json(encode_spans(spans)))
122
+ return SpanExportResult.SUCCESS
123
+ except Exception as exc:
124
+ _logger.warning("telemetry.file_export_failed: traces: %s", exc)
125
+ return SpanExportResult.FAILURE
126
+
127
+ def shutdown(self) -> None:
128
+ self._writer.close()
129
+
130
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
131
+ return True
132
+
133
+
134
+ class FileLogExporter(LogRecordExporter):
135
+ def __init__(self, path: str | None = None) -> None:
136
+ self._writer = _JsonlWriter(path or _default_path(LOGS_FILE))
137
+
138
+ def export(self, batch: Sequence[Any]) -> LogRecordExportResult:
139
+ try:
140
+ from opentelemetry.exporter.otlp.proto.common._log_encoder import encode_logs
141
+
142
+ self._writer.write_line(_to_otlp_json(encode_logs(batch)))
143
+ return LogRecordExportResult.SUCCESS
144
+ except Exception as exc:
145
+ _logger.warning("telemetry.file_export_failed: logs: %s", exc)
146
+ return LogRecordExportResult.FAILURE
147
+
148
+ def shutdown(self) -> None:
149
+ self._writer.close()
150
+
151
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
152
+ return True
153
+
154
+
155
+ class FileMetricExporter(MetricExporter):
156
+ def __init__(
157
+ self,
158
+ path: str | None = None,
159
+ preferred_temporality: dict[type, Any] | None = None,
160
+ preferred_aggregation: dict[type, Any] | None = None,
161
+ ) -> None:
162
+ super().__init__(
163
+ preferred_temporality=preferred_temporality,
164
+ preferred_aggregation=preferred_aggregation,
165
+ )
166
+ self._writer = _JsonlWriter(path or _default_path(METRICS_FILE))
167
+
168
+ def export(
169
+ self, metrics_data: MetricsData, timeout_millis: float = 10_000, **kwargs: Any
170
+ ) -> MetricExportResult:
171
+ try:
172
+ from opentelemetry.exporter.otlp.proto.common.metrics_encoder import encode_metrics
173
+
174
+ self._writer.write_line(_to_otlp_json(encode_metrics(metrics_data)))
175
+ return MetricExportResult.SUCCESS
176
+ except Exception as exc:
177
+ _logger.warning("telemetry.file_export_failed: metrics: %s", exc)
178
+ return MetricExportResult.FAILURE
179
+
180
+ def force_flush(self, timeout_millis: float = 10_000) -> bool:
181
+ return True
182
+
183
+ def shutdown(self, timeout_millis: float = 30_000, **kwargs: Any) -> None:
184
+ self._writer.close()
185
+
186
+
187
+ def make_span_exporter() -> SpanExporter:
188
+ if telemetry_destination() == "file":
189
+ return FileSpanExporter()
190
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
191
+
192
+ return OTLPSpanExporter(endpoint=otlp_endpoint(), insecure=otlp_exporter_insecure())
193
+
194
+
195
+ def make_log_exporter() -> LogRecordExporter:
196
+ if telemetry_destination() == "file":
197
+ return FileLogExporter()
198
+ from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
199
+
200
+ return OTLPLogExporter(endpoint=otlp_endpoint(), insecure=otlp_exporter_insecure())
201
+
202
+
203
+ def make_metric_exporter() -> MetricExporter:
204
+ if telemetry_destination() == "file":
205
+ return FileMetricExporter()
206
+ from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
207
+
208
+ return OTLPMetricExporter(endpoint=otlp_endpoint(), insecure=otlp_exporter_insecure())