Skip to content

Commit d5227e1

Browse files
committed
feat: add logfmt parser
Fixes: #28
1 parent 9436fbe commit d5227e1

File tree

1 file changed

+41
-1
lines changed

1 file changed

+41
-1
lines changed

src/logfmter/formatter.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
22
import numbers
3+
import shlex
34
from types import TracebackType
4-
from typing import Any, Dict, List, Optional, Tuple, Type, cast
5+
from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
56

67
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
78

@@ -42,6 +43,45 @@
4243
REPLACEMENTS.update({"\n": "\\n", "\t": "\\t", "\r": "\\r"})
4344

4445

46+
def parse_logfmt(
47+
line: str, aliases: Optional[Dict[str, str]] = None, convert_numeric: bool = False
48+
) -> Dict[str, Union[str, int, float]]:
49+
"""
50+
Parse a logfmt formatted string.
51+
52+
Raises ValueError on parsing errors.
53+
"""
54+
aliases = aliases or {}
55+
fields = {}
56+
57+
if "\n" in line:
58+
# TODO: be strict. Don't parse what we don't produce?!
59+
raise ValueError()
60+
61+
lexer = shlex.shlex(line, posix=True)
62+
lexer.quotes = '"'
63+
lexer.whitespace_split = True
64+
lexer.commenters = ""
65+
for token in lexer:
66+
value: Union[str, int, float]
67+
key, _, value = token.partition("=")
68+
key = aliases.get(key, key)
69+
for replacement, string in REPLACEMENTS.items():
70+
value = value.replace(string, replacement)
71+
if convert_numeric and value.isdigit():
72+
value = int(value)
73+
elif (
74+
convert_numeric
75+
and value.count(".") == 1
76+
and value.replace(".", "").isdigit()
77+
):
78+
value = float(value)
79+
if not key:
80+
raise ValueError()
81+
fields[key] = value
82+
return fields
83+
84+
4585
class _DefaultFormatter(logging.Formatter):
4686
def format(self, record):
4787
exc_info = record.exc_info

0 commit comments

Comments
 (0)