Skip to content

log

Class info

🛈 DocStrings

Logging configuration for llmling_agent with structlog support.

configure_logging

configure_logging(
    level: LogLevel = "INFO",
    *,
    use_colors: bool | None = None,
    json_logs: bool = False,
    force: bool = False,
) -> None

Configure structlog and standard logging.

Parameters:

Name Type Description Default
level LogLevel

Logging level

'INFO'
use_colors bool | None

Whether to use colored output (auto-detected if None)

None
json_logs bool

Force JSON output regardless of TTY detection

False
force bool

Force reconfiguration even if already configured

False
Source code in src/llmling_agent/log.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def configure_logging(
    level: LogLevel = "INFO",
    *,
    use_colors: bool | None = None,
    json_logs: bool = False,
    force: bool = False,
) -> None:
    """Configure structlog and standard logging.

    Args:
        level: Logging level
        use_colors: Whether to use colored output (auto-detected if None)
        json_logs: Force JSON output regardless of TTY detection
        force: Force reconfiguration even if already configured
    """
    global _LOGGING_CONFIGURED

    if _LOGGING_CONFIGURED and not force:
        return

    if isinstance(level, str):
        level = getattr(logging, level.upper())

    # Configure standard logging as backend
    logging.basicConfig(
        level=level,
        handlers=[logging.StreamHandler(sys.stderr)],
        force=True,
        format="%(message)s",  # structlog handles formatting
    )

    # Configure structlog processors
    processors: list[Any] = [
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
    ]

    # Determine output format
    colors = sys.stderr.isatty() and not json_logs if use_colors is not None else False

    # Add final renderer
    if json_logs or (not colors and not sys.stderr.isatty()):
        processors.append(structlog.processors.JSONRenderer())
    else:
        processors.append(structlog.dev.ConsoleRenderer(colors=colors))

    structlog.configure(
        processors=processors,
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )

    _LOGGING_CONFIGURED = True

get_logger

get_logger(name: str, log_level: LogLevel | None = None) -> BoundLogger

Get a structlog logger for the given name.

Parameters:

Name Type Description Default
name str

The name of the logger, will be prefixed with 'llmling_agent.'

required
log_level LogLevel | None

The logging level to set for the logger

None

Returns:

Type Description
BoundLogger

A structlog BoundLogger instance

Source code in src/llmling_agent/log.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def get_logger(
    name: str, log_level: LogLevel | None = None
) -> structlog.stdlib.BoundLogger:
    """Get a structlog logger for the given name.

    Args:
        name: The name of the logger, will be prefixed with 'llmling_agent.'
        log_level: The logging level to set for the logger

    Returns:
        A structlog BoundLogger instance
    """
    # Ensure basic structlog configuration exists for tests
    if not _LOGGING_CONFIGURED and not structlog.is_configured():
        # Minimal configuration that doesn't interfere with stdio
        structlog.configure(
            processors=[
                structlog.stdlib.filter_by_level,
                structlog.stdlib.add_logger_name,
                structlog.stdlib.add_log_level,
                structlog.processors.StackInfoRenderer(),
                structlog.processors.format_exc_info,
                structlog.dev.ConsoleRenderer(colors=False),
            ],
            wrapper_class=structlog.stdlib.BoundLogger,
            logger_factory=structlog.stdlib.LoggerFactory(),
            cache_logger_on_first_use=True,
        )

    logger = structlog.get_logger(f"llmling_agent.{name}")
    if log_level is not None:
        if isinstance(log_level, str):
            log_level = getattr(logging, log_level.upper())
            assert log_level
        # Set level on underlying stdlib logger
        stdlib_logger = logging.getLogger(f"llmling_agent.{name}")
        stdlib_logger.setLevel(log_level)
    return logger

set_handler_level

set_handler_level(
    level: int,
    logger_names: Sequence[str],
    *,
    session_handler: OutputWriter | None = None,
)

Temporarily set logging level and optionally add session handler.

Parameters:

Name Type Description Default
level int

Logging level to set

required
logger_names Sequence[str]

Names of loggers to configure

required
session_handler OutputWriter | None

Optional output writer for session logging

None
Source code in src/llmling_agent/log.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@contextmanager
def set_handler_level(
    level: int,
    logger_names: Sequence[str],
    *,
    session_handler: OutputWriter | None = None,
):
    """Temporarily set logging level and optionally add session handler.

    Args:
        level: Logging level to set
        logger_names: Names of loggers to configure
        session_handler: Optional output writer for session logging
    """
    loggers = [logging.getLogger(name) for name in logger_names]
    old_levels = [logger.level for logger in loggers]

    handler = None
    if session_handler:
        from slashed.log import SessionLogHandler

        handler = SessionLogHandler(session_handler)
        for logger in loggers:
            logger.addHandler(handler)

    try:
        for logger in loggers:
            logger.setLevel(level)
        yield
    finally:
        for logger, old_level in zip(loggers, old_levels, strict=True):
            logger.setLevel(old_level)
            if handler:
                logger.removeHandler(handler)