Ultimate Python colorized logger: Somewhere over the rainbow

To make logging based debugging and diagnostics more fun, I created the following enhanced Python logging solution. It colorizes and adjusts logging output so that one can work more efficiently with long log transcripts in a local terminal. It’s based on logutils package by Vinay Sajip.

Screen Shot 2013-03-14 at 6.04.36 AM

What it does

  • Targets debugging and diagnostics use cases, not log file writing use cases
  • Detects if you are running a real terminal or logging to a file stream
  • Set ups a custom log handler which colorizes parts of the messages differently
  • Works on UNIX or Windows

Example of a longer terminal logging transcript which does a lot of output in logging.DEBUG level:

Screen Shot 2013-03-14 at 6.10.39 AM

The source code and usage examples below.

1. Further ideas

How to make your life even more easier in Python logging

  • Make this to a proper package or merge with logutils
  • Use terminal info to indent message lines properly, so that 2+ lines indent themselves below the starting line and don’t break the column structure (how to get terminal width in Python?)
  • Make Python logging to store full qualified name to the function, instead of just module/func pair which is useless in bigger projects
  • Make tracebacks clickable in iTerm 2. iTerm 2 link click handler has regex support, but does not know about Python tracebacks and would need patch in iTerm 2

Please post your own ideas 🙂

2. The code

Source code (also on Gist) – for source code colored version see the original blog post:

# -*- coding: utf-8 -*-
"""

    Python logging tuned to extreme.

"""

__author__ = "Mikko Ohtamaa <mikko@opensourcehacker.com>"
__license__ = "MIT"

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

if __name__ == "__main__":
    # Run test output on stdout
    import sys

    root_logger = logging.getLogger()
    root_logger.setLevel(logging.DEBUG)

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

    def test_func():
        logger.debug("debug msg")
        logger.info("info msg")
        logger.warn("warn msg")

    def test_func_2():
        logger.error("error msg")
        logger.critical("critical msg")

        try:
            raise RuntimeError("Opa!")
        except Exception as e:
            logger.exception(e)

    test_func()
    test_func_2()

\"\" Subscribe to RSS feed Image Follow me on Twitter Image Follow me on Facebook Image Follow me Google+

Varnish shell singleliners: reload config, purge cache and test hits

Varnish is a server-side caching daemon. On our production server, Varnish listens to port HTTP 80 and serves at the production server front end cache; we use it mainly to serve JS, CSS and static images blazingly fast.

This blog post is based on default Varnish Ubuntu / Debian installation using apt-get. These instructions were tested on Ubuntu 12.04 LTS and Varnish 3.0.2.

1. Reload edited Varnish configs

Varnish caching rules live in /etc/varnish folder. The config entry point (main file) is /etc/varnish/default.vcl. The daemon itself (ports, etc.) is configured by /etc/defaults/varnish.

Varnish is controlled by an utility program varnishadm.You can use it in a console mode or issue direct command evaluations (think shell, MySQL client). On Ubuntu / Debian default installation varnishadm command as is is enough to control Varnish. However, on custom setup, you might need to guide it to a special console port or point it to a secret file.

Varnish config load is 2 stage process:

  • Parse and load cfg file to a Varnish memory and give it a handle you can later refer to it
  • Activate config by handle (only possible if step 1 success)

Below is an one liner shell script which generates a random handle and uses it to load the config if the config parses successfully.

HANDLE=varnish-cfg-$RANDOM ; \
  varnishadm vcl.load $HANDLE /etc/varnish/default.vcl && \
  varnishadm vcl.use $HANDLE

2. Purging Varnish cache from command line

Another useful snippet is to purge all Varnish cache from command line (invalidate all the cache):

varnishadm "ban.url ."  # Matches all URLs

Note: Command is purge.url in Varnish 2.x.

The cache is kept as shared memorymapped file in /var/lib/varnish/$INSTANCE/varnish_storage.bin. When Varnish is running it should map 1 GB (default) of your virtual memory to this file (as seen in ps, top).

You could also ban by a hostname:

varnishadm "ban req.http.host == opensourcehacker.com"

Here is a shell transcript where we observe that ban works as intended using wget utility.

# Go to /tmp because wget leaves files around
cd /tmp

# 1st load: uncached file, one X-Varnish stamp
wget -S http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg

--2013-02-06 20:02:18--  http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg
Resolving opensourcehacker.com (opensourcehacker.com)... 188.40.123.220
Connecting to opensourcehacker.com (opensourcehacker.com)|188.40.123.220|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Server: Apache/2.2.22 (Ubuntu)
  Last-Modified: Sun, 14 Aug 2011 22:55:01 GMT
  ETag: "2000893-108ec-4aa7f09555b40"
  Cache-Control: max-age=3600
  Expires: Wed, 06 Feb 2013 23:02:19 GMT
  Content-Type: image/jpeg
  Content-Length: 67820
  Accept-Ranges: bytes
  Date: Wed, 06 Feb 2013 22:02:19 GMT
  X-Varnish: 705602514

