verica-observability 0.1.1__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.
- verica_observability-0.1.1/.gitignore +40 -0
- verica_observability-0.1.1/PKG-INFO +57 -0
- verica_observability-0.1.1/README.md +40 -0
- verica_observability-0.1.1/examples/basic.py +26 -0
- verica_observability-0.1.1/pyproject.toml +30 -0
- verica_observability-0.1.1/tests/test_config.py +63 -0
- verica_observability-0.1.1/tests/test_sdk.py +25 -0
- verica_observability-0.1.1/verica/__init__.py +6 -0
- verica_observability-0.1.1/verica/_config.py +50 -0
- verica_observability-0.1.1/verica/_sdk.py +116 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# deps
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
|
|
5
|
+
# python sdk
|
|
6
|
+
sdks/python/.venv/
|
|
7
|
+
sdks/**/__pycache__/
|
|
8
|
+
|
|
9
|
+
# test coverage
|
|
10
|
+
coverage/
|
|
11
|
+
|
|
12
|
+
# build output
|
|
13
|
+
dist/
|
|
14
|
+
.next/
|
|
15
|
+
out/
|
|
16
|
+
*.tsbuildinfo
|
|
17
|
+
|
|
18
|
+
# turbo
|
|
19
|
+
.turbo/
|
|
20
|
+
|
|
21
|
+
# env / secrets
|
|
22
|
+
.env
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
# key material (RSA credential keypair, etc.) — must never be committed; the
|
|
26
|
+
# real values live base64-encoded in .env (and prod secret stores).
|
|
27
|
+
*.pem
|
|
28
|
+
|
|
29
|
+
# logs
|
|
30
|
+
*.log
|
|
31
|
+
npm-debug.log*
|
|
32
|
+
|
|
33
|
+
# os / editor
|
|
34
|
+
.DS_Store
|
|
35
|
+
.idea/
|
|
36
|
+
.vscode/*
|
|
37
|
+
!.vscode/extensions.json
|
|
38
|
+
|
|
39
|
+
# drizzle local
|
|
40
|
+
packages/db/drizzle/meta/_journal.bak
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: verica-observability
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Two-line LLM tracing for Verica: init(token) and your OpenAI/Anthropic calls land as evaluable traces.
|
|
5
|
+
Project-URL: Homepage, https://verica.app
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: evals,llm,observability,opentelemetry,tracing,verica
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
|
|
10
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.33
|
|
11
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.33
|
|
12
|
+
Requires-Dist: opentelemetry-sdk>=1.27
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# verica-observability
|
|
19
|
+
|
|
20
|
+
Two-line LLM tracing for [Verica](https://verica.app): your OpenAI/Anthropic
|
|
21
|
+
calls land as evaluable traces.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install verica-observability
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import verica
|
|
33
|
+
|
|
34
|
+
verica.init(token=os.environ["VERICA_TOKEN"])
|
|
35
|
+
|
|
36
|
+
# Import AFTER init so the client is patched.
|
|
37
|
+
from openai import OpenAI
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Serverless
|
|
41
|
+
|
|
42
|
+
Call `verica.flush()` (or `verica.shutdown()`) before the runtime freezes so
|
|
43
|
+
the span batch is exported.
|
|
44
|
+
|
|
45
|
+
## Options
|
|
46
|
+
|
|
47
|
+
| Option / env var | Default | Notes |
|
|
48
|
+
| -------------------------------------------- | ---------- | ------------------------------- |
|
|
49
|
+
| `token` / `VERICA_TOKEN` | (required) | ingest-scoped API token |
|
|
50
|
+
| `capture_content` / `VERICA_CAPTURE_CONTENT` | `true` | send prompt/response content |
|
|
51
|
+
| `conversation_id` | (none) | stamps `gen_ai.conversation.id` |
|
|
52
|
+
| `service_name` / `OTEL_SERVICE_NAME` | `app` | resource service.name |
|
|
53
|
+
| `debug` / `VERICA_DEBUG` | `false` | log export errors |
|
|
54
|
+
|
|
55
|
+
Fail-open by design: if Verica is unreachable or the token is invalid, spans are
|
|
56
|
+
dropped and your app is never affected. Export errors are silent unless `debug`
|
|
57
|
+
is on.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# verica-observability
|
|
2
|
+
|
|
3
|
+
Two-line LLM tracing for [Verica](https://verica.app): your OpenAI/Anthropic
|
|
4
|
+
calls land as evaluable traces.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install verica-observability
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Use
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import verica
|
|
16
|
+
|
|
17
|
+
verica.init(token=os.environ["VERICA_TOKEN"])
|
|
18
|
+
|
|
19
|
+
# Import AFTER init so the client is patched.
|
|
20
|
+
from openai import OpenAI
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Serverless
|
|
24
|
+
|
|
25
|
+
Call `verica.flush()` (or `verica.shutdown()`) before the runtime freezes so
|
|
26
|
+
the span batch is exported.
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
| Option / env var | Default | Notes |
|
|
31
|
+
| -------------------------------------------- | ---------- | ------------------------------- |
|
|
32
|
+
| `token` / `VERICA_TOKEN` | (required) | ingest-scoped API token |
|
|
33
|
+
| `capture_content` / `VERICA_CAPTURE_CONTENT` | `true` | send prompt/response content |
|
|
34
|
+
| `conversation_id` | (none) | stamps `gen_ai.conversation.id` |
|
|
35
|
+
| `service_name` / `OTEL_SERVICE_NAME` | `app` | resource service.name |
|
|
36
|
+
| `debug` / `VERICA_DEBUG` | `false` | log export errors |
|
|
37
|
+
|
|
38
|
+
Fail-open by design: if Verica is unreachable or the token is invalid, spans are
|
|
39
|
+
dropped and your app is never affected. Export errors are silent unless `debug`
|
|
40
|
+
is on.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Run from the repo (worker/web running, OPENAI_API_KEY + VERICA_TOKEN set):
|
|
2
|
+
# ./sdks/python/.venv/bin/pip install openai
|
|
3
|
+
# ./sdks/python/.venv/bin/python sdks/python/examples/basic.py
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import verica
|
|
7
|
+
|
|
8
|
+
ok = verica.init(
|
|
9
|
+
token=os.environ.get("VERICA_TOKEN"),
|
|
10
|
+
endpoint=os.environ.get("VERICA_ENDPOINT", "http://localhost:3000"),
|
|
11
|
+
service_name="verica-example-python",
|
|
12
|
+
conversation_id="verica-sdk-check-python",
|
|
13
|
+
)
|
|
14
|
+
if not ok:
|
|
15
|
+
raise SystemExit(1)
|
|
16
|
+
|
|
17
|
+
# Import AFTER init so the instrumentation patches the client.
|
|
18
|
+
from openai import OpenAI # noqa: E402
|
|
19
|
+
|
|
20
|
+
client = OpenAI()
|
|
21
|
+
res = client.chat.completions.create(
|
|
22
|
+
model="gpt-4o-mini",
|
|
23
|
+
messages=[{"role": "user", "content": 'Say "verica sdk check python" and nothing else.'}],
|
|
24
|
+
)
|
|
25
|
+
print(res.choices[0].message.content)
|
|
26
|
+
verica.shutdown() # drain the batch before the process exits
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "verica-observability"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Two-line LLM tracing for Verica: init(token) and your OpenAI/Anthropic calls land as evaluable traces."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["verica", "observability", "opentelemetry", "llm", "tracing", "evals"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"opentelemetry-sdk>=1.27",
|
|
15
|
+
"opentelemetry-exporter-otlp-proto-http>=1.27",
|
|
16
|
+
"opentelemetry-instrumentation-openai>=0.33",
|
|
17
|
+
"opentelemetry-instrumentation-anthropic>=0.33",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://verica.app"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["verica"]
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
line-length = 100
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from verica._config import resolve_config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_resolves_from_options():
|
|
5
|
+
cfg, missing = resolve_config({"token": "vk_1", "endpoint": "https://x.app"}, {})
|
|
6
|
+
assert missing == []
|
|
7
|
+
assert cfg.token == "vk_1"
|
|
8
|
+
assert cfg.endpoint == "https://x.app"
|
|
9
|
+
assert cfg.capture_content is True
|
|
10
|
+
assert cfg.service_name == "app"
|
|
11
|
+
assert cfg.debug is False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_env_fallback_and_trailing_slash():
|
|
15
|
+
cfg, missing = resolve_config(
|
|
16
|
+
{}, {"VERICA_TOKEN": "vk_2", "VERICA_ENDPOINT": "https://x.app/"}
|
|
17
|
+
)
|
|
18
|
+
assert missing == []
|
|
19
|
+
assert cfg.endpoint == "https://x.app"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_options_win_over_env():
|
|
23
|
+
cfg, _ = resolve_config(
|
|
24
|
+
{"token": "opt", "endpoint": "https://opt.app"},
|
|
25
|
+
{"VERICA_TOKEN": "env", "VERICA_ENDPOINT": "https://env.app"},
|
|
26
|
+
)
|
|
27
|
+
assert cfg.token == "opt"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_missing_token_reported_not_raised():
|
|
31
|
+
cfg, missing = resolve_config({}, {})
|
|
32
|
+
assert cfg is None
|
|
33
|
+
assert missing == ["token"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_endpoint_defaults_to_hosted_ingest():
|
|
37
|
+
cfg, missing = resolve_config({"token": "t"}, {})
|
|
38
|
+
assert missing == []
|
|
39
|
+
assert cfg.endpoint == "https://ingest.verica.app"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_endpoint_option_and_env_override_the_default():
|
|
43
|
+
cfg, _ = resolve_config({"token": "t", "endpoint": "http://localhost:3001"}, {})
|
|
44
|
+
assert cfg.endpoint == "http://localhost:3001"
|
|
45
|
+
cfg, _ = resolve_config({"token": "t"}, {"VERICA_ENDPOINT": "http://localhost:3001"})
|
|
46
|
+
assert cfg.endpoint == "http://localhost:3001"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_capture_content_env_off_and_option_override():
|
|
50
|
+
base = {"token": "t", "endpoint": "https://x"}
|
|
51
|
+
cfg, _ = resolve_config(base, {"VERICA_CAPTURE_CONTENT": "false"})
|
|
52
|
+
assert cfg.capture_content is False
|
|
53
|
+
cfg, _ = resolve_config({**base, "capture_content": True}, {"VERICA_CAPTURE_CONTENT": "false"})
|
|
54
|
+
assert cfg.capture_content is True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_debug_and_service_name_fallbacks():
|
|
58
|
+
cfg, _ = resolve_config(
|
|
59
|
+
{"token": "t", "endpoint": "https://x"},
|
|
60
|
+
{"VERICA_DEBUG": "1", "OTEL_SERVICE_NAME": "my-svc"},
|
|
61
|
+
)
|
|
62
|
+
assert cfg.debug is True
|
|
63
|
+
assert cfg.service_name == "my-svc"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import verica
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def teardown_function():
|
|
5
|
+
verica.shutdown()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_init_without_config_is_noop_false(monkeypatch):
|
|
9
|
+
monkeypatch.delenv("VERICA_TOKEN", raising=False)
|
|
10
|
+
monkeypatch.delenv("VERICA_ENDPOINT", raising=False)
|
|
11
|
+
assert verica.init() is False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_init_is_idempotent_and_fail_open():
|
|
15
|
+
# Unreachable endpoint: init must still succeed (batch export is async).
|
|
16
|
+
assert verica.init(token="vk_t", endpoint="http://127.0.0.1:9") is True
|
|
17
|
+
assert verica.init(token="vk_t", endpoint="http://127.0.0.1:9") is True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_flush_and_shutdown_never_raise():
|
|
21
|
+
verica.flush()
|
|
22
|
+
verica.shutdown()
|
|
23
|
+
verica.init(token="vk_t", endpoint="http://127.0.0.1:9")
|
|
24
|
+
verica.flush()
|
|
25
|
+
verica.shutdown()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Config resolution: options > env vars > defaults. Pure and import-safe."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Mapping, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Config:
|
|
9
|
+
token: str
|
|
10
|
+
endpoint: str
|
|
11
|
+
capture_content: bool
|
|
12
|
+
conversation_id: Optional[str]
|
|
13
|
+
service_name: str
|
|
14
|
+
debug: bool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Verica's hosted ingest endpoint. Override via the `endpoint` option or the
|
|
18
|
+
# VERICA_ENDPOINT env var for self-host or local dev. Only `token` is required.
|
|
19
|
+
DEFAULT_ENDPOINT = "https://ingest.verica.app"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _truthy(value: Optional[str]) -> bool:
|
|
23
|
+
return value in ("1", "true")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def resolve_config(options: dict, env: Mapping[str, str]) -> "tuple[Optional[Config], list[str]]":
|
|
27
|
+
token = options.get("token") or env.get("VERICA_TOKEN") or ""
|
|
28
|
+
if not token:
|
|
29
|
+
return None, ["token"]
|
|
30
|
+
|
|
31
|
+
raw_endpoint = options.get("endpoint") or env.get("VERICA_ENDPOINT") or DEFAULT_ENDPOINT
|
|
32
|
+
|
|
33
|
+
capture = options.get("capture_content")
|
|
34
|
+
if capture is None:
|
|
35
|
+
raw = env.get("VERICA_CAPTURE_CONTENT")
|
|
36
|
+
capture = _truthy(raw) if raw is not None else True
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
Config(
|
|
40
|
+
token=token,
|
|
41
|
+
endpoint=raw_endpoint.rstrip("/"),
|
|
42
|
+
capture_content=capture,
|
|
43
|
+
conversation_id=options.get("conversation_id"),
|
|
44
|
+
service_name=options.get("service_name") or env.get("OTEL_SERVICE_NAME") or "app",
|
|
45
|
+
debug=options.get("debug")
|
|
46
|
+
if options.get("debug") is not None
|
|
47
|
+
else _truthy(env.get("VERICA_DEBUG")),
|
|
48
|
+
),
|
|
49
|
+
[],
|
|
50
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Fail-open OTel setup: never raise into the host app."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from verica._config import Config, resolve_config
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("verica")
|
|
9
|
+
|
|
10
|
+
_provider = None # type: ignore[var-annotated]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _ConversationIdProcessor:
|
|
14
|
+
"""Stamps gen_ai.conversation.id on every span (Phase 4 session join)."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, conversation_id: str):
|
|
17
|
+
self._id = conversation_id
|
|
18
|
+
|
|
19
|
+
def on_start(self, span, parent_context=None):
|
|
20
|
+
span.set_attribute("gen_ai.conversation.id", self._id)
|
|
21
|
+
|
|
22
|
+
def on_end(self, span):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def shutdown(self):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def force_flush(self, timeout_millis: int = 30000):
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def init(**options) -> bool:
|
|
33
|
+
global _provider
|
|
34
|
+
if _provider is not None:
|
|
35
|
+
logger.warning("verica.init() called twice; ignoring the second call.")
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
cfg, missing = resolve_config(options, os.environ)
|
|
39
|
+
if cfg is None:
|
|
40
|
+
logger.warning("verica: missing %s; tracing is disabled.", " and ".join(missing))
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
from opentelemetry import trace
|
|
45
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
46
|
+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
|
47
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
48
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
49
|
+
|
|
50
|
+
# openllmetry instrumentations gate content capture on this env var.
|
|
51
|
+
os.environ.setdefault("TRACELOOP_TRACE_CONTENT", str(cfg.capture_content).lower())
|
|
52
|
+
|
|
53
|
+
# Spec §5: export errors (401, network) must not spam the host app's
|
|
54
|
+
# logs; the OTLP exporter logs through this logger on every failure.
|
|
55
|
+
if not cfg.debug:
|
|
56
|
+
logging.getLogger("opentelemetry.exporter.otlp.proto.http.trace_exporter").setLevel(
|
|
57
|
+
logging.CRITICAL
|
|
58
|
+
)
|
|
59
|
+
logging.getLogger("opentelemetry.sdk.trace.export").setLevel(logging.CRITICAL)
|
|
60
|
+
|
|
61
|
+
provider = TracerProvider(resource=Resource.create({SERVICE_NAME: cfg.service_name}))
|
|
62
|
+
if cfg.conversation_id:
|
|
63
|
+
provider.add_span_processor(_ConversationIdProcessor(cfg.conversation_id))
|
|
64
|
+
provider.add_span_processor(
|
|
65
|
+
BatchSpanProcessor(
|
|
66
|
+
OTLPSpanExporter(
|
|
67
|
+
endpoint=f"{cfg.endpoint}/v1/traces",
|
|
68
|
+
headers={"Authorization": f"Bearer {cfg.token}"},
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
trace.set_tracer_provider(provider)
|
|
73
|
+
_instrument(provider, cfg)
|
|
74
|
+
_provider = provider
|
|
75
|
+
return True
|
|
76
|
+
except Exception as err: # noqa: BLE001 - fail-open by contract
|
|
77
|
+
logger.warning("verica: init failed; tracing is disabled. %s", err if cfg.debug else "")
|
|
78
|
+
_provider = None
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _instrument(provider, cfg: Config) -> None:
|
|
83
|
+
"""Each instrumentor is optional: a missing/broken one never blocks init."""
|
|
84
|
+
try:
|
|
85
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
86
|
+
|
|
87
|
+
OpenAIInstrumentor().instrument(tracer_provider=provider)
|
|
88
|
+
except Exception as err: # noqa: BLE001
|
|
89
|
+
if cfg.debug:
|
|
90
|
+
logger.warning("verica: openai instrumentation not active: %s", err)
|
|
91
|
+
try:
|
|
92
|
+
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
93
|
+
|
|
94
|
+
AnthropicInstrumentor().instrument(tracer_provider=provider)
|
|
95
|
+
except Exception as err: # noqa: BLE001
|
|
96
|
+
if cfg.debug:
|
|
97
|
+
logger.warning("verica: anthropic instrumentation not active: %s", err)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def flush() -> None:
|
|
101
|
+
try:
|
|
102
|
+
if _provider is not None:
|
|
103
|
+
_provider.force_flush()
|
|
104
|
+
except Exception: # noqa: BLE001
|
|
105
|
+
logger.debug("verica: flush failed", exc_info=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def shutdown() -> None:
|
|
109
|
+
global _provider
|
|
110
|
+
try:
|
|
111
|
+
if _provider is not None:
|
|
112
|
+
_provider.shutdown()
|
|
113
|
+
except Exception: # noqa: BLE001
|
|
114
|
+
logger.debug("verica: shutdown failed", exc_info=True)
|
|
115
|
+
finally:
|
|
116
|
+
_provider = None
|