The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Dancer2::Logger::File;
# ABSTRACT: file-based logging engine for Dancer2
$Dancer2::Logger::File::VERSION = '0.152000';
use Carp 'carp';
use Moo;
use Dancer2::Core::Types;

with 'Dancer2::Core::Role::Logger';

use File::Spec;
use Fcntl qw(:flock SEEK_END);
use Dancer2::FileUtils qw(open_file);
use IO::File;

# FIXME: this is not a good way to do this
has environment => (
    is      => 'ro',
    lazy    => 1,
    default => sub { $_[0]->app->environment },
);

# FIXME: this is not a good way to do this
has location => (
    is      => 'ro',
    lazy    => 1,
    default => sub { $_[0]->app->config_location },
);

has log_dir => (
    is      => 'rw',
    isa     => Str,
    lazy    => 1,
    builder => '_build_log_dir',
    trigger => sub {
        my ( $self, $dir ) = @_;
        if ( !-d $dir && !mkdir $dir ) {
            return carp
              "Log directory \"$dir\" does not exist and unable to create it.";
        }
        return carp "Log directory \"$dir\" is not writable." if !-w $dir;
    },
);

has file_name => (
    is      => 'ro',
    isa     => Str,
    builder => '_build_file_name',
    lazy    => 1
);

has log_file => (
    is      => 'ro',
    isa     => Str,
    lazy    => 1,
    builder => '_build_log_file',
);

has fh => (
    is      => 'ro',
    lazy    => 1,
    builder => '_build_fh',
);

sub _build_log_dir {File::Spec->catdir( $_[0]->location, 'logs' )}

sub _build_file_name {$_[0]->environment . ".log"}

sub _build_log_file {
    my $self = shift;
    return File::Spec->catfile( $self->log_dir, $self->file_name );
}

sub _build_fh {
    my $self    = shift;
    my $logfile = $self->log_file;

    my $fh;
    unless ( $fh = open_file( '>>', $logfile ) ) {
        carp "unable to create or append to $logfile";
        return;
    }

    $fh->autoflush;

    return $fh;
}

sub log {
    my ( $self, $level, $message ) = @_;
    my $fh = $self->fh;

    return unless ( ref $fh && $fh->opened );

    flock( $fh, LOCK_EX )
      or carp "locking logfile $self->{logfile} failed: $!";
    seek( $fh, 0, SEEK_END );
    $fh->print( $self->format_message( $level => $message ) )
      or carp "writing to logfile $self->{logfile} failed";
    flock( $fh, LOCK_UN )
      or carp "unlocking logfile $self->{logfile} failed: $!";
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Dancer2::Logger::File - file-based logging engine for Dancer2

=head1 VERSION

version 0.152000

=head1 DESCRIPTION

This is a logging engine that allows you to save your logs to files on disk.

Logs are not automatically rotated.  Use a log rotation tool like
C<logrotate> in C<copytruncate> mode.

=head1 METHODS

=head2 log($level, $message)

Writes the log message to the file.

=head1 CONFIGURATION

The setting C<logger> should be set to C<File> in order to use this logging
engine in a Dancer2 application.

The follow attributes are supported:

=for :list * log_dir -- directory path to hold log files. Defaults to F<logs> in the application directory
* file_name -- the name of the log file. Defaults to the environment name with a F<.log> suffix

Here is an example configuration that use this logger and stores logs in F</var/log/myapp>:

  logger: "File"

  engines:
    logger:
      File:
        log_dir: "/var/log/myapp"
        file_name: "myapp.log"

=head1 METHODS

=head1 AUTHOR

Dancer Core Developers

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Alexis Sukrieh.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut