#=============================================================================
# Doloforge::Command::Helper : Set of helper commands for various tasks needing the doloforge config
#
# Runs external commands or simple things which happen to benefit from using
# the configuration loading logic of the main "doloforge" command.
#
# Full help :
#   doloforge helper --man
#
# Testing this package :
#   perl ./src/doloforge --config t/doloforge.yaml helper --error2vlogger
#=============================================================================


package Doloforge::Command::Helper;

use strict;
use warnings;

# This base class implements the constructor and help methods in a generic way.
use base qw(Doloforge::Command);

use Cwd qw(realpath);
use IO::Handle;


#=============================================================================
=encoding utf8

=head1 NAME

Doloforge::Helper - Set of helper commands for various tasks needing the doloforge config

=head1 SYNOPSIS

    # Converts Apache's error.log format into a vhost-prefixed pseudo access.log, to feed to vlogger
    doloforge helper --error2vlogger

    # In the Apache config file
    ErrorLog  "| doloforge --quiet helper --error2vlogger | /usr/sbin/vlogger -e    -s error.log  -t error.log.%Y%m%d  -u doloforge /srv/doloforge/local/.doloforge/apache/vlogger"

=head1 DESCRIPTION

Todo

=cut


#=============================================================================
# Method getOptions
#
# Accepted arguments from the command-line, in the form accepted by Getopt::Long.
# Options with no value that match method names of that package will be called as
# methods, with the decoded options as parameters.

sub getOptions {
    return (
        'error2vlogger',        # runs the error.log to access.log pipe format converter, used to feed vlogger
    );
}

#=============================================================================
# Method error2vlogger
#
# Converts an error.log format in STDIN, into a vhost'ed access.log format to STDOUT
# which can be piped to vlogger, which will split it in various log files...

our $daemon_instance = undef;

sub error2vlogger {
    my $self = shift;

    # Prepare to run as a long-running pipe component (lifespan = from the start of Apache, to it's reload a few months later, hopefully)
    STDOUT->autoflush();                # Set autoflush for STDOUT, in order to feed the pipe as quickly as possible
    $daemon_instance = $self;           # Keep track of the current instance, which will be used by the signal handler
    $self->{global}->{verbose} = 0;     # Reduce logging to STDOUT/ERR to nothing, or the piped vlogger will receive lots of trash
    # Trap the signals sent by Apache, and simply exit upon reception (do not catch PIPE, because vlogger can throw it early when starting)
    use sigtrap qw(handler exit_error2vlogger HUP USR1 TERM INT);

    # The vhost names are constructed (see apache_httpd.conf.tmpl) using the local domain name : just use the exact same formula here...
    my $domainname = $self->{global}->{domainname};
    # Get the shared dir location, and it's physical name, after following symlinks
    my $shared_path = $self->{global}->{dir}->{shared};
    my $shared_realpath = realpath($shared_path);

    # Read STDIN line by line, and simply quit when it's closed (Apache reloads)
    $self->info("error2vlogger reading STDIN for $domainname in $self->{global}->{dir}->{shared}/");
    while (<STDIN>) {
        chomp;
        next unless $_; # Ignore empty lines

        # We also need the current timezone, with a quick hack here ("+HHMM" string format) (since the TZ can change in the lifespan of the process, just recompute it each time)
        my $time = time;
        my $tz = sprintf("%+05d", (((localtime($time))[2] * 100 + (localtime($time))[1]) + 2400 - ((gmtime($time))[2] * 100 + (gmtime($time))[1])) % 2400);

        # Identify the timestamp in the source file, as "[Wed Sep  2 09:21:01 2009]"
        my ($dow, $month, $mday, $hour, $min, $sec, $year, $line) = ($_ =~ m{^\[(\w{3}) (\w{3}) +(\d+) (\d{2}):(\d{2}):(\d{2}) (\d{4})\] (.*)});
        # If the timestamp was not identified, just create one from the current time
        unless ($line) {
            $line = "[nodate] $_";
            # This is a bit stupid : convert the localtime to string, and back to split values using a regexp: the simple benefits are that days/months are named, and we use the same regexp as above
            ($dow, $month, $mday, $hour, $min, $sec, $year) = (scalar(localtime($time)) =~ m{^(\w{3}) (\w{3}) +(\d+) (\d{2}):(\d{2}):(\d{2}) (\d{4})$});
        }
        my $date = sprintf("%02d/%s/%04d:%02d:%02d:%02d %s", $mday, $month, $year, $hour, $min, $sec, $tz);

        # Then try to identify the client/project which the line relates to:
        #   - from files paths like /srv/doloforge/shared/[client]/[project]/apache/htdocs/
        #   - or from real physical paths, like PHP likes to log as in /srv/.nfs/doloforge/shared/[client]/[project]/php5/htdocs/
        my ($client, $project) = ($line =~ m!(?:$shared_path|$shared_realpath)/([a-zA-Z][a-zA-Z0-9-]*?[a-zA-Z0-9])/([a-zA-Z][a-zA-Z0-9-]*?[a-zA-Z0-9])/[a-z0-9]*/_?htdocs!);
        #   - or from Mason component paths like /srv/doloforge/shared/.doloforge/mason/components/[client]_[project]/
        ($client, $project) = ($line =~ m!(?:$shared_path|$shared_realpath)/\.doloforge/mason/components/([a-zA-Z][a-zA-Z0-9-]*?[a-zA-Z0-9])_([a-zA-Z][a-zA-Z0-9-]*?[a-zA-Z0-9])/!) unless ($client and $project);
        #   - or from client lib/ tree like /srv/doloforge/shared/[client]/lib/php/
        ($client) = ($line =~ m!(?:$shared_path|$shared_realpath)/([a-zA-Z][a-zA-Z0-9-]*?[a-zA-Z0-9])/lib/[a-z0-9]*/!) unless ($client and $project);

        # Construct a generic domain name from this above, to feed vlogger
        my $vhost = '_default_';
        if ($client) {
            # FIXME : ajouter $stage !
            if ($project) {
                $vhost = "$project.$client.$domainname";
            } else {
                $vhost = "$client.$domainname";
            }
        }

        # Finally, try to read an IP address in the line (the regexp is far from a perfect match, but quite sufficient for this need)
        my ($ip) = ($line =~ m{\[client ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]});
        $ip ||= '127.0.0.1';

        # Write the line with the timestamp as "[02/Sep/2009:08:18:02 +0200]" (mergelog really wants "%d/%3c/%d:%d:%d:%d" after a "[" located at least 9 chars from the beginning of the line)
        # The line formatted as below allow to pipe the error.log files into mergelog + jdresolve, just like the access.log files, which is GOOD...
        print "$vhost $ip - - [$date] $line\n";
    }
}

sub exit_error2vlogger { # 1st argument is signal name
    my($sig) = @_;
    $daemon_instance->info("error2vlogger closing STDIN, caught SIG$sig");
    exit(0);
}



1;
__END__

=head1 AUTHOR

Nicolas Huillard <F<nicolas@huillard.net>>, Dolomède

=head1 SEE ALSO

doloforge(1)

=cut
