kensa-sdk 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.
- kensa_sdk-0.1.0/.gitignore +26 -0
- kensa_sdk-0.1.0/LICENSE +21 -0
- kensa_sdk-0.1.0/PKG-INFO +92 -0
- kensa_sdk-0.1.0/README.md +79 -0
- kensa_sdk-0.1.0/pyproject.toml +37 -0
- kensa_sdk-0.1.0/src/kensa/__init__.py +6 -0
- kensa_sdk-0.1.0/src/kensa/py.typed +0 -0
- kensa_sdk-0.1.0/src/kensa/tracing.py +197 -0
- kensa_sdk-0.1.0/tests/test_tracing.py +356 -0
- kensa_sdk-0.1.0/uv.lock +435 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
node_modules
|
|
2
|
+
.next
|
|
3
|
+
next-env.d.ts
|
|
4
|
+
out
|
|
5
|
+
coverage
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env.*.local
|
|
9
|
+
*.tsbuildinfo
|
|
10
|
+
.DS_Store
|
|
11
|
+
.pnpm-store
|
|
12
|
+
.coverage
|
|
13
|
+
.pytest_cache
|
|
14
|
+
.ruff_cache
|
|
15
|
+
.venv
|
|
16
|
+
__pycache__
|
|
17
|
+
.vercel
|
|
18
|
+
.wrangler/
|
|
19
|
+
.zed
|
|
20
|
+
.specs
|
|
21
|
+
.worktrees
|
|
22
|
+
.codex/
|
|
23
|
+
.claude/
|
|
24
|
+
.agents/
|
|
25
|
+
.env*
|
|
26
|
+
!.env.example
|
kensa_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Borgware
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kensa_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kensa-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thin OpenTelemetry wrapper that exports agent traces to Kensa
|
|
5
|
+
Project-URL: Homepage, https://kensa.sh
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: opentelemetry-api>=1.42.0
|
|
10
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.42.0
|
|
11
|
+
Requires-Dist: opentelemetry-sdk>=1.42.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# kensa-sdk
|
|
15
|
+
|
|
16
|
+
Send your agent's OpenTelemetry traces to [Kensa](https://kensa.sh).
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install kensa-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The package installs as `kensa-sdk` and imports as `kensa`.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
import kensa
|
|
30
|
+
|
|
31
|
+
kensa.init(api_key=os.environ.get("KENSA_API_KEY"))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Spans are sent in the background. Automatic process-exit cleanup uses OpenTelemetry's
|
|
35
|
+
standard shutdown, which currently waits up to 30 seconds to drain its queue. Slow exports
|
|
36
|
+
can therefore leave queued spans unsent, even though each export allows 120 seconds.
|
|
37
|
+
|
|
38
|
+
For a finite run, stop producing spans and call `kensa.flush()` before exiting. In a
|
|
39
|
+
serverless function, flush before the response ends. The installed OpenTelemetry processor
|
|
40
|
+
does not enforce an overall flush deadline, so multiple batches can take longer than
|
|
41
|
+
120 seconds to drain.
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
kensa.flush()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`kensa.init()` sends spans; your existing instrumentation produces them. Without an API
|
|
48
|
+
key it does nothing and logs a warning, and nothing the SDK does raises into your
|
|
49
|
+
application.
|
|
50
|
+
|
|
51
|
+
## Already using OpenTelemetry?
|
|
52
|
+
|
|
53
|
+
`kensa.init()` attaches to the tracer provider your application installed instead of
|
|
54
|
+
replacing it, so call it after that provider is set up. Pass `provider=` if you have
|
|
55
|
+
not registered yours globally:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
kensa.init(api_key=os.environ.get("KENSA_API_KEY"), provider=my_provider)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Your provider's resource is fixed when it is created, so set the service name,
|
|
62
|
+
`service.version`, and `deployment.environment.name` on it yourself, from your
|
|
63
|
+
platform's variables (table below); Kensa logs a warning when the deployment is
|
|
64
|
+
missing. `service_name` only applies when Kensa installs its own provider.
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
| Setting | Set with | Default |
|
|
69
|
+
| ------------ | ------------------------------------------------ | --------------------------- |
|
|
70
|
+
| API key | `init(api_key=...)` or `KENSA_API_KEY` | Required |
|
|
71
|
+
| Service name | `init(service_name=...)` or `OTEL_SERVICE_NAME` | `unknown_service` |
|
|
72
|
+
| Deployment | `OTEL_RESOURCE_ATTRIBUTES` | Detected from your platform |
|
|
73
|
+
|
|
74
|
+
Kensa checks a fix against the traces from the deployment that shipped it, so it needs
|
|
75
|
+
the deployed commit and the environment. `kensa.init()` detects them:
|
|
76
|
+
|
|
77
|
+
| Platform | Commit | Environment |
|
|
78
|
+
| ---------------- | ------------------------ | -------------------------- |
|
|
79
|
+
| Vercel | `VERCEL_GIT_COMMIT_SHA` | `VERCEL_ENV` |
|
|
80
|
+
| Railway | `RAILWAY_GIT_COMMIT_SHA` | `RAILWAY_ENVIRONMENT_NAME` |
|
|
81
|
+
| Render | `RENDER_GIT_COMMIT` | |
|
|
82
|
+
|
|
83
|
+
On Render, set the environment yourself (below). CI and local runs are never detected
|
|
84
|
+
as a deployment.
|
|
85
|
+
|
|
86
|
+
Anywhere else, or to override detection, set the standard OpenTelemetry variable:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
OTEL_RESOURCE_ATTRIBUTES="service.version=<commit sha>,deployment.environment.name=production"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Requires Python 3.12 or later.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# kensa-sdk
|
|
2
|
+
|
|
3
|
+
Send your agent's OpenTelemetry traces to [Kensa](https://kensa.sh).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install kensa-sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The package installs as `kensa-sdk` and imports as `kensa`.
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import kensa
|
|
17
|
+
|
|
18
|
+
kensa.init(api_key=os.environ.get("KENSA_API_KEY"))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Spans are sent in the background. Automatic process-exit cleanup uses OpenTelemetry's
|
|
22
|
+
standard shutdown, which currently waits up to 30 seconds to drain its queue. Slow exports
|
|
23
|
+
can therefore leave queued spans unsent, even though each export allows 120 seconds.
|
|
24
|
+
|
|
25
|
+
For a finite run, stop producing spans and call `kensa.flush()` before exiting. In a
|
|
26
|
+
serverless function, flush before the response ends. The installed OpenTelemetry processor
|
|
27
|
+
does not enforce an overall flush deadline, so multiple batches can take longer than
|
|
28
|
+
120 seconds to drain.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
kensa.flush()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`kensa.init()` sends spans; your existing instrumentation produces them. Without an API
|
|
35
|
+
key it does nothing and logs a warning, and nothing the SDK does raises into your
|
|
36
|
+
application.
|
|
37
|
+
|
|
38
|
+
## Already using OpenTelemetry?
|
|
39
|
+
|
|
40
|
+
`kensa.init()` attaches to the tracer provider your application installed instead of
|
|
41
|
+
replacing it, so call it after that provider is set up. Pass `provider=` if you have
|
|
42
|
+
not registered yours globally:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
kensa.init(api_key=os.environ.get("KENSA_API_KEY"), provider=my_provider)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Your provider's resource is fixed when it is created, so set the service name,
|
|
49
|
+
`service.version`, and `deployment.environment.name` on it yourself, from your
|
|
50
|
+
platform's variables (table below); Kensa logs a warning when the deployment is
|
|
51
|
+
missing. `service_name` only applies when Kensa installs its own provider.
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
| Setting | Set with | Default |
|
|
56
|
+
| ------------ | ------------------------------------------------ | --------------------------- |
|
|
57
|
+
| API key | `init(api_key=...)` or `KENSA_API_KEY` | Required |
|
|
58
|
+
| Service name | `init(service_name=...)` or `OTEL_SERVICE_NAME` | `unknown_service` |
|
|
59
|
+
| Deployment | `OTEL_RESOURCE_ATTRIBUTES` | Detected from your platform |
|
|
60
|
+
|
|
61
|
+
Kensa checks a fix against the traces from the deployment that shipped it, so it needs
|
|
62
|
+
the deployed commit and the environment. `kensa.init()` detects them:
|
|
63
|
+
|
|
64
|
+
| Platform | Commit | Environment |
|
|
65
|
+
| ---------------- | ------------------------ | -------------------------- |
|
|
66
|
+
| Vercel | `VERCEL_GIT_COMMIT_SHA` | `VERCEL_ENV` |
|
|
67
|
+
| Railway | `RAILWAY_GIT_COMMIT_SHA` | `RAILWAY_ENVIRONMENT_NAME` |
|
|
68
|
+
| Render | `RENDER_GIT_COMMIT` | |
|
|
69
|
+
|
|
70
|
+
On Render, set the environment yourself (below). CI and local runs are never detected
|
|
71
|
+
as a deployment.
|
|
72
|
+
|
|
73
|
+
Anywhere else, or to override detection, set the standard OpenTelemetry variable:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
OTEL_RESOURCE_ATTRIBUTES="service.version=<commit sha>,deployment.environment.name=production"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Requires Python 3.12 or later.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kensa-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Thin OpenTelemetry wrapper that exports agent traces to Kensa"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"opentelemetry-api>=1.42.0",
|
|
15
|
+
"opentelemetry-sdk>=1.42.0",
|
|
16
|
+
"opentelemetry-exporter-otlp-proto-http>=1.42.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://kensa.sh"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = ["pytest>=8.0", "ruff>=0.16.4", "ty>=0.0.1a14"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/kensa"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 100
|
|
30
|
+
target-version = "py312"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
addopts = "--strict-markers"
|
|
37
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Send OpenTelemetry spans to Kensa."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Iterator, Mapping
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
from typing import Final
|
|
11
|
+
|
|
12
|
+
from opentelemetry import trace
|
|
13
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
14
|
+
from opentelemetry.sdk.resources import Resource
|
|
15
|
+
from opentelemetry.sdk.trace import SpanProcessor, TracerProvider
|
|
16
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
17
|
+
|
|
18
|
+
__all__ = ["flush", "init"]
|
|
19
|
+
|
|
20
|
+
_LOGGER = logging.getLogger("kensa")
|
|
21
|
+
_DEFAULT_ENDPOINT: Final = "https://ingest.kensa.sh/v1/traces"
|
|
22
|
+
# Ingest accepts at most 4 MiB per request and GenAI spans carry whole prompts, so
|
|
23
|
+
# batches stay far below the exporter's default of 512 spans.
|
|
24
|
+
_MAX_EXPORT_BATCH_SIZE: Final = 24
|
|
25
|
+
_IDENTITY_KEYS: Final = ("service.version", "deployment.environment.name")
|
|
26
|
+
|
|
27
|
+
# Full commit SHAs that hosting platforms expose at runtime. Kensa matches a reported
|
|
28
|
+
# fix's commit against the service.version of later traces. Deploy platforms only: CI
|
|
29
|
+
# and local runs must never pass for production traffic.
|
|
30
|
+
_COMMIT_VARIABLES: Final = (
|
|
31
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
32
|
+
"RENDER_GIT_COMMIT",
|
|
33
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
34
|
+
)
|
|
35
|
+
_ENVIRONMENT_VARIABLES: Final = ("VERCEL_ENV", "RAILWAY_ENVIRONMENT_NAME")
|
|
36
|
+
_ENVIRONMENT_KEYS: Final = ("deployment.environment.name", "deployment.environment")
|
|
37
|
+
_FOREIGN_EXPORTER_PREFIXES: Final = ("OTEL_EXPORTER_OTLP_", "OTEL_PYTHON_EXPORTER_OTLP_")
|
|
38
|
+
_DEFAULT_MAX_QUEUE_SIZE: Final = 2048
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _package_version() -> str | None:
|
|
42
|
+
# pyproject.toml is the only place the version is written; installing stamps it into
|
|
43
|
+
# the package metadata. Frozen or vendored copies may lack that metadata.
|
|
44
|
+
try:
|
|
45
|
+
return version("kensa-sdk")
|
|
46
|
+
except PackageNotFoundError:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_VERSION: Final = _package_version()
|
|
51
|
+
|
|
52
|
+
_processor: SpanProcessor | None = None
|
|
53
|
+
_warned_missing_key = False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _first(env: Mapping[str, str], names: tuple[str, ...]) -> str | None:
|
|
57
|
+
for name in names:
|
|
58
|
+
value = env.get(name, "").strip()
|
|
59
|
+
if value:
|
|
60
|
+
return value
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _detected_attributes(env: Mapping[str, str]) -> dict[str, str]:
|
|
65
|
+
attributes: dict[str, str] = {}
|
|
66
|
+
if commit := _first(env, _COMMIT_VARIABLES):
|
|
67
|
+
attributes["service.version"] = commit
|
|
68
|
+
if environment := _first(env, _ENVIRONMENT_VARIABLES):
|
|
69
|
+
# Both spellings: semantic conventions renamed the attribute, and Kensa prefers
|
|
70
|
+
# the new one.
|
|
71
|
+
attributes["deployment.environment.name"] = environment
|
|
72
|
+
attributes["deployment.environment"] = environment
|
|
73
|
+
return attributes
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _resource(service_name: str | None, env: Mapping[str, str]) -> Resource:
|
|
77
|
+
# Detected values, overridden by OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES
|
|
78
|
+
# (which Resource.create reads), overridden by an explicit service name.
|
|
79
|
+
explicit: dict[str, str] = {"service.name": service_name} if service_name else {}
|
|
80
|
+
configured = Resource.create(explicit)
|
|
81
|
+
detected = _detected_attributes(env)
|
|
82
|
+
# An environment set through OTEL_RESOURCE_ATTRIBUTES, in either spelling, replaces
|
|
83
|
+
# the detected one entirely rather than leaving the two spellings disagreeing.
|
|
84
|
+
if any(key in configured.attributes for key in _ENVIRONMENT_KEYS):
|
|
85
|
+
for key in _ENVIRONMENT_KEYS:
|
|
86
|
+
detected.pop(key, None)
|
|
87
|
+
resource = Resource(detected).merge(configured)
|
|
88
|
+
if _VERSION:
|
|
89
|
+
resource = resource.merge(Resource({"kensa.instrumentation.version": _VERSION}))
|
|
90
|
+
return resource
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@contextmanager
|
|
94
|
+
def _without_otlp_environment() -> Iterator[None]:
|
|
95
|
+
# OTLPSpanExporter reads OTEL_EXPORTER_OTLP_* (certificates, client keys, timeout) and
|
|
96
|
+
# a credential-provider session when constructed. Those belong to whatever other
|
|
97
|
+
# backend the application exports to, so hide them while Kensa's exporter is built.
|
|
98
|
+
hidden = {k: v for k, v in os.environ.items() if k.startswith(_FOREIGN_EXPORTER_PREFIXES)}
|
|
99
|
+
for name in hidden:
|
|
100
|
+
del os.environ[name]
|
|
101
|
+
try:
|
|
102
|
+
yield
|
|
103
|
+
finally:
|
|
104
|
+
os.environ.update(hidden)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _batch_size() -> int:
|
|
108
|
+
# BatchSpanProcessor rejects a batch larger than its queue, so honour a smaller one.
|
|
109
|
+
try:
|
|
110
|
+
queue = int(os.environ.get("OTEL_BSP_MAX_QUEUE_SIZE", _DEFAULT_MAX_QUEUE_SIZE))
|
|
111
|
+
except ValueError:
|
|
112
|
+
queue = _DEFAULT_MAX_QUEUE_SIZE
|
|
113
|
+
return max(1, min(_MAX_EXPORT_BATCH_SIZE, queue))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _warn_missing_key() -> None:
|
|
117
|
+
global _warned_missing_key
|
|
118
|
+
if not _warned_missing_key:
|
|
119
|
+
_warned_missing_key = True
|
|
120
|
+
_LOGGER.warning("KENSA_API_KEY is not set, so no traces are sent to Kensa.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _warn_missing_identity(provider: TracerProvider) -> None:
|
|
124
|
+
attributes = provider.resource.attributes
|
|
125
|
+
missing = [key for key in _IDENTITY_KEYS if not attributes.get(key)]
|
|
126
|
+
if missing:
|
|
127
|
+
_LOGGER.warning(
|
|
128
|
+
"Kensa attached to this application's tracer provider and cannot set %s on "
|
|
129
|
+
"it. Add them to your own Resource so Kensa can tell which deployment a fix "
|
|
130
|
+
"shipped in.",
|
|
131
|
+
", ".join(missing),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def init(
|
|
136
|
+
*,
|
|
137
|
+
api_key: str | None = None,
|
|
138
|
+
service_name: str | None = None,
|
|
139
|
+
provider: TracerProvider | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Start sending this process's traces to Kensa.
|
|
142
|
+
|
|
143
|
+
Only the first call with an API key (``api_key`` or ``KENSA_API_KEY``) takes
|
|
144
|
+
effect. Attaches to the application's tracer provider if it has one, or to
|
|
145
|
+
``provider`` if given; otherwise installs its own. Call it after any code that sets
|
|
146
|
+
up a tracer provider, or that provider cannot be installed.
|
|
147
|
+
"""
|
|
148
|
+
global _processor
|
|
149
|
+
if _processor is not None:
|
|
150
|
+
return
|
|
151
|
+
key = api_key or os.environ.get("KENSA_API_KEY")
|
|
152
|
+
if not key:
|
|
153
|
+
_warn_missing_key()
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
with _without_otlp_environment():
|
|
158
|
+
exporter = OTLPSpanExporter(
|
|
159
|
+
endpoint=os.environ.get("KENSA_ENDPOINT") or _DEFAULT_ENDPOINT,
|
|
160
|
+
headers={"authorization": f"Bearer {key}"},
|
|
161
|
+
# Ingest redacts prompt-rich batches before acknowledging them.
|
|
162
|
+
timeout=120,
|
|
163
|
+
)
|
|
164
|
+
processor = BatchSpanProcessor(exporter, max_export_batch_size=_batch_size())
|
|
165
|
+
existing = provider if provider is not None else trace.get_tracer_provider()
|
|
166
|
+
if isinstance(existing, TracerProvider):
|
|
167
|
+
existing.add_span_processor(processor)
|
|
168
|
+
_warn_missing_identity(existing)
|
|
169
|
+
else:
|
|
170
|
+
# Keep OpenTelemetry's default automatic shutdown and its drain deadline.
|
|
171
|
+
installed = TracerProvider(resource=_resource(service_name, os.environ))
|
|
172
|
+
installed.add_span_processor(processor)
|
|
173
|
+
trace.set_tracer_provider(installed)
|
|
174
|
+
if trace.get_tracer_provider() is not installed:
|
|
175
|
+
# Another vendor's non-SDK provider holds the global slot and cannot take
|
|
176
|
+
# Kensa's processor.
|
|
177
|
+
processor.shutdown()
|
|
178
|
+
_LOGGER.error(
|
|
179
|
+
"Another tracer provider is registered and Kensa cannot attach to it. "
|
|
180
|
+
"Pass your OpenTelemetry SDK TracerProvider as kensa.init(provider=...)."
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
except Exception as error: # Instrumentation must never break the application.
|
|
184
|
+
_LOGGER.warning("Not sending traces to Kensa: %s", error)
|
|
185
|
+
return
|
|
186
|
+
_processor = processor
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def flush() -> None:
|
|
190
|
+
"""Send any buffered spans now.
|
|
191
|
+
|
|
192
|
+
Call after span production stops, including before a serverless response. Automatic
|
|
193
|
+
shutdown uses OpenTelemetry's default deadline and may drop slow batches. The installed
|
|
194
|
+
processor does not enforce an overall flush deadline. A failed send is logged.
|
|
195
|
+
"""
|
|
196
|
+
if _processor is not None and not _processor.force_flush(timeout_millis=120_000):
|
|
197
|
+
_LOGGER.warning("Some spans could not be sent to Kensa.")
|