diff --git a/catfish/utils/logfmt.py b/catfish/utils/logfmt.py new file mode 100644 index 0000000..b5d7488 --- /dev/null +++ b/catfish/utils/logfmt.py @@ -0,0 +1,31 @@ +# Adapted from https://github.com/jkakar/logfmt-python/blob/master/logfmt/formatter.py + +from typing import Any, Dict + +CHARS_TO_ESCAPE = [" ", "=", "\\", '"', "'"] + + +def bool_to_str(i: bool) -> str: + return str(i).lower() + + +def escape_value(value: str): + value = value.replace('"', '\\"') + if any([char in value for char in CHARS_TO_ESCAPE]): + value = '"{}"'.format(value) + return value + + +def logfmt(data: Dict[str, Any]): + out = [] + for k, v in data.items(): + if not k.isidentifier(): + continue + if v is None: + v = "" + elif isinstance(v, bool): + v = bool_to_str(v) + elif isinstance(v, str): + v = escape_value(v) + out.append("{}={}".format(k, v)) + return " ".join(out) diff --git a/tests/test_utils/test_logfmt.py b/tests/test_utils/test_logfmt.py new file mode 100644 index 0000000..1976e96 --- /dev/null +++ b/tests/test_utils/test_logfmt.py @@ -0,0 +1,58 @@ +from catfish.utils.logfmt import logfmt +from tests import BaseTestCase + + +class LogFmtTestCase(BaseTestCase): + def test_simple_logfmt(self): + self.assertEqual( + logfmt({"key1": "value1", "key2": "value2"}), "key1=value1 key2=value2" + ) + + def test_quotes_space(self): + self.assertEqual( + logfmt({"key1": "value1 valuea", "key2": "value2 valueb"}), + 'key1="value1 valuea" key2="value2 valueb"', + ) + + def test_quotes_equals(self): + self.assertEqual( + logfmt({"key1": "value1=valuea", "key2": "value2=valueb"}), + 'key1="value1=valuea" key2="value2=valueb"', + ) + + def test_quotes_quotes(self): + self.assertEqual( + logfmt({"key1": '"value1"', "key2": '"value2"'}), + 'key1="\\"value1\\"" key2="\\"value2\\""', + ) + self.assertEqual( + logfmt({"key1": "'value1'", "key2": "'value2'"}), + "key1=\"'value1'\" key2=\"'value2'\"", + ) + + def test_empty(self): + self.assertEqual(logfmt({}), "") + + def test_boolean_values(self): + self.assertEqual(logfmt({"key1": True, "key2": False}), "key1=true key2=false") + + def test_none_values(self): + self.assertEqual(logfmt({"key1": None, "key2": None}), "key1= key2=") + + def test_numbers(self): + self.assertEqual( + logfmt({"key1": 12345, "key2": -12345}), "key1=12345 key2=-12345" + ) + self.assertEqual( + logfmt({"key1": "12345", "key2": "-12345"}), "key1=12345 key2=-12345" + ) + + def test_float(self): + self.assertEqual( + logfmt({"key1": 12345.67, "key2": -12345.67}), + "key1=12345.67 key2=-12345.67", + ) + self.assertEqual( + logfmt({"key1": "12345.67", "key2": "-12345.67"}), + "key1=12345.67 key2=-12345.67", + )