e-volv-logs 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.
- e_volv_logs-0.1.0/.gitignore +145 -0
- e_volv_logs-0.1.0/PKG-INFO +158 -0
- e_volv_logs-0.1.0/README.md +145 -0
- e_volv_logs-0.1.0/e_volv_logs/__init__.py +40 -0
- e_volv_logs-0.1.0/e_volv_logs/integrations/__init__.py +2 -0
- e_volv_logs-0.1.0/e_volv_logs/integrations/fastapi.py +4 -0
- e_volv_logs-0.1.0/e_volv_logs/integrations/langchain.py +4 -0
- e_volv_logs-0.1.0/evolve_logs/__init__.py +95 -0
- e_volv_logs-0.1.0/evolve_logs/client.py +362 -0
- e_volv_logs-0.1.0/evolve_logs/context.py +123 -0
- e_volv_logs-0.1.0/evolve_logs/handler.py +66 -0
- e_volv_logs-0.1.0/evolve_logs/integrations/__init__.py +4 -0
- e_volv_logs-0.1.0/evolve_logs/integrations/fastapi.py +79 -0
- e_volv_logs-0.1.0/evolve_logs/integrations/langchain.py +161 -0
- e_volv_logs-0.1.0/evolve_logs/patches.py +65 -0
- e_volv_logs-0.1.0/evolve_logs/types.py +49 -0
- e_volv_logs-0.1.0/project.json +16 -0
- e_volv_logs-0.1.0/pyproject.toml +57 -0
- e_volv_logs-0.1.0/tests/conftest.py +53 -0
- e_volv_logs-0.1.0/tests/test_batching.py +77 -0
- e_volv_logs-0.1.0/tests/test_conformance.py +156 -0
- e_volv_logs-0.1.0/tests/test_context.py +191 -0
- e_volv_logs-0.1.0/tests/test_handler.py +171 -0
- e_volv_logs-0.1.0/tests/test_langchain.py +56 -0
- e_volv_logs-0.1.0/tests/test_retry.py +70 -0
- e_volv_logs-0.1.0/tests/test_shim.py +19 -0
- e_volv_logs-0.1.0/uv.lock +1619 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# compiled output
|
|
4
|
+
dist
|
|
5
|
+
tmp
|
|
6
|
+
out-tsc
|
|
7
|
+
|
|
8
|
+
# dependencies
|
|
9
|
+
node_modules
|
|
10
|
+
|
|
11
|
+
# IDEs and editors
|
|
12
|
+
/.idea
|
|
13
|
+
.project
|
|
14
|
+
.classpath
|
|
15
|
+
.c9/
|
|
16
|
+
*.launch
|
|
17
|
+
.settings/
|
|
18
|
+
*.sublime-workspace
|
|
19
|
+
|
|
20
|
+
# IDE - VSCode
|
|
21
|
+
.vscode/*
|
|
22
|
+
!.vscode/settings.json
|
|
23
|
+
!.vscode/tasks.json
|
|
24
|
+
!.vscode/launch.json
|
|
25
|
+
!.vscode/extensions.json
|
|
26
|
+
|
|
27
|
+
# misc
|
|
28
|
+
/.sass-cache
|
|
29
|
+
/connect.lock
|
|
30
|
+
/coverage
|
|
31
|
+
/libpeerconnection.log
|
|
32
|
+
npm-debug.log
|
|
33
|
+
yarn-error.log
|
|
34
|
+
testem.log
|
|
35
|
+
/typings
|
|
36
|
+
|
|
37
|
+
# System Files
|
|
38
|
+
.DS_Store
|
|
39
|
+
Thumbs.db
|
|
40
|
+
|
|
41
|
+
.nx/cache
|
|
42
|
+
.nx/workspace-data
|
|
43
|
+
.cursor/rules/nx-rules.mdc
|
|
44
|
+
.github/instructions/nx.instructions.md
|
|
45
|
+
|
|
46
|
+
.claude/worktrees
|
|
47
|
+
.claude/settings.local.json
|
|
48
|
+
.nx/polygraph
|
|
49
|
+
|
|
50
|
+
# Next.js
|
|
51
|
+
.next
|
|
52
|
+
out
|
|
53
|
+
# Terraform
|
|
54
|
+
infra/.terraform/
|
|
55
|
+
infra/*.tfstate
|
|
56
|
+
infra/*.tfstate.backup
|
|
57
|
+
# Terraform writes rollback copies as terraform.tfstate.<epoch>.backup, which
|
|
58
|
+
# the pattern above does not match — the timestamp sits before `.backup`. One
|
|
59
|
+
# such file was sitting untracked on 2026-08-09 holding 68 plaintext
|
|
60
|
+
# secret_data values (Surreal password, JWT secret, vault key, Stripe live key,
|
|
61
|
+
# GitHub App private key), fully stageable by `git add -A`.
|
|
62
|
+
infra/*.tfstate.*.backup
|
|
63
|
+
infra/terraform.tfvars
|
|
64
|
+
# Any sibling of the real tfvars — .bak, .orig, .save, dated copies. The exact
|
|
65
|
+
# path above does not match them, so an editor backup or a hand-rolled copy of
|
|
66
|
+
# a file holding every production secret lands in `git status` as untracked and
|
|
67
|
+
# would be staged by `git add -A`. Nearly happened on 2026-08-09.
|
|
68
|
+
infra/terraform.tfvars.*
|
|
69
|
+
!infra/terraform.tfvars.example
|
|
70
|
+
infra/.terraform.lock.hcl
|
|
71
|
+
|
|
72
|
+
# Environment files — never commit; contain live credentials
|
|
73
|
+
.env
|
|
74
|
+
.env.local
|
|
75
|
+
|
|
76
|
+
# Untracked 2026-08-07: this working copy accumulates real credential values as
|
|
77
|
+
# the remediation gates are worked through, so it cannot be a tracked file.
|
|
78
|
+
# The sanitised instructions live in docs/GATE-2-CREDENTIALS.md, which stays
|
|
79
|
+
# tracked and must never carry a real value.
|
|
80
|
+
docs/PRODUCTION-FIX-PLAN.md
|
|
81
|
+
.env.*.local
|
|
82
|
+
apps/*/.env
|
|
83
|
+
apps/*/.env.local
|
|
84
|
+
|
|
85
|
+
# Python bytecode caches
|
|
86
|
+
__pycache__/
|
|
87
|
+
*.pyc
|
|
88
|
+
|
|
89
|
+
# Nx/TS incremental build info
|
|
90
|
+
apps/evolve-backend/tsconfig.tsbuildinfo
|
|
91
|
+
|
|
92
|
+
# Test output (nx python targets write here)
|
|
93
|
+
reports/
|
|
94
|
+
|
|
95
|
+
# Terraform plan files embed sensitive variable values — never commit them.
|
|
96
|
+
# `-out=` takes an arbitrary name, so the two patterns below only catch plans a
|
|
97
|
+
# person happened to call "tfplan". Files named tfdom/tfgh/tfpost were found
|
|
98
|
+
# untracked on 2026-08-09. Plans are zip archives, so a name-based rule is the
|
|
99
|
+
# only defence — hence the explicit directory below for anything ad hoc.
|
|
100
|
+
infra/tfplan
|
|
101
|
+
infra/*.tfplan
|
|
102
|
+
infra/tf-plans/
|
|
103
|
+
|
|
104
|
+
# Playwright MCP writes browser console logs, traces and screenshots here when
|
|
105
|
+
# an agent drives the app. Session artefacts, not source — and they capture
|
|
106
|
+
# whatever the logged-in page happened to print, so they do not belong in git.
|
|
107
|
+
.playwright-mcp/
|
|
108
|
+
|
|
109
|
+
# Coverage artefacts — rewritten by every test run, never useful in a diff.
|
|
110
|
+
apps/evolve-ai/.coverage
|
|
111
|
+
test-output
|
|
112
|
+
|
|
113
|
+
# E2E build/output artefacts
|
|
114
|
+
apps/*-e2e/out-tsc/
|
|
115
|
+
apps/*-e2e/test-output/
|
|
116
|
+
playwright-report/
|
|
117
|
+
test-results/
|
|
118
|
+
|
|
119
|
+
# Memtrace (local code index — machine-local, never committed)
|
|
120
|
+
.memdb/
|
|
121
|
+
.memtrace/
|
|
122
|
+
.memtrace-workspace
|
|
123
|
+
|
|
124
|
+
# Local MCP server config — holds a Memtrace licence key.
|
|
125
|
+
.mcp.json
|
|
126
|
+
|
|
127
|
+
# Written by the Firebase Auth emulator that docs/E2E-TESTING.md tells you to start.
|
|
128
|
+
firebase-debug.log
|
|
129
|
+
firestore-debug.log
|
|
130
|
+
ui-debug.log
|
|
131
|
+
|
|
132
|
+
# TypeScript incremental build state (packages/logs-js writes one at its root).
|
|
133
|
+
*.tsbuildinfo
|
|
134
|
+
.logs-keys.json
|
|
135
|
+
.logs-keys-mspeed.json
|
|
136
|
+
|
|
137
|
+
# Recording rig: a saved production session, equivalent to a password.
|
|
138
|
+
tools/record/.auth/
|
|
139
|
+
|
|
140
|
+
# Recording rig: raw .webm captures and crop manifests. Only the rendered
|
|
141
|
+
# mp4/jpg under public/media/tour is kept.
|
|
142
|
+
tools/record/out/
|
|
143
|
+
|
|
144
|
+
# Recording rig scratch probes.
|
|
145
|
+
tools/record/*.tmp.ts
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: e-volv-logs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evolve Log Manager SDK — logs, traces and error capture for Python.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Pactify-Pty-Ltd/theevolve/tree/main/packages/logs-py
|
|
6
|
+
Project-URL: Source, https://github.com/Pactify-Pty-Ltd/theevolve
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.28.0
|
|
10
|
+
Provides-Extra: langchain
|
|
11
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# e-volv-logs
|
|
15
|
+
|
|
16
|
+
Evolve Log Manager SDK for Python — batched log shipping, trace context and
|
|
17
|
+
error capture against the Evolve ingest endpoint
|
|
18
|
+
(`POST /api/public/v1/logs`). Companion: the Node.js SDK `@e-volv/logs`.
|
|
19
|
+
|
|
20
|
+
Python 3.10+.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install e-volv-logs
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import e_volv_logs as evolve_logs
|
|
30
|
+
from e_volv_logs import init, log, span, trace
|
|
31
|
+
|
|
32
|
+
init(
|
|
33
|
+
key="evk_…", # project ingest key
|
|
34
|
+
url="https://your-host/api/public/v1/logs",
|
|
35
|
+
service="api",
|
|
36
|
+
environment="production",
|
|
37
|
+
release="1.4.2",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
log.info("order created", {"orderId": "o_1", "total": 42.5})
|
|
41
|
+
log.error("payment failed", {"orderId": "o_1"})
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
await charge()
|
|
45
|
+
except Exception as err:
|
|
46
|
+
log.exception(err, {"orderId": "o_1"}) # exception.type/message/stack
|
|
47
|
+
|
|
48
|
+
# Traces live in contextvars — they flow across await. httpx/requests
|
|
49
|
+
# requests made inside a trace get a W3C traceparent header automatically.
|
|
50
|
+
with trace():
|
|
51
|
+
with span("db.query", {"table": "orders"}):
|
|
52
|
+
await db.query("SELECT …")
|
|
53
|
+
await httpx.get("https://internal/svc") # traceparent injected
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `key` or `url` is missing, `init()` returns a no-op client and warns once.
|
|
57
|
+
The SDK never raises into user code.
|
|
58
|
+
|
|
59
|
+
## Batching, retries, drops
|
|
60
|
+
|
|
61
|
+
- Flushes at **200 events**, **2 s** (age of the oldest buffered event), or a
|
|
62
|
+
**512 KB** payload — whichever comes first. Bodies are gzipped
|
|
63
|
+
(`Content-Encoding: gzip`).
|
|
64
|
+
- **429** retries with exponential backoff (0.5 s doubling, capped at 10 s),
|
|
65
|
+
up to 3 attempts. **413** halves the batch and retries.
|
|
66
|
+
- When the pending buffer exceeds **2× batch size**, the **oldest** events are
|
|
67
|
+
dropped; losses are visible on `client.dropped`.
|
|
68
|
+
- `atexit` flushes whatever is pending.
|
|
69
|
+
|
|
70
|
+
## Options
|
|
71
|
+
|
|
72
|
+
`init(key=, url=, service=, environment=, release=, redact_keys=[...],
|
|
73
|
+
sample_rate=1.0, capture_excepthook=True)` — `redact_keys` are merged into the
|
|
74
|
+
backstop regex `password|secret|token|authorization|cookie|set-cookie|api[-_]?key`
|
|
75
|
+
(case-insensitive), applied to attrs before queueing.
|
|
76
|
+
|
|
77
|
+
## stdlib logging
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import logging
|
|
81
|
+
from e_volv_logs import init, LogHandler
|
|
82
|
+
|
|
83
|
+
init(key="evk_…", url="…", service="api")
|
|
84
|
+
logging.getLogger("myapp").addHandler(LogHandler())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Levels map to OTel severities (DEBUG→5, INFO→9, WARNING→13, ERROR→17,
|
|
88
|
+
CRITICAL→21). `extra={...}` on the log call becomes event attrs; `exc_info`
|
|
89
|
+
becomes `exception.*` fields. `sys.excepthook` capture is on by default.
|
|
90
|
+
|
|
91
|
+
## FastAPI / Starlette
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from e_volv_logs.integrations.fastapi import EvolveLogsMiddleware
|
|
95
|
+
|
|
96
|
+
app.add_middleware(EvolveLogsMiddleware) # one root span per request
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The middleware continues the trace when the inbound request carries an
|
|
100
|
+
`x-evolve-traceparent` header (Evolve backend → evolve-ai propagation),
|
|
101
|
+
otherwise starts a fresh root trace.
|
|
102
|
+
|
|
103
|
+
## LangChain and LangGraph
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install "e-volv-logs[langchain]"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from e_volv_logs.integrations.langchain import EvolveLogsCallbackHandler
|
|
111
|
+
|
|
112
|
+
result = graph.invoke(
|
|
113
|
+
{"question": "…"},
|
|
114
|
+
config={"callbacks": [EvolveLogsCallbackHandler()]},
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
One span per LangChain run, nested by `parent_run_id`, all sharing one trace —
|
|
119
|
+
so a LangGraph node's tool calls and model calls appear inside the node on the
|
|
120
|
+
Evolve trace graph. Span names:
|
|
121
|
+
|
|
122
|
+
| Callback | Span name |
|
|
123
|
+
| -------------------------------------- | -------------- |
|
|
124
|
+
| `on_chain_start` with `langgraph_node` | `graph.<node>` |
|
|
125
|
+
| `on_chain_start` otherwise | `chain.<name>` |
|
|
126
|
+
| `on_tool_start` | `tool.<name>` |
|
|
127
|
+
| `on_llm_start` / `on_chat_model_start` | `llm.<model>` |
|
|
128
|
+
|
|
129
|
+
Errors end the span through `_log_span_error` (severity error, `exception.*`
|
|
130
|
+
attrs), exactly like `span()` raised inside a body.
|
|
131
|
+
|
|
132
|
+
## Queue hops
|
|
133
|
+
|
|
134
|
+
A span on the consumer side joins the producer's trace only if the traceparent
|
|
135
|
+
travels with the job:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from e_volv_logs import run_with_traceparent, span, traceparent
|
|
139
|
+
|
|
140
|
+
# producer
|
|
141
|
+
queue.enqueue(work, payload, trace=traceparent())
|
|
142
|
+
|
|
143
|
+
# consumer: the body runs as a new hop of the producer's trace.
|
|
144
|
+
with run_with_traceparent(job.trace), span("queue.work", {"queue": "work"}):
|
|
145
|
+
handle(job)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`run_with_traceparent(header)` parses a W3C traceparent; an absent or malformed
|
|
149
|
+
header starts a fresh trace, so a producer that sends nothing still yields a
|
|
150
|
+
trace of its own.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd packages/logs-py
|
|
156
|
+
uv sync
|
|
157
|
+
uv run pytest tests/ -q
|
|
158
|
+
```
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# e-volv-logs
|
|
2
|
+
|
|
3
|
+
Evolve Log Manager SDK for Python — batched log shipping, trace context and
|
|
4
|
+
error capture against the Evolve ingest endpoint
|
|
5
|
+
(`POST /api/public/v1/logs`). Companion: the Node.js SDK `@e-volv/logs`.
|
|
6
|
+
|
|
7
|
+
Python 3.10+.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install e-volv-logs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import e_volv_logs as evolve_logs
|
|
17
|
+
from e_volv_logs import init, log, span, trace
|
|
18
|
+
|
|
19
|
+
init(
|
|
20
|
+
key="evk_…", # project ingest key
|
|
21
|
+
url="https://your-host/api/public/v1/logs",
|
|
22
|
+
service="api",
|
|
23
|
+
environment="production",
|
|
24
|
+
release="1.4.2",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
log.info("order created", {"orderId": "o_1", "total": 42.5})
|
|
28
|
+
log.error("payment failed", {"orderId": "o_1"})
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
await charge()
|
|
32
|
+
except Exception as err:
|
|
33
|
+
log.exception(err, {"orderId": "o_1"}) # exception.type/message/stack
|
|
34
|
+
|
|
35
|
+
# Traces live in contextvars — they flow across await. httpx/requests
|
|
36
|
+
# requests made inside a trace get a W3C traceparent header automatically.
|
|
37
|
+
with trace():
|
|
38
|
+
with span("db.query", {"table": "orders"}):
|
|
39
|
+
await db.query("SELECT …")
|
|
40
|
+
await httpx.get("https://internal/svc") # traceparent injected
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If `key` or `url` is missing, `init()` returns a no-op client and warns once.
|
|
44
|
+
The SDK never raises into user code.
|
|
45
|
+
|
|
46
|
+
## Batching, retries, drops
|
|
47
|
+
|
|
48
|
+
- Flushes at **200 events**, **2 s** (age of the oldest buffered event), or a
|
|
49
|
+
**512 KB** payload — whichever comes first. Bodies are gzipped
|
|
50
|
+
(`Content-Encoding: gzip`).
|
|
51
|
+
- **429** retries with exponential backoff (0.5 s doubling, capped at 10 s),
|
|
52
|
+
up to 3 attempts. **413** halves the batch and retries.
|
|
53
|
+
- When the pending buffer exceeds **2× batch size**, the **oldest** events are
|
|
54
|
+
dropped; losses are visible on `client.dropped`.
|
|
55
|
+
- `atexit` flushes whatever is pending.
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
`init(key=, url=, service=, environment=, release=, redact_keys=[...],
|
|
60
|
+
sample_rate=1.0, capture_excepthook=True)` — `redact_keys` are merged into the
|
|
61
|
+
backstop regex `password|secret|token|authorization|cookie|set-cookie|api[-_]?key`
|
|
62
|
+
(case-insensitive), applied to attrs before queueing.
|
|
63
|
+
|
|
64
|
+
## stdlib logging
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import logging
|
|
68
|
+
from e_volv_logs import init, LogHandler
|
|
69
|
+
|
|
70
|
+
init(key="evk_…", url="…", service="api")
|
|
71
|
+
logging.getLogger("myapp").addHandler(LogHandler())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Levels map to OTel severities (DEBUG→5, INFO→9, WARNING→13, ERROR→17,
|
|
75
|
+
CRITICAL→21). `extra={...}` on the log call becomes event attrs; `exc_info`
|
|
76
|
+
becomes `exception.*` fields. `sys.excepthook` capture is on by default.
|
|
77
|
+
|
|
78
|
+
## FastAPI / Starlette
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from e_volv_logs.integrations.fastapi import EvolveLogsMiddleware
|
|
82
|
+
|
|
83
|
+
app.add_middleware(EvolveLogsMiddleware) # one root span per request
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The middleware continues the trace when the inbound request carries an
|
|
87
|
+
`x-evolve-traceparent` header (Evolve backend → evolve-ai propagation),
|
|
88
|
+
otherwise starts a fresh root trace.
|
|
89
|
+
|
|
90
|
+
## LangChain and LangGraph
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install "e-volv-logs[langchain]"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from e_volv_logs.integrations.langchain import EvolveLogsCallbackHandler
|
|
98
|
+
|
|
99
|
+
result = graph.invoke(
|
|
100
|
+
{"question": "…"},
|
|
101
|
+
config={"callbacks": [EvolveLogsCallbackHandler()]},
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
One span per LangChain run, nested by `parent_run_id`, all sharing one trace —
|
|
106
|
+
so a LangGraph node's tool calls and model calls appear inside the node on the
|
|
107
|
+
Evolve trace graph. Span names:
|
|
108
|
+
|
|
109
|
+
| Callback | Span name |
|
|
110
|
+
| -------------------------------------- | -------------- |
|
|
111
|
+
| `on_chain_start` with `langgraph_node` | `graph.<node>` |
|
|
112
|
+
| `on_chain_start` otherwise | `chain.<name>` |
|
|
113
|
+
| `on_tool_start` | `tool.<name>` |
|
|
114
|
+
| `on_llm_start` / `on_chat_model_start` | `llm.<model>` |
|
|
115
|
+
|
|
116
|
+
Errors end the span through `_log_span_error` (severity error, `exception.*`
|
|
117
|
+
attrs), exactly like `span()` raised inside a body.
|
|
118
|
+
|
|
119
|
+
## Queue hops
|
|
120
|
+
|
|
121
|
+
A span on the consumer side joins the producer's trace only if the traceparent
|
|
122
|
+
travels with the job:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from e_volv_logs import run_with_traceparent, span, traceparent
|
|
126
|
+
|
|
127
|
+
# producer
|
|
128
|
+
queue.enqueue(work, payload, trace=traceparent())
|
|
129
|
+
|
|
130
|
+
# consumer: the body runs as a new hop of the producer's trace.
|
|
131
|
+
with run_with_traceparent(job.trace), span("queue.work", {"queue": "work"}):
|
|
132
|
+
handle(job)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`run_with_traceparent(header)` parses a W3C traceparent; an absent or malformed
|
|
136
|
+
header starts a fresh trace, so a producer that sends nothing still yields a
|
|
137
|
+
trace of its own.
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
cd packages/logs-py
|
|
143
|
+
uv sync
|
|
144
|
+
uv run pytest tests/ -q
|
|
145
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Evolve Log Manager SDK for Python (e-volv-logs)."""
|
|
2
|
+
|
|
3
|
+
from evolve_logs import * # noqa: F403
|
|
4
|
+
from evolve_logs import (
|
|
5
|
+
FLUSH_MS,
|
|
6
|
+
MAX_BATCH,
|
|
7
|
+
MAX_PAYLOAD_BYTES,
|
|
8
|
+
SEVERITY,
|
|
9
|
+
LogHandler,
|
|
10
|
+
LogsClient,
|
|
11
|
+
current_context,
|
|
12
|
+
get_client,
|
|
13
|
+
init,
|
|
14
|
+
log,
|
|
15
|
+
patch_httpx,
|
|
16
|
+
patch_requests,
|
|
17
|
+
run_with_traceparent,
|
|
18
|
+
span,
|
|
19
|
+
trace,
|
|
20
|
+
traceparent,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"FLUSH_MS",
|
|
25
|
+
"MAX_BATCH",
|
|
26
|
+
"MAX_PAYLOAD_BYTES",
|
|
27
|
+
"SEVERITY",
|
|
28
|
+
"LogHandler",
|
|
29
|
+
"LogsClient",
|
|
30
|
+
"current_context",
|
|
31
|
+
"get_client",
|
|
32
|
+
"init",
|
|
33
|
+
"log",
|
|
34
|
+
"patch_httpx",
|
|
35
|
+
"patch_requests",
|
|
36
|
+
"run_with_traceparent",
|
|
37
|
+
"span",
|
|
38
|
+
"trace",
|
|
39
|
+
"traceparent",
|
|
40
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Evolve Log Manager SDK for Python.
|
|
2
|
+
|
|
3
|
+
Batches events (200 events / 2 s / 512 KB), gzips the payload, retries with
|
|
4
|
+
backoff on 429/413, and drops oldest when the pending buffer exceeds 2x the
|
|
5
|
+
batch size. Trace context lives in :mod:`contextvars`, so it flows across
|
|
6
|
+
``await``. Everything is fail-silent: a logging SDK must never raise into user
|
|
7
|
+
code.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from evolve_logs.client import (
|
|
11
|
+
FLUSH_MS,
|
|
12
|
+
MAX_BATCH,
|
|
13
|
+
MAX_PAYLOAD_BYTES,
|
|
14
|
+
LogsClient,
|
|
15
|
+
init,
|
|
16
|
+
)
|
|
17
|
+
from evolve_logs.context import (
|
|
18
|
+
current_context,
|
|
19
|
+
run_with_traceparent,
|
|
20
|
+
span,
|
|
21
|
+
trace,
|
|
22
|
+
traceparent,
|
|
23
|
+
)
|
|
24
|
+
from evolve_logs.handler import LogHandler
|
|
25
|
+
from evolve_logs.patches import patch_httpx, patch_requests
|
|
26
|
+
from evolve_logs.types import SEVERITY
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"FLUSH_MS",
|
|
30
|
+
"MAX_BATCH",
|
|
31
|
+
"MAX_PAYLOAD_BYTES",
|
|
32
|
+
"SEVERITY",
|
|
33
|
+
"LogHandler",
|
|
34
|
+
"LogsClient",
|
|
35
|
+
"current_context",
|
|
36
|
+
"init",
|
|
37
|
+
"log",
|
|
38
|
+
"patch_httpx",
|
|
39
|
+
"patch_requests",
|
|
40
|
+
"run_with_traceparent",
|
|
41
|
+
"span",
|
|
42
|
+
"trace",
|
|
43
|
+
"traceparent",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _LogProxy:
|
|
48
|
+
"""Module-level logger bound to the default client (set by ``init``)."""
|
|
49
|
+
|
|
50
|
+
def trace(self, message, attrs=None):
|
|
51
|
+
client = get_client()
|
|
52
|
+
if client:
|
|
53
|
+
client.trace(message, attrs)
|
|
54
|
+
|
|
55
|
+
def debug(self, message, attrs=None):
|
|
56
|
+
client = get_client()
|
|
57
|
+
if client:
|
|
58
|
+
client.debug(message, attrs)
|
|
59
|
+
|
|
60
|
+
def info(self, message, attrs=None):
|
|
61
|
+
client = get_client()
|
|
62
|
+
if client:
|
|
63
|
+
client.info(message, attrs)
|
|
64
|
+
|
|
65
|
+
def warn(self, message, attrs=None):
|
|
66
|
+
client = get_client()
|
|
67
|
+
if client:
|
|
68
|
+
client.warn(message, attrs)
|
|
69
|
+
|
|
70
|
+
warning = warn
|
|
71
|
+
|
|
72
|
+
def error(self, message, attrs=None):
|
|
73
|
+
client = get_client()
|
|
74
|
+
if client:
|
|
75
|
+
client.error(message, attrs)
|
|
76
|
+
|
|
77
|
+
def fatal(self, message, attrs=None):
|
|
78
|
+
client = get_client()
|
|
79
|
+
if client:
|
|
80
|
+
client.fatal(message, attrs)
|
|
81
|
+
|
|
82
|
+
def exception(self, err, attrs=None):
|
|
83
|
+
client = get_client()
|
|
84
|
+
if client:
|
|
85
|
+
client.exception(err, attrs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
log = _LogProxy()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_client() -> LogsClient | None:
|
|
92
|
+
"""The client installed by :func:`init`, or None before init."""
|
|
93
|
+
from evolve_logs.client import get_client as _get
|
|
94
|
+
|
|
95
|
+
return _get()
|