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?

5
  • 1
    Have you considered logging to DBI? That would make it easy to split/filter by user after the event. Commented Aug 21, 2013 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. Commented Aug 21, 2013 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. Commented Aug 21, 2013 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. Commented Aug 21, 2013 at 23:13
  • Not as far as I can see. It's just "$AUTOLOAD()" - metacpan.org/source/MSCHILLI/Log-Log4perl-1.42/lib/Log/Log4perl/… Commented Aug 22, 2013 at 5:52

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.