package Zonemaster::Engine::Logger::Entry; use v5.16.0; use warnings; use version; our $VERSION = version->declare("v1.1.8"); use Carp qw( confess ); use Time::HiRes qw[time]; use JSON::PP; use Class::Accessor; use Zonemaster::Engine::Profile; use base qw(Class::Accessor); use overload '""' => \&string; our %numeric = ( DEBUG3 => -2, DEBUG2 => -1, DEBUG => 0, INFO => 1, NOTICE => 2, WARNING => 3, ERROR => 4, CRITICAL => 5, ); our $start_time = time(); my $json = JSON::PP->new->allow_blessed->convert_blessed->canonical; my $test_levels_config; __PACKAGE__->mk_ro_accessors(qw(tag args timestamp testcase module)); sub new { my ( $proto, $attrs ) = @_; # tag, testcase and module required, args optional, other built confess "Attribute \(tag\) is required" if !exists $attrs->{tag}; confess "Attribute \(testcase\) is required" if !exists $attrs->{testcase}; confess "Attribute \(module\) is required" if !exists $attrs->{module}; confess "Argument must be a HASHREF: args" if exists $attrs->{args} && ref $attrs->{args} ne 'HASH'; my $time = time() - $start_time; $time =~ s/,/\./; $attrs->{timestamp} = $time; # lazy attributes $attrs->{_level} = delete $attrs->{level} if exists $attrs->{level}; my $class = ref $proto || $proto; return Class::Accessor::new( $class, $attrs ); } sub level { my $self = shift; # Lazy default value if ( !exists $self->{_level} ) { $self->{_level} = $self->_build_level(); } return $self->{_level} } sub _build_level { my ( $self ) = @_; my $string; if ( !defined $test_levels_config ) { $test_levels_config = Zonemaster::Engine::Profile->effective->get( q{test_levels} ); } if ( exists $test_levels_config->{ uc $self->module }{ $self->tag } ) { $string = uc $test_levels_config->{ uc $self->module }{ $self->tag }; } else { $string = 'DEBUG'; } if ( defined $numeric{$string} ) { return $string; } else { die "Unknown level string: $string"; } } sub _set_level { my ( $self, $level ) = @_; $self->{_level} = $level } sub numeric_level { my ( $self ) = @_; return $numeric{ $self->level }; } sub levels { return %numeric; } sub string { my ( $self ) = @_; return sprintf( '%s%s:%s %s', $self->module, $self->testcase ? q{:} . $self->testcase : q{}, $self->tag, $self->argstr ); } sub argstr { my ( $self ) = @_; my $argstr = q{}; ## no critic (TestingAndDebugging::ProhibitNoWarnings) no warnings 'uninitialized'; if ( $self->args ) { my $p_args = $self->printable_args; $argstr = join( q{; }, map { $_ . q{=} . ( ref( $p_args->{$_} ) ? $json->encode( $p_args->{$_} ) : $p_args->{$_} ) } sort keys %{$p_args} ); } return $argstr; } sub printable_args { my ( $self ) = @_; if ( $self->args ) { my %p_args; foreach my $key_arg ( keys %{ $self->args } ) { if ( not ref( $self->args->{$key_arg} ) ) { $p_args{$key_arg} = $self->args->{$key_arg}; } elsif ( $key_arg eq q{asn} and ref( $self->args->{$key_arg} ) eq q{ARRAY} ) { $p_args{q{asn}} = join( q{,}, @{ $self->args->{$key_arg} } ); } else { $p_args{$key_arg} = $self->args->{$key_arg}; } } return \%p_args; } return; } ## end sub printable_args ### ### Class method ### sub start_time_now { $start_time = time(); return; } sub reset_config { undef $test_levels_config; return; } 1; =head1 NAME Zonemaster::Engine::Logger::Entry - module for single log entries =head1 SYNOPSIS Zonemaster::Engine->logger->add( TAG => { some => 'arguments' }); There should never be a need to create a log entry object in isolation. They should always be associated with and created via a logger object. =head1 CLASS METHODS =over =item new Construct a new object. =item levels Returns a hash where the keys are log levels as strings and the corresponding values their numeric value. =item start_time_now() Set the logger's start time to the current time. =item reset_config() Clear the test level cached configuration. =back =head1 ATTRIBUTES =over =item module The name of the module associated to the entry, or "System". =item testcase The name of the test case which generated the entry, or "Unspecified". =item tag The tag that was set when the entry was created. =item args The argument hash reference that was provided when the entry was created. =item timestamp The time after the current program started running when this entry was created. This is a floating-point value with the precision provided by L. =item level The log level associated to this log entry. =back =head1 METHODS =over =item string Simple method to generate a string representation of the log entry. Overloaded to the stringification operator. =item argstr Returns the string representation of the message arguments. =item printable_args Used to transform data from an internal/JSON representation to a "user friendly" representation one. =item numeric_level Returns the log level of the entry in numeric form. =back =cut