Skip to content

Commit

Permalink
Add support for hooks
Browse files Browse the repository at this point in the history
User can define a function to be called at a specific time.
At the moment, there is only one hook: build_context.
It is called right before passing the message and structured data
to the Adapter.

The hook makes using the context easier.
Instead of defining the context everytime when you call
a logging function, e.g. `$log->debugf()`,
you can use the hook to define repeating information,
like file name and line or timestamp.
Because the hooks support several hooked sub routines,
you can add more context temporarily when you need it.

User can define the hooks either when `use`ing Log::Any
or at any point afterwards.
There can be several subroutines for each hook.
They are executed in the order the hooks are added.

Examples:

    use Log::Any q($log), hooks => { build_context => [ \&build_context, ] };
    push @{ $log->hooks->{'build_context'} }, \&build_more_context;

Signed-off-by: Mikko Koivunalho <mikkoi@cpan.org>
  • Loading branch information
mikkoi committed May 3, 2023
1 parent 9dee212 commit 2179a83
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 4 deletions.
18 changes: 17 additions & 1 deletion lib/Log/Any.pm
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,25 @@ sub get_logger {
my $adapter = $class->_manager->get_adapter( $category );
my $context = $class->_manager->get_context();

my $hooks_params =
defined $params{hooks} ? delete $params{hooks} : {};
my $hooks = {};
for my $hook_name (Log::Any::Adapter::Util::hook_names()) {
if( defined $hooks_params->{$hook_name} ) {
if( ref $hooks_params->{$hook_name} ne 'ARRAY' ) {
require Carp;
Carp::croak("Fault in hook definition: not array");
}
$hooks->{$hook_name} = $hooks_params->{$hook_name};
} else {
$hooks->{$hook_name} = [];
}
}

require_dynamic($proxy_class);
return $proxy_class->new(
%params, adapter => $adapter, category => $category, context => $context
%params, adapter => $adapter, category => $category, context => $context,
hooks => $hooks,
);
}

Expand Down
14 changes: 13 additions & 1 deletion lib/Log/Any/Adapter/Util.pm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ our @EXPORT_OK = qw(
logging_aliases
logging_and_detection_methods
logging_methods
hook_names
make_method
numeric_level
read_file
Expand All @@ -47,7 +48,8 @@ push @EXPORT_OK, keys %LOG_LEVELS;
our %EXPORT_TAGS = ( 'levels' => [ keys %LOG_LEVELS ] );

my ( %LOG_LEVEL_ALIASES, @logging_methods, @logging_aliases, @detection_methods,
@detection_aliases, @logging_and_detection_methods );
@detection_aliases, @logging_and_detection_methods,
@hook_names );

BEGIN {
%LOG_LEVEL_ALIASES = (
Expand All @@ -63,6 +65,8 @@ BEGIN {
@detection_methods = map { "is_$_" } @logging_methods;
@detection_aliases = map { "is_$_" } @logging_aliases;
@logging_and_detection_methods = ( @logging_methods, @detection_methods );
@hook_names =
qw(build_context);
}

=sub logging_methods
Expand All @@ -89,6 +93,14 @@ Returns a list of logging and detection methods (but not aliases).

sub logging_and_detection_methods { @logging_and_detection_methods }

=sub hook_names
Returns a list of hook names.
=cut

sub hook_names { @hook_names }

=sub log_level_aliases
Returns key/value pairs mapping aliases to "official" names. E.g. "err" maps
Expand Down
20 changes: 18 additions & 2 deletions lib/Log/Any/Proxy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ sub new {
require Carp;
Carp::croak("$class requires a 'context' parameter");
}
unless ( $self->{hooks} ) {
require Carp;
Carp::croak("$class requires a 'hooks' parameter");
}
bless $self, $class;
$self->init(@_);
return $self;
Expand All @@ -67,7 +71,7 @@ sub clone {

sub init { }

for my $attr (qw/adapter category filter formatter prefix context/) {
for my $attr (qw/adapter category filter formatter prefix context hooks/) {
no strict 'refs';
*{$attr} = sub { return $_[0]->{$attr} };
}
Expand All @@ -91,14 +95,26 @@ foreach my $name ( Log::Any::Adapter::Util::logging_methods(), keys(%aliases) )
my ( $self, @parts ) = @_;
return if !$self->{adapter}->$is_realname && !defined wantarray;

# Execute hook: build_context
my $caller = (caller 0)[0] ne 'Log::Any::Proxy'
&& (caller 0)[3] eq 'Log::Any::Proxy::__ANON__'
? [ caller 0 ] : [ caller 1 ];
my %items;
foreach my $hook (@{ $self->{hooks}->{build_context} }) {
my %i = $hook->( $realname, $self->{category}, $caller, \%items);
@items{keys %i} = @i{keys %i};
}

my $structured_logging =
$self->{adapter}->can('structured') && !$self->{filter};

my $data_from_parts = pop @parts
if ( @parts && ( ( ref $parts[-1] || '' ) eq ref {} ) );
my $data_from_context = $self->{context};
my $data_from_hooks = \%items;
my $data =
{ map {%$_} grep {$_ && %$_} $data_from_context, $data_from_parts };
{ map {%$_} grep {$_ && %$_} $data_from_context, $data_from_parts,
$data_from_hooks, };

if ($structured_logging) {
unshift @parts, $self->{prefix} if $self->{prefix};
Expand Down
90 changes: 90 additions & 0 deletions t/hooks.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use strict;
use warnings;
use Test::More tests => 1;

use Log::Any::Adapter;
use Log::Any '$log';

use FindBin;
use lib $FindBin::RealBin;
use TestAdapters;

sub create_normal_log_lines {
my ($log) = @_;

$log->info('some info');
$log->infof( 'more %s', 'info' );
$log->infof( 'info %s %s', { with => 'data' }, 'and more text' );
$log->debug( "program started",
{ progname => "foo.pl", pid => 1234, perl_version => "5.20.0" } );

}

Log::Any::Adapter->set('+TestAdapters::Structured');

push @{ $log->hooks->{'build_context'} }, \&build_context;
create_normal_log_lines($log);
pop @{ $log->hooks->{'build_context'} };

sub build_context {
my ($lvl, $cat, $caller, $data) = @_;
my %ctx;
$ctx{lvl} = $lvl;
$ctx{cat} = $cat;
$ctx{file} = $caller->[1];
$ctx{line} = $caller->[2];
$ctx{n} = 1;
return %ctx;
}

is_deeply(
\@TestAdapters::STRUCTURED_LOG,
[
{ messages => ['some info'], level => 'info', category => 'main',
data => [ {
'line' => 15,
'cat' => 'main',
'lvl' => 'info',
'file' => 't/hooks.t',
'n' => 1,
}],
},
{ messages => ['more info'], level => 'info', category => 'main',
data => [ {
'line' => 16,
'cat' => 'main',
'lvl' => 'info',
'file' => 't/hooks.t',
'n' => 1,
}],
},
{ messages => ['info {with => "data"} and more text'],
level => 'info',
category => 'main',
data => [
{
'line' => 17,
'cat' => 'main',
'lvl' => 'info',
'file' => 't/hooks.t',
'n' => 1,
},
],
},
{ messages => ['program started'],
level => 'debug',
category => 'main',
data => [
{
perl_version => "5.20.0", progname => "foo.pl", pid => 1234,
'line' => 18,
'cat' => 'main',
'lvl' => 'debug',
'file' => 't/hooks.t',
'n' => 1,
}
]
},
],
'identical output of normal log lines when using structured log adapter'
);

0 comments on commit 2179a83

Please sign in to comment.