Skip to content

Commit ab3e2f0

Browse files
committed
add JSON logging support to logging configuration
1 parent ff4826e commit ab3e2f0

File tree

1 file changed

+34
-5
lines changed

1 file changed

+34
-5
lines changed

lenskit/lenskit/logging/config.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import logging
88
import os
99
import re
10+
import sys
1011
import warnings
1112
from pathlib import Path
13+
from typing import Literal, TypeAlias
1214

1315
import structlog
1416

@@ -24,6 +26,7 @@
2426
structlog.stdlib.PositionalArgumentsFormatter(),
2527
structlog.processors.MaybeTimeStamper(),
2628
]
29+
LogFormat: TypeAlias = Literal["json", "logfmt", "text"]
2730

2831
_active_config: LoggingConfig | None = None
2932

@@ -64,8 +67,10 @@ class LoggingConfig: # pragma: nocover
6467
"""
6568

6669
level: int = logging.INFO
70+
stream_json: bool = False
6771
file: Path | None = None
6872
file_level: int | None = None
73+
file_format: LogFormat = "json"
6974

7075
def __init__(self):
7176
# initialize configuration from environment variables
@@ -85,6 +90,12 @@ def effective_level(self) -> int:
8590
else:
8691
return self.level
8792

93+
def 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+
8899
def set_verbose(self, verbose: bool | int = True):
89100
"""
90101
Enable verbose logging.
@@ -108,12 +119,15 @@ def set_verbose(self, verbose: bool | int = True):
108119
else:
109120
self.level = logging.INFO
110121

111-
def log_file(self, path: os.PathLike[str], level: int | None = None):
122+
def log_file(
123+
self, path: os.PathLike[str], level: int | None = None, format: LogFormat = "json"
124+
):
112125
"""
113126
Configure a log file.
114127
"""
115128
self.file = Path(path)
116129
self.file_level = level
130+
self.file_format = format
117131

118132
def apply(self):
119133
"""
@@ -123,8 +137,15 @@ def apply(self):
123137

124138
setup_console()
125139
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)
128149

129150
eff_lvl = self.effective_level
130151
structlog.configure(
@@ -136,7 +157,7 @@ def apply(self):
136157
processors=[
137158
remove_internal,
138159
format_timestamp,
139-
structlog.dev.ConsoleRenderer(colors=term.supports_color),
160+
proc_fmt,
140161
],
141162
foreign_pre_chain=CORE_PROCESSORS,
142163
)
@@ -147,11 +168,19 @@ def apply(self):
147168
if self.file:
148169
file_level = self.file_level if self.file_level is not None else self.level
149170
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+
150179
ffmt = structlog.stdlib.ProcessorFormatter(
151180
processors=[
152181
remove_internal,
153182
structlog.processors.ExceptionPrettyPrinter(),
154-
structlog.processors.JSONRenderer(),
183+
proc_fmt,
155184
],
156185
foreign_pre_chain=CORE_PROCESSORS,
157186
)

0 commit comments

Comments
 (0)