latitude-telemetry 0.1.0b1__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.
- latitude_telemetry-0.1.0b1/.gitignore +141 -0
- latitude_telemetry-0.1.0b1/.python-version +1 -0
- latitude_telemetry-0.1.0b1/PKG-INFO +71 -0
- latitude_telemetry-0.1.0b1/README.md +33 -0
- latitude_telemetry-0.1.0b1/pyproject.toml +90 -0
- latitude_telemetry-0.1.0b1/scripts/format.py +8 -0
- latitude_telemetry-0.1.0b1/scripts/lint.py +9 -0
- latitude_telemetry-0.1.0b1/scripts/test.py +7 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/__init__.py +1 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/env/__init__.py +1 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/env/env.py +18 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/exporter/__init__.py +2 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/exporter/exporter.py +172 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/exporter/payloads.py +62 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/py.typed +0 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/telemetry/__init__.py +2 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/telemetry/telemetry.py +238 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/telemetry/types.py +57 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/util/__init__.py +1 -0
- latitude_telemetry-0.1.0b1/src/latitude_telemetry/util/utils.py +103 -0
- latitude_telemetry-0.1.0b1/tests/__init__.py +0 -0
- latitude_telemetry-0.1.0b1/tests/telemetry/__init__.py +0 -0
- latitude_telemetry-0.1.0b1/tests/telemetry/instrument_test.py +7 -0
- latitude_telemetry-0.1.0b1/tests/utils/__init__.py +2 -0
- latitude_telemetry-0.1.0b1/tests/utils/fixtures.py +0 -0
- latitude_telemetry-0.1.0b1/tests/utils/utils.py +71 -0
- latitude_telemetry-0.1.0b1/uv.lock +1313 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Custom
|
|
2
|
+
.vscode/
|
|
3
|
+
.superinvoke_cache/
|
|
4
|
+
.idea/
|
|
5
|
+
*.tmlanguage.cache
|
|
6
|
+
*.tmPreferences.cache
|
|
7
|
+
*.stTheme.cache
|
|
8
|
+
*.sublime-workspace
|
|
9
|
+
.DS_Store
|
|
10
|
+
*.env
|
|
11
|
+
|
|
12
|
+
# Byte-compiled / optimized / DLL files
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.py[cod]
|
|
15
|
+
*$py.class
|
|
16
|
+
|
|
17
|
+
# C extensions
|
|
18
|
+
*.so
|
|
19
|
+
|
|
20
|
+
# Distribution / packaging
|
|
21
|
+
.Python
|
|
22
|
+
build/
|
|
23
|
+
develop-eggs/
|
|
24
|
+
dist/
|
|
25
|
+
downloads/
|
|
26
|
+
eggs/
|
|
27
|
+
.eggs/
|
|
28
|
+
lib/
|
|
29
|
+
lib64/
|
|
30
|
+
parts/
|
|
31
|
+
sdist/
|
|
32
|
+
var/
|
|
33
|
+
wheels/
|
|
34
|
+
share/python-wheels/
|
|
35
|
+
*.egg-info/
|
|
36
|
+
.installed.cfg
|
|
37
|
+
*.egg
|
|
38
|
+
MANIFEST
|
|
39
|
+
|
|
40
|
+
# PyInstaller
|
|
41
|
+
*.manifest
|
|
42
|
+
*.spec
|
|
43
|
+
|
|
44
|
+
# Installer logs
|
|
45
|
+
pip-log.txt
|
|
46
|
+
pip-delete-this-directory.txt
|
|
47
|
+
|
|
48
|
+
# Unit test / coverage reports
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
.coverage
|
|
53
|
+
.coverage.*
|
|
54
|
+
.cache
|
|
55
|
+
nosetests.xml
|
|
56
|
+
coverage.xml
|
|
57
|
+
*.cover
|
|
58
|
+
*.py,cover
|
|
59
|
+
.hypothesis/
|
|
60
|
+
.pytest_cache/
|
|
61
|
+
cover/
|
|
62
|
+
|
|
63
|
+
# Translations
|
|
64
|
+
*.mo
|
|
65
|
+
*.pot
|
|
66
|
+
|
|
67
|
+
# Django stuff:
|
|
68
|
+
*.log
|
|
69
|
+
local_settings.py
|
|
70
|
+
db.sqlite3
|
|
71
|
+
db.sqlite3-journal
|
|
72
|
+
|
|
73
|
+
# Flask stuff:
|
|
74
|
+
instance/
|
|
75
|
+
.webassets-cache
|
|
76
|
+
|
|
77
|
+
# Scrapy stuff:
|
|
78
|
+
.scrapy
|
|
79
|
+
|
|
80
|
+
# Sphinx documentation
|
|
81
|
+
docs/_build/
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
.pybuilder/
|
|
85
|
+
target/
|
|
86
|
+
|
|
87
|
+
# Jupyter Notebook
|
|
88
|
+
.ipynb_checkpoints
|
|
89
|
+
|
|
90
|
+
# IPython
|
|
91
|
+
profile_default/
|
|
92
|
+
ipython_config.py
|
|
93
|
+
|
|
94
|
+
# pyenv
|
|
95
|
+
-
|
|
96
|
+
|
|
97
|
+
# pdm
|
|
98
|
+
.pdm.toml
|
|
99
|
+
|
|
100
|
+
# PEP 582
|
|
101
|
+
__pypackages__/
|
|
102
|
+
|
|
103
|
+
# Celery stuff
|
|
104
|
+
celerybeat-schedule
|
|
105
|
+
celerybeat.pid
|
|
106
|
+
|
|
107
|
+
# SageMath parsed files
|
|
108
|
+
*.sage.py
|
|
109
|
+
|
|
110
|
+
# Environments
|
|
111
|
+
.env
|
|
112
|
+
.venv
|
|
113
|
+
venv/
|
|
114
|
+
venv.bak/
|
|
115
|
+
|
|
116
|
+
# Spyder project settings
|
|
117
|
+
.spyderproject
|
|
118
|
+
.spyproject
|
|
119
|
+
|
|
120
|
+
# Rope project settings
|
|
121
|
+
.ropeproject
|
|
122
|
+
|
|
123
|
+
# mkdocs documentation
|
|
124
|
+
/site
|
|
125
|
+
|
|
126
|
+
# mypy
|
|
127
|
+
.mypy_cache/
|
|
128
|
+
.dmypy.json
|
|
129
|
+
dmypy.json
|
|
130
|
+
|
|
131
|
+
# Pyre type checker
|
|
132
|
+
.pyre/
|
|
133
|
+
|
|
134
|
+
# pytype static type analyzer
|
|
135
|
+
.pytype/
|
|
136
|
+
|
|
137
|
+
# Cython debug symbols
|
|
138
|
+
cython_debug/
|
|
139
|
+
|
|
140
|
+
# Ruff
|
|
141
|
+
.ruff_cache/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.9.21
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: latitude-telemetry
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: Latitude Telemetry for Python
|
|
5
|
+
Project-URL: repository, https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python
|
|
6
|
+
Project-URL: homepage, https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python#readme
|
|
7
|
+
Project-URL: documentation, https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python#readme
|
|
8
|
+
Author-email: Latitude Data SL <hello@latitude.so>
|
|
9
|
+
Maintainer-email: Latitude Data SL <hello@latitude.so>
|
|
10
|
+
License-Expression: LGPL-3.0
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: httpx>=0.27.2
|
|
13
|
+
Requires-Dist: openinference-instrumentation-litellm>=0.1.5
|
|
14
|
+
Requires-Dist: opentelemetry-api>=1.29.0
|
|
15
|
+
Requires-Dist: opentelemetry-instrumentation-alephalpha>=0.36.0
|
|
16
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.36.0
|
|
17
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.36.0
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-cohere>=0.36.0
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.36.0
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-groq>=0.36.0
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-haystack>=0.36.0
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.36.0
|
|
23
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.36.0
|
|
24
|
+
Requires-Dist: opentelemetry-instrumentation-mistralai>=0.36.0
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-ollama>=0.36.0
|
|
26
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.36.0
|
|
27
|
+
Requires-Dist: opentelemetry-instrumentation-replicate>=0.36.0
|
|
28
|
+
Requires-Dist: opentelemetry-instrumentation-sagemaker>=0.36.0
|
|
29
|
+
Requires-Dist: opentelemetry-instrumentation-threading>=0.50b0
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-together>=0.36.0
|
|
31
|
+
Requires-Dist: opentelemetry-instrumentation-transformers>=0.36.0
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-vertexai>=0.36.0
|
|
33
|
+
Requires-Dist: opentelemetry-instrumentation-watsonx>=0.36.0
|
|
34
|
+
Requires-Dist: opentelemetry-sdk>=1.29.0
|
|
35
|
+
Requires-Dist: pydantic>=2.10.3
|
|
36
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Latitude Telemetry for Python
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install latitude-telemetry
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python `3.9` or higher.
|
|
46
|
+
|
|
47
|
+
Go to the [documentation](TODO) to learn more.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
TODO
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Find more [examples](TODO).
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
Requires uv `0.5.10` or higher.
|
|
60
|
+
|
|
61
|
+
- Install dependencies: `uv venv && uv sync --all-extras --all-groups`
|
|
62
|
+
- Add [dev] dependencies: `uv add [--dev] <package>`
|
|
63
|
+
- Run linter: `uv run scripts/lint.py`
|
|
64
|
+
- Run formatter: `uv run scripts/format.py`
|
|
65
|
+
- Run tests: `uv run scripts/test.py`
|
|
66
|
+
- Build package: `uv build`
|
|
67
|
+
- Publish package: `uv publish`
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
The Telemetry is licensed under the [LGPL-3.0 License](https://opensource.org/licenses/LGPL-3.0) - read the [LICENSE](/LICENSE) file for details.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Latitude Telemetry for Python
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
pip install latitude-telemetry
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Requires Python `3.9` or higher.
|
|
8
|
+
|
|
9
|
+
Go to the [documentation](TODO) to learn more.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
TODO
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Find more [examples](TODO).
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
Requires uv `0.5.10` or higher.
|
|
22
|
+
|
|
23
|
+
- Install dependencies: `uv venv && uv sync --all-extras --all-groups`
|
|
24
|
+
- Add [dev] dependencies: `uv add [--dev] <package>`
|
|
25
|
+
- Run linter: `uv run scripts/lint.py`
|
|
26
|
+
- Run formatter: `uv run scripts/format.py`
|
|
27
|
+
- Run tests: `uv run scripts/test.py`
|
|
28
|
+
- Build package: `uv build`
|
|
29
|
+
- Publish package: `uv publish`
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
The Telemetry is licensed under the [LGPL-3.0 License](https://opensource.org/licenses/LGPL-3.0) - read the [LICENSE](/LICENSE) file for details.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "latitude-telemetry"
|
|
3
|
+
version = "0.1.0-beta.1"
|
|
4
|
+
description = "Latitude Telemetry for Python"
|
|
5
|
+
authors = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
|
|
6
|
+
maintainers = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "LGPL-3.0"
|
|
9
|
+
urls.repository = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python"
|
|
10
|
+
urls.homepage = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python#readme"
|
|
11
|
+
urls.documentation = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/python#readme"
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"opentelemetry-api>=1.29.0",
|
|
15
|
+
"opentelemetry-sdk>=1.29.0",
|
|
16
|
+
"opentelemetry-instrumentation-alephalpha>=0.36.0",
|
|
17
|
+
"opentelemetry-instrumentation-anthropic>=0.36.0",
|
|
18
|
+
"opentelemetry-instrumentation-bedrock>=0.36.0",
|
|
19
|
+
"opentelemetry-instrumentation-cohere>=0.36.0",
|
|
20
|
+
"opentelemetry-instrumentation-google-generativeai>=0.36.0",
|
|
21
|
+
"opentelemetry-instrumentation-groq>=0.36.0",
|
|
22
|
+
"opentelemetry-instrumentation-haystack>=0.36.0",
|
|
23
|
+
"opentelemetry-instrumentation-langchain>=0.36.0",
|
|
24
|
+
"openinference-instrumentation-litellm>=0.1.5",
|
|
25
|
+
"opentelemetry-instrumentation-llamaindex>=0.36.0",
|
|
26
|
+
"opentelemetry-instrumentation-mistralai>=0.36.0",
|
|
27
|
+
"opentelemetry-instrumentation-ollama>=0.36.0",
|
|
28
|
+
"opentelemetry-instrumentation-openai>=0.36.0",
|
|
29
|
+
"opentelemetry-instrumentation-replicate>=0.36.0",
|
|
30
|
+
"opentelemetry-instrumentation-sagemaker>=0.36.0",
|
|
31
|
+
"opentelemetry-instrumentation-together>=0.36.0",
|
|
32
|
+
"opentelemetry-instrumentation-threading>=0.50b0",
|
|
33
|
+
"opentelemetry-instrumentation-transformers>=0.36.0",
|
|
34
|
+
"opentelemetry-instrumentation-vertexai>=0.36.0",
|
|
35
|
+
"opentelemetry-instrumentation-watsonx>=0.36.0",
|
|
36
|
+
"httpx>=0.27.2",
|
|
37
|
+
"pydantic>=2.10.3",
|
|
38
|
+
"typing-extensions>=4.12.2",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest-asyncio>=0.24.0",
|
|
44
|
+
"pytest-xdist>=3.6.1",
|
|
45
|
+
"pytest>=8.3.4",
|
|
46
|
+
"respx>=0.22.0",
|
|
47
|
+
"pyright>=1.1.392",
|
|
48
|
+
"ruff>=0.8.3",
|
|
49
|
+
"sh>=1.14.3",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.pyright]
|
|
53
|
+
pythonVersion = "3.9"
|
|
54
|
+
typeCheckingMode = "strict"
|
|
55
|
+
reportMissingTypeStubs = false
|
|
56
|
+
reportUnnecessaryIsInstance = false
|
|
57
|
+
reportPrivateUsage = false
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
target-version = "py39"
|
|
61
|
+
line-length = 120
|
|
62
|
+
indent-width = 4
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = ["B", "C4", "E", "F", "I", "W", "UP"]
|
|
66
|
+
ignore = [
|
|
67
|
+
"F401",
|
|
68
|
+
"F403",
|
|
69
|
+
# Needed because unnecessary str() on field aliases are needed
|
|
70
|
+
# https://docs.pydantic.dev/2.8/concepts/fields/#field-aliases
|
|
71
|
+
"UP018",
|
|
72
|
+
# Needed because typing.List and typing.Dict are semi-deprecated
|
|
73
|
+
# in new Python versions but we want to maintain compatibility
|
|
74
|
+
"UP006",
|
|
75
|
+
"UP035",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff.format]
|
|
79
|
+
quote-style = "double"
|
|
80
|
+
indent-style = "space"
|
|
81
|
+
|
|
82
|
+
[tool.pytest.ini_options]
|
|
83
|
+
addopts = "-p no:warnings -n auto"
|
|
84
|
+
xfail_strict = true
|
|
85
|
+
asyncio_mode = "auto"
|
|
86
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
87
|
+
|
|
88
|
+
[build-system]
|
|
89
|
+
requires = ["hatchling"]
|
|
90
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .telemetry import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .env import *
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from latitude_telemetry.util import Model, get_env
|
|
2
|
+
|
|
3
|
+
DEFAULT_GATEWAY_HOSTNAME = "gateway.latitude.so"
|
|
4
|
+
DEFAULT_GATEWAY_PORT = 443
|
|
5
|
+
DEFAULT_GATEWAY_SSL = True
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Env(Model):
|
|
9
|
+
GATEWAY_HOSTNAME: str
|
|
10
|
+
GATEWAY_PORT: int
|
|
11
|
+
GATEWAY_SSL: bool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
env = Env(
|
|
15
|
+
GATEWAY_HOSTNAME=get_env("GATEWAY_HOSTNAME", DEFAULT_GATEWAY_HOSTNAME),
|
|
16
|
+
GATEWAY_PORT=get_env("GATEWAY_PORT", DEFAULT_GATEWAY_PORT),
|
|
17
|
+
GATEWAY_SSL=get_env("GATEWAY_SSL", DEFAULT_GATEWAY_SSL),
|
|
18
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any, Dict, List, Sequence
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from opentelemetry.sdk import trace as otel
|
|
6
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
7
|
+
from opentelemetry.trace import format_span_id, format_trace_id
|
|
8
|
+
|
|
9
|
+
from latitude_telemetry.exporter.payloads import (
|
|
10
|
+
Attribute,
|
|
11
|
+
AttributeValue,
|
|
12
|
+
CreateTraceRequestBody,
|
|
13
|
+
Event,
|
|
14
|
+
Link,
|
|
15
|
+
Resource,
|
|
16
|
+
ResourceSpan,
|
|
17
|
+
ScopeSpan,
|
|
18
|
+
Span,
|
|
19
|
+
Status,
|
|
20
|
+
)
|
|
21
|
+
from latitude_telemetry.telemetry.types import GatewayOptions
|
|
22
|
+
from latitude_telemetry.util import Model
|
|
23
|
+
|
|
24
|
+
RETRIABLE_STATUSES = [408, 409, 429, 500, 502, 503, 504]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExporterOptions(Model):
|
|
28
|
+
api_key: str
|
|
29
|
+
gateway: GatewayOptions
|
|
30
|
+
retries: int
|
|
31
|
+
delay: float
|
|
32
|
+
timeout: float
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Exporter(SpanExporter):
|
|
36
|
+
_options: ExporterOptions
|
|
37
|
+
|
|
38
|
+
def __init__(self, options: ExporterOptions):
|
|
39
|
+
self._options = options
|
|
40
|
+
|
|
41
|
+
def export(self, spans: Sequence[otel.ReadableSpan]) -> SpanExportResult:
|
|
42
|
+
if not spans:
|
|
43
|
+
return SpanExportResult.SUCCESS
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
self._send(self._serialize(list(spans)))
|
|
47
|
+
|
|
48
|
+
except Exception:
|
|
49
|
+
return SpanExportResult.FAILURE
|
|
50
|
+
|
|
51
|
+
return SpanExportResult.SUCCESS
|
|
52
|
+
|
|
53
|
+
def shutdown(self) -> None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def _serialize(self, spans: List[otel.ReadableSpan]) -> List[ResourceSpan]:
|
|
60
|
+
if not spans:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
ResourceSpan(
|
|
65
|
+
resource=Resource(attributes=self._serialize_attributes(dict(spans[0].resource.attributes or {}))),
|
|
66
|
+
scope_spans=[
|
|
67
|
+
ScopeSpan(
|
|
68
|
+
spans=[
|
|
69
|
+
Span(
|
|
70
|
+
# Note: span.context should not be None
|
|
71
|
+
trace_id=format_trace_id(span.context.trace_id if span.context else 0),
|
|
72
|
+
# Note: span.context should not be None
|
|
73
|
+
span_id=format_span_id(span.context.span_id if span.context else 0),
|
|
74
|
+
parent_span_id=format_span_id(span.parent.span_id) if span.parent else None,
|
|
75
|
+
name=span.name,
|
|
76
|
+
kind=span.kind.value,
|
|
77
|
+
# Note: span.start_time should not be None
|
|
78
|
+
start_time=str(span.start_time or 0),
|
|
79
|
+
end_time=str(span.end_time) if span.end_time else None,
|
|
80
|
+
status=Status(
|
|
81
|
+
code=span.status.status_code.value,
|
|
82
|
+
message=span.status.description,
|
|
83
|
+
),
|
|
84
|
+
events=[
|
|
85
|
+
Event(
|
|
86
|
+
name=event.name,
|
|
87
|
+
time=str(event.timestamp),
|
|
88
|
+
attributes=self._serialize_attributes(dict(event.attributes or {})),
|
|
89
|
+
)
|
|
90
|
+
for event in span.events
|
|
91
|
+
],
|
|
92
|
+
links=[
|
|
93
|
+
Link(
|
|
94
|
+
trace_id=format_trace_id(link.context.trace_id),
|
|
95
|
+
span_id=format_span_id(link.context.span_id),
|
|
96
|
+
attributes=self._serialize_attributes(dict(link.attributes or {})),
|
|
97
|
+
)
|
|
98
|
+
for link in span.links
|
|
99
|
+
],
|
|
100
|
+
attributes=self._serialize_attributes(dict(span.attributes or {})),
|
|
101
|
+
)
|
|
102
|
+
for span in spans
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
def _serialize_attributes(self, attributes: Dict[str, Any]) -> List[Attribute]:
|
|
110
|
+
serialized_attributes: List[Attribute] = []
|
|
111
|
+
|
|
112
|
+
for key, value in attributes.items():
|
|
113
|
+
serialized_value = AttributeValue(string=str(value))
|
|
114
|
+
|
|
115
|
+
if isinstance(value, str):
|
|
116
|
+
serialized_value = AttributeValue(string=value)
|
|
117
|
+
|
|
118
|
+
elif isinstance(value, bool):
|
|
119
|
+
serialized_value = AttributeValue(boolean=value)
|
|
120
|
+
|
|
121
|
+
elif isinstance(value, int):
|
|
122
|
+
serialized_value = AttributeValue(integer=value)
|
|
123
|
+
|
|
124
|
+
elif isinstance(value, float):
|
|
125
|
+
serialized_value = AttributeValue(integer=int(value))
|
|
126
|
+
|
|
127
|
+
serialized_attributes.append(Attribute(key=key, value=serialized_value))
|
|
128
|
+
|
|
129
|
+
return serialized_attributes
|
|
130
|
+
|
|
131
|
+
def _send(self, spans: List[ResourceSpan]) -> None:
|
|
132
|
+
client = httpx.Client(
|
|
133
|
+
headers={
|
|
134
|
+
"Authorization": f"Bearer {self._options.api_key}",
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
},
|
|
137
|
+
timeout=self._options.timeout,
|
|
138
|
+
follow_redirects=False,
|
|
139
|
+
max_redirects=0,
|
|
140
|
+
)
|
|
141
|
+
response = None
|
|
142
|
+
attempt = 1
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
method = "POST"
|
|
146
|
+
url = f"{self._options.gateway.base_url}/otlp/v1/traces"
|
|
147
|
+
content = CreateTraceRequestBody(resource_spans=spans).model_dump_json()
|
|
148
|
+
|
|
149
|
+
while attempt <= self._options.retries:
|
|
150
|
+
try:
|
|
151
|
+
response = client.request(method=method, url=url, content=content)
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
except Exception as exception:
|
|
157
|
+
if attempt >= self._options.retries:
|
|
158
|
+
raise exception
|
|
159
|
+
|
|
160
|
+
if response and response.status_code in RETRIABLE_STATUSES:
|
|
161
|
+
time.sleep(self._options.delay * (2 ** (attempt - 1)))
|
|
162
|
+
else:
|
|
163
|
+
raise exception
|
|
164
|
+
|
|
165
|
+
finally:
|
|
166
|
+
if response:
|
|
167
|
+
response.close()
|
|
168
|
+
|
|
169
|
+
attempt += 1
|
|
170
|
+
|
|
171
|
+
finally:
|
|
172
|
+
client.close()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from latitude_telemetry.util import Field, Model
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AttributeValue(Model):
|
|
7
|
+
string: Optional[str] = Field(default=None, alias=str("stringValue"))
|
|
8
|
+
integer: Optional[int] = Field(default=None, alias=str("intValue"))
|
|
9
|
+
boolean: Optional[bool] = Field(default=None, alias=str("boolValue"))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Attribute(Model):
|
|
13
|
+
key: str
|
|
14
|
+
value: AttributeValue
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Resource(Model):
|
|
18
|
+
attributes: List[Attribute]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Status(Model):
|
|
22
|
+
code: int
|
|
23
|
+
message: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Event(Model):
|
|
27
|
+
name: str
|
|
28
|
+
time: str = Field(alias=str("timeUnixNano"))
|
|
29
|
+
attributes: Optional[List[Attribute]] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Link(Model):
|
|
33
|
+
trace_id: str = Field(alias=str("traceId"))
|
|
34
|
+
span_id: str = Field(alias=str("spanId"))
|
|
35
|
+
attributes: Optional[List[Attribute]] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Span(Model):
|
|
39
|
+
trace_id: str = Field(alias=str("traceId"))
|
|
40
|
+
span_id: str = Field(alias=str("spanId"))
|
|
41
|
+
parent_span_id: Optional[str] = Field(default=None, alias=str("parentSpanId"))
|
|
42
|
+
name: str
|
|
43
|
+
kind: int
|
|
44
|
+
start_time: str = Field(alias=str("startTimeUnixNano"))
|
|
45
|
+
end_time: Optional[str] = Field(default=None, alias=str("endTimeUnixNano"))
|
|
46
|
+
status: Optional[Status] = None
|
|
47
|
+
events: Optional[List[Event]] = None
|
|
48
|
+
links: Optional[List[Link]] = None
|
|
49
|
+
attributes: Optional[List[Attribute]] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ScopeSpan(Model):
|
|
53
|
+
spans: List[Span]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ResourceSpan(Model):
|
|
57
|
+
resource: Resource
|
|
58
|
+
scope_spans: List[ScopeSpan] = Field(alias=str("scopeSpans"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CreateTraceRequestBody(Model):
|
|
62
|
+
resource_spans: List[ResourceSpan] = Field(alias=str("resourceSpans"))
|
|
File without changes
|