7
7
import logging
8
8
import os
9
9
import re
10
+ import sys
10
11
import warnings
11
12
from pathlib import Path
13
+ from typing import Literal , TypeAlias
12
14
13
15
import structlog
14
16
24
26
structlog .stdlib .PositionalArgumentsFormatter (),
25
27
structlog .processors .MaybeTimeStamper (),
26
28
]
29
+ LogFormat : TypeAlias = Literal ["json" , "logfmt" , "text" ]
27
30
28
31
_active_config : LoggingConfig | None = None
29
32
@@ -64,8 +67,10 @@ class LoggingConfig: # pragma: nocover
64
67
"""
65
68
66
69
level : int = logging .INFO
70
+ stream_json : bool = False
67
71
file : Path | None = None
68
72
file_level : int | None = None
73
+ file_format : LogFormat = "json"
69
74
70
75
def __init__ (self ):
71
76
# initialize configuration from environment variables
@@ -85,6 +90,12 @@ def effective_level(self) -> int:
85
90
else :
86
91
return self .level
87
92
93
+ def set_term_json (self , flag : bool = True ):
94
+ """
95
+ Configure logging to stream JSON lines to the stderr (useful for web services).
96
+ """
97
+ self .stream_json = flag
98
+
88
99
def set_verbose (self , verbose : bool | int = True ):
89
100
"""
90
101
Enable verbose logging.
@@ -108,12 +119,15 @@ def set_verbose(self, verbose: bool | int = True):
108
119
else :
109
120
self .level = logging .INFO
110
121
111
- def log_file (self , path : os .PathLike [str ], level : int | None = None ):
122
+ def set_log_file (
123
+ self , path : os .PathLike [str ], level : int | None = None , format : LogFormat = "json"
124
+ ):
112
125
"""
113
126
Configure a log file.
114
127
"""
115
128
self .file = Path (path )
116
129
self .file_level = level
130
+ self .file_format = format
117
131
118
132
def apply (self ):
119
133
"""
@@ -123,8 +137,15 @@ def apply(self):
123
137
124
138
setup_console ()
125
139
root = logging .getLogger ()
126
- term = ConsoleHandler ()
127
- term .setLevel (self .level )
140
+
141
+ if self .stream_json :
142
+ term = logging .StreamHandler (sys .stderr )
143
+ term .setLevel (self .level )
144
+ proc_fmt = structlog .processors .JSONRenderer ()
145
+ else :
146
+ term = ConsoleHandler ()
147
+ term .setLevel (self .level )
148
+ proc_fmt = structlog .dev .ConsoleRenderer (colors = term .supports_color )
128
149
129
150
eff_lvl = self .effective_level
130
151
structlog .configure (
@@ -136,7 +157,7 @@ def apply(self):
136
157
processors = [
137
158
remove_internal ,
138
159
format_timestamp ,
139
- structlog . dev . ConsoleRenderer ( colors = term . supports_color ) ,
160
+ proc_fmt ,
140
161
],
141
162
foreign_pre_chain = CORE_PROCESSORS ,
142
163
)
@@ -147,11 +168,19 @@ def apply(self):
147
168
if self .file :
148
169
file_level = self .file_level if self .file_level is not None else self .level
149
170
file = logging .FileHandler (self .file , mode = "w" )
171
+
172
+ if self .file_format == "json" :
173
+ proc_fmt = structlog .processors .JSONRenderer ()
174
+ elif self .file_format == "logfmt" :
175
+ proc_fmt = structlog .processors .LogfmtRenderer (key_order = ["event" , "timestamp" ])
176
+ else :
177
+ proc_fmt = structlog .processors .KeyValueRenderer (key_order = ["event" , "timestamp" ])
178
+
150
179
ffmt = structlog .stdlib .ProcessorFormatter (
151
180
processors = [
152
181
remove_internal ,
153
182
structlog .processors .ExceptionPrettyPrinter (),
154
- structlog . processors . JSONRenderer () ,
183
+ proc_fmt ,
155
184
],
156
185
foreign_pre_chain = CORE_PROCESSORS ,
157
186
)
0 commit comments