Skip to content

Commit 1aab02d

Browse files
authored
Add support for using extra={...} on stdlib loggers
1 parent 7164058 commit 1aab02d

File tree

4 files changed

+64
-9
lines changed

4 files changed

+64
-9
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,21 @@ handler.setFormatter(ecs_logging.StdlibFormatter())
4242
logger.addHandler(handler)
4343

4444
# Emit a log!
45-
logger.debug("Example message!")
45+
logger.debug("Example message!", extra={"http.request.method": "get"})
4646
```
4747
```json
4848
{
4949
"@timestamp": "2020-03-20T18:11:37.895Z",
5050
"ecs": {
5151
"version": "1.5.0"
5252
},
53+
"http": {
54+
"request": {
55+
"method": "get"
56+
}
57+
},
5358
"log": {
54-
"level": "DEBUG",
59+
"level": "debug",
5560
"logger": "app",
5661
"origin": {
5762
"file": {

ecs_logging/_stdlib.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ class StdlibFormatter(logging.Formatter):
1818
"message": "log.original",
1919
"name": "log.logger",
2020
}
21+
LOGRECORD_DICT = {
22+
"name",
23+
"msg",
24+
"args",
25+
"levelname",
26+
"levelno",
27+
"pathname",
28+
"filename",
29+
"module",
30+
"exc_info",
31+
"exc_text",
32+
"stack_info",
33+
"lineno",
34+
"funcName",
35+
"created",
36+
"msecs",
37+
"relativeCreated",
38+
"thread",
39+
"threadName",
40+
"processName",
41+
"process",
42+
}
2143
converter = time.gmtime
2244

2345
def __init__(self):
@@ -48,9 +70,15 @@ def format_to_ecs(self, record):
4870
available["message"] = record.getMessage()
4971

5072
for attribute in set(self.WANTED_ATTRS).intersection(available):
51-
merge_dicts(
52-
de_dot(self.WANTED_ATTRS[attribute], getattr(record, attribute)), result
53-
)
73+
ecs_attr = self.WANTED_ATTRS[attribute]
74+
value = getattr(record, attribute)
75+
if ecs_attr == "log.level" and isinstance(value, str):
76+
value = value.lower()
77+
merge_dicts(de_dot(ecs_attr, value), result)
78+
79+
# Merge in any keys that were set within 'extra={...}'
80+
for key in set(available.keys()).difference(self.LOGRECORD_DICT):
81+
merge_dicts(de_dot(key, available[key]), result)
5482

5583
# The following is mostly for the ecs format. You can't have 2x
5684
# 'message' keys in WANTED_ATTRS, so we set the value to

ecs_logging/_structlog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class StructlogFormatter:
1313
def __call__(self, _, name, event_dict):
1414
# type: (Any, str, Dict[str, Any]) -> str
1515
event_dict = normalize_dict(event_dict)
16-
event_dict.setdefault("log", {}).setdefault("level", name)
16+
event_dict.setdefault("log", {}).setdefault("level", name.lower())
1717
event_dict = self.format_to_ecs(event_dict)
1818
return json_dumps(event_dict)
1919

tests/test_stdlib_formatter.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import mock
23
import ecs_logging
34
from .compat import StringIO
45

@@ -24,7 +25,7 @@ def test_record_formatted():
2425

2526
assert formatter.format(make_record()) == (
2627
'{"@timestamp":"2020-03-20T14:12:46.123Z","ecs":{"version":"1.5.0"},'
27-
'"log":{"level":"DEBUG","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
28+
'"log":{"level":"debug","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
2829
'"function":"test_function"},"original":"1: hello"},"message":"1: hello"}'
2930
)
3031

@@ -39,7 +40,7 @@ def format_to_ecs(self, record):
3940
formatter = CustomFormatter()
4041
assert formatter.format(make_record()) == (
4142
'{"@timestamp":"2020-03-20T14:12:46.123Z","custom":"field","ecs":{"version":"1.5.0"},'
42-
'"log":{"level":"DEBUG","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
43+
'"log":{"level":"debug","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
4344
'"function":"test_function"},"original":"1: hello"},"message":"1: hello"}'
4445
)
4546

@@ -53,6 +54,27 @@ def test_can_be_set_on_handler():
5354

5455
assert stream.getvalue() == (
5556
'{"@timestamp":"2020-03-20T14:12:46.123Z","ecs":{"version":"1.5.0"},'
56-
'"log":{"level":"DEBUG","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
57+
'"log":{"level":"debug","logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},'
5758
'"function":"test_function"},"original":"1: hello"},"message":"1: hello"}\n'
5859
)
60+
61+
62+
@mock.patch("time.time")
63+
def test_extra_is_merged(time):
64+
time.return_value = 1584720997.187709
65+
66+
stream = StringIO()
67+
handler = logging.StreamHandler(stream)
68+
handler.setFormatter(ecs_logging.StdlibFormatter())
69+
logger = logging.getLogger("test-logger")
70+
logger.addHandler(handler)
71+
logger.setLevel(logging.INFO)
72+
73+
logger.info("hey world", extra={"tls": {"cipher": "AES"}, "tls.established": True})
74+
75+
assert stream.getvalue() == (
76+
'{"@timestamp":"2020-03-20T16:16:37.187Z","ecs":{"version":"1.5.0"},"log":'
77+
'{"level":"info","logger":"test-logger","origin":{"file":{"line":73,"name":'
78+
'"test_stdlib_formatter.py"},"function":"test_extra_is_merged"},"original":"hey '
79+
'world"},"message":"hey world","tls":{"cipher":"AES","established":true}}\n'
80+
)

0 commit comments

Comments
 (0)