|
1 | | -from __future__ import annotations |
| 1 | +"""Loguru configuration. |
| 2 | +Re-exported from agenticlayer_shared for backward compatibility. |
| 3 | +""" |
2 | 4 |
|
3 | | -import inspect |
4 | | -import json |
5 | | -import logging |
6 | | -import os |
7 | | -import sys |
8 | | -import traceback |
9 | | -from logging import Logger |
10 | | -from typing import Callable, NotRequired, TextIO, TypedDict, Union |
| 5 | +from agenticlayer_shared.loguru_config import InterceptHandler, setup_logging |
11 | 6 |
|
12 | | -import loguru |
13 | | -from loguru import logger |
14 | | - |
15 | | - |
16 | | -class JsonRecord(TypedDict): |
17 | | - timestamp: str |
18 | | - name: str | None |
19 | | - level: str |
20 | | - message: str |
21 | | - function: str |
22 | | - module: str |
23 | | - line: int |
24 | | - exception: NotRequired[dict[str, str]] |
25 | | - |
26 | | - |
27 | | -def _serialize(record: loguru.Record) -> str: |
28 | | - log: JsonRecord = { |
29 | | - "timestamp": record["time"].isoformat(), |
30 | | - "name": record["name"], |
31 | | - "level": record["level"].name, |
32 | | - "message": record["message"], |
33 | | - "function": record["function"], |
34 | | - "module": record["module"], |
35 | | - "line": record["line"], |
36 | | - } |
37 | | - |
38 | | - if record["exception"] is not None: |
39 | | - log["exception"] = { |
40 | | - "stack": "".join( |
41 | | - traceback.format_exception( |
42 | | - record["exception"].type, |
43 | | - record["exception"].value, |
44 | | - record["exception"].traceback, |
45 | | - ) |
46 | | - ), |
47 | | - "kind": getattr(record["exception"].type, "__name__", "None"), |
48 | | - "message": str(record["exception"].value), |
49 | | - } |
50 | | - |
51 | | - return json.dumps(log) |
52 | | - |
53 | | - |
54 | | -def _json_sink(message: loguru.Message) -> None: |
55 | | - serialized = _serialize(message.record) |
56 | | - sys.stderr.write(serialized + "\n") |
57 | | - |
58 | | - |
59 | | -def _configure_loguru() -> None: |
60 | | - sink: Union[TextIO, Callable[[loguru.Message], None]] |
61 | | - log_format = os.environ.get("LOG_FORMAT", "Text") |
62 | | - if log_format == "JSON": |
63 | | - sink = _json_sink |
64 | | - else: |
65 | | - sink = sys.stderr |
66 | | - |
67 | | - log_level = os.environ.get("LOGLEVEL", "INFO") |
68 | | - |
69 | | - logger.remove() |
70 | | - logger.add( |
71 | | - sink, |
72 | | - # Log INFO by default |
73 | | - filter={ |
74 | | - "": log_level, |
75 | | - # Reduce verbosity of some noisy loggers |
76 | | - "a2a.utils.telemetry": "INFO", |
77 | | - }, |
78 | | - ) |
79 | | - |
80 | | - |
81 | | -def setup_logging() -> None: |
82 | | - """Initializes the application so that logging is handled by loguru""" |
83 | | - |
84 | | - _configure_loguru() |
85 | | - |
86 | | - # Some libraries we use log to standard logging and not to loguru. To also get the logs from these frameworks, we |
87 | | - # add a handler to the root logger of standard logging, that converts the log entries to loguru. This way |
88 | | - # loguru has the final say regarding logging, and we don't get a mixture of both logging frameworks |
89 | | - logging.basicConfig(handlers=[InterceptHandler()], level=logging.NOTSET, force=True) |
90 | | - |
91 | | - # We have to replace the existing handlers from the loggers we want to intercept as well. |
92 | | - for _log, _logger in logging.root.manager.loggerDict.items(): |
93 | | - # print("Checking logger: ", _log, " ", _logger) |
94 | | - if not isinstance(_logger, Logger) or len(_logger.handlers) == 0: |
95 | | - # logger not yet created or no custom handlers. Skipping |
96 | | - continue |
97 | | - for handler in _logger.handlers: |
98 | | - if not isinstance(handler, logging.StreamHandler): |
99 | | - # We only replace stream handlers, which write to the console |
100 | | - # NullHandlers and other handlers are not replaced |
101 | | - continue |
102 | | - _logger.removeHandler(handler) |
103 | | - _logger.addHandler(InterceptHandler()) |
104 | | - |
105 | | - # Prevent duplicate logs |
106 | | - if _logger.propagate: |
107 | | - logger.debug("Disable propagate for logger {}", _log) |
108 | | - _logger.propagate = False |
109 | | - |
110 | | - |
111 | | -class InterceptHandler(logging.Handler): |
112 | | - """ |
113 | | - A Handler for the standard python logging that sends all incoming logs to loguru. Taken from the loguru documentation |
114 | | - https://loguru.readthedocs.io/en/stable/overview.html |
115 | | - """ |
116 | | - |
117 | | - def emit(self, record: logging.LogRecord) -> None: |
118 | | - # Get corresponding Loguru level if it exists. |
119 | | - level: str | int |
120 | | - try: |
121 | | - level = logger.level(record.levelname).name |
122 | | - except ValueError: |
123 | | - level = record.levelno |
124 | | - |
125 | | - # Find caller from where originated the logged message. |
126 | | - frame, depth = inspect.currentframe(), 0 |
127 | | - while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__): |
128 | | - frame = frame.f_back |
129 | | - depth += 1 |
130 | | - |
131 | | - logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) |
| 7 | +__all__ = ["InterceptHandler", "setup_logging"] |
0 commit comments