diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3de1f01..04d0022 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,20 +1,14 @@ name: Test python-json-logger -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - - workflow_dispatch: +on: [push, pull_request, workflow_dispatch] jobs: - build: + test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: [pypy3, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10.0-beta.4] + python-version: [pypy-3.8, 3.6, 3.7, 3.8, 3.9, '3.10'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1253304..9f69af2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - test: + publish: runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index d9743fc..c6fb4d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.3] - 2022-07-08 +### Added +- Add PEP 561 marker/basic mypy configuration. - @bringhurst +- Workaround logging.LogRecord.msg type of string. - @bringhurst +### Changed +- Changed a link archive of the reference page in case it's down. - @ahonnecke +- Removed unnecessary try-except around OrderedDict usage - @sozofaan +- Update documentation link to json module + use https - @deronnax +- Dropped 3.5 support. - @bringhurst + ## [2.0.2] - 2021-07-27 ### Added - Officially supporting 3.9 - @felixonmars. @@ -36,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 'stack_info' flag in logging calls is now respected in JsonFormatter by [@ghShu](https://github.com/ghShu) +[2.0.3]: https://github.com/madzak/python-json-logger/compare/v2.0.2...v2.0.3 [2.0.2]: https://github.com/madzak/python-json-logger/compare/v2.0.1...v2.0.2 [2.0.1]: https://github.com/madzak/python-json-logger/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/madzak/python-json-logger/compare/v0.1.11...v2.0.0 diff --git a/README.md b/README.md index 2346b22..80ae309 100644 --- a/README.md +++ b/README.md @@ -170,3 +170,5 @@ External Examples ================= - [Wesley Tanaka - Structured log files in Python using python-json-logger](https://wtanaka.com/node/8201) + +- [Archive](https://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201) diff --git a/setup.cfg b/setup.cfg index af27e21..6c12f72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,52 @@ # 3. If at all possible, it is good practice to do this. If you cannot, you # will need to generate wheels for each Python version that you support. python-tag=py3 + +[mypy] + +# For details on each flag, please see the mypy documentation at: +# https://mypy.readthedocs.io/en/stable/config_file.html#config-file + +# Import Discovery +mypy_path = src +namespace_packages = true + +# Disallow dynamic typing +disallow_any_unimported = true +disallow_any_expr = false +disallow_any_decorated = true +disallow_any_explicit = false +disallow_any_generics = false +disallow_subclassing_any = true + +# Untyped definitions and calls +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true + +# None and Optional handling +no_implicit_optional = true + +# Configuring warnings +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_return_any = true +warn_unreachable = true + +# Miscellaneous strictness flags +implicit_reexport = true +strict_equality = true + +# Configuring error messages +show_error_context = true +show_column_numbers = true +show_error_codes = true +pretty = true +show_absolute_path = true + +# Miscellaneous +warn_unused_configs = true +verbosity = 0 diff --git a/setup.py b/setup.py index bfb9537..de584dd 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="python-json-logger", - version="2.0.2", + version="2.0.3", url="http://github.com/madzak/python-json-logger", license="BSD", include_package_data=True, @@ -23,7 +23,7 @@ python_requires='>=3.5', test_suite="tests.tests", classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', diff --git a/src/pythonjsonlogger/jsonlogger.py b/src/pythonjsonlogger/jsonlogger.py index 2bddf52..35ce850 100644 --- a/src/pythonjsonlogger/jsonlogger.py +++ b/src/pythonjsonlogger/jsonlogger.py @@ -9,20 +9,22 @@ import traceback import importlib +from typing import Any, Dict, Union, List, Tuple + from inspect import istraceback from collections import OrderedDict # skip natural LogRecord attributes # http://docs.python.org/library/logging.html#logrecord-attributes -RESERVED_ATTRS = ( +RESERVED_ATTRS: Tuple[str, ...] = ( 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'module', 'msecs', 'message', 'msg', 'name', 'pathname', 'process', 'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName') -def merge_record_extra(record, target, reserved): +def merge_record_extra(record: logging.LogRecord, target: Dict, reserved: Union[Dict, List]) -> Dict: """ Merges extra attributes from LogRecord object into target dictionary @@ -80,7 +82,7 @@ class JsonFormatter(logging.Formatter): def __init__(self, *args, **kwargs): """ :param json_default: a function for encoding non-standard objects - as outlined in http://docs.python.org/2/library/json.html + as outlined in https://docs.python.org/3/library/json.html :param json_encoder: optional custom encoder :param json_serializer: a :meth:`json.dumps`-compatible callable that will be used to serialize the log record. @@ -138,17 +140,30 @@ def _str_to_fn(self, fn_as_str): module = importlib.import_module(path) return getattr(module, function) - def parse(self): + def parse(self) -> List[str]: """ Parses format string looking for substitutions This method is responsible for returning a list of fields (as strings) to include in all log messages. - """ - standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE) - return standard_formatters.findall(self._fmt) + """ + if isinstance(self._style, logging.StringTemplateStyle): + formatter_style_pattern = re.compile(r'\$\{(.+?)\}', re.IGNORECASE) + elif isinstance(self._style, logging.StrFormatStyle): + formatter_style_pattern = re.compile(r'\{(.+?)\}', re.IGNORECASE) + # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle so + # it needs to be checked last. + elif isinstance(self._style, logging.PercentStyle): + formatter_style_pattern = re.compile(r'%\((.+?)\)s', re.IGNORECASE) + else: + raise ValueError('Invalid format: %s' % self._fmt) - def add_fields(self, log_record, record, message_dict): + if self._fmt: + return formatter_style_pattern.findall(self._fmt) + else: + return [] + + def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: Dict[str, Any]) -> None: """ Override this method to implement custom logic for adding fields. """ @@ -180,15 +195,17 @@ def jsonify_log_record(self, log_record): indent=self.json_indent, ensure_ascii=self.json_ensure_ascii) - def serialize_log_record(self, log_record): + def serialize_log_record(self, log_record: Dict[str, Any]) -> str: """Returns the final representation of the log record.""" return "%s%s" % (self.prefix, self.jsonify_log_record(log_record)) - def format(self, record): + def format(self, record: logging.LogRecord) -> str: """Formats a log record and serializes to json""" - message_dict = {} - if isinstance(record.msg, dict): - message_dict = record.msg + message_dict: Dict[str, Any] = {} + # FIXME: logging.LogRecord.msg and logging.LogRecord.message in typeshed + # are always type of str. We shouldn't need to override that. + if isinstance(record.msg, dict): # type: ignore + message_dict = record.msg # type: ignore record.message = None else: record.message = record.getMessage() @@ -211,11 +228,8 @@ def format(self, record): # Python2.7 doesn't have stack_info. pass - try: - log_record = OrderedDict() - except NameError: - log_record = {} - + log_record: Dict[str, Any] + log_record = OrderedDict() self.add_fields(log_record, record, message_dict) log_record = self.process_log_record(log_record) diff --git a/src/pythonjsonlogger/py.typed b/src/pythonjsonlogger/py.typed new file mode 100644 index 0000000..89afa56 --- /dev/null +++ b/src/pythonjsonlogger/py.typed @@ -0,0 +1 @@ +# PEP-561 marker. https://mypy.readthedocs.io/en/latest/installed_packages.html diff --git a/tox.ini b/tox.ini index 7eb1076..dc5078f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] -envlist = pypy3, py35, py36, py37, py38, py39, py310 +envlist = pypy38, py36, py37, py38, py39, py310 [gh-actions] python = - pypy-3: pypy3 - 3.5: py35 + pypy-3.8: pypy38 3.6: py36 3.7: py37 3.8: py38 @@ -12,4 +11,8 @@ python = 3.10: py310 [testenv] -commands = python -m unittest discover +deps = + mypy +commands = + python -m unittest discover + mypy src