Writing a new Dancer logger backend

As you may have noticed by now, it's pretty simple to write backends to Dancer. This time we'll be writing a logger backend.

How do loggers work?

When Dancer passes all the information it wants (or is asked by you) to log, it will pass it on to the logger class which will then use the picked backend to actually do the correct thing with the information (such as presenting it on the screen or saving it to a file).

Dancer already has a few logging backends, such as:

  • Console

    The new default for development environment, showing all the errors in the console from which you started the application. This helps you see everything right there on your screen while developing instead of making you check log files.

  • File

    The default for most production environments, saving all the information in a chosen specific log file. This is also the default behavior with many web servers such as Apache.

  • Syslog

    This logger sends your message to the system logger, allowing for collection, storage and indexing your information along with the system information. This is used in heavy production environments to centralized all the logging data from machines to a major location which then uses it to monitor the servers and provide statistic information.

There are a few more loggers available on CPAN that you can download and configure your application to use. Feel free to explore.

Deciding on the logger backend

While writing log messages to a file is what most people do, that's just boring. Let's try to do something more interesting with our log messages!

Do you like spinners? Of course you do, everyone does! Let's make a log backend that simply runs a spinner on our console. Every log message that comes in will make the spinner advance a bit.

Module skeleton

This should be the simplest skeleton, including just the required modules:

package Dancer::Logger::Spinner;

use strict;
use warnings;

use base 'Dancer::Logger::Abstract';

sub _log {
    ...
}

1;

If you've ever developed a Dancer engine or plugin, you'd notice that Dancer makes usage of abstract base classes. This ensures you implement the correct methods and helps you enforce present and future portability with Dancer itself. It also makes our module object oriented.

Methods

There is only one subroutine we need to implement for our logger:

  • _log

    This subroutine does the work. It gets two parameters: the level of the logging and the message itself. This helps you determine how important the message is.

    For our spinner, since we want to make it rock as much as possible, any message counts as another spin, so we won't differentiate between different levels.

    sub _log {
        my ( $self, $level, $message ) = @_;
        $self->advance_spinner();
    }

    Of course, if we aren't using the message level or the message itself, why are we expanding it out of @_? Just to show how it works. We could effectively write it the same as such:

    sub _log {
        my $self = shift;
        $self->advance_spinner();
    }

Of course we haven't really written the advance_spinner method yet. We're also missing one important thing, the initialization of the spinner, the characters used by it and the counter we'll use to keep track of the array of characters.

We can use the init method in the base class we inherited (Dancer::Logger::Abstract, remember?) to take care of all these things at initialization.

sub init {
    my $self = shift;
    $self->{'spinner_chars'} = [ '\\', '|', '/', '-', 'x' ];
    $self->{'spinner_count'} = 0;
}

Now we need to implement the advance_spinner subroutine:

sub advance_spinner {
    my $self  = shift;
    my $count = $self->{'spinner_count'};
    my @chars = @{ $self->{'spinner_chars'} };

    # these chars lifted from Brandon L. Black's Term::Spinner
    print STDERR "\010 \010";
    print STDERR $chars[$count];

    # if we reached over the array end, let's get back to the start
    # let's also increment the counter before doing the check
    ++$count > $#chars and $count = 0;

    # update the hash
    $self->{'spinner_count'} = $count;
}

One nice thing we can add is printing a newline when the code finishes so your terminal doesn't end up on the same line with the last printed character:

sub DESTROY {
    print STDERR "\n";
}

And that's pretty much it.

(we're printing to STDERR to enforce pipe flushing)

CPAN, anyone?

While you've been reading this entry, I've taken the liberty to upload what we just wrote to CPAN and it should now be available as Dancer::Logger::Spinner. Nice, isn't it?

Feel free to send me your names so I could add you to the CREDITS section in the POD! :)

See also

  • The previous article on Dancer Internals
  • The previous article on Writing a new Dancer serializer
  • The following article on Writing a new Dancer session backend

Author

This article has been written by Sawyer X <xsawyerx@cpan.org> for the Perl Dancer Advent Calendar 2010.