use strict; use warnings; package Zonemaster::Backend::Log; use English qw( $PID ); use POSIX; use JSON::PP; use IO::Handle; use Log::Any::Adapter::Util (); use Carp; use Data::Dumper; use base qw(Log::Any::Adapter::Base); my $default_level = Log::Any::Adapter::Util::numeric_level('info'); sub init { my ($self) = @_; if ( defined $self->{log_level} && $self->{log_level} =~ /\D/ ) { $self->{log_level} = lc $self->{log_level}; my $numeric_level = Log::Any::Adapter::Util::numeric_level( $self->{log_level} ); if ( !defined($numeric_level) ) { croak "Error: Unrecognized log level " . $self->{log_level} . "\n"; } $self->{log_level} = $numeric_level; } $self->{log_level} //= $default_level; my $fd; if ( !exists $self->{file} || $self->{file} eq '-') { if ( $self->{stderr} ) { $fd = fileno(STDERR); } else { $fd = fileno(STDOUT); } } else { open( $fd, '>>', $self->{file} ) or croak "Can't open log file: $!"; } $self->{handle} = IO::Handle->new_from_fd( $fd, "w" ) or croak "Can't fdopen file: $!"; $self->{handle}->autoflush(1); if ( !exists $self->{formatter} ) { if ( $self->{json} ) { $self->{formatter} = \&format_json; } else { $self->{formatter} = \&format_text; } } } sub format_text { my ($self, $log_params) = @_; my $msg; $msg .= sprintf "%s ", $log_params->{timestamp}; delete $log_params->{timestamp}; $msg .= sprintf( "[%d] [%s] [%s] %s", delete $log_params->{pid}, uc delete $log_params->{level}, delete $log_params->{category}, delete $log_params->{message} ); if ( %$log_params ) { local $Data::Dumper::Indent = 0; local $Data::Dumper::Terse = 1; my $data = Dumper($log_params); $msg .= " Extra parameters: $data"; } return $msg } sub format_json { my ($self, $log_params) = @_; my $js = JSON::PP->new; $js->canonical( 1 ); return $js->encode( $log_params ); } sub structured { my ($self, $level, $category, $string, @items) = @_; my $log_level = Log::Any::Adapter::Util::numeric_level($level); return if $log_level > $self->{log_level}; my %log_params = ( timestamp => strftime( "%FT%TZ", gmtime ), level => $level, category => $category, message => $string, pid => $PID, ); for my $item ( @items ) { if (ref($item) eq 'HASH') { for my $key (keys %$item) { $log_params{$key} = $item->{$key}; } } } my $msg = $self->{formatter}->($self, \%log_params); $self->{handle}->print($msg . "\n"); } # From Log::Any::Adapter::File foreach my $method ( Log::Any::Adapter::Util::detection_methods() ) { no strict 'refs'; my $base = substr($method,3); my $method_level = Log::Any::Adapter::Util::numeric_level( $base ); *{$method} = sub { return !!( $method_level <= $_[0]->{log_level} ); }; } 1;