66
77from rich .logging import RichHandler
88
9- from constants import LIGHTSPEED_STACK_LOG_LEVEL_ENV_VAR , DEFAULT_LOG_LEVEL
9+ from constants import (
10+ LIGHTSPEED_STACK_LOG_LEVEL_ENV_VAR ,
11+ DEFAULT_LOG_LEVEL ,
12+ DEFAULT_LOG_FORMAT ,
13+ )
14+
15+
16+ def resolve_log_level () -> int :
17+ """
18+ Resolve and validate the log level from environment variable.
19+
20+ Reads the LIGHTSPEED_STACK_LOG_LEVEL environment variable and validates
21+ it against Python's logging module. If the environment variable is not set,
22+ defaults to DEFAULT_LOG_LEVEL. If the value is invalid, logs a warning and
23+ falls back to DEFAULT_LOG_LEVEL.
24+
25+ Parameters:
26+ None
27+
28+ Returns:
29+ int: A valid logging level constant (e.g., logging.INFO, logging.DEBUG).
30+ """
31+ level_str = os .environ .get (LIGHTSPEED_STACK_LOG_LEVEL_ENV_VAR , DEFAULT_LOG_LEVEL )
32+
33+ # Validate the level string and convert to logging level constant
34+ validated_level = getattr (logging , level_str .upper (), None )
35+ if not isinstance (validated_level , int ):
36+ # Write directly to stderr instead of using a logger. This function is
37+ # called at module-import time (before logging is configured), so routing
38+ # through a logger produces inconsistent output depending on root-logger
39+ # state.
40+ print (
41+ f"WARNING: Invalid log level '{ level_str } ', "
42+ f"falling back to { DEFAULT_LOG_LEVEL } " ,
43+ file = sys .stderr ,
44+ )
45+ validated_level = getattr (logging , DEFAULT_LOG_LEVEL )
46+
47+ return validated_level
48+
49+
50+ def create_log_handler () -> logging .Handler :
51+ """
52+ Create and return a configured log handler based on TTY availability.
53+
54+ If stderr is connected to a terminal (TTY), returns a RichHandler for
55+ rich-formatted console output. Otherwise, returns a StreamHandler with
56+ plain-text formatting suitable for non-TTY environments (e.g., containers).
57+
58+ Parameters:
59+ None
60+
61+ Returns:
62+ logging.Handler: A configured handler instance (RichHandler or StreamHandler).
63+ """
64+ if sys .stderr .isatty ():
65+ # RichHandler's columnar layout assumes a real terminal.
66+ # RichHandler handles its own formatting, so no formatter is set.
67+ return RichHandler ()
68+
69+ # In containers without a TTY, Rich falls back to 80 columns and
70+ # the columns consume most of that width, leaving ~40 chars for the actual message.
71+ # Tracebacks become nearly unreadable. Use a plain StreamHandler instead.
72+ handler = logging .StreamHandler ()
73+ handler .setFormatter (logging .Formatter (DEFAULT_LOG_FORMAT ))
74+ return handler
1075
1176
1277def get_logger (name : str ) -> logging .Logger :
@@ -15,8 +80,8 @@ def get_logger(name: str) -> logging.Logger:
1580
1681 The returned logger has its level set based on the LIGHTSPEED_STACK_LOG_LEVEL
1782 environment variable (defaults to INFO), its handlers replaced with a single
18- RichHandler for rich-formatted console output , and propagation to ancestor
19- loggers disabled.
83+ handler ( RichHandler for TTY or StreamHandler for non-TTY) , and propagation
84+ to ancestor loggers disabled.
2085
2186 Parameters:
2287 name (str): Name of the logger to retrieve or create.
@@ -30,34 +95,7 @@ def get_logger(name: str) -> logging.Logger:
3095 if logger .handlers :
3196 return logger
3297
33- # RichHandler's columnar layout (timestamp, level, right-aligned filename) assumes
34- # a real terminal. In containers without a TTY, Rich falls back to 80 columns and
35- # the columns consume most of that width, leaving ~40 chars for the actual message.
36- # Tracebacks become nearly unreadable. Use a plain StreamHandler when there's no TTY.
37- if sys .stderr .isatty ():
38- logger .handlers = [RichHandler ()]
39- else :
40- handler = logging .StreamHandler ()
41- handler .setFormatter (
42- logging .Formatter (
43- "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d %(message)s"
44- )
45- )
46- logger .handlers = [handler ]
98+ logger .handlers = [create_log_handler ()]
4799 logger .propagate = False
48-
49- # Read log level from environment variable with default fallback
50- level_str = os .environ .get (LIGHTSPEED_STACK_LOG_LEVEL_ENV_VAR , DEFAULT_LOG_LEVEL )
51-
52- # Validate the level string and convert to logging level constant
53- validated_level = getattr (logging , level_str .upper (), None )
54- if not isinstance (validated_level , int ):
55- logger .warning (
56- "Invalid log level '%s', falling back to %s" ,
57- level_str ,
58- DEFAULT_LOG_LEVEL ,
59- )
60- validated_level = getattr (logging , DEFAULT_LOG_LEVEL )
61-
62- logger .setLevel (validated_level )
100+ logger .setLevel (resolve_log_level ())
63101 return logger
0 commit comments