preaction / Log-Any

Simple, fast Perl logging API compatible with any logging system
Other
13 stars 19 forks source link

Add support for hooks #96

Open mikkoi opened 1 year ago

mikkoi commented 1 year ago

REASON: I create logs in JSON. I want to log things like timestamp, file name and line number in every message. These change on every row. Context is very useful but to make it truly useful, there should be a way to generate it automatically. Using filter does not fill this need because filter() is not applied with structured logging. Structured logging is what I need to use when writing logs with JSON.

Hooks could fill this need. Besides, if we later decide to add more hooks, we can do it easily and in a compatible way.

EXPLANATION: 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 every time 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 useing 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;
mikkoi commented 1 year ago

Your custom adapter would also be able to add the timestamp/caller info to Log::Any when it's being used by other CPAN modules. As it is, adding a hook to a proxy would only add the information to that specific proxy, not when another module gets its own proxy.

Yes, you are right. I see that now. The context is not (might not be) the right place for technical information. Adapter is. For example, I have been thinking about using epoch (gmtime) in logs to reduce the overhead, and then make the logviewer translate those values into (local) timestamps so the viewer can enjoy localised time and date, instead of being forced to convert the timestamps in his mind when reading.

Having the timestamps added in the Adapter, the user, not the designer, can decide when to use them.

But the caller() info is another matter, I think. The Adapter would need to know how many layers of stack to go back to. We already have the problem that using tracef() (or equivalent) adds another layer compared with trace(). That's why the code is accommodating to that:

    my $caller = (caller 0)[0] ne 'Log::Any::Proxy'
                && (caller 0)[3] eq 'Log::Any::Proxy::__ANON__'
            ? [ caller 0 ] : [ caller 1 ];

Running the caller in Adapter would mean that we can't change the internals of Log::Any any more.

Unless, we provide the stack level number to the hook instead of the whole caller info... [?]

mikkoi commented 1 year ago

I moved the caller() stuff to Log::Any::Adapter::Util. Maybe it works better there...

mikkoi commented 1 year ago

Rebased to master, applied version 1.715.