Skip to content

Commit 3869cbb

Browse files
authored
Make JsonLogFormatter easier to extend (#37)
1 parent e19b3f8 commit 3869cbb

File tree

2 files changed

+33
-13
lines changed

2 files changed

+33
-13
lines changed

src/dockerflow/logging.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,36 +82,43 @@ def __init__(self, fmt=None, datefmt=None, style="%", logger_name="Dockerflow"):
8282
self.logger_name = logger_name
8383
self.hostname = socket.gethostname()
8484

85-
def format(self, record):
85+
def is_value_jsonlike(self, value):
86+
"""
87+
Return True if the value looks like JSON. Use only on strings.
88+
"""
89+
return value.startswith('{') and value.endswith('}')
90+
91+
def convert_record(self, record):
8692
"""
87-
Map from Python LogRecord attributes to JSON log format fields
93+
Convert a Python LogRecord attribute into a dict that follows MozLog
94+
application logging standard.
8895
8996
* from - https://docs.python.org/3/library/logging.html#logrecord-attributes
9097
* to - https://wiki.mozilla.org/Firefox/Services/Logging
9198
"""
92-
out = dict(
93-
Timestamp=int(record.created * 1e9),
94-
Type=record.name,
95-
Logger=self.logger_name,
96-
Hostname=self.hostname,
97-
EnvVersion=self.LOGGING_FORMAT_VERSION,
98-
Severity=self.SYSLOG_LEVEL_MAP.get(
99+
out = {
100+
'Timestamp': int(record.created * 1e9),
101+
'Type': record.name,
102+
'Logger': self.logger_name,
103+
'Hostname': self.hostname,
104+
'EnvVersion': self.LOGGING_FORMAT_VERSION,
105+
'Severity': self.SYSLOG_LEVEL_MAP.get(
99106
record.levelno, self.DEFAULT_SYSLOG_LEVEL
100107
),
101-
Pid=record.process,
102-
)
108+
'Pid': record.process,
109+
}
103110

104111
# Include any custom attributes set on the record.
105112
# These would usually be collected metrics data.
106-
fields = dict()
113+
fields = {}
107114
for key, value in record.__dict__.items():
108115
if key not in self.EXCLUDED_LOGRECORD_ATTRS:
109116
fields[key] = value
110117

111118
# Only include the 'msg' key if it has useful content
112119
# and is not already a JSON blob.
113120
message = record.getMessage()
114-
if message and not message.startswith("{") and not message.endswith("}"):
121+
if message and not self.is_value_jsonlike(message):
115122
fields["msg"] = message
116123

117124
# If there is an error, format it for nice output.
@@ -120,7 +127,14 @@ def format(self, record):
120127
fields["traceback"] = safer_format_traceback(*record.exc_info)
121128

122129
out["Fields"] = fields
130+
return out
123131

132+
def format(self, record):
133+
"""
134+
Format a Python LogRecord into a JSON string following MozLog
135+
application logging standard.
136+
"""
137+
out = self.convert_record(record)
124138
return json.dumps(out, cls=SafeJSONEncoder)
125139

126140

tests/test_logging.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def test_basic_operation(caplog):
6363
caplog.set_level(logging.DEBUG)
6464
logging.debug(message_text)
6565
details = assert_records(caplog.records)
66+
assert details == formatter.convert_record(caplog.records[0])
6667

6768
assert "Timestamp" in details
6869
assert "Hostname" in details
@@ -79,6 +80,7 @@ def test_custom_paramters(caplog):
7980
logger = logging.getLogger("tests.test_logging")
8081
logger.warning("custom test %s", "one", extra={"more": "stuff"})
8182
details = assert_records(caplog.records)
83+
assert details == formatter.convert_record(caplog.records[0])
8284

8385
assert details["Type"] == "tests.test_logging"
8486
assert details["Severity"] == 4
@@ -124,6 +126,10 @@ def test_ignore_json_message(caplog):
124126
details = assert_records(caplog.records)
125127
assert "msg" not in details["Fields"]
126128

129+
assert formatter.is_value_jsonlike('{"spam": "eggs"}')
130+
assert not formatter.is_value_jsonlike('{"spam": "eggs"')
131+
assert not formatter.is_value_jsonlike('"spam": "eggs"}')
132+
127133

128134
# https://mana.mozilla.org/wiki/pages/viewpage.action?pageId=42895640
129135
JSON_LOGGING_SCHEMA = json.loads(

0 commit comments

Comments
 (0)