use strict;
use 5.008_005;
our $VERSION = '0.04';
use Moo;
use File::Basename 'basename';
has tag_prefix => (
is => 'lazy',
isa => Str,
);
has host => (
is => 'ro',
isa => Str,
);
has port => (
is => 'ro',
isa => Str,
);
has timeout => (
is => 'ro',
isa => Num,
predicate => '_has_timeout',
);
has socket => (
is => 'ro',
isa => Str,
);
has prefer_integer => (
is => 'ro',
isa => Str,
predicate => '_has_prefer_integer',
);
has event_time => (
is => 'ro',
isa => Bool,
);
has buffer_limit => (
is => 'ro',
isa => Int,
predicate => '_has_buffer_limit',
);
has buffer_overflow_handler => (
is => 'ro',
isa => CodeRef,
predicate => '_has_buffer_overflow_handler',
);
has truncate_buffer_at_overflow => (
is => 'ro',
isa => Bool,
);
sub _build_tag_prefix {
my $self = shift;
return $self->{tag_prefix} || $self->app_name || $ENV{DANCER_APPDIR} || basename($0);
}
sub _connect {
my $self = shift;
return defined $self->socket
? IO::Socket::UNIX->new( Peer => $self->socket )
: IO::Socket::INET->new(
PeerAddr => $self->host || '127.0.0.1',
PeerPort => $self->port || 24224,
Proto => 'tcp',
Timeout => $self->_has_timeout ? $self->timeout : 3.0,
ReuseAddr => 1,
);
}
sub _fluent {
my $self = shift;
return unless $self->_connect;
unless ( exists $self->{_fluent} ) {
$self->{_fluent} = Fluent::Logger->new(
host => $self->host || '127.0.0.1',
port => $self->port || 24224,
timeout => $self->_has_timeout ? $self->timeout : 3.0,
socket => $self->socket,
prefer_integer => $self->_has_prefer_integer ? $self->prefer_integer : 1,
event_time => $self->event_time || 0,
buffer_limit => $self->_has_buffer_limit ? $self->buffer_limit : 8388608,
buffer_overflow_handler => $self->_has_buffer_overflow_handler ? $self->buffer_overflow_handler : sub { undef },
truncate_buffer_at_overflow => $self->truncate_buffer_at_overflow || 0,
);
}
return $self->{_fluent};
}
sub DESTROY {
my $self = shift;
return unless $self->_fluent;
$self->_fluent->{pending} ||= ''; # Fluent::Logger->close performs length checks without checking if value is defined first
$self->_fluent->close;
}
sub log {
my ($self, $level, $message) = @_;
my $fluent_message = {
env => $ENV{DANCER_ENVIRONMENT} || $ENV{PLACK_ENV} || 'development',
timestamp => Time::Moment->now_utc->strftime("%Y-%m-%dT%H:%M:%S.%6N%Z"),
host => hostname(),
level => $level,
message => $message,
pid => $$
};
# Queue pending messages until connectivity is restored
unless ( $self->_fluent ) {
push @{ $self->{pending} }, $fluent_message;
return;
}
if ( @{ $self->{pending} } ) {
while ( my $pending_message = shift @{ $self->{pending} } ) {
$self->_fluent->post( $self->tag_prefix, $pending_message );
}
}
$self->_fluent->post( $self->tag_prefix, $fluent_message );
}
1;
__END__
=encoding utf-8
=head1 NAME
Dancer2::Logger::Fluent - Dancer2 logger engine for Fluent::Logger
=head1 VERSION
version 0.04
=head1 SYNOPSIS
use Dancer2::Logger::Fluent;
=head1 DESCRIPTION
Implements a structured event logger for Fluent via L<Fluent::Logger>.
When a connection to the C<fluentd> agent can't be established, messages
are "queued" internally. These messages will be flushed upon subsequent
calls to C<log()>, as soon as a connection is established.
=head1 METHODS
=head2 log($level, $message)
Writes the log message to Fluent.
=head1 CONFIGURATION
The setting B<logger> should be set to C<Fluent> in order to use this logging
engine in a Dancer2 application.
Below is a simple sample configuration:
logger: "Fluent"
engines:
logger:
Fluent:
tag_prefix: "myapp"
host: "127.0.0.1"
port: 24224
The full list of allowed options are as follows:
=over 4
=item tag_prefix
Tag prepended to every message, defaults to the configured I<appname> or,
if not defined, to the executable's basename.
=item host
Host running the C<fluentd> agent, defaults to '127.0.0.1'.
=item port
Port listened by the C<fluentd> agent, defaults to 24224.
=item timeout
Timeout in seconds, defaults to 3.0 as implemented in
L<Fluent::Logger>.
=item socket
Socket file location, defaults to undef as implemented in
L<Fluent::Logger>.
=item prefer_integer
Whether integer is preferred as cascaded to
Data::MessagePack->prefer_integer. Defaults to 1.
=item event_time
Whether event timestamps (includes nanoseconds as supported by
C<fluentd> >= 0.14.0) will be included. Defaults to 0.
=item buffer_limit
Buffer size limit, defaults to 8388608 (8MB) as implemented in
L<Fluent::Logger>.
=item buffer_overflow_handler
Custom coderef to handle buffer overflow in the event of connection
failure, to mitigate loss of data in the event of connection failure.
=item truncate_buffer_at_overflow
When I<truncate_buffer_at_overflow> is true and pending buffer size is
larger than I<buffer_limit>, pending buffer will still be kept but last
message will not be sent and will not be appended to the buffer.
Defaults to 0.
=back
=head1 MESSAGE FORMAT
Messages to C<fluentd> will be a hash containing the following:
{
env => $environment,
timestamp => $current_timestamp,
host => $hostname,
level => $level,
message => $message,
pid => $$
}
=head1 AUTHOR
Arnold Tan Casis E<lt>atancasis@cpan.orgE<gt>
=head1 COPYRIGHT
Copyright 2017- Arnold Tan Casis
=head1 LICENSE
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 SEE ALSO
See L<Dancer2> for details about logging in route handlers.
See L<http://fluent.github.com> for details on C<fluentd> itself.
=cut