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.
@@ -0,0 +1,181 @@
1
+ """
2
+ SDK giriş noktası.
3
+
4
+ Görev: `instrument()` ile provider'ı kurar, global OpenTelemetry provider'ı yapar (elle açılan span'ler de gitsin diye),
5
+ göndericiyi bağlar ve yüklü LLM/framework kütüphanelerini enstrümante eder. HTTP, SQL ve Redis yalnız istenirse
6
+ açılır. `flush()` ve `shutdown()` bekleyen span'leri gönderir.
7
+ Yapmaz: Span'i kendisi çevirmez veya göndermez (json_exporter.py); entegrasyon ayrıntısı içermez (integrations/).
8
+ """
9
+
10
+ import logging
11
+ import os
12
+ from collections.abc import Callable, Iterable
13
+
14
+ from opentelemetry import trace
15
+ from opentelemetry.sdk.trace import TracerProvider
16
+
17
+ from lanternfish.configuration import resolve_api_key, resolve_endpoint, resolve_environment, resolve_release
18
+ from lanternfish.errors import LanternfishConfigurationError
19
+ from lanternfish.exporters import configure_exporter
20
+ from lanternfish.integrations import (
21
+ instrument_anthropic,
22
+ instrument_autogen,
23
+ instrument_crewai,
24
+ instrument_gemini,
25
+ instrument_groq,
26
+ instrument_haystack,
27
+ instrument_http,
28
+ instrument_langchain,
29
+ instrument_llamaindex,
30
+ instrument_mcp,
31
+ instrument_mcp_server,
32
+ instrument_ollama,
33
+ instrument_openai,
34
+ instrument_openai_agents,
35
+ instrument_pydantic_ai,
36
+ instrument_redis,
37
+ instrument_sql,
38
+ )
39
+ from lanternfish.providers import create_tracer_provider
40
+ from lanternfish.span_processor import AGENT_NAME_KEY, ENVIRONMENT_KEY, RELEASE_KEY
41
+
42
+ logger = logging.getLogger("lanternfish")
43
+
44
+ Integration = Callable[[TracerProvider], None]
45
+
46
+ # Yüklü olan her zaman açılır: agent'ın LLM ve framework çağrıları.
47
+ _INTEGRATIONS: list[tuple[str, Integration]] = [
48
+ ("openai", instrument_openai), # Azure OpenAI istemcisi de aynı paketten izlenir.
49
+ ("anthropic", instrument_anthropic),
50
+ ("gemini", instrument_gemini),
51
+ ("groq", instrument_groq),
52
+ ("ollama", instrument_ollama),
53
+ ("langchain", instrument_langchain),
54
+ ("crewai", instrument_crewai),
55
+ ("llamaindex", instrument_llamaindex),
56
+ ("haystack", instrument_haystack),
57
+ ("openai_agents", instrument_openai_agents),
58
+ ("pydantic_ai", instrument_pydantic_ai),
59
+ ("autogen", instrument_autogen),
60
+ ("mcp", instrument_mcp),
61
+ ("mcp_server", instrument_mcp_server),
62
+ ]
63
+ # Yalnız `include=` ile açılır: agent dışındaki her sorgu ve istek de kayıt olur, kotayı ve gürültüyü artırır.
64
+ OPTIONAL_INTEGRATIONS: dict[str, Integration] = {
65
+ "http": instrument_http,
66
+ "sql": instrument_sql,
67
+ "redis": instrument_redis,
68
+ }
69
+
70
+ CONTENT_SWITCH_ENV = "TRACELOOP_TRACE_CONTENT"
71
+
72
+ _provider: TracerProvider | None = None
73
+ _owns_provider = False
74
+
75
+
76
+ def instrument(
77
+ api_key: str | None = None,
78
+ endpoint: str | None = None,
79
+ *,
80
+ agent: str | None = None,
81
+ release: str | None = None,
82
+ environment: str | None = None,
83
+ include: Iterable[str] = (),
84
+ capture_content: bool = True,
85
+ ) -> None:
86
+ """Lanternfish izlemesini başlatır. Uygulama açılışında bir kez çağrılır; ikinci çağrı etkisizdir.
87
+
88
+ Args:
89
+ api_key: Proje API anahtarı; verilmezse LANTERNFISH_API_KEY.
90
+ endpoint: Backend adresi; verilmezse LANTERNFISH_ENDPOINT. Varsayılan adres yoktur.
91
+ agent: Her run'a yazılacak agent adı (ör. "travel-agent").
92
+ release: Çalışan sürüm; verilmezse LANTERNFISH_RELEASE ya da CI'ın commit değişkeni (GITHUB_SHA vb.).
93
+ environment: Ortam adı (ör. "production"); verilmezse LANTERNFISH_ENVIRONMENT.
94
+ include: Ek olarak açılacak entegrasyonlar: "http", "sql", "redis".
95
+ capture_content: LLM entegrasyonları prompt ve yanıt metnini kaydetsin mi. Kişisel veri işleyen agent'larda
96
+ False verin; model, token ve süre yine kaydedilir.
97
+
98
+ Raises:
99
+ LanternfishConfigurationError: Anahtar veya adres eksik/geçersizse ya da `include` tanınmayan ad içeriyorsa.
100
+ """
101
+ global _provider, _owns_provider
102
+ if _provider is not None:
103
+ logger.warning("Lanternfish: instrument() was already called; the second call is ignored.")
104
+ return
105
+
106
+ resolved_endpoint = resolve_endpoint(endpoint)
107
+ resolved_key = resolve_api_key(api_key)
108
+ optional = _optional_integrations(include)
109
+
110
+ # Uygulama kendi OpenTelemetry provider'ını kurduysa (Logfire, kendi OTel kurulumu) ona eklenir; ikinci bir global
111
+ # provider kurulamaz ve elle açılan span'ler onunkine giderdi.
112
+ existing = trace.get_tracer_provider()
113
+ owns_provider = not isinstance(existing, TracerProvider)
114
+ provider = create_tracer_provider() if owns_provider else existing
115
+ configure_exporter(
116
+ provider,
117
+ resolved_key,
118
+ resolved_endpoint,
119
+ {
120
+ AGENT_NAME_KEY: agent or "",
121
+ RELEASE_KEY: resolve_release(release) or "",
122
+ ENVIRONMENT_KEY: resolve_environment(environment) or "",
123
+ },
124
+ )
125
+ if owns_provider:
126
+ trace.set_tracer_provider(provider)
127
+ _provider, _owns_provider = provider, owns_provider
128
+
129
+ # Hazır LLM instrumentor'ları (OpenLLMetry) metni bu değişkene göre kaydeder; her çağrıda okurlar.
130
+ os.environ[CONTENT_SWITCH_ENV] = "true" if capture_content else "false"
131
+ enabled = [name for name, integration in [*_INTEGRATIONS, *optional] if _enable(name, integration, provider)]
132
+ logger.info("Lanternfish: tracing to %s (%s)", resolved_endpoint, ", ".join(enabled) or "manual spans only")
133
+
134
+
135
+ def flush(timeout_seconds: float = 10.0) -> bool:
136
+ """Bekleyen span'leri hemen gönderir. Serverless işlevlerin ve kısa betiklerin sonunda çağırın.
137
+
138
+ Returns:
139
+ Süre dolmadan gönderildiyse True; instrument() çağrılmadıysa da True.
140
+ """
141
+ if _provider is None:
142
+ return True
143
+ return _provider.force_flush(int(timeout_seconds * 1000))
144
+
145
+
146
+ def shutdown() -> None:
147
+ """Bekleyen span'leri gönderir ve izlemeyi kapatır. Süreç kapanırken kendiliğinden de çalışır.
148
+
149
+ Provider uygulamanınsa yalnız bekleyenler gönderilir; uygulamanın provider'ı kapatılmaz.
150
+ """
151
+ global _provider
152
+ if _provider is None:
153
+ return
154
+ if _owns_provider:
155
+ _provider.shutdown()
156
+ else:
157
+ _provider.force_flush()
158
+ _provider = None
159
+
160
+
161
+ def _optional_integrations(include: Iterable[str]) -> list[tuple[str, Integration]]:
162
+ """`include` adlarını entegrasyonlara çevirir; tanınmayan adı hemen bildirir."""
163
+ names = list(dict.fromkeys(name.strip().lower() for name in include))
164
+ unknown = [name for name in names if name not in OPTIONAL_INTEGRATIONS]
165
+ if unknown:
166
+ allowed = ", ".join(sorted(OPTIONAL_INTEGRATIONS))
167
+ raise LanternfishConfigurationError(f"Unknown include= value(s): {', '.join(unknown)}. Use: {allowed}.")
168
+ return [(name, OPTIONAL_INTEGRATIONS[name]) for name in names]
169
+
170
+
171
+ def _enable(name: str, integration: Integration, provider: TracerProvider) -> bool:
172
+ """Bir entegrasyonu açar; kütüphane yüklü değilse veya uyumsuzsa sessizce atlar."""
173
+ try:
174
+ integration(provider)
175
+ except ModuleNotFoundError:
176
+ logger.debug("Lanternfish: %s skipped (package not installed)", name)
177
+ return False
178
+ except Exception as error: # noqa: BLE001 Bir entegrasyonun hatası agent'ı durdurmamalı.
179
+ logger.warning("Lanternfish: %s instrumentation was skipped (%s: %s)", name, type(error).__name__, error)
180
+ return False
181
+ return True
@@ -0,0 +1,61 @@
1
+ """
2
+ Entegrasyon kayıt defteri.
3
+
4
+ Görev: Her entegrasyonu ilk çağrıldığında içe aktaran ince sarmalayıcıları dışa verir. İlgili kütüphane yüklü değilse
5
+ ImportError çağırana (instrument()) çıkar; instrument() o entegrasyonu "atlandı" sayar.
6
+ Yapmaz: Enstrümantasyon mantığı içermez (her biri kendi modülünde).
7
+ """
8
+
9
+ from collections.abc import Callable
10
+ from importlib import import_module
11
+
12
+ from opentelemetry.sdk.trace import TracerProvider
13
+
14
+
15
+ def _lazy(module: str, function: str) -> Callable[[TracerProvider], None]:
16
+ """Modülü ancak entegrasyon açılırken içe aktaran sarmalayıcı döndürür."""
17
+
18
+ def enable(tracer_provider: TracerProvider) -> None:
19
+ getattr(import_module(f"lanternfish.integrations.{module}"), function)(tracer_provider)
20
+
21
+ enable.__name__ = function
22
+ return enable
23
+
24
+
25
+ instrument_openai = _lazy("openai", "instrument_openai")
26
+ instrument_anthropic = _lazy("anthropic", "instrument_anthropic")
27
+ instrument_gemini = _lazy("gemini", "instrument_gemini")
28
+ instrument_groq = _lazy("groq", "instrument_groq")
29
+ instrument_ollama = _lazy("ollama", "instrument_ollama")
30
+ instrument_langchain = _lazy("langchain", "instrument_langchain")
31
+ instrument_crewai = _lazy("crewai", "instrument_crewai")
32
+ instrument_llamaindex = _lazy("llamaindex", "instrument_llamaindex")
33
+ instrument_haystack = _lazy("haystack", "instrument_haystack")
34
+ instrument_openai_agents = _lazy("openai_agents", "instrument_openai_agents")
35
+ instrument_pydantic_ai = _lazy("pydantic_ai", "instrument_pydantic_ai")
36
+ instrument_autogen = _lazy("autogen", "instrument_autogen")
37
+ instrument_mcp = _lazy("mcp", "instrument_mcp")
38
+ instrument_mcp_server = _lazy("mcp", "instrument_mcp_server")
39
+ instrument_http = _lazy("http", "instrument_http")
40
+ instrument_sql = _lazy("sql", "instrument_sql")
41
+ instrument_redis = _lazy("redis", "instrument_redis")
42
+
43
+ __all__ = [
44
+ "instrument_anthropic",
45
+ "instrument_autogen",
46
+ "instrument_crewai",
47
+ "instrument_gemini",
48
+ "instrument_groq",
49
+ "instrument_haystack",
50
+ "instrument_http",
51
+ "instrument_langchain",
52
+ "instrument_llamaindex",
53
+ "instrument_mcp",
54
+ "instrument_mcp_server",
55
+ "instrument_ollama",
56
+ "instrument_openai",
57
+ "instrument_openai_agents",
58
+ "instrument_pydantic_ai",
59
+ "instrument_redis",
60
+ "instrument_sql",
61
+ ]
@@ -0,0 +1,20 @@
1
+ # anthropic.py
2
+ # Görev: Anthropic Claude çağrılarını OpenTelemetry ile otomatik enstrümante etmek
3
+ # Yapmaz: TracerProvider veya exporter oluşturmaz
4
+
5
+ from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+
8
+ from lanternfish.integrations.instrumentor import enable_instrumentor
9
+
10
+
11
+ def instrument_anthropic(tracer_provider: TracerProvider) -> None:
12
+ """Anthropic SDK çağrılarını verilen tracer provider ile izler.
13
+
14
+ messages.create() çağrılarını yakalar; model adı, input/output token
15
+ sayıları, latency ve hata durumlarını gen_ai.* attribute'ları olarak kaydeder.
16
+
17
+ Args:
18
+ tracer_provider: Span'lerin gönderileceği TracerProvider.
19
+ """
20
+ enable_instrumentor(AnthropicInstrumentor(), tracer_provider)
@@ -0,0 +1,75 @@
1
+ # autogen.py
2
+ # Gorev: AutoGen 0.7+ (autogen-agentchat) agent/team run() cagrilarini OpenTelemetry ile enstrumante etmek
3
+ # Yapmaz: TracerProvider veya exporter olusturmaz
4
+ #
5
+ # Gercek API (autogen-agentchat >= 0.6):
6
+ # AssistantAgent.run(task, cancellation_token=None, ...) <- async
7
+ # RoundRobinGroupChat.run(task, ...) <- async (ayni imza)
8
+ # TaskResult.messages -> list[BaseAgentEvent | BaseChatMessage]
9
+ # TaskResult.stop_reason -> str | None
10
+
11
+ import functools
12
+ from typing import Any
13
+
14
+ from opentelemetry import trace
15
+ from opentelemetry.sdk.trace import TracerProvider
16
+
17
+
18
+ def instrument_autogen(tracer_provider: TracerProvider) -> None:
19
+ """AutoGen AssistantAgent ve RoundRobinGroupChat run() cagrilarini izler.
20
+
21
+ Agent adi, model adi, mesaj sayisi ve stop_reason gen_ai.* + agent.*
22
+ attribute'lari olarak kaydedilir.
23
+
24
+ Args:
25
+ tracer_provider: Span'lerin gonderilecegi TracerProvider.
26
+ """
27
+ # Kütüphane yüklü değilse ImportError instrument()'a çıkar; o entegrasyon atlanır.
28
+ from autogen_agentchat.agents import AssistantAgent
29
+ from autogen_agentchat.teams import RoundRobinGroupChat
30
+
31
+ tracer = trace.get_tracer("lanternfish.autogen", tracer_provider=tracer_provider)
32
+
33
+ def _make_traced_run(original_run: Any, span_name: str) -> Any:
34
+ @functools.wraps(original_run)
35
+ async def traced_run(self: Any, *, task: Any = None, **kwargs: Any) -> Any:
36
+ agent_name = getattr(self, "name", type(self).__name__)
37
+
38
+ # Model bilgisini cek (AssistantAgent'ta _model_client var)
39
+ model_client = getattr(self, "_model_client", None)
40
+ model_name: str = "unknown"
41
+ if model_client is not None:
42
+ # OpenAIChatCompletionClient: _resolved_model (str) veya model / _model_name
43
+ model_name = (
44
+ getattr(model_client, "_resolved_model", None)
45
+ or getattr(model_client, "model", None)
46
+ or getattr(model_client, "_model_name", None)
47
+ or getattr(model_client, "_model", None)
48
+ or type(model_client).__name__
49
+ )
50
+
51
+ with tracer.start_as_current_span(span_name) as span:
52
+ span.set_attribute("gen_ai.system", "autogen")
53
+ span.set_attribute("agent.name", str(agent_name))
54
+ span.set_attribute("gen_ai.request.model", str(model_name))
55
+
56
+ if task is not None:
57
+ span.set_attribute("agent.task_preview", str(task)[:120])
58
+
59
+ # task keyword-only: positional olarak gecirilemez
60
+ result = await original_run(self, task=task, **kwargs)
61
+
62
+ messages = getattr(result, "messages", None)
63
+ if messages is not None:
64
+ span.set_attribute("agent.message_count", len(messages))
65
+
66
+ stop_reason = getattr(result, "stop_reason", None)
67
+ if stop_reason is not None:
68
+ span.set_attribute("agent.stop_reason", str(stop_reason))
69
+
70
+ return result
71
+
72
+ return traced_run
73
+
74
+ AssistantAgent.run = _make_traced_run(AssistantAgent.run, "autogen.agent.run")
75
+ RoundRobinGroupChat.run = _make_traced_run(RoundRobinGroupChat.run, "autogen.team.run")
@@ -0,0 +1,17 @@
1
+ # crewai.py
2
+ # Görev: CrewAI çağrılarını OpenTelemetry ile otomatik enstrümante etmek
3
+ # Yapmaz: TracerProvider veya exporter oluşturmaz
4
+
5
+ from opentelemetry.instrumentation.crewai import CrewAIInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+
8
+ from lanternfish.integrations.instrumentor import enable_instrumentor
9
+
10
+
11
+ def instrument_crewai(tracer_provider: TracerProvider) -> None:
12
+ """CrewAI agent/task/crew çağrılarını verilen tracer provider ile izler.
13
+
14
+ Args:
15
+ tracer_provider: Span'lerin gönderileceği TracerProvider.
16
+ """
17
+ enable_instrumentor(CrewAIInstrumentor(), tracer_provider)
@@ -0,0 +1,24 @@
1
+ # gemini.py
2
+ # Gorev: Google Gemini (google-genai SDK) cagrilarini OpenTelemetry ile otomatik enstrumante etmek
3
+ # Yapmaz: TracerProvider veya exporter olusturmaz
4
+ #
5
+ # Not: Bu dosya google-genai SDK'yi izler (google.genai), eski google-generativeai
6
+ # paketini degil. Dogru SDK: pip install google-genai
7
+
8
+ from opentelemetry.instrumentation.google_generativeai import GoogleGenerativeAiInstrumentor
9
+ from opentelemetry.sdk.trace import TracerProvider
10
+
11
+ from lanternfish.integrations.instrumentor import enable_instrumentor
12
+
13
+
14
+ def instrument_gemini(tracer_provider: TracerProvider) -> None:
15
+ """Google Gemini SDK cagrilarini verilen tracer provider ile izler.
16
+
17
+ google.genai.Client uzerindeki generate_content() cagrilarini yakalar;
18
+ model adi, token sayilari, latency ve hata durumlarini gen_ai.* attribute'lari
19
+ olarak kaydeder.
20
+
21
+ Args:
22
+ tracer_provider: Span'lerin gonderilecegi TracerProvider.
23
+ """
24
+ enable_instrumentor(GoogleGenerativeAiInstrumentor(), tracer_provider)
@@ -0,0 +1,21 @@
1
+ # groq.py
2
+ # Gorev: Groq hizli inference cagrilarini OpenTelemetry ile otomatik enstrumante etmek
3
+ # Yapmaz: TracerProvider veya exporter olusturmaz
4
+
5
+ from opentelemetry.instrumentation.groq import GroqInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+
8
+ from lanternfish.integrations.instrumentor import enable_instrumentor
9
+
10
+
11
+ def instrument_groq(tracer_provider: TracerProvider) -> None:
12
+ """Groq SDK cagrilarini verilen tracer provider ile izler.
13
+
14
+ groq.Groq().chat.completions.create() cagrilarini yakalar; model adi,
15
+ token sayilari ve latency gen_ai.* attribute'lari olarak kaydedilir.
16
+ Groq'un cok dusuk latency'si (<100ms) span'lerde gorulur.
17
+
18
+ Args:
19
+ tracer_provider: Span'lerin gonderilecegi TracerProvider.
20
+ """
21
+ enable_instrumentor(GroqInstrumentor(), tracer_provider)
@@ -0,0 +1,65 @@
1
+ # haystack.py
2
+ # Gorev: Haystack 2.x (haystack-ai) pipeline cagrilarini OpenTelemetry ile enstrumante etmek
3
+ # Yapmaz: TracerProvider veya exporter olusturmaz
4
+ #
5
+ # Haystack 2.x gercek API:
6
+ # Pipeline.run(data: dict, include_outputs_from=None, ...) <- sync
7
+ # Pipeline.graph <- networkx.MultiDiGraph, nodes listesi buradan alinir
8
+ # Sonuc: dict { component_name: { output_key: value } }
9
+
10
+ import functools
11
+ import os
12
+ from typing import Any
13
+
14
+ from opentelemetry import trace
15
+ from opentelemetry.sdk.trace import TracerProvider
16
+
17
+
18
+ def instrument_haystack(tracer_provider: TracerProvider) -> None:
19
+ """Haystack 2.x Pipeline.run() cagrilarini verilen tracer provider ile izler.
20
+
21
+ Pipeline.run() sararak; component sayisi, input verisi (ozet) ve
22
+ cikti anahtarlari haystack.* attribute'lari olarak kaydedilir.
23
+
24
+ Args:
25
+ tracer_provider: Span'lerin gonderilecegi TracerProvider.
26
+ """
27
+ # Haystack, global provider görünce kendi izlemesini açar ve açılışta deneme span'i gönderir; pipeline'ı
28
+ # burada izlediğimiz için onunki kapatılır (aksi hâlde her açılışta anlamsız bir run oluşur).
29
+ os.environ.setdefault("HAYSTACK_AUTO_TRACE_ENABLED", "false")
30
+ # Kütüphane yüklü değilse ImportError instrument()'a çıkar; o entegrasyon atlanır.
31
+ from haystack import Pipeline
32
+
33
+ tracer = trace.get_tracer("lanternfish.haystack", tracer_provider=tracer_provider)
34
+ original_run = Pipeline.run
35
+
36
+ @functools.wraps(original_run)
37
+ def traced_run(self: Any, data: Any = None, **kwargs: Any) -> Any:
38
+ with tracer.start_as_current_span("haystack.pipeline.run") as span:
39
+ span.set_attribute("gen_ai.system", "haystack")
40
+
41
+ # Haystack 2.x: graph is a networkx.MultiDiGraph
42
+ graph = getattr(self, "graph", None)
43
+ if graph is not None:
44
+ nodes = list(graph.nodes)
45
+ span.set_attribute("haystack.component_count", len(nodes))
46
+ span.set_attribute("haystack.components", str(nodes[:10]))
47
+
48
+ # data dict icinden query bul (orn: {"query": {"text": "..."}} veya {"text_file_converter": {...}})
49
+ if isinstance(data, dict):
50
+ span.set_attribute("haystack.input_components", str(list(data.keys())[:5]))
51
+ for val in data.values():
52
+ if isinstance(val, dict):
53
+ for k, v in val.items():
54
+ if "query" in k.lower() or k == "query":
55
+ span.set_attribute("haystack.query_preview", str(v)[:120])
56
+ break
57
+
58
+ result = original_run(self, data, **kwargs)
59
+
60
+ if isinstance(result, dict):
61
+ span.set_attribute("haystack.output_components", str(list(result.keys())[:5]))
62
+
63
+ return result
64
+
65
+ Pipeline.run = traced_run
@@ -0,0 +1,129 @@
1
+ """
2
+ HTTP istemci entegrasyonu (yalnız `include=["http"]` ile açılır).
3
+
4
+ Görev: requests, httpx (sync/async) ve aiohttp isteklerini span olarak kaydeder: yöntem, host, yol, durum kodu, süre
5
+ ve hata türü. Lanternfish'in kendi gönderimlerini kaydetmez.
6
+ Yapmaz: İstek/yanıt gövdesini, başlıkları veya sorgu parametrelerini kaydetmez (parola, token, kişisel veri taşıyabilir);
7
+ yanıtı okumaz (akış hâlindeki yanıtı bozmamak için).
8
+ """
9
+
10
+ import functools
11
+ from collections.abc import Iterator
12
+ from contextlib import contextmanager
13
+ from typing import Any
14
+ from urllib.parse import urlsplit
15
+
16
+ from opentelemetry import trace as otel_trace
17
+ from opentelemetry.sdk.trace import TracerProvider
18
+ from opentelemetry.trace import Span, StatusCode, Tracer
19
+
20
+ # Lanternfish'in kendi API yolları: bunları izlemek sonsuz döngü ve gürültü olurdu.
21
+ _OWN_PATHS = ("/v1/traces", "/v1/deployments")
22
+
23
+
24
+ def instrument_http(tracer_provider: TracerProvider) -> None:
25
+ """Yüklü HTTP kütüphanelerinin merkezi istek yöntemini sarar.
26
+
27
+ Args:
28
+ tracer_provider: Span'lerin gönderileceği provider.
29
+ """
30
+ tracer = otel_trace.get_tracer("lanternfish.http", tracer_provider=tracer_provider)
31
+ _instrument_requests(tracer)
32
+ _instrument_httpx(tracer)
33
+ _instrument_aiohttp(tracer)
34
+
35
+
36
+ @contextmanager
37
+ def _request_span(tracer: Tracer, system: str, method: str, url: str) -> Iterator[Span | None]:
38
+ """İstek için span açar; Lanternfish'in kendi isteğiyse span açmadan geçer."""
39
+ parts = urlsplit(url)
40
+ if parts.path.startswith(_OWN_PATHS):
41
+ yield None
42
+ return
43
+ host = parts.netloc or "unknown"
44
+ with tracer.start_as_current_span(f"http {method.upper()} {host}", record_exception=False,
45
+ set_status_on_exception=False) as span:
46
+ span.set_attribute("http.system", system)
47
+ span.set_attribute("http.method", method.upper())
48
+ span.set_attribute("http.host", host)
49
+ span.set_attribute("http.path", parts.path or "/")
50
+ try:
51
+ yield span
52
+ except Exception as error:
53
+ span.set_status(StatusCode.ERROR, type(error).__name__)
54
+ span.set_attribute("http.error.type", type(error).__name__)
55
+ raise
56
+
57
+
58
+ def _record_status(span: Span | None, status_code: int) -> None:
59
+ """Durum kodunu yazar; 4xx/5xx yanıtı hata sayar."""
60
+ if span is None:
61
+ return
62
+ span.set_attribute("http.status_code", status_code)
63
+ if status_code >= 400:
64
+ span.set_status(StatusCode.ERROR, f"HTTP {status_code}")
65
+ else:
66
+ span.set_status(StatusCode.OK)
67
+
68
+
69
+ def _instrument_requests(tracer: Tracer) -> None:
70
+ """requests.Session.request() sarmalayıcısı."""
71
+ try:
72
+ import requests
73
+ except ImportError:
74
+ return
75
+ original = requests.Session.request
76
+
77
+ @functools.wraps(original)
78
+ def traced(self: Any, method: str, url: str, *args: Any, **kwargs: Any) -> Any:
79
+ with _request_span(tracer, "requests", method, str(url)) as span:
80
+ response = original(self, method, url, *args, **kwargs)
81
+ _record_status(span, response.status_code)
82
+ return response
83
+
84
+ requests.Session.request = traced
85
+
86
+
87
+ def _instrument_httpx(tracer: Tracer) -> None:
88
+ """httpx.Client.send() ve httpx.AsyncClient.send() sarmalayıcıları."""
89
+ try:
90
+ import httpx
91
+ except ImportError:
92
+ return
93
+ original_sync = httpx.Client.send
94
+ original_async = httpx.AsyncClient.send
95
+
96
+ @functools.wraps(original_sync)
97
+ def traced_sync(self: Any, request: Any, **kwargs: Any) -> Any:
98
+ with _request_span(tracer, "httpx", request.method, str(request.url)) as span:
99
+ response = original_sync(self, request, **kwargs)
100
+ _record_status(span, response.status_code)
101
+ return response
102
+
103
+ @functools.wraps(original_async)
104
+ async def traced_async(self: Any, request: Any, **kwargs: Any) -> Any:
105
+ with _request_span(tracer, "httpx", request.method, str(request.url)) as span:
106
+ response = await original_async(self, request, **kwargs)
107
+ _record_status(span, response.status_code)
108
+ return response
109
+
110
+ httpx.Client.send = traced_sync
111
+ httpx.AsyncClient.send = traced_async
112
+
113
+
114
+ def _instrument_aiohttp(tracer: Tracer) -> None:
115
+ """aiohttp.ClientSession._request() sarmalayıcısı."""
116
+ try:
117
+ import aiohttp
118
+ except ImportError:
119
+ return
120
+ original = aiohttp.ClientSession._request
121
+
122
+ @functools.wraps(original)
123
+ async def traced(self: Any, method: str, str_or_url: Any, **kwargs: Any) -> Any:
124
+ with _request_span(tracer, "aiohttp", method, str(str_or_url)) as span:
125
+ response = await original(self, method, str_or_url, **kwargs)
126
+ _record_status(span, response.status)
127
+ return response
128
+
129
+ aiohttp.ClientSession._request = traced
@@ -0,0 +1,24 @@
1
+ """
2
+ Hazır OpenTelemetry instrumentor'larını açma yardımcısı.
3
+
4
+ Görev: Bir instrumentor'ı yalnız hedef kütüphane yüklü ve sürümü uyumluyken açar; değilse ModuleNotFoundError
5
+ fırlatır, böylece instrument() onu "atlandı" sayar ve kullanıcının ekranına uyumsuzluk satırı düşmez.
6
+ Yapmaz: Hangi instrumentor'ın açılacağına karar vermez (instrument.py).
7
+ """
8
+
9
+ from opentelemetry.instrumentation.dependencies import get_dependency_conflicts
10
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
11
+ from opentelemetry.sdk.trace import TracerProvider
12
+
13
+
14
+ def enable_instrumentor(instrumentor: BaseInstrumentor, tracer_provider: TracerProvider) -> None:
15
+ """Instrumentor'ı bağımlılığı karşılanıyorsa ve henüz açık değilse verilen provider ile açar.
16
+
17
+ Raises:
18
+ ModuleNotFoundError: Hedef kütüphane yüklü değil veya sürümü desteklenmiyorsa.
19
+ """
20
+ conflict = get_dependency_conflicts(instrumentor.instrumentation_dependencies())
21
+ if conflict is not None:
22
+ raise ModuleNotFoundError(str(conflict))
23
+ if not instrumentor.is_instrumented_by_opentelemetry:
24
+ instrumentor.instrument(tracer_provider=tracer_provider)
@@ -0,0 +1,21 @@
1
+ # langchain.py
2
+ # Görev: LangChain çağrılarını OpenTelemetry ile otomatik enstrümante etmek
3
+ # Yapmaz: TracerProvider veya exporter oluşturmaz
4
+
5
+ from opentelemetry.instrumentation.langchain import LangchainInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+
8
+ from lanternfish.integrations.instrumentor import enable_instrumentor
9
+
10
+
11
+ def instrument_langchain(tracer_provider: TracerProvider) -> None:
12
+ """LangChain çağrılarını verilen tracer provider ile izler.
13
+
14
+ Tool call'lar dahil tüm LangChain callback olayları otomatik yakalanır;
15
+ tool span'leri OpenTelemetry GenAI konvansiyonuna göre
16
+ "execute_tool <tool_name>" adıyla oluşturulur (örn. "execute_tool multiply").
17
+
18
+ Args:
19
+ tracer_provider: Span'lerin gönderileceği TracerProvider.
20
+ """
21
+ enable_instrumentor(LangchainInstrumentor(), tracer_provider)
@@ -0,0 +1,21 @@
1
+ # llamaindex.py
2
+ # Gorev: LlamaIndex RAG cagrilarini OpenTelemetry ile otomatik enstrumante etmek
3
+ # Yapmaz: TracerProvider veya exporter olusturmaz
4
+
5
+ from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+
8
+ from lanternfish.integrations.instrumentor import enable_instrumentor
9
+
10
+
11
+ def instrument_llamaindex(tracer_provider: TracerProvider) -> None:
12
+ """LlamaIndex RAG pipeline cagrilarini verilen tracer provider ile izler.
13
+
14
+ QueryEngine.query(), Retriever.retrieve() ve LLM cagrilarini otomatik
15
+ yakalar; node sayisi, token kullanimi ve latency gen_ai.* attribute'lari
16
+ olarak kaydedilir.
17
+
18
+ Args:
19
+ tracer_provider: Span'lerin gonderilecegi TracerProvider.
20
+ """
21
+ enable_instrumentor(LlamaIndexInstrumentor(), tracer_provider)