Skip to content

Commit 5c84c27

Browse files
Merge pull request #46 from NYPL/SCC-4840/json-logging
Scc 4840/json logging WIP
2 parents ffa087a + 3277b42 commit 5c84c27

4 files changed

Lines changed: 52 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
## v1.8.0 8/19/25
3+
- Add optional JSON structured logging
4+
25
## v1.7.0 6/13/25
36
- Use fastavro for avro encoding/decoding
47

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "nypl_py_utils"
7-
version = "1.7.0"
7+
version = "1.8.0"
88
authors = [
99
{ name="Aaron Friedman", email="aaronfriedman@nypl.org" },
1010
]
@@ -38,6 +38,9 @@ kms-client = [
3838
"boto3>=1.26.5",
3939
"botocore>=1.29.5"
4040
]
41+
log_helper = [
42+
"structlog>=25.4.0"
43+
]
4144
mysql-client = [
4245
"mysql-connector-python>=8.0.32"
4346
]
@@ -78,7 +81,7 @@ research-catalog-identifier-helper = [
7881
"requests>=2.28.1"
7982
]
8083
development = [
81-
"nypl_py_utils[avro-client,kinesis-client,kms-client,mysql-client,oauth2-api-client,postgresql-client,redshift-client,s3-client,secrets-manager-client,sftp-client,config-helper,obfuscation-helper,patron-data-helper,research-catalog-identifier-helper]",
84+
"nypl_py_utils[avro-client,kinesis-client,kms-client,mysql-client,oauth2-api-client,postgresql-client,redshift-client,s3-client,secrets-manager-client,sftp-client,config-helper,obfuscation-helper,patron-data-helper,research-catalog-identifier-helper,log_helper]",
8285
"flake8>=6.0.0",
8386
"freezegun>=1.2.2",
8487
"mock>=4.0.3",

src/nypl_py_utils/functions/log_helper.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import structlog
2+
13
import logging
24
import os
35
import sys
@@ -11,7 +13,31 @@
1113
}
1214

1315

14-
def create_log(module):
16+
# Configure structlog to be machine-readable first and foremost
17+
# while still making it easy for humans to parse
18+
# End result (without additional bindings) is JSON like this:
19+
#
20+
# { "logger": "module param"
21+
# "message": "this is a test log event",
22+
# "level": "info",
23+
# "timestamp": "2023-11-01 18:50:47"}
24+
#
25+
def get_structlog(module):
26+
structlog.configure(
27+
processors=[
28+
structlog.processors.add_log_level,
29+
structlog.processors.TimeStamper(fmt="iso"),
30+
structlog.processors.EventRenamer("message"),
31+
structlog.processors.JSONRenderer(),
32+
],
33+
context_class=dict,
34+
logger_factory=structlog.PrintLoggerFactory(),
35+
)
36+
37+
return structlog.get_logger(module)
38+
39+
40+
def standard_logger(module):
1541
logger = logging.getLogger(module)
1642
if logger.hasHandlers():
1743
logger.handlers = []
@@ -28,5 +54,11 @@ def create_log(module):
2854
console_log.setFormatter(formatter)
2955

3056
logger.addHandler(console_log)
31-
3257
return logger
58+
59+
60+
def create_log(module, json=False):
61+
if (json):
62+
return get_structlog(module)
63+
else:
64+
return standard_logger(module)

tests/test_log_helper.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1+
import json
12
import logging
23
import os
34
import time
45

56
from freezegun import freeze_time
7+
68
from nypl_py_utils.functions.log_helper import create_log
79

810

911
@freeze_time('2023-01-01 19:00:00')
1012
class TestLogHelper:
13+
def test_json_logging(self, capsys):
14+
logger = create_log('test_log', json=True)
15+
logger.info('test', some="json")
16+
output = json.loads(capsys.readouterr().out)
17+
assert output.get("message") == 'test'
18+
assert output.get("some") == 'json'
19+
assert output.get('level') == 'info'
20+
assert output.get('timestamp') == '2023-01-01T19:00:00Z'
1121

1222
def test_default_logging(self, caplog):
1323
logger = create_log('test_log')

0 commit comments

Comments
 (0)