Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install python
uses: actions/setup-python@v2.2.2
with:
python-version: 3.9
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
Expand All @@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [2.7, 3.7, 3.8, 3.9]
python-version: [2.7, 3.7, 3.8, 3.9, "3.10"]
steps:
- uses: actions/checkout@v2
- name: Install python ${{matrix.python-version}}
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
- name: Install python
uses: actions/setup-python@v2.2.2
with:
python-version: 3.9
python-version: "3.7" # matches readthedocs.yml
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools twine
Expand Down
65 changes: 15 additions & 50 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,71 +13,36 @@ Usage

Configuration
^^^^^^^^^^^^^
The expectation is that you are using the ``logging.config.dictConfig`` function
somewhere to configure the Python logging module. This library exposes two functions
that return configuration dictionaries appropriate to services and command-line
applications:

.. code-block:: json
:get_cli_configuration:
Returns a configuration that generates human-readable logs

{
"version": 1,
"filters": {
"defaultsetter": {
"()": "jsonscribe.AttributeSetter",
"add_fields": {
"correlation_id": "ext://UUID"
}
}
},
"formatters": {
"jsonlines": {
"()": "jsonscribe.JSONFormatter",
"include_fields": [
"name",
"levelname",
"asctime",
"message",
"module",
"correlation_id",
"exc_info"
],
}
},
"handlers": {
"loggly": {
"class": "logging.StreamHandler",
"formatter": "jsonlines",
"filters": ["defaultsetter"],
"stream": "ext://sys.stdout"
}
},
"loggers": {
"somepackage": {
"level": "DEBUG",
}
},
"root": {
"level": "INFO",
"handlers": ["jsonlines"]
}
}
:get_service_configuration:
Returns a configuration that generates machine-readable logs

Logging
^^^^^^^
The following snippet is the simplest usage. It is nothing more than the
textbook usage of the logging module. It uses the logging configuration from
above and generates a JSON blob.
textbook usage of the logging module. It uses the logging configuration for
a service and generates a JSON blob.

.. code-block:: python

import logging.config
import json

import jsonscribe

if __name__ == '__main__':
config = json.load(open('config.json'))
logging.config.dictConfig(config)
logging.config.dictConfig(jsonscribe.get_service_configuration())
logger = logging.getLogger(__package__).getChild('main')
logger.info('processing request')

The JSON message looks something like the following. It is reformatted to
make it readable. The default is to render it as compact JSON.
The JSON message looks something like the following (reformatted to
make it readable). The default is to render it as compact JSON.

.. code-block:: json

Expand Down
38 changes: 36 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
Developer Interface
===================

Logging configuration
---------------------
The ``jsonscribe.config`` module contains functions that generate sane :func:`logging.config.dictConfig`
values for difference scenarios. Each of the configuration functions uses a template that you can modify before
calling the function (see :data:`~jsonscribe.config.CONFIGURATION_DEFAULTS`). The configuration functions
also obey the following environment variables if they are present.

.. envvar:: DEBUG

If this environment variable is set and it can be parsed as a boolean value and the result is *truthy*,
then the root log level will be :data:`logging.DEBUG` instead of the default value. See
:func:`~jsonscribe.utils.parse_boolean` for the list of acceptable values.

.. envvar:: LOG_FORMAT

If this environment variable is set, then it overrides the default formatter used by the configuration
functions. This can be useful if you want to run a service from a shell and want human-readable logs.

.. warning::

If ``LOG_FORMAT`` is set to a value that is not a configured formatter, then the configuration functions
will raise a :exc:`ValueError`.

.. autofunction:: jsonscribe.config.get_service_configuration

.. autofunction:: jsonscribe.config.get_cli_configuration

.. autodata:: jsonscribe.config.CONFIGURATION_DEFAULTS
:no-value:

Implementation classes
----------------------
.. autoclass:: jsonscribe.AttributeSetter
:members:


.. autoclass:: jsonscribe.JSONFormatter
:members:
:private-members:

Helpers
-------
.. autofunction:: jsonscribe.utils.parse_boolean

.. autoclass:: jsonscribe.utils.UTCZone
:members:

.. autodata:: jsonscribe.utils.utc

