The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CTK::Log; # $Id: Log.pm 193 2017-04-29 07:30:55Z minus $
use Moose::Role; # use Data::Dumper; $Data::Dumper::Deparse = 1;
use Moose::Util::TypeConstraints;

=head1 NAME

CTK::Log - CTK Logging methods

=head1 VERSION

Version 2.60

=head1 SYNOPSIS

    $c = new CTK (
            loglevel     => 'info', # or '1'
            logfile      => CTK::catfile($LOGDIR,'foo.log'),
            logseparator => " ", # as default
        );

    $c->log( INFO => " ... Blah-Blah-Blah ... " );

    $c->log_except();  # 9 exception
    $c->log_fatal();   # 8 fatal
    $c->log_emerg();   # 7 system is unusable
    $c->log_alert();   # 6 action must be taken immediately
    $c->log_crit();    # 5 critical conditions
    $c->log_error();   # 4 error conditions
    $c->log_warning(); # 3 warning conditions
    $c->log_notice();  # 2 normal but significant condition
    $c->log_info();    # 1 informational
    $c->log_debug();   # 0 debug-level messages (default)

=head1 DESCRIPTION

All of methods are returned by log-records

    # Log File defined in constructor
    $c = new CTK (
            loglevel     => 'info', # or '1'
            logfile      => CTK::catfile($LOGDIR,'foo.log'),
            logseparator => " ", # as default
        );

    # Log File defined after constructor
    $c->logfile(catfile($c->logdir(),
            dformat($cmddata{logfile}, {
                        COMMAND  => $command,
                        PREFIX   => $c->prefix(),
                        SUFFIX   => $c->suffix(),
                        EXT      => 'log',
                        DEFAULT  => LOGFILE,
                    }
                )
            )
        );

=head2 log

    $c->log( INFO => " ... Blah-Blah-Blah ... " );

Logging with info level (1). Same as log_info

=head2 log_debug, logdebug, logdebugging

    $c->log_debug();

Level 0: debug-level messages (default)

=head2 log_info, info, loginf, loginfo, loginformation

    $c->log_info();

Level 1: informational

=head2 log_notice, notice, lognote, lognotice

    $c->log_notice();

Level 2: normal but significant condition

=head2 log_warning, warning, logwarn, logwarning

    $c->log_warning();

Level 3: warning conditions

=head2 log_error, error, logerror

    $c->log_error();

Level 4: error conditions

=head2 log_crit, crit, logcrit, logcritical

    $c->log_crit();

Level 5: critical conditions

=head2 log_alert, alert, logalert

    $c->log_alert();

Level 6: action must be taken immediately

=head2 log_emerg, emerg, logemerg, logemergency

    $c->log_emerg();

Level 7: system is unusable

=head2 log_fatal, fatal, logfatal

    $c->log_fatal();

Level 8: fatal

=head2 log_except, except, logexcept, logexception

    $c->log_except();

Level 9: exception

=head1 AUTHOR

Sergey Lepenkov (Serz Minus) L<http://www.serzik.com> E<lt>minus@mail333.comE<gt>

=head1 COPYRIGHT

Copyright (C) 1998-2017 D&D Corporation. All Rights Reserved

=head1 LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms and conditions as Perl itself.

This program is distributed under the GNU LGPL v3 (GNU Lesser General Public License version 3).

See C<LICENSE> file

=cut

use constant {
    LOGLEVELS       => {
        'debug'   => 0,
        'info'    => 1,
        'notice'  => 2,
        'warning' => 3,
        'error'   => 4,
        'crit'    => 5,
        'alert'   => 6,
        'emerg'   => 7,
        'fatal'   => 8,
        'except'  => 9,
    },
};

use vars qw/$VERSION/;
$VERSION = '2.60';

subtype 'LogLevels'
    => as 'Int'
    => where   { $_ >= 0 and $_ <= 9 }
    => message { "The LogLevel $_ not valid" };

coerce 'LogLevels'
    => from 'Str'
    => via { ($_ && LOGLEVELS->{$_}) ? LOGLEVELS->{$_} : 0 };

has 'loglevel' => (
    is         => 'rw',
    isa        => 'LogLevels',
    default    => 0,
    lazy       => 1,
    coerce     => 1,
);

has 'logfile' => (
    is         => 'rw',
    isa        => 'Str',
    default    => '',
    trigger => sub {
            my $self = shift;
            my $val = shift || '';
            my $old_val = shift || '';
            $self->handle($val) if $val && $val ne $old_val;
        },

);

subtype 'LogSeparators'
    => as 'Str'
    => where   { defined($_) && $_ ne '' }
    => message { "The logseparator not valid" };

has 'logseparator' => (
    is         => 'rw',
    isa        => 'LogSeparators',
    default    => ' ',
    lazy       => 1,
);

subtype 'LogHandler'
    => as 'FileHandle'
    => where   { $_->opened },
    => message { "File's handler do't opened!" };

