lanternfish 0.3.0__py3-none-any.whl
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.
- lanternfish/__init__.py +61 -0
- lanternfish/configuration.py +81 -0
- lanternfish/context.py +67 -0
- lanternfish/decorators.py +222 -0
- lanternfish/deployments.py +69 -0
- lanternfish/errors.py +9 -0
- lanternfish/exporters.py +34 -0
- lanternfish/instrument.py +181 -0
- lanternfish/integrations/__init__.py +61 -0
- lanternfish/integrations/anthropic.py +20 -0
- lanternfish/integrations/autogen.py +75 -0
- lanternfish/integrations/crewai.py +17 -0
- lanternfish/integrations/gemini.py +24 -0
- lanternfish/integrations/groq.py +21 -0
- lanternfish/integrations/haystack.py +65 -0
- lanternfish/integrations/http.py +129 -0
- lanternfish/integrations/instrumentor.py +24 -0
- lanternfish/integrations/langchain.py +21 -0
- lanternfish/integrations/llamaindex.py +21 -0
- lanternfish/integrations/mcp.py +208 -0
- lanternfish/integrations/ollama.py +73 -0
- lanternfish/integrations/openai.py +17 -0
- lanternfish/integrations/openai_agents.py +78 -0
- lanternfish/integrations/pydantic_ai.py +66 -0
- lanternfish/integrations/redis.py +296 -0
- lanternfish/integrations/sql.py +300 -0
- lanternfish/json_exporter.py +170 -0
- lanternfish/providers.py +14 -0
- lanternfish/py.typed +0 -0
- lanternfish/semantic.py +794 -0
- lanternfish/span_processor.py +69 -0
- lanternfish-0.3.0.dist-info/METADATA +137 -0
- lanternfish-0.3.0.dist-info/RECORD +35 -0
- lanternfish-0.3.0.dist-info/WHEEL +5 -0
- lanternfish-0.3.0.dist-info/top_level.txt +1 -0
lanternfish/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
# Görev: Lanternfish SDK'nin public API'sini dışa aktarmak
|
|
3
|
+
# Yapmaz: Instrumentation mantığı içermez
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
from lanternfish.context import UserScope, set_user
|
|
8
|
+
from lanternfish.decorators import span, span_context, trace
|
|
9
|
+
from lanternfish.deployments import record_deployment
|
|
10
|
+
from lanternfish.errors import LanternfishConfigurationError, LanternfishDeploymentError
|
|
11
|
+
from lanternfish.instrument import flush, instrument, shutdown
|
|
12
|
+
from lanternfish.semantic import (
|
|
13
|
+
confidence_tracking,
|
|
14
|
+
context_retrieval,
|
|
15
|
+
decision,
|
|
16
|
+
memory_access,
|
|
17
|
+
planning,
|
|
18
|
+
reasoning,
|
|
19
|
+
reflection,
|
|
20
|
+
set_planning_attribute,
|
|
21
|
+
set_reasoning_attribute,
|
|
22
|
+
set_reflection_attribute,
|
|
23
|
+
set_verification_attribute,
|
|
24
|
+
tool_usage,
|
|
25
|
+
verification,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Sürüm tek kaynaktan, pyproject'ten okunur; kaynak ağaçtan çalışırken
|
|
29
|
+
# paket kurulu olmayabilir.
|
|
30
|
+
try:
|
|
31
|
+
__version__ = version("lanternfish")
|
|
32
|
+
except PackageNotFoundError:
|
|
33
|
+
__version__ = "0.0.0+local"
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"__version__",
|
|
37
|
+
"instrument",
|
|
38
|
+
"flush",
|
|
39
|
+
"shutdown",
|
|
40
|
+
"UserScope",
|
|
41
|
+
"LanternfishConfigurationError",
|
|
42
|
+
"LanternfishDeploymentError",
|
|
43
|
+
"record_deployment",
|
|
44
|
+
"set_user",
|
|
45
|
+
"trace",
|
|
46
|
+
"span",
|
|
47
|
+
"span_context",
|
|
48
|
+
"planning",
|
|
49
|
+
"reasoning",
|
|
50
|
+
"reflection",
|
|
51
|
+
"verification",
|
|
52
|
+
"tool_usage",
|
|
53
|
+
"memory_access",
|
|
54
|
+
"context_retrieval",
|
|
55
|
+
"confidence_tracking",
|
|
56
|
+
"decision",
|
|
57
|
+
"set_planning_attribute",
|
|
58
|
+
"set_reasoning_attribute",
|
|
59
|
+
"set_reflection_attribute",
|
|
60
|
+
"set_verification_attribute",
|
|
61
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDK yapılandırmasının çözümü.
|
|
3
|
+
|
|
4
|
+
Görev: Endpoint, API anahtarı, sürüm (release) ve ortam değerlerini argümandan ya da ortam değişkeninden okur ve
|
|
5
|
+
doğrular. Sürüm verilmezse yaygın CI/hosting değişkenlerinden (Git commit) bulur.
|
|
6
|
+
Yapmaz: Ağa çıkmaz, provider kurmaz, varsayılan bir Cloud adresi seçmez.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from urllib.parse import urlsplit
|
|
11
|
+
|
|
12
|
+
from lanternfish.errors import LanternfishConfigurationError
|
|
13
|
+
|
|
14
|
+
# Sürümü kendiliğinden bulmak için okunan değişkenler; ilk dolu olan kazanır. Platformların kendi adları.
|
|
15
|
+
RELEASE_ENV_VARS = (
|
|
16
|
+
"LANTERNFISH_RELEASE",
|
|
17
|
+
"GIT_COMMIT_SHA",
|
|
18
|
+
"GITHUB_SHA",
|
|
19
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
20
|
+
"RENDER_GIT_COMMIT",
|
|
21
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
22
|
+
"HEROKU_SLUG_COMMIT",
|
|
23
|
+
"SOURCE_COMMIT",
|
|
24
|
+
)
|
|
25
|
+
SHORT_SHA_LENGTH = 12
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_endpoint(endpoint: str | None) -> str:
|
|
29
|
+
"""API taban adresini argümandan veya LANTERNFISH_ENDPOINT'ten çözer ve doğrular.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
LanternfishConfigurationError: Adres yoksa, mutlak http(s) adresi değilse veya sorgu içeriyorsa.
|
|
33
|
+
"""
|
|
34
|
+
value = (endpoint or os.getenv("LANTERNFISH_ENDPOINT", "")).strip().rstrip("/")
|
|
35
|
+
if not value:
|
|
36
|
+
raise LanternfishConfigurationError(
|
|
37
|
+
"Lanternfish endpoint is required. Pass endpoint=... or set LANTERNFISH_ENDPOINT. "
|
|
38
|
+
"The SDK never selects a destination automatically."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
parsed = urlsplit(value)
|
|
42
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
43
|
+
raise LanternfishConfigurationError("Lanternfish endpoint must be an absolute http:// or https:// URL.")
|
|
44
|
+
if parsed.query or parsed.fragment:
|
|
45
|
+
raise LanternfishConfigurationError("Lanternfish endpoint must not contain a query string or fragment.")
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def resolve_api_key(api_key: str | None) -> str:
|
|
50
|
+
"""API anahtarını argümandan veya LANTERNFISH_API_KEY'den çözer.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
LanternfishConfigurationError: Anahtar yoksa.
|
|
54
|
+
"""
|
|
55
|
+
value = (api_key or os.getenv("LANTERNFISH_API_KEY", "")).strip()
|
|
56
|
+
if not value:
|
|
57
|
+
raise LanternfishConfigurationError(
|
|
58
|
+
"Lanternfish API key is required. Pass api_key=... or set LANTERNFISH_API_KEY. "
|
|
59
|
+
"Create one in Settings → API keys."
|
|
60
|
+
)
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def resolve_release(release: str | None) -> str | None:
|
|
65
|
+
"""Çalışan kodun sürümünü döndürür: argüman, LANTERNFISH_RELEASE ya da CI'ın commit değişkeni.
|
|
66
|
+
|
|
67
|
+
Commit SHA'sı ilk 12 karaktere kısaltılır; sürüm adı (ör. "v1.8.2") olduğu gibi kalır.
|
|
68
|
+
"""
|
|
69
|
+
if release and release.strip():
|
|
70
|
+
return release.strip()
|
|
71
|
+
for name in RELEASE_ENV_VARS:
|
|
72
|
+
value = os.getenv(name, "").strip()
|
|
73
|
+
if value:
|
|
74
|
+
return value if name == "LANTERNFISH_RELEASE" else value[:SHORT_SHA_LENGTH]
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def resolve_environment(environment: str | None) -> str | None:
|
|
79
|
+
"""Ortam adını (ör. "production") argümandan veya LANTERNFISH_ENVIRONMENT'tan döndürür."""
|
|
80
|
+
value = (environment or os.getenv("LANTERNFISH_ENVIRONMENT", "")).strip()
|
|
81
|
+
return value or None
|
lanternfish/context.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Son kullanıcı kimliği.
|
|
3
|
+
|
|
4
|
+
Görev: Agent'ı kullanan son kullanıcının kimliğini, oturumunu ve etiketlerini bağlama koyar; bundan sonra açılan her
|
|
5
|
+
span bu bilgiyi taşır. `with` ile kullanılırsa blok bitince eski bağlama döner.
|
|
6
|
+
Yapmaz: Span açmaz veya göndermez; değeri span'e damgalamak span_processor.py'nin işidir.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from types import TracebackType
|
|
10
|
+
|
|
11
|
+
from opentelemetry import context
|
|
12
|
+
|
|
13
|
+
from lanternfish.span_processor import SESSION_ID_KEY, TAGS_KEY, USER_ID_KEY
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UserScope:
|
|
17
|
+
"""`set_user()` sonucunun kapsamı: `with` bloğundan çıkınca kimliği bağlamdan kaldırır."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, token: object) -> None:
|
|
20
|
+
"""Kapsamı, bağlama eklenen değerin geri alma anahtarıyla kurar."""
|
|
21
|
+
self._token = token
|
|
22
|
+
self._closed = False
|
|
23
|
+
|
|
24
|
+
def __enter__(self) -> "UserScope":
|
|
25
|
+
"""Kimlik zaten bağlamda; bloğa olduğu gibi girer."""
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def __exit__(
|
|
29
|
+
self,
|
|
30
|
+
exc_type: type[BaseException] | None,
|
|
31
|
+
exc: BaseException | None,
|
|
32
|
+
traceback: TracebackType | None,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Bloktan çıkarken kimliği bağlamdan kaldırır."""
|
|
35
|
+
self.close()
|
|
36
|
+
|
|
37
|
+
def close(self) -> None:
|
|
38
|
+
"""Kimliği bağlamdan kaldırır; ikinci çağrı etkisizdir."""
|
|
39
|
+
if not self._closed:
|
|
40
|
+
context.detach(self._token) # type: ignore[arg-type]
|
|
41
|
+
self._closed = True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_user(user_id: str, session_id: str | None = None, tags: dict[str, str] | None = None) -> UserScope:
|
|
45
|
+
"""Bundan sonra açılan span'lere son kullanıcı kimliğini ekler.
|
|
46
|
+
|
|
47
|
+
Web sunucusunda istek başına `with` ile kullanın; böylece kimlik bir sonraki isteğe taşınmaz::
|
|
48
|
+
|
|
49
|
+
with lanternfish.set_user("user_42", session_id="sess_9"):
|
|
50
|
+
run_agent(question)
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
user_id: Son kullanıcının kimliği.
|
|
54
|
+
session_id: Sohbet veya oturum kimliği.
|
|
55
|
+
tags: Run'ı süzmek için düz etiketler.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
`with` ile kullanılabilen ya da `close()` ile kapatılabilen kapsam.
|
|
59
|
+
"""
|
|
60
|
+
ctx = context.get_current()
|
|
61
|
+
if user_id:
|
|
62
|
+
ctx = context.set_value(USER_ID_KEY, user_id, ctx)
|
|
63
|
+
if session_id:
|
|
64
|
+
ctx = context.set_value(SESSION_ID_KEY, session_id, ctx)
|
|
65
|
+
if tags:
|
|
66
|
+
ctx = context.set_value(TAGS_KEY, tags, ctx)
|
|
67
|
+
return UserScope(context.attach(ctx))
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# decorators.py
|
|
2
|
+
# Gorev: @trace ve @span decorator + span() context manager ile manual instrumentation
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz; instrument() ile aktif provider kullanilir
|
|
4
|
+
#
|
|
5
|
+
# Kullanim:
|
|
6
|
+
# @trace -> span adi = fn adi, parametreler otomatik yakalanir
|
|
7
|
+
# @trace(name="x") -> custom span adi
|
|
8
|
+
# @span -> span adi = fn adi, parametreler yakalanmaz
|
|
9
|
+
# @span("x") / @span(name="x") -> custom span adi, decorator
|
|
10
|
+
# with span("x") as s: -> context manager, manuel attribute
|
|
11
|
+
# with span_context("x") as s: -> span_context alias'i
|
|
12
|
+
|
|
13
|
+
import functools
|
|
14
|
+
import inspect
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from typing import Any, Callable, Generator
|
|
17
|
+
|
|
18
|
+
from opentelemetry import trace as otel_trace
|
|
19
|
+
from opentelemetry.trace import StatusCode
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── @trace ────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
def trace(func: Callable | None = None, *, name: str | None = None, capture_args: bool = True) -> Any:
|
|
25
|
+
"""Custom fonksiyonlari OpenTelemetry span olarak izler.
|
|
26
|
+
|
|
27
|
+
Parametreler otomatik olarak manual.param.<isim> attribute'i olur.
|
|
28
|
+
Exception durumunda span status ERROR olarak isaretlenir.
|
|
29
|
+
|
|
30
|
+
Iki kullanim sekli:
|
|
31
|
+
@trace -> span adi fonksiyon adindan alinir
|
|
32
|
+
@trace(name="custom_name") -> span adi manuel verilir
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
func: Direkt decorator olarak kullanildiginda gelen fonksiyon.
|
|
36
|
+
name: Opsiyonel custom span adi.
|
|
37
|
+
capture_args: False ise parametre degerleri span'e yazilmaz (kisisel veri tasiyan fonksiyonlar icin).
|
|
38
|
+
"""
|
|
39
|
+
def decorator(fn: Callable) -> Callable:
|
|
40
|
+
span_name = name or fn.__name__
|
|
41
|
+
|
|
42
|
+
if inspect.iscoroutinefunction(fn):
|
|
43
|
+
@functools.wraps(fn)
|
|
44
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
45
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
46
|
+
with tracer.start_as_current_span(span_name) as s:
|
|
47
|
+
if capture_args:
|
|
48
|
+
_add_params(s, fn, args, kwargs)
|
|
49
|
+
try:
|
|
50
|
+
result = await fn(*args, **kwargs)
|
|
51
|
+
s.set_status(StatusCode.OK)
|
|
52
|
+
return result
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
s.set_status(StatusCode.ERROR, str(exc))
|
|
55
|
+
s.record_exception(exc)
|
|
56
|
+
raise
|
|
57
|
+
return async_wrapper
|
|
58
|
+
else:
|
|
59
|
+
@functools.wraps(fn)
|
|
60
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
61
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
62
|
+
with tracer.start_as_current_span(span_name) as s:
|
|
63
|
+
if capture_args:
|
|
64
|
+
_add_params(s, fn, args, kwargs)
|
|
65
|
+
try:
|
|
66
|
+
result = fn(*args, **kwargs)
|
|
67
|
+
s.set_status(StatusCode.OK)
|
|
68
|
+
return result
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
s.set_status(StatusCode.ERROR, str(exc))
|
|
71
|
+
s.record_exception(exc)
|
|
72
|
+
raise
|
|
73
|
+
return sync_wrapper
|
|
74
|
+
|
|
75
|
+
if func is None:
|
|
76
|
+
return decorator
|
|
77
|
+
return decorator(func)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── @span / span() context manager ────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
class _SpanProxy:
|
|
83
|
+
"""span("name") cagrisi icin dual-use proxy.
|
|
84
|
+
|
|
85
|
+
Hem @span("name") decorator hem with span("name") as s: context manager
|
|
86
|
+
olarak kullanilabilir. Parametreler otomatik yakalanmaz; caller s.set_attribute()
|
|
87
|
+
ile manuel ekler.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, span_name: str) -> None:
|
|
91
|
+
self._span_name = span_name
|
|
92
|
+
self._cm: Any = None
|
|
93
|
+
|
|
94
|
+
def __call__(self, fn: Callable) -> Callable:
|
|
95
|
+
"""@span("name") seklinde decorator olarak kullanildiginda."""
|
|
96
|
+
return _make_span_wrapper(fn, self._span_name)
|
|
97
|
+
|
|
98
|
+
def __enter__(self) -> Any:
|
|
99
|
+
"""with span("name") as s: seklinde context manager olarak kullanildiginda."""
|
|
100
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
101
|
+
self._cm = tracer.start_as_current_span(self._span_name)
|
|
102
|
+
s = self._cm.__enter__()
|
|
103
|
+
self._span_ref = s
|
|
104
|
+
return s
|
|
105
|
+
|
|
106
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
|
|
107
|
+
if exc_val is not None:
|
|
108
|
+
self._span_ref.set_status(StatusCode.ERROR, str(exc_val))
|
|
109
|
+
self._span_ref.record_exception(exc_val)
|
|
110
|
+
else:
|
|
111
|
+
self._span_ref.set_status(StatusCode.OK)
|
|
112
|
+
return self._cm.__exit__(exc_type, exc_val, exc_tb)
|
|
113
|
+
|
|
114
|
+
# async context manager destegi
|
|
115
|
+
async def __aenter__(self) -> Any:
|
|
116
|
+
return self.__enter__()
|
|
117
|
+
|
|
118
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
|
|
119
|
+
return self.__exit__(exc_type, exc_val, exc_tb)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def span(func_or_name: Callable | str | None = None, *, name: str | None = None) -> Any:
|
|
123
|
+
"""Kod blogunu veya fonksiyonu izler. Hafif versiyon (@trace'in aksine parametre yakalamaz).
|
|
124
|
+
|
|
125
|
+
Uc kullanim sekli:
|
|
126
|
+
@span -> decorator, span adi = fn adi
|
|
127
|
+
@span("custom") / @span(name=..) -> decorator, custom span adi
|
|
128
|
+
with span("name") as s: -> context manager, s.set_attribute() ile manuel attr
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
func_or_name: Fonksiyon (@span direkt), string (span adi) veya None.
|
|
132
|
+
name: Keyword-only custom span adi.
|
|
133
|
+
"""
|
|
134
|
+
# Durum 1: @span (parametresiz, direkt fonksiyon uzerinde)
|
|
135
|
+
if callable(func_or_name):
|
|
136
|
+
return _make_span_wrapper(func_or_name, func_or_name.__name__)
|
|
137
|
+
|
|
138
|
+
# Durum 2: span("name") veya span(name="name") -> _SpanProxy doner
|
|
139
|
+
actual_name: str = (
|
|
140
|
+
func_or_name if isinstance(func_or_name, str)
|
|
141
|
+
else name or "span"
|
|
142
|
+
)
|
|
143
|
+
return _SpanProxy(actual_name)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@contextmanager
|
|
147
|
+
def span_context(name: str) -> Generator[Any, None, None]:
|
|
148
|
+
"""Context manager olarak span olusturur.
|
|
149
|
+
|
|
150
|
+
Kullanim:
|
|
151
|
+
with span_context("operation") as s:
|
|
152
|
+
s.set_attribute("key", "value")
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name: Span adi.
|
|
156
|
+
|
|
157
|
+
Yields:
|
|
158
|
+
Aktif OpenTelemetry span.
|
|
159
|
+
"""
|
|
160
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
161
|
+
with tracer.start_as_current_span(name) as s:
|
|
162
|
+
try:
|
|
163
|
+
yield s
|
|
164
|
+
s.set_status(StatusCode.OK)
|
|
165
|
+
except Exception as exc:
|
|
166
|
+
s.set_status(StatusCode.ERROR, str(exc))
|
|
167
|
+
s.record_exception(exc)
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ── Internal helpers ──────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
def _make_span_wrapper(fn: Callable, span_name: str) -> Callable:
|
|
174
|
+
"""Sync veya async fonksiyon icin span wrapper olusturur (parametresiz)."""
|
|
175
|
+
if inspect.iscoroutinefunction(fn):
|
|
176
|
+
@functools.wraps(fn)
|
|
177
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
178
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
179
|
+
with tracer.start_as_current_span(span_name) as s:
|
|
180
|
+
try:
|
|
181
|
+
result = await fn(*args, **kwargs)
|
|
182
|
+
s.set_status(StatusCode.OK)
|
|
183
|
+
return result
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
s.set_status(StatusCode.ERROR, str(exc))
|
|
186
|
+
s.record_exception(exc)
|
|
187
|
+
raise
|
|
188
|
+
return async_wrapper
|
|
189
|
+
else:
|
|
190
|
+
@functools.wraps(fn)
|
|
191
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
192
|
+
tracer = otel_trace.get_tracer("lanternfish.manual")
|
|
193
|
+
with tracer.start_as_current_span(span_name) as s:
|
|
194
|
+
try:
|
|
195
|
+
result = fn(*args, **kwargs)
|
|
196
|
+
s.set_status(StatusCode.OK)
|
|
197
|
+
return result
|
|
198
|
+
except Exception as exc:
|
|
199
|
+
s.set_status(StatusCode.ERROR, str(exc))
|
|
200
|
+
s.record_exception(exc)
|
|
201
|
+
raise
|
|
202
|
+
return sync_wrapper
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _add_params(s: Any, fn: Callable, args: tuple, kwargs: dict) -> None:
|
|
206
|
+
"""Fonksiyon parametrelerini span attribute olarak ekler (trace icin).
|
|
207
|
+
|
|
208
|
+
self ve cls atlanir. 500 karakteri asan degerler kisaltilir.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
sig = inspect.signature(fn)
|
|
212
|
+
bound = sig.bind(*args, **kwargs)
|
|
213
|
+
bound.apply_defaults()
|
|
214
|
+
for param_name, param_value in bound.arguments.items():
|
|
215
|
+
if param_name in ("self", "cls"):
|
|
216
|
+
continue
|
|
217
|
+
value_str = str(param_value)
|
|
218
|
+
if len(value_str) > 500:
|
|
219
|
+
value_str = value_str[:500] + "... [truncated]"
|
|
220
|
+
s.set_attribute(f"manual.param.{param_name}", value_str)
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Deployment kaydı — sürümü Lanternfish'e bildirir.
|
|
2
|
+
|
|
3
|
+
Görev: Bir sürümün ne zaman yayına çıktığını Lanternfish'e kaydeder; Lanternfish bu kaydı olay listesinde gösterir ve
|
|
4
|
+
"deployment öncesi bir saat" ile karşılaştırma yapar. CI betiğinden `instrument()` çağrılmadan kullanılır.
|
|
5
|
+
Yapmaz: Span üretmez, tekrar denemez; başarısızlığı sessizce yutmaz (CI adımı hatayı görmeli).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from lanternfish.configuration import resolve_api_key, resolve_endpoint
|
|
13
|
+
from lanternfish.errors import LanternfishDeploymentError
|
|
14
|
+
|
|
15
|
+
MetadataValue = str | int | float | bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def record_deployment(
|
|
19
|
+
version: str,
|
|
20
|
+
*,
|
|
21
|
+
deployed_at: datetime | None = None,
|
|
22
|
+
metadata: dict[str, MetadataValue] | None = None,
|
|
23
|
+
api_key: str | None = None,
|
|
24
|
+
endpoint: str | None = None,
|
|
25
|
+
timeout_seconds: int = 10,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""Bir deployment kaydeder ve Lanternfish'in verdiği kimliği döndürür.
|
|
28
|
+
|
|
29
|
+
Örnek (CI'da, deploy adımından sonra)::
|
|
30
|
+
|
|
31
|
+
import lanternfish
|
|
32
|
+
lanternfish.record_deployment("v1.4.2", metadata={"commit": "a1b2c3d", "environment": "production"})
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
version: Yayına çıkan sürüm (ör. "v1.4.2" veya commit kısaltması).
|
|
36
|
+
deployed_at: Deployment'ın bittiği zaman; verilmezse Lanternfish isteği aldığı anı kullanır. Saat dilimi verin.
|
|
37
|
+
metadata: En çok 20 düz etiket (ör. commit, ortam).
|
|
38
|
+
api_key: Proje API anahtarı; verilmezse LANTERNFISH_API_KEY.
|
|
39
|
+
endpoint: Lanternfish API adresi; verilmezse LANTERNFISH_ENDPOINT.
|
|
40
|
+
timeout_seconds: İstek için üst süre.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Kaydın kimliği (ör. "dep_…").
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
LanternfishConfigurationError: API anahtarı veya adres yoksa.
|
|
47
|
+
LanternfishDeploymentError: Lanternfish kaydı kabul etmezse veya ulaşılamazsa.
|
|
48
|
+
"""
|
|
49
|
+
key = resolve_api_key(api_key)
|
|
50
|
+
payload: dict[str, object] = {"version": version, "metadata": metadata or {}}
|
|
51
|
+
if deployed_at is not None:
|
|
52
|
+
payload["deployed_at"] = deployed_at.isoformat()
|
|
53
|
+
url = f"{resolve_endpoint(endpoint)}/v1/deployments"
|
|
54
|
+
try:
|
|
55
|
+
response = requests.post(url, json=payload, headers={"x-api-key": key}, timeout=timeout_seconds)
|
|
56
|
+
except requests.RequestException as error:
|
|
57
|
+
raise LanternfishDeploymentError(f"Lanternfish could not be reached at {url}: {error}") from error
|
|
58
|
+
if response.status_code != 201:
|
|
59
|
+
raise LanternfishDeploymentError(_failure_message(response))
|
|
60
|
+
return str(response.json()["id"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _failure_message(response: requests.Response) -> str:
|
|
64
|
+
"""Backend'in hata ayrıntısını, yoksa durum kodunu cümleye çevirir."""
|
|
65
|
+
try:
|
|
66
|
+
detail = response.json().get("detail")
|
|
67
|
+
except ValueError:
|
|
68
|
+
detail = None
|
|
69
|
+
return f"Lanternfish rejected the deployment ({response.status_code}): {detail or response.reason}"
|
lanternfish/errors.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Public exceptions raised by the Lanternfish SDK."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LanternfishConfigurationError(ValueError):
|
|
5
|
+
"""The SDK cannot start because required configuration is missing or invalid."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LanternfishDeploymentError(RuntimeError):
|
|
9
|
+
"""Lanternfish did not record the deployment (rejected, or unreachable)."""
|
lanternfish/exporters.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gönderim zinciri.
|
|
3
|
+
|
|
4
|
+
Görev: Damgalayıcıyı ve toplu JSON göndericisini tracer provider'a bağlar.
|
|
5
|
+
Yapmaz: TracerProvider oluşturmaz, enstrümantasyon yapmaz.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
9
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
10
|
+
|
|
11
|
+
from lanternfish.json_exporter import JSONSpanExporter
|
|
12
|
+
from lanternfish.span_processor import LanternfishContextProcessor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def configure_exporter(
|
|
16
|
+
tracer_provider: TracerProvider,
|
|
17
|
+
api_key: str,
|
|
18
|
+
endpoint: str,
|
|
19
|
+
static_attributes: dict[str, str] | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Damgalayıcıyı ve toplu göndericiyi tracer provider'a bağlar.
|
|
22
|
+
|
|
23
|
+
Toplu gönderim arka planda yapılır; agent her span için ağı beklemez. Bekleyen span'ler süreç kapanırken veya
|
|
24
|
+
`lanternfish.flush()` çağrılınca gönderilir.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
tracer_provider: İşlemcilerin ekleneceği provider.
|
|
28
|
+
api_key: Proje API anahtarı.
|
|
29
|
+
endpoint: Backend taban adresi.
|
|
30
|
+
static_attributes: Her span'e yazılacak sabit öznitelikler (agent adı, sürüm, ortam).
|
|
31
|
+
"""
|
|
32
|
+
# Damgalayıcı önce eklenir: span başlarken çalışması gerekir.
|
|
33
|
+
tracer_provider.add_span_processor(LanternfishContextProcessor(static_attributes))
|
|
34
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(JSONSpanExporter(endpoint=endpoint, api_key=api_key)))
|