├── test
├── __init__.py
├── test_PathPrettifier.py
├── test_MultilinePrinter.py
├── test_JSONPrettifier.py
├── test_SyntaxHighlighter.py
├── test_XMLPrettifier.py
└── test_NumericRounder.py
├── .github
├── FUNDING.yml
└── workflows
│ └── semgrep.yml
├── requirements.txt
├── fast-requirements.txt
├── test-requirements.txt
├── MANIFEST.in
├── structlog_pretty
├── __init__.py
├── utils.py
└── processors.py
├── setup.cfg
├── tox.ini
├── circle.yml
├── .pylintrc
├── CHANGELOG.md
├── .coafile
├── LICENSE
├── setup.py
├── .gitignore
└── README.rst
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: underyx
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pygments==2.*
2 |
--------------------------------------------------------------------------------
/fast-requirements.txt:
--------------------------------------------------------------------------------
1 | lxml==5.*
2 | orjson==3.*
3 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | pytest==8.*
2 | pytest-cov==5.*
3 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include requirements.txt
3 | include fast-requirements.txt
4 | include test-requirements.txt
5 |
--------------------------------------------------------------------------------
/structlog_pretty/__init__.py:
--------------------------------------------------------------------------------
1 | from .processors import (
2 | NumericRounder,
3 | JSONPrettifier,
4 | XMLPrettifier,
5 | SyntaxHighlighter,
6 | MultilinePrinter,
7 | PathPrettifier,
8 | )
9 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [tool:pytest]
2 | norecursedirs =
3 | .git
4 | dist
5 | build
6 | .tox
7 | venv
8 | .env
9 | env
10 | testpaths =
11 | test
12 |
13 | [coverage:run]
14 | branch = True
15 | source = structlog_pretty
16 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = tests-py{38,312}
3 |
4 | [testenv]
5 | usedevelop=True
6 | deps =
7 | -rrequirements.txt
8 | -rtest-requirements.txt
9 | -rfast-requirements.txt
10 | commands =
11 | tests: pytest {posargs:} --cov
12 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependencies:
3 | override:
4 | - pip install coverage==7.* tox==4* tox-pyenv==1.* tox-battery==0.6.*
5 | - pyenv local 3.12.4 3.8.19
6 | - tox --notest
7 | cache_directories:
8 | - .tox
9 | test:
10 | override:
11 | - tox
12 | post:
13 | - bash <(curl -s https://codecov.io/bash)
14 |
--------------------------------------------------------------------------------
/.github/workflows/semgrep.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request: {}
3 | push:
4 | branches:
5 | - main
6 | - master
7 | name: Semgrep
8 | jobs:
9 | semgrep:
10 | name: Scan
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: returntocorp/semgrep-action@v1
15 | with:
16 | auditOn: push
17 | publishToken: ${{ secrets.SEMGREP_APP_TOKEN }}
18 | publishDeployment: 28
19 |
--------------------------------------------------------------------------------
/structlog_pretty/utils.py:
--------------------------------------------------------------------------------
1 | from xml.dom.minidom import Node
2 |
3 |
4 | def strip_minidom_whitespace(node):
5 | """Strips all whitespace from a minidom XML node and its children
6 |
7 | This operation is made in-place."""
8 | for child in node.childNodes:
9 | if child.nodeType == Node.TEXT_NODE:
10 | if child.nodeValue:
11 | child.nodeValue = child.nodeValue.strip()
12 | elif child.nodeType == Node.ELEMENT_NODE:
13 | strip_minidom_whitespace(child)
14 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | # jobs=0 means 'use all CPUs'
3 | jobs=0
4 |
5 | [MESSAGES CONTROL]
6 | disable =
7 | missing-docstring,
8 | line-too-long,
9 | invalid-name,
10 | no-value-for-parameter,
11 | no-member,
12 | unused-argument,
13 | broad-except,
14 | relative-import,
15 | wrong-import-position,
16 | bare-except,
17 | locally-disabled,
18 | protected-access,
19 | abstract-method,
20 | no-self-use,
21 | fixme,
22 | too-few-public-methods,
23 |
24 | [REPORTS]
25 | output-format=colorized
26 |
27 | [FORMAT]
28 | logging-modules=
29 | logging,
30 | structlog,
31 |
32 | [TYPECHECK]
33 | # pygments: sys.modules hacks causing false positives - https://github.com/PyCQA/pylint/issues/491
34 | ignored-modules=
35 | pygments.lexers,
36 | pygments.formatters,
37 |
--------------------------------------------------------------------------------
/test/test_PathPrettifier.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 | from structlog_pretty.processors import PathPrettifier as uut
5 |
6 |
7 | BASE_DIR = Path("/tmp")
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ["param", "expected"],
12 | [
13 | (Path("/tmp/foo.py"), "foo.py"),
14 | (Path("/tmp/dir/foo.py"), "dir/foo.py"),
15 | (Path("foo.py"), "foo.py"),
16 | (Path("/elsewhere/foo.py"), "/elsewhere/foo.py"),
17 | (1, 1),
18 | (None, None),
19 | ("/tmp/dir/foo.py", "/tmp/dir/foo.py"),
20 | ],
21 | )
22 | def test_run(param, expected):
23 | processor = uut(BASE_DIR)
24 | event_dict = processor(None, None, {"param": param})
25 | assert type(event_dict["param"]) == type(expected) # pylint: disable=unidiomatic-typecheck
26 | assert event_dict == {"param": expected}
27 |
--------------------------------------------------------------------------------
/test/test_MultilinePrinter.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 | import pytest
4 | from structlog_pretty.processors import MultilinePrinter as uut
5 |
6 | cases = ['', 'foo', 'foo\n', 'foo\n\n\n', 'foo\nbar']
7 |
8 |
9 | @pytest.mark.parametrize(['param'], [[case] for case in cases])
10 | def test_run(param):
11 | buffer = io.StringIO()
12 | processor = uut(fields=['param'], target=buffer)
13 | event_dict = processor(None, None, {'param': param})
14 | assert buffer.getvalue() == param
15 | assert event_dict == {}
16 |
17 |
18 | @pytest.mark.parametrize(['param'], [[case] for case in cases])
19 | def test_fields_setting(param):
20 | buffer = io.StringIO()
21 | processor = uut(fields=['not_the_param'], target=buffer)
22 | event_dict = processor(None, None, {'param': param})
23 | assert buffer.getvalue() == ''
24 | assert event_dict == {'param': param}
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.4.3 (2024-07-15)
4 |
5 | - Keep any trailing whitespace passed into SyntaxHighlighter
6 |
7 | ## 0.4.2 (2024-07-15)
8 |
9 | - Fix typing import unavailable on 3.8
10 | - Remove trailing newline added by SyntaxHighlighter
11 |
12 | ## 0.4.1 (2024-06-11)
13 |
14 | - Export PathPrettifier via top-level package
15 |
16 | ## 0.4.0 (2024-06-11)
17 |
18 | - Use orjson for fast JSON prettifying instead of rapidjsonX
19 | - Add PathPrettifier
20 |
21 | ## 0.3.0 (2020-07-08)
22 |
23 | - Fix SyntaxHighlighter - skip code highlighting when there is no code to highlight.
24 |
25 | ## 0.2.0 (2020-07-08)
26 |
27 | - Fix JSONPrettifier, XMLPrettifier - skip code coloring when there is no code to color.
28 |
29 | ## 0.1.1 (2016-12-15)
30 |
31 | - Fix NumericRounder converting booleans into floats
32 |
33 | ## 0.1.0.post1 (2016-11-21)
34 |
35 | No changes, just a re-release due to an issue with the build environment used for the 0.1.0 wheel.
36 |
37 | ## 0.1.0 (2016-11-20)
38 |
39 | Initial release.
40 |
--------------------------------------------------------------------------------
/.coafile:
--------------------------------------------------------------------------------
1 | [Default]
2 | files = **.(py|md|rst|yml), tox.ini, .coafile
3 | ignore = (.tox|env|.env|venv)/**
4 |
5 | indent_size = 4
6 | use_spaces = True
7 | max_line_length = 120
8 | max_lines_per_file = 1000
9 | file_naming_convention = snake
10 |
11 | [filenames]
12 | bears = FilenameBear
13 | files = structlog_pretty/**.py
14 |
15 | [lengths]
16 | bears = LineCountBear, LineLengthBear
17 |
18 | [spacing]
19 | ignore = (.tox|env|.env|venv)/**, **.yml, tox.ini, .coafile
20 | bears = SpaceConsistencyBear
21 |
22 | [config-spacing]
23 | files = **.yml, tox.ini, .coafile
24 | bears = SpaceConsistencyBear
25 | indent_size = 2
26 |
27 | [python-semantic]
28 | files = **.py
29 | bears = RadonBear, PyUnusedCodeBear
30 | language = python
31 |
32 | [yaml]
33 | files = **.(yml|yaml)
34 | bears = YAMLLintBear
35 |
36 | [restructuredtext]
37 | files = **.rst
38 | bears = reSTLintBear
39 |
40 | [commit]
41 | bears = GitCommitBear
42 | shortlog_length = 72
43 |
44 | [keywords]
45 | bears = KeywordBear
46 | keywords, ci_keywords = TODO, FIXME, pdb.set_trace() # Ignore KeywordBear
47 | cs_keywords =
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Bence Nagy (underyx)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import io
2 | from setuptools import setup
3 |
4 | with io.open("requirements.txt") as f:
5 | install_requires = f.read().splitlines()
6 |
7 | with io.open("test-requirements.txt") as f:
8 | tests_require = f.read().splitlines()
9 |
10 | with io.open("fast-requirements.txt") as f:
11 | fast_extra_requires = f.read().splitlines()
12 |
13 | with io.open("README.rst") as f:
14 | long_description = f.read()
15 |
16 | setup(
17 | name="structlog-pretty",
18 | version="0.4.3",
19 | url="https://github.com/underyx/structlog-pretty",
20 | author="Bence Nagy",
21 | author_email="bence@underyx.me",
22 | maintainer="Bence Nagy",
23 | maintainer_email="bence@underyx.me",
24 | download_url="https://github.com/underyx/structlog-pretty/releases",
25 | description="A collection of structlog processors for prettier output",
26 | long_description=long_description,
27 | packages=["structlog_pretty"],
28 | install_requires=install_requires,
29 | tests_require=tests_require,
30 | extras_require={"fast": fast_extra_requires},
31 | classifiers=[
32 | "Development Status :: 3 - Alpha",
33 | "License :: OSI Approved :: MIT License",
34 | "Operating System :: OS Independent",
35 | "Programming Language :: Python :: 3 :: Only",
36 | ],
37 | )
38 |
--------------------------------------------------------------------------------
/test/test_JSONPrettifier.py:
--------------------------------------------------------------------------------
1 | from textwrap import dedent
2 |
3 | import pytest
4 | from structlog_pretty.processors import JSONPrettifier as uut
5 |
6 |
7 | cases = [
8 | ('null', 'null'),
9 | ('{}', '{}'),
10 | ('{"key": "value"}', dedent('''
11 | {
12 | "key": "value"
13 | }
14 | ''').strip()),
15 | ('{"key": ["value", "value"]}', dedent('''
16 | {
17 | "key": [
18 | "value",
19 | "value"
20 | ]
21 | }
22 | ''').strip()),
23 | ]
24 | modes = ('slow', 'fast')
25 |
26 |
27 | @pytest.mark.parametrize(['mode', 'param', 'expected'], [
28 | [mode] + list(case) for mode in modes for case in cases
29 | ])
30 | def test_run(mode, param, expected, monkeypatch):
31 | monkeypatch.setattr('structlog_pretty.processors.fast_json_available', mode == 'fast')
32 | processor = uut(json_fields=['param'])
33 | event_dict = processor(None, None, {'param': param})
34 | assert event_dict == {'param': expected}
35 |
36 |
37 | @pytest.mark.parametrize(['mode', 'param', 'expected'], [
38 | (mode, case[0], case[0]) for mode in modes for case in cases
39 | ])
40 | def test_field_name_setting(mode, param, expected, monkeypatch):
41 | monkeypatch.setattr('structlog_pretty.processors.fast_json_available', mode == 'fast')
42 | processor = uut(json_fields=['not_the_param'])
43 | event_dict = processor(None, None, {'param': param})
44 | assert event_dict == {'param': expected}
45 |
--------------------------------------------------------------------------------
/test/test_SyntaxHighlighter.py:
--------------------------------------------------------------------------------
1 | import re
2 | from structlog_pretty.processors import SyntaxHighlighter as uut
3 |
4 |
5 | def test_json():
6 | processor = uut(field_map={"body": "json"})
7 | event_dict = processor(None, None, {"body": '{"ping": true}'})
8 | assert "\x1b[" in event_dict["body"], "should have at least one ANSI escape code"
9 | assert not event_dict["body"].endswith(
10 | "\n"
11 | ), "should not have trailing newline added"
12 |
13 |
14 | def test_retain_whitespace():
15 | processor = uut(field_map={"body": "json"})
16 | event_dict = processor(None, None, {"body": '{"ping": true}\n\n'})
17 | match = re.search(r"\s*$", event_dict["body"])
18 | assert match is not None
19 | assert match.group() == "\n\n"
20 |
21 |
22 | def test_missing_json():
23 | processor = uut(field_map={"body": "json"})
24 | event_dict = processor(None, None, {"not_body": '{"ping": true}'})
25 | assert event_dict["not_body"] == '{"ping": true}'
26 |
27 |
28 | def test_multiple_fields():
29 | processor = uut(field_map={"body": "json", "body_2": "json"})
30 | event_dict = processor(None, None, {"body": "null", "body_2": "null"})
31 | assert "\x1b[" in event_dict["body"], "should have at least one ANSI escape code"
32 | assert "\x1b[" in event_dict["body_2"], "should have at least one ANSI escape code"
33 |
34 |
35 | def test_multiple_languages():
36 | processor = uut(field_map={"body": "json", "body_2": "xml"})
37 | event_dict = processor(None, None, {"body": "null", "body_2": "