Coverage for src / quber / utils / logging.py: 100%
37 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
1"""
2Logging configuration with Logfire integration.
4LangSmith tracing lives in `quber.agents.langsmith_tracer` and posts runs
5to LangSmith's native `/runs/multipart` endpoint with explicit
6`inputs`/`outputs` fields — the same pattern as `~/.claude/hooks/stop_hook.sh`.
7That keeps the LangSmith UI showing actual prompts and responses rather
8than empty input/output panels (the OTLP path's schema-translation gap).
9"""
11import logging
12import sys
13from typing import Any, Optional
15import logfire
16from loguru import logger
18from quber.settings import get_settings
21def setup_logging(
22 service_name: str = "quber",
23 enable_logfire: bool = True,
24 log_level: str = "INFO",
25 suppress_external: bool = True,
26) -> None:
27 """
28 Set up logging with loguru and optional logfire integration.
30 Args:
31 service_name: Name for the service in logfire
32 enable_logfire: Whether to enable logfire telemetry
33 log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
34 suppress_external: Whether to suppress verbose external library logs
35 """
36 if suppress_external:
37 logging.getLogger("docling").setLevel(logging.WARNING)
38 logging.getLogger("docling.document_converter").setLevel(logging.WARNING)
39 logging.getLogger("docling_core").setLevel(logging.WARNING)
40 logging.getLogger("httpx").setLevel(logging.WARNING)
41 logging.getLogger("httpcore").setLevel(logging.WARNING)
42 logging.getLogger("anthropic").setLevel(logging.WARNING)
43 logging.getLogger("openai").setLevel(logging.WARNING)
44 logging.getLogger("langsmith").setLevel(logging.WARNING)
46 import warnings
48 warnings.filterwarnings("ignore", category=DeprecationWarning, module="docling")
50 logger.remove()
51 logger.add(
52 sys.stderr,
53 format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
54 level=log_level,
55 )
57 logfire_token = get_settings().obs.logfire_token
58 if enable_logfire and logfire_token:
59 try:
60 logfire.configure(
61 service_name=service_name,
62 token=logfire_token,
63 console=False,
64 )
65 logfire.instrument_anthropic()
66 logfire.instrument_openai()
67 logfire.instrument_pydantic_ai()
69 if log_level == "DEBUG":
70 logger.info(f"Logfire telemetry enabled for service: {service_name}")
71 except Exception as e:
72 logger.warning(f"Failed to configure logfire: {e}")
73 else:
74 if not logfire_token:
75 logger.debug("Logfire token not found, telemetry disabled")
78def get_logger(name: Optional[str] = None) -> Any:
79 """
80 Get a logger instance.
82 Args:
83 name: Optional name for the logger context
85 Returns:
86 Logger instance
87 """
88 if name:
89 return logger.bind(context=name)
90 return logger