# 2st load: cached file, two X-Varnish stamps
wget -S http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg
--2013-02-06 20:02:21--  http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg
Resolving opensourcehacker.com (opensourcehacker.com)... 188.40.123.220
Connecting to opensourcehacker.com (opensourcehacker.com)|188.40.123.220|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Server: Apache/2.2.22 (Ubuntu)
  Last-Modified: Sun, 14 Aug 2011 22:55:01 GMT
  ETag: "2000893-108ec-4aa7f09555b40"
  Cache-Control: max-age=3600
  Expires: Wed, 06 Feb 2013 23:02:19 GMT
  Content-Type: image/jpeg
  Content-Length: 67820
  Accept-Ranges: bytes
  Date: Wed, 06 Feb 2013 22:02:22 GMT
  X-Varnish: 705602515 705602514

# Purge
varnishadm "ban.url ."

# It's non-cached again
wget -S http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg
--2013-02-06 20:02:34--  http://opensourcehacker.com/wp-content/uploads/2011/08/Untitled-41.jpg
Resolving opensourcehacker.com (opensourcehacker.com)... 188.40.123.220
Connecting to opensourcehacker.com (opensourcehacker.com)|188.40.123.220|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Server: Apache/2.2.22 (Ubuntu)
  Last-Modified: Sun, 14 Aug 2011 22:55:01 GMT
  ETag: "2000893-108ec-4aa7f09555b40"
  Cache-Control: max-age=3600
  Expires: Wed, 06 Feb 2013 23:02:35 GMT
  Content-Type: image/jpeg
  Content-Length: 67820
  Accept-Ranges: bytes
  Date: Wed, 06 Feb 2013 22:02:35 GMT
  X-Varnish: 705602516

3. Restart Varnish on Ubuntu

This forces config flush, not sure about whether cache file storage gets reset(?).

service varnish restart

4. Further ideas

If someone knowns where to get Varnish VCL syntax highlighter for Sublime Text 2 (TextMate) that would make my life easier, used in the combination SFTP plug-in.

 

\"\" Subscribe to RSS feed Image Follow me on Twitter Image Follow me on Facebook Image Follow me Google+

Sendmail using Nullmailer and Gmail account on Linux server

When you run VPS servers or other “low end” boxes it is common scenario need to setup a outgoing email mail transfer agent to get reports from cron jobs or enable PHP scripts to send email.

Linux offers some really heavy weight options for sending email, including Sendmail, Postfix and Exim4. However, if you are a low end box user, you run low end services of which requirements are likely to send one email per month from your puny cron job or web contact form. This makes the options above

  • Plain scary
  • Very difficult to configure, maintain, due to extra complexity not necessary for your use case
  • Open some attack surface
  • You still need a master SMTP where you push forward your email messages

They don’t scale down.

Meet Nullmailer. Nullmailer is a simple MTA (mail transfer agent) and sendmail command provider which simply dumps your email output forward to SMTP service.

But that’s not all. If you are using low end boxes you probably don’t need good SMTP service from your VPS provider  (spam makes them expensive to provide). So, here is the trick: you are probably using already a free web email provider like gmail, Hotmail or whatever happens to be the dominating internet company of the day. Nullmailer can be configured to use authenticated SMTP sending through any email provider quite easily.

Below is a setup script for Ubuntu / Debian (tested on Ubuntu 12.04 LTS) which setups xinetd tunnel to connect to GMail’s smtp.google.com and send emails as your authenticated Gmail user. The orignal idea is described in this blog post by Jon Spriggs.

The downside of this hacky solution include

  • No real email From addresses: all email is aliased to your authentcated SMTP user
  • SMTP user password is stored on the server as plain text, so you probably want to create a rogue gmail user for this
  • No locally buffered email (not sure about this with Nullmailer?) – if SMTP acts strangely be prepared for major screw up
  • Using sendmail command with this solution may cause delays and other issues in the script (not sure how timeouting is handled with Nullmailer?)

The script is also available on Github, hosted inside ZtaneSH project. Please contribute your changes back on Github in a case you come across of improvements.

Nothing else is needed, except running the script as instructed.

#!/bin/bash
#
# Setup nullmailer on Ubuntu using your Gmail account as SMTP
# - you get a working sendmail command without requiring to setup complex SMTP
# stuff. Mostly useful with cron scripts.
#
# Orignal script based on https://jon.sprig.gs/blog/post/9
#

# Abort script if any command fails
set -e

#
# Create a gmail SSL wrapper script
#

if [ -z "$GMAIL_USER" ] || [ -z "$GMAIL_PASSWORD" ] || [ -z "$TEST_ADDRESS" ] ; then
    echo "Setup sendmail via gmail proxy on your Ubuntu box"
    echo "Usage:"
    echo "GMAIL_USER=foobar@gmail.com GMAIL_PASSWORD=12312312 TEST_ADDRESS=youremail@example.com sh setup-nullmailer.sh"
    exit 1
fi

# install required software
sudo apt-get install -y openssl xinetd nullmailer