coerce 'LogHandler'
    => from 'Str'
    => via { FileHandle->new($_,'a') };

has 'handle'   => (
    is         => 'rw', # 'ro',
    isa        => 'LogHandler',
    coerce     => 1,
    predicate  => 'sethandle',
);

around BUILDARGS => sub {
    my $orig = shift;
    my $class = shift;

    #CTK::debug("BUILDARGS called");
    #CTK::debug(Dumper(\@_));

    if ( @_ && ! ref($_[0]) ) {
        my %p = @_;
        unless (defined $p{handle}) {
            # Íå çàäàí ïàðàìåòð handle, ïîäñòàâëÿåì çíà÷åíèå ñàìè, èç logfile, èíà÷å -- íè÷åãî!
            if (defined $p{logfile}) {
                $p{handle} = $p{logfile};
            }
        }
        return $class->$orig(%p);
    } elsif ( @_ && ref($_[0]) eq 'HASH' ) {
        my $p = $_[0];
        unless (defined $p->{handle}) {
            # Íå çàäàí ïàðàìåòð handle, ïîäñòàâëÿåì çíà÷åíèå ñàìè, èç logfile, èíà÷å -- íè÷åãî!
            if (defined $p->{logfile}) {
                $p->{handle} = $p->{logfile};
            }
        }

    }
    return $class->$orig(@_);

};

after DEMOLISH => sub {
    my $self = shift;
    #CTK::debug("DEMOLISH called");

    if ($self->sethandle()) {
        my $fh = $self->handle;
        $fh->close() if $fh;
        #warn("DEMOLISH called: handle cleaned !!") if $fh;
    }
};

sub log {
    my $self  = shift;
    my $level = shift;
    my @l = @_;

    my $loglevels = LOGLEVELS;
    my %levels  = %$loglevels;
    my %rlevels = reverse %$loglevels;

    my $proc = 'log_debug'; # Îáðàáîò÷èê ïî óìîë÷àíèþ
    if (defined($level) && ($level =~ /^[0-9]+$/) && defined $rlevels{$level}) {
        $proc = 'log_'.$rlevels{$level};
        #CTK::debug ("FIRST: $proc");
    } elsif (defined($level) && ($level =~ /^[a-z0-9]+$/i) && defined $levels{lc($level)}) {
        $proc = 'log_'.lc($level);
        #CTK::debug ("SECOND: $proc");
    } else {
        unshift @l, $level if defined $level;
        #CTK::debug (@l);
    }

    # Çàïóñêàåì îáðàáîò÷èê ïî èìåíè ïðîöåäóðû
    confess "Undefinned the LogLevel!" unless $proc;
    my $lcode = __PACKAGE__->can("$proc");
    if ($lcode && ref($lcode) eq 'CODE') {
        return $self->$proc(@l); #return &{$lcode}($self,@l);
    } else {
        confess "Can't call method or procedure \"$proc\"!";
    }
    return undef;
}
sub log_debug {
    my $l = "debug";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub logdebug { log_debug @_ }
sub logdebugging { log_debug @_ }
sub info {
    my $l = "info";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_info { info @_ }
sub loginfo { info @_ }
sub loginformation { info @_ }
sub loginf { info @_ }
sub notice {
    my $l = "notice";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_notice { notice @_ }
sub lognotice { notice @_ }
sub lognote { notice @_ }
sub warning {
    my $l = "warning";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_warning { warning @_ }
sub logwarning { warning @_ }
sub logwarn { warning @_ }
sub error {
    my $l = "error";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_error { error @_ }
sub logerror { error @_ }
sub crit {
    my $l = "crit";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_crit { crit @_ }
sub logcrit { crit @_ }
sub logcritical { crit @_ }
sub alert {
    my $l = "alert";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_alert { alert @_ }
sub logalert { alert @_ }
sub emerg {
    my $l = "emerg";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_emerg { emerg @_ }
sub logemerg { emerg @_ }
sub logemergency { emerg @_ }
sub fatal {
    my $l = "fatal";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_fatal { fatal @_ }
sub logfatal { fatal @_ }
sub except {
    my $l = "except";
    _flush(LOGLEVELS->{$l}, $l, @_);
}
sub log_except { except @_ }
sub logexcept { except @_ }
sub logexception { except @_ }

sub _flush {
    # ñáðàñûâàåì â ôàéë áóôåð
    my $ilevel = shift;
    my $level  = shift;
    my $self   = shift;
    my @buffer = ();
    local $\;

    return '' if $ilevel < $self->loglevel;

    # Ñîçäàåì áóôåðíûé ñòåê
    push @buffer, "[".scalar(localtime(time()))."]";
    push @buffer, "[$level]" if defined $level;
    push @buffer, "[$$]";

    push @buffer, @_;
    if ($self->sethandle()) {
        my $fh = $self->handle();
        $fh->print(join($self->logseparator(), @buffer),"\n");
    }
    return join($self->logseparator(), @buffer);
}

1;
__END__