Honor OTEL_SERVICE_NAME and set service name/version for all processes

This commit is contained in:
Yiorgis Gozadinos 2026-07-10 11:11:38 +03:00
parent da0748916b
commit da79f92af1
No known key found for this signature in database
6 changed files with 87 additions and 13 deletions

View file

@ -1,6 +1,10 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
### Changed
- Logfire spans carry `service.version` and a per-process `service.name` (`haiku-ingester`, `haiku-rag`, `haiku-rag-app`); `OTEL_SERVICE_NAME` / `LOGFIRE_SERVICE_NAME` override the default.
## [0.65.0] - 2026-07-09 ## [0.65.0] - 2026-07-09
### Added ### Added

View file

@ -20,6 +20,7 @@ from haiku.rag.client import HaikuRAG
from haiku.rag.config import load_yaml_config from haiku.rag.config import load_yaml_config
from haiku.rag.config.models import AppConfig from haiku.rag.config.models import AppConfig
from haiku.rag.skills.rag import create_skill, get_agent_preamble from haiku.rag.skills.rag import create_skill, get_agent_preamble
from haiku.rag.telemetry import configure as configure_telemetry
from haiku.rag.utils import get_model from haiku.rag.utils import get_model
from haiku.skills import ( from haiku.skills import (
SkillDeps, SkillDeps,
@ -30,14 +31,7 @@ from haiku.skills.prompts import build_system_prompt
load_dotenv(find_dotenv(usecwd=True)) load_dotenv(find_dotenv(usecwd=True))
# Configure logfire (only sends data if LOGFIRE_TOKEN is present) configure_telemetry(service_name="haiku-rag-app")
try:
import logfire
logfire.configure(send_to_logfire="if-token-present", console=False)
logfire.instrument_pydantic_ai()
except Exception:
pass
logging.basicConfig( logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"

View file

@ -40,7 +40,7 @@ from haiku.skills.agent import (
from haiku.skills.models import Skill from haiku.skills.models import Skill
from haiku.skills.prompts import build_system_prompt from haiku.skills.prompts import build_system_prompt
configure_telemetry() configure_telemetry(service_name="haiku-rag")
if TYPE_CHECKING: if TYPE_CHECKING:
from textual.app import ComposeResult from textual.app import ComposeResult

View file

@ -137,7 +137,9 @@ def main(
from haiku.rag.telemetry import configure as configure_telemetry from haiku.rag.telemetry import configure as configure_telemetry
is_production = get_config().environment != "development" is_production = get_config().environment != "development"
configure_telemetry(console=False if is_production else None) configure_telemetry(
service_name="haiku-rag", console=False if is_production else None
)
if get_config().environment != "development": if get_config().environment != "development":
# Suppress warnings in production # Suppress warnings in production

View file

@ -1,3 +1,5 @@
import os
from importlib import metadata
from typing import Literal from typing import Literal
from logfire import Logfire, attach_context, get_context from logfire import Logfire, attach_context, get_context
@ -25,8 +27,10 @@ def configure(
Silently no-ops on failure so a missing/misconfigured LOGFIRE_TOKEN Silently no-ops on failure so a missing/misconfigured LOGFIRE_TOKEN
never crashes the app. never crashes the app.
- service_name: identifies the process in the Logfire UI (e.g. - service_name: the default name for this process in the Logfire UI
"haiku-ingester"). Falls back to logfire's default when None. (e.g. "haiku-ingester"). The OTEL_SERVICE_NAME / LOGFIRE_SERVICE_NAME
env vars, when set, take precedence so operators can distinguish
concurrent processes.
- console: False (default) suppresses span lines on stderr so they - console: False (default) suppresses span lines on stderr so they
don't interleave with RichHandler logs. Pass None to let logfire don't interleave with RichHandler logs. Pass None to let logfire
decide (its own default applies). decide (its own default applies).
@ -34,8 +38,21 @@ def configure(
try: try:
import logfire as _lf import logfire as _lf
# An explicit service_name arg would beat the env vars in logfire's
# precedence; deferring to None when an env var is set lets the
# operator's OTEL_SERVICE_NAME / LOGFIRE_SERVICE_NAME win over our
# per-process default.
env_service = os.environ.get("OTEL_SERVICE_NAME") or os.environ.get(
"LOGFIRE_SERVICE_NAME"
)
try:
service_version = metadata.version("haiku.rag-slim")
except metadata.PackageNotFoundError: # pragma: no cover
service_version = None
_lf.configure( _lf.configure(
service_name=service_name, service_name=None if env_service else service_name,
service_version=service_version,
send_to_logfire="if-token-present", send_to_logfire="if-token-present",
console=console, console=console,
) )

57
tests/test_telemetry.py Normal file
View file

@ -0,0 +1,57 @@
from importlib import metadata
import logfire
import pytest
from haiku.rag import telemetry
@pytest.fixture
def captured_configure(monkeypatch):
"""Capture the kwargs telemetry.configure() passes to logfire.configure,
and no-op the instrumentation so tests don't touch a real exporter."""
captured: dict = {}
def _fake_configure(**kwargs):
captured.update(kwargs)
monkeypatch.setattr(logfire, "configure", _fake_configure)
monkeypatch.setattr(logfire, "instrument_pydantic_ai", lambda: None)
return captured
def test_default_service_name_used_when_env_unset(captured_configure, monkeypatch):
monkeypatch.delenv("OTEL_SERVICE_NAME", raising=False)
monkeypatch.delenv("LOGFIRE_SERVICE_NAME", raising=False)
telemetry.configure(service_name="haiku-ingester")
assert captured_configure["service_name"] == "haiku-ingester"
def test_otel_service_name_overrides_default(captured_configure, monkeypatch):
monkeypatch.setenv("OTEL_SERVICE_NAME", "customer-ingester")
telemetry.configure(service_name="haiku-ingester")
# Deferring to logfire (service_name=None) lets it read the env var,
# so the customer's OTEL_SERVICE_NAME wins over our default.
assert captured_configure["service_name"] is None
def test_logfire_service_name_overrides_default(captured_configure, monkeypatch):
monkeypatch.delenv("OTEL_SERVICE_NAME", raising=False)
monkeypatch.setenv("LOGFIRE_SERVICE_NAME", "customer-ingester")
telemetry.configure(service_name="haiku-ingester")
assert captured_configure["service_name"] is None
def test_service_version_is_package_version(captured_configure, monkeypatch):
monkeypatch.delenv("OTEL_SERVICE_NAME", raising=False)
monkeypatch.delenv("LOGFIRE_SERVICE_NAME", raising=False)
telemetry.configure(service_name="haiku-rag")
assert captured_configure["service_version"] == metadata.version("haiku.rag-slim")