omnia-tracing 0.1.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.
omnia_tracing-0.1.0/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PY_PY=pypi-AgEIcHlwaS5vcmcCJDZlMDMxNDM5LTU2NjQtNDA0OS1iMDBmLTg3YTEyN2I1MGY5ZQACKlszLCIwYzc3YjA4Zi0yMjM1LTRiYWEtODEzMC1kOGNjYTc2MDY0MDAiXQAABiC-pMRm8OFpi3aPTC8WcA6RYye-O9lYrXl4X16aViCdbw
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: omnia-tracing
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Omnia tracing — standard OpenTelemetry, curated. One install, one line; eject anytime, your spans don't change.
|
|
5
|
+
Project-URL: Repository, https://github.com/omnia-v/omnia-tracing
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
|
|
9
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.30
|
|
10
|
+
Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.30
|
|
11
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.30
|
|
12
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.30
|
|
13
|
+
Requires-Dist: opentelemetry-sdk>=1.27
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# omnia-tracing
|
|
17
|
+
|
|
18
|
+
Standard OpenTelemetry, curated. One install, one line; eject anytime — your spans don't change.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from omnia_tracing import setup
|
|
22
|
+
|
|
23
|
+
tracing = setup() # reads OMNIA_API_KEY and OMNIA_TAG
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
See the [repository README](https://github.com/omnia-v/omnia-tracing) for the full story, and `docs/eject.md` for the identical setup in vanilla OpenTelemetry.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# omnia-tracing
|
|
2
|
+
|
|
3
|
+
Standard OpenTelemetry, curated. One install, one line; eject anytime — your spans don't change.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from omnia_tracing import setup
|
|
7
|
+
|
|
8
|
+
tracing = setup() # reads OMNIA_API_KEY and OMNIA_TAG
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See the [repository README](https://github.com/omnia-v/omnia-tracing) for the full story, and `docs/eject.md` for the identical setup in vanilla OpenTelemetry.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "omnia-tracing"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Omnia tracing — standard OpenTelemetry, curated. One install, one line; eject anytime, your spans don't change."
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"opentelemetry-sdk>=1.27",
|
|
14
|
+
"opentelemetry-exporter-otlp-proto-http>=1.27",
|
|
15
|
+
"opentelemetry-instrumentation-openai>=0.30",
|
|
16
|
+
"opentelemetry-instrumentation-anthropic>=0.30",
|
|
17
|
+
"opentelemetry-instrumentation-google-generativeai>=0.30",
|
|
18
|
+
"opentelemetry-instrumentation-langchain>=0.30",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Repository = "https://github.com/omnia-v/omnia-tracing"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = ["pytest>=8"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/omnia_tracing"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Omnia tracing — standard OpenTelemetry, curated.
|
|
2
|
+
|
|
3
|
+
One install, one line; eject anytime, your spans don't change. This package
|
|
4
|
+
contains NO instrumentation code of its own: it pins and configures standard,
|
|
5
|
+
ecosystem-maintained OpenTelemetry pieces. The identical setup without this
|
|
6
|
+
package is documented in docs/eject.md.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
DEFAULT_ENDPOINT = "https://gateway.omnia-voice.com/v1/traces"
|
|
15
|
+
TAG_ATTRIBUTE = "omnia.tag"
|
|
16
|
+
|
|
17
|
+
__all__ = ["setup", "resolve_config", "ResolvedConfig", "Tracing", "DEFAULT_ENDPOINT", "TAG_ATTRIBUTE"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ResolvedConfig:
|
|
22
|
+
endpoint: str
|
|
23
|
+
headers: dict[str, str]
|
|
24
|
+
service_name: str | None
|
|
25
|
+
resource_attributes: dict[str, str] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_config(
|
|
29
|
+
api_key: str | None = None,
|
|
30
|
+
tag: str | None = None,
|
|
31
|
+
service_name: str | None = None,
|
|
32
|
+
endpoint: str | None = None,
|
|
33
|
+
env: dict[str, str] | None = None,
|
|
34
|
+
) -> ResolvedConfig:
|
|
35
|
+
"""Pure configuration assembly — unit-testable without starting a pipeline."""
|
|
36
|
+
e = os.environ if env is None else env
|
|
37
|
+
key = api_key or e.get("OMNIA_API_KEY")
|
|
38
|
+
if not key:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"omnia-tracing: no API key. Pass setup(api_key=...) or set OMNIA_API_KEY. "
|
|
41
|
+
"Refusing to start a tracer that exports nowhere."
|
|
42
|
+
)
|
|
43
|
+
resource_attributes: dict[str, str] = {}
|
|
44
|
+
resolved_tag = tag or e.get("OMNIA_TAG")
|
|
45
|
+
if resolved_tag:
|
|
46
|
+
resource_attributes[TAG_ATTRIBUTE] = resolved_tag
|
|
47
|
+
return ResolvedConfig(
|
|
48
|
+
endpoint=endpoint or e.get("OMNIA_OTLP_ENDPOINT") or DEFAULT_ENDPOINT,
|
|
49
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
50
|
+
service_name=service_name or e.get("OTEL_SERVICE_NAME"),
|
|
51
|
+
resource_attributes=resource_attributes,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Tracing:
|
|
56
|
+
"""Handle returned by setup(): shutdown() flushes and stops.
|
|
57
|
+
|
|
58
|
+
`instrumented` names the libraries actually being traced — the ones both
|
|
59
|
+
installed in this environment and successfully instrumented."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, provider, instrumented: list[str]) -> None:
|
|
62
|
+
self._provider = provider
|
|
63
|
+
self.instrumented = instrumented
|
|
64
|
+
|
|
65
|
+
def shutdown(self) -> None:
|
|
66
|
+
self._provider.shutdown()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def setup(
|
|
70
|
+
api_key: str | None = None,
|
|
71
|
+
tag: str | None = None,
|
|
72
|
+
service_name: str | None = None,
|
|
73
|
+
endpoint: str | None = None,
|
|
74
|
+
) -> Tracing:
|
|
75
|
+
"""Start standard OpenTelemetry tracing, exporting to Omnia.
|
|
76
|
+
|
|
77
|
+
Call ONCE, at startup, before constructing LLM clients. Instruments
|
|
78
|
+
OpenAI, Anthropic, Gemini and LangChain via the ecosystem's standard
|
|
79
|
+
instrumentation packages.
|
|
80
|
+
"""
|
|
81
|
+
config = resolve_config(api_key, tag, service_name, endpoint)
|
|
82
|
+
|
|
83
|
+
# Imports live here, not module top: `import omnia_tracing` must stay
|
|
84
|
+
# side-effect free so resolve_config is usable (and testable) alone.
|
|
85
|
+
from opentelemetry.sdk.resources import Resource
|
|
86
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
87
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
88
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
89
|
+
|
|
90
|
+
attrs: dict[str, str] = dict(config.resource_attributes)
|
|
91
|
+
if config.service_name:
|
|
92
|
+
attrs["service.name"] = config.service_name
|
|
93
|
+
provider = TracerProvider(resource=Resource.create(attrs))
|
|
94
|
+
provider.add_span_processor(
|
|
95
|
+
BatchSpanProcessor(
|
|
96
|
+
OTLPSpanExporter(endpoint=config.endpoint, headers=config.headers)
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Each instrumentation package imports its TARGET library at import time,
|
|
101
|
+
# so instrument only what this environment actually has — an
|
|
102
|
+
# Anthropic-only app must not be forced to install `openai`.
|
|
103
|
+
candidates = (
|
|
104
|
+
("openai", "opentelemetry.instrumentation.openai", "OpenAIInstrumentor"),
|
|
105
|
+
("anthropic", "opentelemetry.instrumentation.anthropic", "AnthropicInstrumentor"),
|
|
106
|
+
(
|
|
107
|
+
"google-generativeai",
|
|
108
|
+
"opentelemetry.instrumentation.google_generativeai",
|
|
109
|
+
"GoogleGenerativeAiInstrumentor",
|
|
110
|
+
),
|
|
111
|
+
("langchain", "opentelemetry.instrumentation.langchain", "LangchainInstrumentor"),
|
|
112
|
+
)
|
|
113
|
+
instrumented: list[str] = []
|
|
114
|
+
import importlib
|
|
115
|
+
|
|
116
|
+
for name, module_path, class_name in candidates:
|
|
117
|
+
try:
|
|
118
|
+
instrumentor = getattr(importlib.import_module(module_path), class_name)()
|
|
119
|
+
except ImportError:
|
|
120
|
+
continue # target library not installed — nothing to trace
|
|
121
|
+
if not instrumentor.is_instrumented_by_opentelemetry:
|
|
122
|
+
instrumentor.instrument(tracer_provider=provider)
|
|
123
|
+
instrumented.append(name)
|
|
124
|
+
|
|
125
|
+
return Tracing(provider, instrumented)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from omnia_tracing import DEFAULT_ENDPOINT, TAG_ATTRIBUTE, resolve_config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_refuses_without_api_key():
|
|
7
|
+
with pytest.raises(ValueError, match="OMNIA_API_KEY"):
|
|
8
|
+
resolve_config(env={})
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_defaults_to_gateway_with_bearer():
|
|
12
|
+
c = resolve_config(api_key="sk_x", env={})
|
|
13
|
+
assert c.endpoint == DEFAULT_ENDPOINT
|
|
14
|
+
assert c.headers["Authorization"] == "Bearer sk_x"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_reads_env():
|
|
18
|
+
c = resolve_config(
|
|
19
|
+
env={
|
|
20
|
+
"OMNIA_API_KEY": "sk_env",
|
|
21
|
+
"OMNIA_TAG": "checkout-agent",
|
|
22
|
+
"OMNIA_OTLP_ENDPOINT": "https://other.example/v1/traces",
|
|
23
|
+
"OTEL_SERVICE_NAME": "svc",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
assert c.headers["Authorization"] == "Bearer sk_env"
|
|
27
|
+
assert c.resource_attributes[TAG_ATTRIBUTE] == "checkout-agent"
|
|
28
|
+
assert c.endpoint == "https://other.example/v1/traces"
|
|
29
|
+
assert c.service_name == "svc"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_options_beat_env():
|
|
33
|
+
c = resolve_config(
|
|
34
|
+
api_key="sk_opt",
|
|
35
|
+
tag="t2",
|
|
36
|
+
env={"OMNIA_API_KEY": "sk_env", "OMNIA_TAG": "t1"},
|
|
37
|
+
)
|
|
38
|
+
assert c.headers["Authorization"] == "Bearer sk_opt"
|
|
39
|
+
assert c.resource_attributes[TAG_ATTRIBUTE] == "t2"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_no_tag_means_no_attribute():
|
|
43
|
+
c = resolve_config(api_key="k", env={})
|
|
44
|
+
assert c.resource_attributes == {}
|