pax_global_header00006660000000000000000000000064132654024650014521gustar00rootroot0000000000000052 comment=f5f3f7427cc98b3a39be820020ce833549d96e45 prettylog-0.1.0/000077500000000000000000000000001326540246500135505ustar00rootroot00000000000000prettylog-0.1.0/.coveragerc000066400000000000000000000000241326540246500156650ustar00rootroot00000000000000[run] branch = True prettylog-0.1.0/.gitignore000066400000000000000000000037341326540246500155470ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### VirtualEnv template # Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python [Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json ### IPythonNotebook template # Temporary data .ipynb_checkpoints/ ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ docs/source/apidoc # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea/ ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties /htmlcov /temp .DS_Store /*/version.py /.pytest_cacheprettylog-0.1.0/.travis.yml000066400000000000000000000004101326540246500156540ustar00rootroot00000000000000language: python matrix: include: - python: 3.4 env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 install: - pip install tox coveralls codecov script: - tox after_success: - codecov - coveralls prettylog-0.1.0/pylama.ini000066400000000000000000000002101326540246500155250ustar00rootroot00000000000000[pylama] ignore=C901,E0603,E252,E402,E711,E712,E722,W0401 [pylama:pycodestyle] max_line_length = 80 [pylama:tests/_*.py] ignore=E0100 prettylog-0.1.0/pytest.ini000066400000000000000000000002171326540246500156010ustar00rootroot00000000000000[pytest] addopts = --cov prettylog --cov-report=term-missing --doctest-modules --pylama src --pylama testsprettylog-0.1.0/setup.py000066400000000000000000000024021326540246500152600ustar00rootroot00000000000000from setuptools import setup setup( name='prettylog', version='0.1.0', platforms="all", author="Dmitry Orlov", author_email="me@mosquito.su", maintainer="Dmitry Orlov", maintainer_email="me@mosquito.su", description="Let's write beautiful logs", package_dir={'': 'src'}, packages=[''], license="Apache 2", classifiers=[ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3 :: Only', ], install_requires=[ 'colorlog', 'ujson<1.40', ], extras_require={ 'develop': [ 'coverage!=4.3', 'pylama', 'pytest', 'pytest-cov', 'timeout-decorator', 'tox>=2.4', ], }, ) prettylog-0.1.0/src/000077500000000000000000000000001326540246500143375ustar00rootroot00000000000000prettylog-0.1.0/src/prettylog.py000066400000000000000000000111171326540246500167430ustar00rootroot00000000000000import logging import logging.handlers import sys import traceback import ujson from logging.handlers import MemoryHandler from enum import IntEnum from types import MappingProxyType from typing import Union from colorlog import ColoredFormatter class LogFormat(IntEnum): stream = 0 color = 1 json = 2 syslog = 3 @classmethod def choices(cls): return tuple(cls._member_names_) class JSONLogFormatter(logging.Formatter): LEVELS = MappingProxyType({ logging.CRITICAL: "crit", logging.FATAL: "fatal", logging.ERROR: "error", logging.WARNING: "warn", logging.WARN: "warn", logging.INFO: "info", logging.DEBUG: "debug", logging.NOTSET: None, }) FIELD_MAPPING = MappingProxyType({ 'filename': ('code_file', str), 'funcName': ('code_func', str), 'lineno': ('code_line', int), 'module': ('code_module', str), 'name': ('identifier', str), 'msg': ('message_raw', str), 'process': ('pid', int), 'processName': ('process_name', str), 'threadName': ('thread_name', str), }) def format(self, record): record_dict = MappingProxyType(record.__dict__) data = dict(errno=0 if not record.exc_info else 255) for key, value in self.FIELD_MAPPING.items(): mapping, field_type = value v = record_dict.get(key) if not isinstance(v, field_type): v = field_type(v) data[mapping] = v for key in record_dict: if key in data: continue elif key[0] == "_": continue value = record_dict[key] if value is None: continue data[key] = value for idx, item in enumerate(data.pop('args', [])): data['argument_%d' % idx] = str(item) payload = { '@fields': data, 'msg': record.getMessage(), 'level': self.LEVELS[record.levelno] } if record.exc_info: payload['stackTrace'] = "\n".join( traceback.format_exception(*record.exc_info) ) json_string = ujson.dumps( payload, ensure_ascii=False, escape_forward_slashes=False, ) return json_string def json_formatter(stream=None): stream = stream or sys.stdout formatter = JSONLogFormatter() handler = logging.StreamHandler(stream) handler.setFormatter(formatter) return handler def color_formatter(stream=None): stream = stream or sys.stderr handler = logging.StreamHandler(stream) handler.setFormatter(ColoredFormatter( "%(blue)s[T:%(threadName)s]%(reset)s " "%(log_color)s%(levelname)s:%(name)s%(reset)s: " "%(message_log_color)s%(message)s", datefmt=None, reset=True, log_colors={ 'DEBUG': 'cyan', 'INFO': 'green', 'WARNING': 'yellow', 'ERROR': 'red', 'CRITICAL': 'red,bg_white', }, secondary_log_colors={ 'message': { 'WARNING': 'bold', 'ERROR': 'bold', 'CRITICAL': 'bold', }, }, style='%' )) return handler def create_logging_handler(log_format: LogFormat=LogFormat.color): if log_format == LogFormat.stream: return logging.StreamHandler() elif log_format == LogFormat.json: return json_formatter() elif log_format == LogFormat.color: return color_formatter() elif log_format == LogFormat.syslog: return logging.handlers.SysLogHandler(address='/dev/log') raise NotImplementedError def wrap_logging_handler(handler: logging.Handler, buffer_size: int = 1024, logger_class=MemoryHandler) -> logging.Handler: buffered_handler = logger_class(buffer_size, target=handler) return buffered_handler def basic_config(level: int=logging.INFO, log_format: Union[str, LogFormat]=LogFormat.color, buffered=True, buffer_size: int=1024): if isinstance(level, str): level = getattr(logging, level.upper()) logging.basicConfig() logger = logging.getLogger() logger.handlers.clear() if isinstance(log_format, str): log_format = LogFormat[log_format] handler = create_logging_handler(log_format) if buffered: handler = wrap_logging_handler( handler, buffer_size=buffer_size, ) logging.basicConfig( level=level, handlers=[handler] ) prettylog-0.1.0/tests/000077500000000000000000000000001326540246500147125ustar00rootroot00000000000000prettylog-0.1.0/tests/__init__.py000066400000000000000000000000001326540246500170110ustar00rootroot00000000000000prettylog-0.1.0/tests/test_simple.py000066400000000000000000000033721326540246500176210ustar00rootroot00000000000000import json import logging import uuid import pytest from prettylog import basic_config, LogFormat, create_logging_handler def test_choices(): choices = LogFormat.choices() assert isinstance(choices, tuple) for i in ('stream', 'color', 'json', 'syslog'): assert i in choices def test_configure_logging_json(capsys): capsys.readouterr() data = str(uuid.uuid4()) basic_config(level='debug', log_format='json', buffered=False) logging.info(data) stdout, stderr = capsys.readouterr() json_result = json.loads(stdout.strip()) assert json_result['msg'] == data logging.basicConfig(handlers=[], level=logging.INFO) def test_configure_logging_stderr(capsys): data = str(uuid.uuid4()) basic_config(level=logging.DEBUG, log_format='stream', buffered=False) logging.info(data) stdout, stderr = capsys.readouterr() assert data in stderr logging.basicConfig(handlers=[]) @pytest.mark.parametrize('fmt', LogFormat.choices()) def test_formats(fmt): basic_config(level='debug', log_format=fmt, buffered=False) logging.error("test") logging.info("foo %r", None) logging.debug({"foo": [None, 1]}) try: raise Exception except: logging.exception("Error") def test_invalid_handler(): with pytest.raises(NotImplementedError): create_logging_handler('example.com') def test_buferred(capsys): capsys.readouterr() data = str(uuid.uuid4()) basic_config(level='debug', log_format='json', buffered=True, buffer_size=10) logging.info("0 %r", data) stdout, stderr = capsys.readouterr() assert not stdout logging.getLogger().handlers[0].flush() stdout, stderr = capsys.readouterr() assert data in stdout prettylog-0.1.0/tox.ini000066400000000000000000000001241326540246500150600ustar00rootroot00000000000000[tox] envlist = py3{4,5,6} [testenv] extras = develop commands = pytest tests