sudo tee /usr/bin/gmail-smtp <<EOF >/dev/null
#!/bin/sh
/usr/bin/openssl s_client -connect smtp.gmail.com:465 -quiet 2>/dev/null
EOF
sudo chmod +x /usr/bin/gmail-smtp

#
# Create xinetd.d entry which wraps SMTP traffic to port 10025 go
# go to gmail
#

sudo tee /etc/xinetd.d/gmail-smtp <<EOF >/dev/null
# default: on
# description: Gmail SMTP wrapper for clients without SSL support
# Thanks to http://ubuntuforums.org/showthread.php?t=918335 for this install guide
service gmail-smtp
{
    disable         = no
    bind            = localhost
    port            = 10025
    socket_type     = stream
    protocol        = tcp
    wait            = no
    user            = root
    server          = /usr/bin/gmail-smtp
    type            = unlisted
}
EOF
sudo /etc/init.d/xinetd reload

#
# Set nullmail to use xinetd
#
sudo tee /etc/nullmailer/remotes <<EOF >/dev/null
127.0.0.1 smtp --port=10025 --user=$GMAIL_USER --pass=$GMAIL_PASSWORD
EOF
sudo /etc/init.d/nullmailer reload

# send test email
echo "This is a test message from ${USER}@${HOSTNAME} at $(date)" | sendmail $TEST_ADDRESS

echo "Test mail send to $TEST_ADDRESS"

\"\" Subscribe to RSS feed Image Follow me on Twitter Image Follow me on Facebook Image Follow me Google+

Sevabot – Skype bot with UNIX scripting and HTTP webhooks integration

Skype is the most popular IM client with over 200 million monthly users. It’s easy and reliable: anyone can use it anywhere, even on mobile. When you are working with virtual teams, Skype group chats become vital tool of communication.

Skype lacks good built-in support for external service messages, or robots, sending and receiving automated messages. Sevabot, a friendly Skypebot, addresses this situation by allowing you to script Skype easily, and even remotely, over simple HTTP interface and with any UNIX scripting language. You can even send Skype messages to your group chat directly from a web browser Javascript.

Image

Sevabot loves you.

1. Use cases

Sevabot is geared to be an AI support person for a virtual development and operations teams.

  • Receive development status information to Skype chat (continuous integration like Jenkins, Travis, Github and Subversion commits)
  • Get operational alerts like overloaded servers, service down (e.g. Zabbix monitoring alerts)
  • Add your own group chat commands to make Sevabot perform automatized tasks for you (you can write scripts in *any* UNIX supported programming language)

Image

The scope of Sevabot does not need to be limited to software development: You could, e.g., make Sevabot interact with Google Docs or Salesforce.

2. Installation

Sevabot can be run on Linux server, Linux desktop and OSX desktop. Due to how Skype is built you need to run a Skype GUI client inside a virtual X server to deploy Skype on a production server. Vagrant automatized server configuration is supported for automatically creating and deploying a virtual machine running Sevabot.

Sevabot can be run Windows, in theory, but the authors have not had inspiration to explore this cunning option.

3. Programming

Sevabot is written in Python. Scripting Sevabot does not, however, need Python knowledge as

  • Sevabot modules are normal UNIX scripts which you can write in Bash, Python, Perl, Ruby, Javascript, Haskel or whatever is your daily drug
  • External services call Sevabot over HTTP webhooks and any programming language can do HTTP requets

You can even script Sevabot without programming knowledge, as there exist HTTP webhook middleman services like Zapier, which allow you to connect event sources (e..g Github API) and sinks (Sevabot HTTP interface).

In fact Sevabot is a middleman between Flask web server framework (enables HTTP interface) and Skype4Py API (control Skype GUI client with Python).

Here is a simple example how to send a message to a Skype chat from a Bash shell script. It’s a Subversion post-commit hook which displays the commit message in a specific Skype group chat (full example):

#!/bin/bash

repo="$1"

rev="$2"

svnlook="/usr/bin/svnlook"

# Get last commit author
author=`$svnlook author $repo`

# Get last commit message
commit_message=`$svnlook log $repo`

# List of changed files
changed=`$svnlook changed $repo`

# Chat id
chat="YOUR-CHAT-ID-HERE"

# Sevabot endpoint informaiton
# Shared secret
secret="YOUR-SHARED-SECRET-HERE"
msgaddress="http://YOURSERVER.COM:5000/msg/"

# Create chat message
msg="★ $author - $commit_message $changed"

# Sign the message with MD5
md5=`echo -n "$chat$msg$secret" | md5sum`

#md5sum command prints a '-' to the end. Let's get rid of that.
for m in $md5; do
    break
done

# Call Sevabot's HTTP interface
curl $msgaddress --data-urlencode chat="$chat" --data-urlencode msg="$msg" --data-urlencode md5="$m"

exit 0

4. You want it – come for us

Go to Sevabot Github project page for more information. Also check the community information; we have around five active contributors currently.

ImageFeliz Natal e boas festas, Mikko and Sevabot

 

 

 

 

 

\"\" Subscribe to RSS feed Image Follow me on Twitter Image Follow me on Facebook Image Follow me Google+