├── .coafile ├── .github ├── FUNDING.yml └── workflows │ └── semgrep.yml ├── .gitignore ├── .pylintrc ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── circle.yml ├── fast-requirements.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── structlog_pretty ├── __init__.py ├── processors.py └── utils.py ├── test-requirements.txt ├── test ├── __init__.py ├── test_JSONPrettifier.py ├── test_MultilinePrinter.py ├── test_NumericRounder.py ├── test_PathPrettifier.py ├── test_SyntaxHighlighter.py └── test_XMLPrettifier.py └── tox.ini /.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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: underyx 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== Python ===##### 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | #####=== Vim ===##### 61 | [._]*.s[a-w][a-z] 62 | [._]s[a-w][a-z] 63 | *.un~ 64 | Session.vim 65 | .netrwhist 66 | *~ 67 | 68 | #####=== VirtualEnv ===##### 69 | # Virtualenv 70 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 71 | .Python 72 | [Bb]in 73 | [Ii]nclude 74 | [Ll]ib 75 | [Ss]cripts 76 | pyvenv.cfg 77 | pip-selfcheck.json 78 | 79 | #####=== OSX ===##### 80 | .DS_Store 81 | .AppleDouble 82 | .LSOverride 83 | 84 | # Icon must end with two \r 85 | Icon 86 | 87 | # Thumbnails 88 | ._* 89 | 90 | # Files that might appear on external disk 91 | .Spotlight-V100 92 | .Trashes 93 | 94 | # Directories potentially created on remote AFP share 95 | .AppleDB 96 | .AppleDesktop 97 | Network Trash Folder 98 | Temporary Items 99 | .apdisk 100 | 101 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include requirements.txt 3 | include fast-requirements.txt 4 | include test-requirements.txt 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | structlog-pretty 2 | ================ 3 | 4 | .. image:: https://circleci.com/gh/underyx/structlog-pretty.svg?style=shield 5 | :target: https://circleci.com/gh/underyx/structlog-pretty 6 | :alt: CI Status 7 | 8 | .. image:: https://codecov.io/gh/underyx/structlog-pretty/branch/master/graph/badge.svg 9 | :target: https://codecov.io/gh/underyx/structlog-pretty 10 | :alt: Code Coverage 11 | 12 | A collection of structlog_ processors for prettier output: a code syntax 13 | highlighter, JSON and XML prettifiers, a multiline string printer, and 14 | a numeric value rounder. 15 | 16 | Installation 17 | ------------ 18 | 19 | First of all, sorry, grandma, but ``structlog-pretty`` requires Python 3. 20 | 21 | You can just install the library with pip:: 22 | 23 | pip install structlog-pretty 24 | 25 | or, if you want faster prettifying processors:: 26 | 27 | pip install structlog-pretty[fast] 28 | 29 | The downside of the faster processors is that they will build C extensions and 30 | they need ``libxml`` to be installed. 31 | 32 | Usage 33 | ----- 34 | 35 | Add structlog-pretty processors to your structlog configuration 36 | 37 | .. code-block:: python 38 | 39 | import structlog 40 | import structlog_pretty 41 | 42 | structlog.configure( 43 | # ... 44 | processors=[ 45 | structlog.stdlib.add_log_level, 46 | structlog_pretty.NumericRounder(digits=2, only_fields=['timing']) 47 | structlog.processors.JSONRenderer(), 48 | ], 49 | ) 50 | 51 | A nice example of a processor pipeline for the *prettiest* logs could be 52 | 53 | .. code-block:: python 54 | 55 | processors=[ 56 | # ... 57 | structlog_pretty.JSONPrettifier(['request', 'response']), 58 | structlog_pretty.XMLPrettifier(['soap_response']), 59 | structlog_pretty.PathPrettifier(), 60 | structlog_pretty.SyntaxHighlighter({'request': 'json', 'response': 'json', 'soap_response': 'xml'}), 61 | structlog_pretty.MultilinePrinter(['request', 'response', 'soap_response']), 62 | # ... 63 | ], 64 | 65 | .. _structlog: https://github.com/hynek/structlog 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /fast-requirements.txt: -------------------------------------------------------------------------------- 1 | lxml==5.* 2 | orjson==3.* 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pygments==2.* 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /structlog_pretty/__init__.py: -------------------------------------------------------------------------------- 1 | from .processors import ( 2 | NumericRounder, 3 | JSONPrettifier, 4 | XMLPrettifier, 5 | SyntaxHighlighter, 6 | MultilinePrinter, 7 | PathPrettifier, 8 | ) 9 | -------------------------------------------------------------------------------- /structlog_pretty/processors.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, print_function 2 | 3 | from pathlib import Path 4 | import re 5 | import sys 6 | import json 7 | 8 | try: 9 | import orjson 10 | 11 | fast_json_available = True 12 | except ImportError: 13 | fast_json_available = False 14 | 15 | from typing import Optional 16 | from xml.dom.minidom import parseString as parse_xml_string 17 | 18 | try: 19 | from lxml import etree 20 | 21 | fast_xml_available = True 22 | except ImportError: 23 | fast_xml_available = False 24 | 25 | from pygments import highlight 26 | from pygments.lexers import get_lexer_by_name 27 | from pygments.formatters import TerminalFormatter 28 | 29 | from . import utils 30 | 31 | 32 | class NumericRounder(object): 33 | """A processor for rounding numbers in the event values 34 | 35 | For instance, ``1.162537216`` will be changed to ``1.163``. 36 | """ 37 | 38 | def __init__(self, digits=3, only_fields=None): 39 | """Create a processor that rounds numbers in the event values 40 | 41 | :param digits: The number of digits to round to 42 | :param only_fields: An iterable specifying the fields to round 43 | """ 44 | self.digits = digits 45 | try: 46 | self.only_fields = set(only_fields) 47 | except TypeError: 48 | self.only_fields = None 49 | 50 | def __call__(self, _, __, event_dict): 51 | for key, value in event_dict.items(): 52 | if self.only_fields is not None and key not in self.only_fields: 53 | continue 54 | if isinstance(value, bool): 55 | continue # don't convert True to 1.0 56 | 57 | try: 58 | event_dict[key] = round(value, self.digits) 59 | except TypeError: 60 | continue 61 | 62 | return event_dict 63 | 64 | 65 | class JSONPrettifier(object): 66 | """A processor for prettifying JSON strings 67 | 68 | For instance, ``{"numbers":[1,2]}`` will be changed to this:: 69 | 70 | { 71 | "numbers": [ 72 | 1, 73 | 2 74 | ] 75 | } 76 | """ 77 | 78 | def __init__(self, json_fields): 79 | """Create a processor that prettifies JSON strings in the event values 80 | 81 | :param json_fields: An iterable specifying the fields to prettify 82 | """ 83 | self.fields = json_fields 84 | self.prettify = ( 85 | self.fast_prettify if fast_json_available else self.slow_prettify 86 | ) 87 | 88 | @staticmethod 89 | def slow_prettify(code): 90 | return json.dumps(json.loads(code), indent=2) 91 | 92 | @staticmethod 93 | def fast_prettify(code): 94 | return orjson.dumps(orjson.loads(code), option=orjson.OPT_INDENT_2).decode() 95 | 96 | def __call__(self, _, __, event_dict): 97 | for field in self.fields: 98 | try: 99 | code = event_dict[field] 100 | except KeyError: 101 | continue 102 | if not code: 103 | continue 104 | event_dict[field] = self.prettify(code) 105 | 106 | return event_dict 107 | 108 | 109 | class XMLPrettifier(object): 110 | """A processor for prettifying XML strings 111 | 112 | For instance, ``