1+ import json
12import logging
23import os
34from typing import Optional
1213}
1314
1415
16+ class JSONFormatter (logging .Formatter ):
17+ """JSON formatter that outputs single-line structured logs."""
18+
19+ def format (self , record ):
20+ log_entry = {
21+ "timestamp" : self .formatTime (record , self .datefmt ),
22+ "level" : record .levelname ,
23+ "logger" : record .name ,
24+ "message" : record .getMessage (),
25+ }
26+
27+ # Add exception info if present
28+ if record .exc_info :
29+ log_entry ["exception" ] = self .formatException (record .exc_info )
30+
31+ # Add extra fields if present
32+ for key , value in record .__dict__ .items ():
33+ if key not in [
34+ "name" ,
35+ "msg" ,
36+ "args" ,
37+ "levelname" ,
38+ "levelno" ,
39+ "pathname" ,
40+ "filename" ,
41+ "module" ,
42+ "exc_info" ,
43+ "exc_text" ,
44+ "stack_info" ,
45+ "lineno" ,
46+ "funcName" ,
47+ "created" ,
48+ "msecs" ,
49+ "relativeCreated" ,
50+ "thread" ,
51+ "threadName" ,
52+ "processName" ,
53+ "process" ,
54+ "message" ,
55+ ]:
56+ log_entry [key ] = value
57+
58+ return json .dumps (log_entry , ensure_ascii = False )
59+
60+
1561def configure_logger (name : Optional [str ] = None ) -> logging .Logger :
1662 """
1763 Configure and return a logger instance with consistent formatting and level.
@@ -34,37 +80,37 @@ def configure_logger(name: Optional[str] = None) -> logging.Logger:
3480 if not logger .handlers :
3581 console_handler = logging .StreamHandler ()
3682 console_handler .setLevel (log_level )
37- formatter = logging . Formatter ( "%(levelname)s: %(message)s" )
83+ formatter = JSONFormatter ( )
3884 console_handler .setFormatter (formatter )
3985 logger .addHandler (console_handler )
4086
4187 return logger
4288
4389
4490def setup_uvicorn_logging ():
45- """Configure uvicorn and other loggers to use normal formatting."""
91+ """Configure uvicorn and other loggers to use JSON formatting."""
4692 # Disable uvicorn access logging since we handle it with middleware
4793 logging .getLogger ("uvicorn.access" ).disabled = True
4894
49- # Create a standard formatter
50- standard_formatter = logging . Formatter ( "%(levelname)s: %(message)s" )
95+ # Create a JSON formatter
96+ json_formatter = JSONFormatter ( )
5197
5298 # Get all existing loggers and configure them
5399 for logger_name in ["uvicorn" , "uvicorn.error" , "fastapi" ]:
54100 logger = logging .getLogger (logger_name )
55101 for handler in logger .handlers :
56- handler .setFormatter (standard_formatter )
102+ handler .setFormatter (json_formatter )
57103
58104 # Configure root logger
59105 root_logger = logging .getLogger ()
60106 for handler in root_logger .handlers :
61- handler .setFormatter (standard_formatter )
107+ handler .setFormatter (json_formatter )
62108
63109 # Set up a hook to catch any new handlers that get added later
64110 original_add_handler = logging .Logger .addHandler
65111
66112 def patched_add_handler (self , hdlr ):
67- hdlr .setFormatter (standard_formatter )
113+ hdlr .setFormatter (json_formatter )
68114 return original_add_handler (self , hdlr )
69115
70116 logging .Logger .addHandler = patched_add_handler
0 commit comments