Creating a standalone Damon with Moose and friends

2009-01-30 − 🏷 daemon 🏷 logging 🏷 moose 🏷 perl

I recently needed to create a persistent daemon to do some maintenance tasks for iusethis. Turns out that building these with the postmodern object system Moose is  really easy.

For those of you not in the know, Moose has the concept of 'Roles', stolen from the Perl 6 project. Roles work kind of like a mix-in that you can add to your class to make it super powerful :)   (For the extra curious, you can read more details about Roles in Perl6 here).

In order to get my daemon running, I just created a Class like this:

package iusethis::Daemon;
use Moose;
with MooseX::Daemonize;

after 'start' => sub {
    my $self=shift;
    return unless $self->is_daemon();
    while(1) { $self->do_menial_tasks(); sleep($until_lunch); }
1;

Some notes about the code above

  • You can see how the role is added using the 'with' keyword.
  • In Moose, the 'after' modifier means that our code will hook up to the 'after' method provided by a superclass or Role and run after that method.
  • The return is because the code forks, to avoid it from running in both the child and the parent.

I also created a simple script to initialize it (scripts/daemon.pl in my case):

#!/usr/bin/perl -w
use strict;
use iusethis::Daemon;
my $daemon = iusethis::Daemon->new_with_options();
my ($command) = @{$daemon->extra_argv};
$daemon->$command if $daemon->can($command);
warn($daemon->status_message); exit($daemon->exit_code);

And just like that we have a fully operational persistent server process. You could stuff this thing into /etc/init.d/ if you like, and it will respond to all the expected things like start, stop, restart status. the Daemonize role also supports MooseX::GetOpt. It is enabled by running new_with_options like we did in the class above. This which means your daemon already responds to a common set of command line switches. Run it with -h to see this output:

usage: daemon.pl [-f] [long options...]
        --pidbase
        --progname
        --stop_timeout
        --basedir
        --pidfile
        -f --foreground

From the 'pidbase' argument, you might have guessed that your fledgling daemon stores the process id in a file just like all the grown up daemons  do, in order to be able to check if it's still alive.  By default, it will store this file in /var/run/, and pidbase is useful for specifying a different path, for instance if you need to run your daemon on a system where you don't have root privileges. the -f flag is also useful for debugging your daemon, as it will keep the process running in the foreground, rather than detaching it.

Running in the foreground is nice when you are writing your daemon, but once you have it running, it's nice to have some log output to see what it's been up to lately. I decide to use MooseX::LogDispatch, which is a Moose role which makes it trivial to integrate Log::Dispatch into your daemon. This is what I added to my class to get it running:

with qw/MooseX::LogDispatch::Levels/;
has log_dispatch_conf => (
   is => 'ro',
   isa => 'HashRef',
   lazy => 1,
   required => 1,
              default => sub {
     my $self = shift;
        {
          class     => 'Log::Dispatch::File',
          min_level => 'debug',
          mode      => 'append',
          filename  => "/var/log/daemon.log",
            format  => '[%d] [%p] %m%n',
        }
    },
 );

For a more trivial case, you could have just passed in the file name to log to, but I wanted to tweak the log format. (You can read more about the format here). This style also allows you to easily adjust the verbosity of your logging. Since this default is lazily evaluated, you could create separate setters for parts of the format and override them on the command line, thanks to MooseX::GetOpt. Once this is set up, all you need to do is use the appropriate level like this

$self->info('Arne sucks at squash');

and an appropriate message is created in your log file:

[Thu Jan 29 23:50:09 2009] [info] Arne sucks at squash.

And that's all you need. I hope this article was useful in showing how you can create your own hell-spawn. Enjoy, and use responsibly. :)