6 changes: 2 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
source_suffix = '.rst'
master_doc = 'index'
html_extra_path = ['../CODE_OF_CONDUCT.md']
html_static_path = ['.']
html_theme = 'alabaster'
html_sidebars = {'**': ['about.html', 'navigation.html', 'searchbox.html']}
extensions = []
html_theme = 'sphinx_rtd_theme'
extensions = ['sphinx_rtd_theme']

# see https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html
extensions.append('sphinx.ext.intersphinx')
Expand Down
3 changes: 3 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ Examples
.. literalinclude:: ../examples/simple.py
:linenos:

.. literalinclude:: ../examples/attributes.py
:linenos:

.. literalinclude:: ../examples/adapter.py
:linenos:
2 changes: 2 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Release History
- Integrate with readthedocs.org.
- Integrate with codecov.io.
- Adopt the Contributor Covenant.
- Add ``jsonscribe.config`` module.
- Advertise support for 3.10

2.0.0 (11-May-2021)
-------------------
Expand Down
23 changes: 13 additions & 10 deletions examples/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
since it is simply a usage of :class:`logging.LoggerAdapter`.

"""
import logging
import logging.config
import uuid

import jsonscribe
Expand All @@ -36,15 +36,18 @@ def process(self, message):


if __name__ == '__main__':
logging.basicConfig(
level=logging.INFO,
format=('%(levelname)1.1s %(name)s: %(message)s'
'\t{correlation_id=%(correlation_id)s}'))
root_logger = logging.getLogger()
handler = root_logger.handlers[0]
handler.addFilter(
jsonscribe.AttributeSetter(
add_fields={'correlation_id': 'ext://UUID'}))
config = jsonscribe.get_cli_configuration()
config['filters']['attr-setter'] = {
'()': 'jsonscribe.AttributeSetter',
'add_fields': {
'correlation_id': 'ext://UUID'
}
}
config['handlers']['console']['filters'].append('attr-setter')
config['formatters']['plain']['format'] = (
'%(levelname)1.1s %(name)s: %(message)s'
'\t{correlation_id=%(correlation_id)s}')
logging.config.dictConfig(config)

processor = Processor()
for i in range(0, 10):
Expand Down
36 changes: 36 additions & 0 deletions examples/attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Example of logging JSON lines with custom attributes.

This example uses :class:`jsonscribe.AttributeSetter` to create
custom attributes on log records and updates the configuration
to pass the new attribute along in the JSON log bodies.

"""
import logging.config

import jsonscribe


def failure():
logger = logging.getLogger('simple.failure')
try:
raise RuntimeError('failed')
except Exception:
logger.exception('something failed')


if __name__ == '__main__':
config = jsonscribe.get_service_configuration()
config['filters']['attr-setter'] = {
'()': 'jsonscribe.AttributeSetter',
'add_fields': {
'correlation_id': 'ext://UUID'
}
}
config['formatters']['json']['include_fields'].append('correlation_id')
config['handlers']['console']['filters'].append('attr-setter')

logging.config.dictConfig(config)
logger = logging.getLogger('simple')
logger.info('hi there')
failure()
23 changes: 3 additions & 20 deletions examples/simple.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
"""
Example of logging JSON lines.

This example uses :class:`jsonscribe.AttributeSetter` and
:class:`jsonscribe.JSONFormatter` to emit log lines as JSON objects.

"""
import logging
"""Example of logging JSON lines."""
import logging.config

import jsonscribe

Expand All @@ -19,18 +13,7 @@ def failure():


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
root_logger = logging.getLogger()
handler = root_logger.handlers[0]
handler.addFilter(
jsonscribe.AttributeSetter(
add_fields={'correlation_id': 'ext://UUID'}))
handler.setFormatter(
jsonscribe.JSONFormatter(include_fields=[
'name', 'levelname', 'asctime', 'message', 'module',
'correlation_id', 'exc_info'
]))

logging.config.dictConfig(jsonscribe.get_service_configuration())
logger = logging.getLogger('simple')
logger.info('hi there')
failure()
3 changes: 3 additions & 0 deletions jsonscribe/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from jsonscribe.config import get_cli_configuration, get_service_configuration
from jsonscribe.filters import AttributeSetter
from jsonscribe.formatters import JSONFormatter

Expand All @@ -7,6 +8,8 @@
__all__ = [
'AttributeSetter',
'JSONFormatter',
'get_cli_configuration',
'get_service_configuration',
'version',
'version_info',
]
Loading