Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
laysakura committed Dec 3, 2013
0 parents commit 48b3438
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .gitignore
@@ -0,0 +1,19 @@
*~
*.pyc
core

bin/
dist/
lib/
man/
doc/
local/
include/
*.egg-info/
*.egg/

*.egg
.travis.yml.*
MANIFEST

htmlcov/
22 changes: 22 additions & 0 deletions .travis.yml
@@ -0,0 +1,22 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "pypy"

# command to install dependencies
install:
- "pip install ."
- "python setup.py test" # for installing `tests_require`
- "pip install nose-cov" # why `nose-cov` isn't correctlly installed by `setup.py test` ?

# command to run tests
script:
- "python setup.py nosetests"

branches:
only:
- master
- dev
7 changes: 7 additions & 0 deletions CHANGES.rst
@@ -0,0 +1,7 @@
Changelog for rainbow_logging_handler
=====================================

1.0.0 (unreleased)
------------------

- Nothing changed yet.
24 changes: 24 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
2 changes: 2 additions & 0 deletions MANIFEST.in
@@ -0,0 +1,2 @@
include LICENSE.txt
include CHANGES.rst
10 changes: 10 additions & 0 deletions README.rst
@@ -0,0 +1,10 @@
zest_test
=========

A framework to manage shell commands' inputs/outputs as relational data.

License
-------

This is free and unencumbered public domain software. For more information,
see <http://unlicense.org/> or the accompanying `UNLICENSE` file.
156 changes: 156 additions & 0 deletions rainbow_logging_handler/__init__.py
@@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
"""
Python logging tuned to extreme.
"""
import logging

from logutils.colorize import ColorizingStreamHandler


class RainbowLoggingHandler(ColorizingStreamHandler):
""" A colorful logging handler optimized for terminal debugging aestetichs.
- Designed for diagnosis and debug mode output - not for disk logs
- Highlight the content of logging message in more readable manner
- Show function and line, so you can trace where your logging messages
are coming from
- Keep timestamp compact
- Extra module/function output for traceability
The class provide few options as member variables you
would might want to customize after instiating the handler.
"""

# Define color for message payload
level_map = {
logging.DEBUG: (None, 'cyan', False),
logging.INFO: (None, 'white', False),
logging.WARNING: (None, 'yellow', True),
logging.ERROR: (None, 'red', True),
logging.CRITICAL: ('red', 'white', True),
}

date_format = "%H:%M:%S"

#: How many characters reserve to function name logging
who_padding = 22

#: Show logger name
show_name = True

def get_color(self, fg=None, bg=None, bold=False):
"""
Construct a terminal color code
:param fg: Symbolic name of foreground color
:param bg: Symbolic name of background color
:param bold: Brightness bit
"""
params = []
if bg in self.color_map:
params.append(str(self.color_map[bg] + 40))
if fg in self.color_map:
params.append(str(self.color_map[fg] + 30))
if bold:
params.append('1')

color_code = ''.join((self.csi, ';'.join(params), 'm'))

return color_code

def colorize(self, record):
"""
Get a special format string with ASCII color codes.
"""

# Dynamic message color based on logging level
if record.levelno in self.level_map:
fg, bg, bold = self.level_map[record.levelno]
else:
# Defaults
bg = None
fg = "white"
bold = False

# Magician's hat
# https://www.youtube.com/watch?v=1HRa4X07jdE
template = [
"[",
self.get_color("black", None, True),
"%(asctime)s",
self.reset,
"] ",
self.get_color("white", None, True) if self.show_name else "",
"%(name)s " if self.show_name else "",
"%(padded_who)s",
self.reset,
" ",
self.get_color(bg, fg, bold),
"%(message)s",
self.reset,
]

format = "".join(template)

who = [self.get_color("green"),
getattr(record, "funcName", ""),
"()",
self.get_color("black", None, True),
":",
self.get_color("cyan"),
str(getattr(record, "lineno", 0))]

who = "".join(who)

# We need to calculate padding length manualy
# as color codes mess up string length based calcs
unformatted_who = getattr(record, "funcName", "") + "()" + \
":" + str(getattr(record, "lineno", 0))

if len(unformatted_who) < self.who_padding:
spaces = " " * (self.who_padding - len(unformatted_who))
else:
spaces = ""

record.padded_who = who + spaces

formatter = logging.Formatter(format, self.date_format)
self.colorize_traceback(formatter, record)
output = formatter.format(record)
# Clean cache so the color codes of traceback don't leak to other formatters
record.ext_text = None
return output

def colorize_traceback(self, formatter, record):
"""
Turn traceback text to red.
"""
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
record.exc_text = "".join([
self.get_color("red"),
formatter.formatException(record.exc_info),
self.reset,
])

def format(self, record):
"""
Formats a record for output.
Takes a custom formatting path on a terminal.
"""
if self.is_tty:
message = self.colorize(record)
else:
message = logging.StreamHandler.format(self, record)

return message
27 changes: 27 additions & 0 deletions rainbow_logging_handler/test/test_logging.py
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from nose.tools import *
import sys
import logging
from rainbow_logging_handler import RainbowLoggingHandler


def test_usage():
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)

handler = RainbowLoggingHandler(sys.stderr)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
root_logger.addHandler(handler)
logger = logging.getLogger('test')

logger.debug("debug msg")
logger.info("info msg")
logger.warn("warn msg")
logger.error("error msg")
logger.critical("critical msg")

try:
raise RuntimeError("Opa!")
except Exception as e:
logger.exception(e)
36 changes: 36 additions & 0 deletions setup.py
@@ -0,0 +1,36 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup


setup(
name = 'rainbow_logging_handler',
description = 'Ultimate Python colorized logger',
long_description = open('README.rst').read(),
url = 'https://github.com/laysakura/rainbow_logging_handler',
license = 'LICENSE.txt',
version = '1.0.0',
author = 'Mikko Ohtamaa, Sho Nakatani',
author_email = 'mikko@opensourcehacker.com, lay.sakura@gmail.com',
install_requires = [
'logutils',
],
tests_require = [
'nose',
],
packages = [
'rainbow_logging_handler',
'rainbow_logging_handler.test',
],
scripts = [
],
classifiers = '''
Programming Language :: Python
Development Status :: 5 - Production/Stable
License :: Public Domain
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
Programming Language :: Python :: Implementation :: PyPy
Operating System :: POSIX :: Linux
'''.strip().splitlines()
)

0 comments on commit 48b3438

Please sign in to comment.