44Production-ready logging with JSON format for log aggregation systems.
55"""
66
7- import logging
8- import sys
9-
10- try :
11- from pythonjsonlogger import jsonlogger
12- _JSONLOGGER_AVAILABLE = True
13- except ImportError : # pragma: no cover - fallback when dependency missing
14- _JSONLOGGER_AVAILABLE = False
15- jsonlogger = None
16-
17-
18- if _JSONLOGGER_AVAILABLE :
19-
20- class CustomJsonFormatter (jsonlogger .JsonFormatter ):
21- """
22- Custom JSON formatter with additional fields
23-
24- Adds request_id, service_name, and environment to all log records.
25- """
26-
27- def add_fields (self , log_record , record , message_dict ):
28- super (CustomJsonFormatter , self ).add_fields (
29- log_record , record , message_dict
30- )
31-
32- # Add service identification
33- log_record ["service" ] = "flamehaven-filesearch"
34- log_record ["version" ] = "1.2.2"
35-
36- # Add request ID if available
37- if hasattr (record , "request_id" ):
38- log_record ["request_id" ] = record .request_id
39-
40- # Add environment (from env var or default to 'development')
41- import os
42-
43- log_record ["environment" ] = os .getenv ("ENVIRONMENT" , "development" )
44-
45- # Ensure timestamp is present
46- if "timestamp" not in log_record :
47- from datetime import datetime
48-
49- log_record ["timestamp" ] = datetime .utcnow ().isoformat () + "Z"
50-
51- # Add level name
52- log_record ["level" ] = record .levelname
53- else :
54-
55- class CustomJsonFormatter (logging .Formatter ):
56- """Fallback plain formatter when python-json-logger is unavailable."""
57-
58- def format (self , record ):
59- base = super ().format (record )
60- return base
7+ import logging
8+ import sys
9+
10+ try :
11+ from pythonjsonlogger import jsonlogger
12+
13+ _JSONLOGGER_AVAILABLE = True
14+ except ImportError : # pragma: no cover - fallback when dependency missing
15+ _JSONLOGGER_AVAILABLE = False
16+ jsonlogger = None
17+
18+
19+ if _JSONLOGGER_AVAILABLE :
20+
21+ class CustomJsonFormatter (jsonlogger .JsonFormatter ):
22+ """
23+ Custom JSON formatter with additional fields
24+
25+ Adds request_id, service_name, and environment to all log records.
26+ """
27+
28+ def add_fields (self , log_record , record , message_dict ):
29+ super (CustomJsonFormatter , self ).add_fields (
30+ log_record , record , message_dict
31+ )
32+
33+ # Add service identification
34+ log_record ["service" ] = "flamehaven-filesearch"
35+ log_record ["version" ] = "1.2.2"
36+
37+ # Add request ID if available
38+ if hasattr (record , "request_id" ):
39+ log_record ["request_id" ] = record .request_id
40+
41+ # Add environment (from env var or default to 'development')
42+ import os
43+
44+ log_record ["environment" ] = os .getenv ("ENVIRONMENT" , "development" )
45+
46+ # Ensure timestamp is present
47+ if "timestamp" not in log_record :
48+ from datetime import datetime
49+
50+ log_record ["timestamp" ] = datetime .utcnow ().isoformat () + "Z"
51+
52+ # Add level name
53+ log_record ["level" ] = record .levelname
54+
55+ else :
56+
57+ class CustomJsonFormatter (logging .Formatter ):
58+ """Fallback plain formatter when python-json-logger is unavailable."""
59+
60+ def format (self , record ):
61+ base = super ().format (record )
62+ return base
6163
6264
6365def setup_json_logging (log_level = logging .INFO , ** kwargs ):
@@ -71,36 +73,36 @@ def setup_json_logging(log_level=logging.INFO, **kwargs):
7173 # Support logging.basicConfig-style signatures (level=...)
7274 effective_level = kwargs .get ("level" , log_level )
7375
74- # Create JSON formatter (or fallback)
75- if _JSONLOGGER_AVAILABLE :
76- formatter = CustomJsonFormatter (
77- "%(timestamp)s %(level)s %(name)s %(message)s %(request_id)s "
78- "%(service)s %(version)s %(environment)s" ,
79- rename_fields = {
80- "levelname" : "level" ,
81- "name" : "logger" ,
82- "asctime" : "timestamp" ,
83- },
84- datefmt = "%Y-%m-%dT%H:%M:%S" ,
85- )
86- else :
87- formatter = logging .Formatter (
88- "%(asctime)s %(levelname)s %(name)s %(message)s" ,
89- datefmt = "%Y-%m-%dT%H:%M:%S" ,
90- )
76+ # Create JSON formatter (or fallback)
77+ if _JSONLOGGER_AVAILABLE :
78+ formatter = CustomJsonFormatter (
79+ "%(timestamp)s %(level)s %(name)s %(message)s %(request_id)s "
80+ "%(service)s %(version)s %(environment)s" ,
81+ rename_fields = {
82+ "levelname" : "level" ,
83+ "name" : "logger" ,
84+ "asctime" : "timestamp" ,
85+ },
86+ datefmt = "%Y-%m-%dT%H:%M:%S" ,
87+ )
88+ else :
89+ formatter = logging .Formatter (
90+ "%(asctime)s %(levelname)s %(name)s %(message)s" ,
91+ datefmt = "%Y-%m-%dT%H:%M:%S" ,
92+ )
9193
9294 # Configure root logger
9395 root_logger = logging .getLogger ()
9496 root_logger .setLevel (effective_level )
9597
9698 # Remove existing handlers
97- for handler in root_logger .handlers [:]:
98- root_logger .removeHandler (handler )
99-
100- # Add handler to stdout
101- json_handler = logging .StreamHandler (sys .stdout )
102- json_handler .setFormatter (formatter )
103- root_logger .addHandler (json_handler )
99+ for handler in root_logger .handlers [:]:
100+ root_logger .removeHandler (handler )
101+
102+ # Add handler to stdout
103+ json_handler = logging .StreamHandler (sys .stdout )
104+ json_handler .setFormatter (formatter )
105+ root_logger .addHandler (json_handler )
104106
105107 # Set level for specific loggers
106108 logging .getLogger ("uvicorn.access" ).setLevel (logging .WARNING )
0 commit comments