@@ -31,72 +31,101 @@ def health_check_filter(record: LogRecord) -> bool:
31
31
"GC_DEBUG_LOGGING" , cast = lambda x : x .split ("|" ) if x else [], default = ""
32
32
)
33
33
34
+ # This shared_processors approach is modified from structlog's
35
+ # documentation for how to handle non-structured logs in a structured format
36
+ # https://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging
37
+ timestamper = structlog .processors .TimeStamper (fmt = "iso" )
38
+
39
+ shared_processors = [
40
+ structlog .contextvars .merge_contextvars ,
41
+ structlog .stdlib .add_logger_name ,
42
+ structlog .stdlib .add_log_level ,
43
+ structlog .stdlib .PositionalArgumentsFormatter (),
44
+ structlog .processors .CallsiteParameterAdder (
45
+ {
46
+ structlog .processors .CallsiteParameter .FILENAME ,
47
+ structlog .processors .CallsiteParameter .FUNC_NAME ,
48
+ structlog .processors .CallsiteParameter .LINENO ,
49
+ }
50
+ ),
51
+ structlog .processors .StackInfoRenderer (),
52
+ structlog .processors .format_exc_info ,
53
+ structlog .processors .UnicodeDecoder (),
54
+ ]
55
+
56
+
57
+ # These loggers duplicate django-structlog's request start/finish
58
+ # logs, as well as our nginx request logs. We want to keep only the
59
+ # start/finish logs and the nginx logs for access logging, otherwise
60
+ # we are just duplicating information!
61
+ _UNWANTED_LOGGERS = {
62
+ "uvicorn.access" ,
63
+ "django.request" ,
64
+ }
65
+
66
+
67
+ def suppress_unwanted_logs (record : LogRecord ) -> bool :
68
+ return (
69
+ record .name not in _UNWANTED_LOGGERS
70
+ and "GET /healthcheck" not in record .getMessage ()
71
+ )
72
+
73
+
34
74
# Logging configuration
35
75
LOGGING = {
36
- # NOTE: Most of this is inherited from the default configuration
37
76
"version" : 1 ,
38
77
"disable_existing_loggers" : False ,
39
78
"filters" : {
40
- "require_debug_false" : {"()" : "django.utils.log.RequireDebugFalse" },
41
- "require_debug_true" : {"()" : "django.utils.log.RequireDebugTrue" },
42
- "health_check" : {
79
+ "suppress_unwanted_logs" : {
43
80
"()" : "django.utils.log.CallbackFilter" ,
44
- "callback" : health_check_filter ,
81
+ "callback" : suppress_unwanted_logs ,
45
82
},
46
83
},
47
84
"formatters" : {
48
- "console" : {
49
- "format" : "[{asctime} - {name} - {lineno:>3}][{levelname}] {message}" ,
50
- "style" : "{" ,
51
- },
52
85
"structured" : {
53
86
"()" : structlog .stdlib .ProcessorFormatter ,
54
- "processor" : structlog .processors .JSONRenderer ()
55
- if LOG_PROCESSOR == "json"
56
- else structlog .dev .ConsoleRenderer (),
87
+ "processor" : (
88
+ structlog .processors .JSONRenderer ()
89
+ if LOG_PROCESSOR == "json"
90
+ else structlog .dev .ConsoleRenderer ()
91
+ ),
92
+ "foreign_pre_chain" : [
93
+ timestamper ,
94
+ # Explanations from https://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging
95
+ # Add the log level and a timestamp to the event_dict if the log entry
96
+ # is not from structlog.
97
+ structlog .stdlib .add_log_level ,
98
+ # Add extra attributes of LogRecord objects to the event dictionary
99
+ # so that values passed in the extra parameter of log methods pass
100
+ # through to log output.
101
+ structlog .stdlib .ExtraAdder (),
102
+ ]
103
+ + shared_processors ,
57
104
},
58
105
},
59
106
"handlers" : {
60
- # Default console logger
61
- "console" : {
62
- "level" : LOG_LEVEL ,
63
- "class" : "logging.StreamHandler" ,
64
- "formatter" : "console" ,
65
- },
66
107
"console_structured" : {
67
108
"level" : LOG_LEVEL ,
68
109
"class" : "logging.StreamHandler" ,
69
110
"formatter" : "structured" ,
111
+ "filters" : ["suppress_unwanted_logs" ],
70
112
},
71
113
},
114
+ "root" : {
115
+ "handlers" : ["console_structured" ],
116
+ "level" : LOG_LEVEL ,
117
+ "propagate" : False ,
118
+ },
72
119
"loggers" : {
73
- # Application
74
- "django_structlog" : {
75
- "handlers" : ["console_structured" ],
76
- "level" : LOG_LEVEL ,
77
- "propagate" : False ,
78
- },
79
- "api" : {
80
- "handlers" : ["console_structured" ],
81
- "level" : LOG_LEVEL ,
82
- "propagate" : False ,
83
- },
84
- # External
85
120
"django" : {
86
- "handlers" : ["console " ],
121
+ "handlers" : ["console_structured " ],
87
122
# Keep this at info to avoid django internal debug logs;
88
123
# we just want our own debug logs when log level is set to debug
89
124
"level" : "INFO" ,
90
125
"propagate" : False ,
91
126
},
92
- "uvicorn.error" : { # Using just "uvicorn" will re-enable access logs
93
- "handlers" : ["console" ],
94
- "level" : LOG_LEVEL ,
95
- "propagate" : False ,
96
- },
97
- # Default handler for all other loggers
98
- "" : {
99
- "handlers" : ["console" ],
127
+ "uvicorn" : {
128
+ "handlers" : ["console_structured" ],
100
129
"level" : LOG_LEVEL ,
101
130
},
102
131
},
@@ -105,22 +134,11 @@ def health_check_filter(record: LogRecord) -> bool:
105
134
# https://django-structlog.readthedocs.io/en/latest/getting_started.html
106
135
structlog .configure (
107
136
processors = [
108
- structlog . contextvars . merge_contextvars ,
137
+ timestamper ,
109
138
structlog .stdlib .filter_by_level ,
110
- structlog .processors .TimeStamper (fmt = "iso" ),
111
- structlog .stdlib .add_logger_name ,
112
- structlog .stdlib .add_log_level ,
113
- structlog .stdlib .PositionalArgumentsFormatter (),
114
- structlog .processors .CallsiteParameterAdder (
115
- {
116
- structlog .processors .CallsiteParameter .FILENAME ,
117
- structlog .processors .CallsiteParameter .FUNC_NAME ,
118
- structlog .processors .CallsiteParameter .LINENO ,
119
- }
120
- ),
121
- structlog .processors .StackInfoRenderer (),
122
- structlog .processors .format_exc_info ,
123
- structlog .processors .UnicodeDecoder (),
139
+ ]
140
+ + shared_processors
141
+ + [
124
142
structlog .stdlib .ProcessorFormatter .wrap_for_formatter ,
125
143
],
126
144
logger_factory = structlog .stdlib .LoggerFactory (),
0 commit comments