2

I have a mojolicious web app that uses Log4perl for logging. It is a multi user application and sometimes it is difficult to follow the various threads in log file when more than one user is accessing the application. What I would like to do is have each user (the population is under 25 users) activity be logged to a separate file. E.g. ./log/userX.log ./log/userY.log, etc.

I thought about using something like this in the config file: log4perl.appender.MAIN.filename=sub { return get_user_filename(); } but the logger is defined in the Mojolicious startup subroutine and the user isn't known until request time.

Another idea that seems more promising, is to write a bridge route that creates an appender for the user and then assigns that to the logger. I could then cache the appender for later reuse (or destroy and recreate).

I'll be playing with the second option, but if anyone has tried to do this before and wants to share their wisdom, I would appreciate it.

-- Update -- So in my bridge route I'm doing the following:

my $user = $self->req->headers->header('authuser'); # from apache
my $appender = Log::Log4perl::Appender->new(
    'Log::Log4perl::Appender::File',
    name => $user . "_file_appender",
    filename => "/tmp/$user.log",
    mode => "append",
);
$appender->layout($layout); # previously defined
$appender->level($loglevel); # again previously defined and omitted for brevity
Log::Log4perl::get_logger($user)->add_appender($appender);
$self->app->log(Log::Log4perl::get_logger($user));

I'm getting the following error, however:

Can't locate object method File:() in Log::Log4perl::Appender at       /usr/local/share/perl/5.14.2/Log/Log4perl/Appender.pm line 282, <DATA> line 747.

/tmp/user.log is being created, though (zero length) Latest CPAN instal of Log::Log4perl. Any ideas?

Todd
  • 698
  • 6
  • 19
  • 1
    Have you considered logging to DBI? That would make it easy to split/filter by user after the event. – Richard Huxton Aug 21 '13 at 19:03
  • Yes, but I'm not using a DBI database. (I know, I could choose from a dozen different DB's with different modules) Files are my desire, easy to read and easy to manipulate. – Todd Aug 21 '13 at 19:24
  • Your error is probably due to a typo. It's looking for "File:" on the Appender class. Presumably a mis-spelt "::File" somewhere. – Richard Huxton Aug 21 '13 at 22:47
  • Thanks, but I have triple checked that. See the first param to Log::Log4perl::Appender - 'Log::Log4perl::Appender::File' The ':" after File in the error message is from The Appener.pm die message. – Todd Aug 21 '13 at 23:13
  • Not as far as I can see. It's just "$AUTOLOAD()" - https://metacpan.org/source/MSCHILLI/Log-Log4perl-1.42/lib/Log/Log4perl/Appender.pm – Richard Huxton Aug 22 '13 at 05:52

1 Answers1

1

I liked Richard's comment and already wrote this up before you said you didn't want to go the DB route. So I include it for others. Aside: I think DB logging is pretty heavy handed sometimes and I personally would typically choose files too.

Somewhere in the top of the request/dispatch cycle (Catalyst example)-

Log::Log4perl::MDC->put( "user", $ctx->user_exists ? $ctx->user->id : 0 );

Then in the Log4perl config-

log4perl.appender.toDBI                   = Log::Log4perl::Appender::DBI
log4perl.appender.toDBI.Threshold         = INFO
log4perl.appender.toDBI.layout            = Log::Log4perl::Layout::NoopLayout
log4perl.appender.toDBI.datasource        = sub { "DBI:mysql:" . db_name_function() }
log4perl.appender.toDBI.attrs.f_encoding  = utf8
log4perl.appender.toDBI.username          = db_username
log4perl.appender.toDBI.password          = s3cr37
log4perl.appender.toDBI.sql               = INSERT INTO toDBI \
                                                         ( user, file, line, message ) \
                                                  VALUES ( ?, ?, ?, ? )
log4perl.appender.toDBI.usePreparedStmt   = 1
log4perl.appender.toDBI.params.1          = %X{user}
log4perl.appender.toDBI.params.2          = %F
log4perl.appender.toDBI.params.3          = %L
log4perl.appender.toDBI.params.4          = %m

As far as files go, I think it might be possible but won't be much fun, seems likely to introduce bugs, and is probably overdone. It is trivial to add something like user:{userid} to your logging and then grep/ack the logfile with that to get exactly the user's request/log thread.

Ashley
  • 4,307
  • 2
  • 19
  • 28
  • Thanks for the suggestions. I may have to fall back to using user param and grep, but I feel like my code above is close. Not sure why I'm getting the error I am though. – Todd Aug 21 '13 at 19:45
  • I would try the following and see what happens: inline and simplify the "previously defined" layout, inline the level, change it to a screen log. One of these will likely show where the issue with the file appender lies. – Ashley Aug 22 '13 at 14:09