@@ -125,6 +125,37 @@ def format(self, record: logging.LogRecord) -> str:
125125
126126
127127_initialized = False
128+ _human_readable = False
129+ _original_add_handler = logging .Logger .addHandler
130+
131+
132+ def set_human_readable (enabled : bool ) -> None :
133+ """Set log output format and re-initialize structlog if already configured."""
134+ global _human_readable , _initialized
135+ _human_readable = enabled
136+ _initialized = False
137+ _initialize ()
138+
139+
140+ _LEVEL_COLORS = {
141+ "DEBUG" : _FG_CODES ["cyan" ],
142+ "INFO" : _FG_CODES ["green" ],
143+ "WARNING" : _FG_CODES ["yellow" ],
144+ "ERROR" : _FG_CODES ["red" ],
145+ "CRITICAL" : _FG_CODES ["red" ] + _BG_CODES ["white" ],
146+ }
147+
148+
149+ class ThirdPartyHumanReadableFormatter (logging .Formatter ):
150+ """Formatter for third-party logs in human-readable mode with colors and source location."""
151+
152+ def format (self , record : logging .LogRecord ) -> str :
153+ timestamp = datetime .fromtimestamp (record .created , tz = UTC ).isoformat ()
154+ color = _LEVEL_COLORS .get (record .levelname , "" )
155+ reset = _RESET if color else ""
156+ filename = record .pathname .rsplit ("/" , 1 )[- 1 ] if record .pathname else ""
157+ msg = record .getMessage ()
158+ return f"{ timestamp } { record .name } { color } { record .levelname } { reset } { msg } ({ filename } :{ record .lineno } )"
128159
129160
130161def _initialize () -> None :
@@ -133,36 +164,51 @@ def _initialize() -> None:
133164 if _initialized :
134165 return
135166
136- structlog .configure (
137- processors = [
167+ if _human_readable :
168+ processors = [
169+ structlog .processors .KeyValueRenderer (key_order = ["event" ]),
170+ ]
171+ else :
172+ processors = [
138173 structlog .stdlib .add_logger_name ,
139174 structlog .stdlib .add_log_level ,
140175 structlog .processors .TimeStamper (fmt = "ISO" , utc = True ),
141176 structlog .processors .JSONRenderer (),
142- ],
177+ ]
178+
179+ structlog .configure (
180+ processors = processors ,
143181 wrapper_class = structlog .stdlib .BoundLogger ,
144182 logger_factory = structlog .stdlib .LoggerFactory (),
145- cache_logger_on_first_use = True ,
183+ cache_logger_on_first_use = False ,
146184 )
147185
148- # Patch addHandler so new loggers get JSON formatting
149- original_add_handler = logging .Logger .addHandler
186+ third_party_formatter = ThirdPartyHumanReadableFormatter () if _human_readable else ThirdPartyJSONFormatter ()
187+
188+ # Formatters that should not be replaced:
189+ # - JSONOnlyFormatter: used by StructlogWrapper (structlog already formats the message)
190+ # - WrapperLogFormatter: used by setup_logging's QueueHandlers (only protected in HR mode)
191+ _skip_formatters : tuple [type , ...] = (JSONOnlyFormatter ,)
192+ if _human_readable :
193+ _skip_formatters = (* _skip_formatters , WrapperLogFormatter )
150194
195+ # Patch addHandler so new loggers (e.g. from ocp_resources/simple_logger)
196+ # get the correct formatter on their handlers.
151197 def patched_add_handler (self : logging .Logger , hdlr : logging .Handler ) -> None :
152- if not isinstance (hdlr .formatter , ( ThirdPartyJSONFormatter , JSONOnlyFormatter ) ):
153- hdlr .setFormatter (fmt = ThirdPartyJSONFormatter () )
154- original_add_handler (self , hdlr ) # noqa: FCN001
198+ if not isinstance (hdlr .formatter , _skip_formatters ):
199+ hdlr .setFormatter (fmt = third_party_formatter )
200+ _original_add_handler (self , hdlr ) # noqa: FCN001
155201
156202 logging .Logger .addHandler = patched_add_handler # type: ignore[method-assign]
157203
158- # Apply JSON formatter to all existing handlers on all loggers
204+ # Apply formatter to all existing handlers on all loggers
159205 all_loggers = [logging .getLogger ()] + [
160206 logger for logger in logging .root .manager .loggerDict .values () if isinstance (logger , logging .Logger )
161207 ]
162208 for logger in all_loggers :
163209 for handler in logger .handlers :
164- if isinstance (handler .formatter , ( logging . Formatter , type ( None )) ):
165- handler .setFormatter (fmt = ThirdPartyJSONFormatter () )
210+ if not isinstance (handler .formatter , _skip_formatters ):
211+ handler .setFormatter (fmt = third_party_formatter )
166212
167213 _initialized = True
168214
@@ -185,8 +231,14 @@ def _log(self, level: str, msg: Any, *args: Any, **kwargs: Any) -> None:
185231 if args :
186232 msg_str = msg_str % args
187233
188- log_method = getattr (self ._logger , level .lower ())
189- log_method (event = msg_str , ** kwargs )
234+ if _human_readable :
235+ std_logger = logging .getLogger (self .name )
236+ log_method = getattr (std_logger , level .lower ())
237+ extra_str = " " .join (f"{ k } ={ v } " for k , v in kwargs .items ()) if kwargs else ""
238+ log_method (f"{ msg_str } { extra_str } " if extra_str else msg_str , stacklevel = 3 ) # noqa: FCN001
239+ else :
240+ log_method = getattr (self ._logger , level .lower ())
241+ log_method (event = msg_str , ** kwargs )
190242
191243 def info (self , msg : Any , * args : Any , ** kwargs : Any ) -> None :
192244 self ._log ("info" , msg , * args , ** kwargs ) # noqa: FCN001
0 commit comments