package Object::Remote::Logging::Logger;
use Moo;
-use Scalar::Util qw(weaken);
+use Carp qw(croak);
+#TODO sigh invoking a logger with a log level name the same
+#as an attribute could happen - restrict attributes to _ prefix
+#and restrict log levels to not start with out that prefix?
has format => ( is => 'ro', required => 1, default => sub { '%l: %s' } );
has level_names => ( is => 'ro', required => 1 );
-has min_level => ( is => 'ro', required => 1, default => 'info' );
+has min_level => ( is => 'ro', required => 1, default => sub { 'info' } );
has max_level => ( is => 'lazy', required => 1 );
has _level_active => ( is => 'lazy' );
-sub BUILD {
- my ($self) = @_;
- our $METHODS_INSTALLED;
- $self->_install_methods unless $METHODS_INSTALLED;
+#just a stub so it doesn't get to AUTOLOAD
+sub BUILD { }
+sub DESTROY { }
+
+sub AUTOLOAD {
+ my $self = shift;
+ (my $method) = (our $AUTOLOAD =~ /([^:]+)$/);
+
+ no strict 'refs';
+
+ if ($method =~ m/^_/) {
+ croak "invalid method name $method for " . ref($self);
+ }
+
+ if ($method =~ m/^is_(.+)/) {
+ my $level_name = $1;
+ my $is_method = "is_$level_name";
+ *{$is_method} = sub { shift(@_)->_level_active->{$level_name} };
+ return $self->$is_method;
+ }
+
+ my $level_name = $method;
+ *{$level_name} = sub {
+ my $self = shift;
+ unless(exists($self->_level_active->{$level_name})) {
+ croak "$level_name is not a valid log level name";
+ }
+
+ $self->_log($level_name, @_);
+ };
+
+ return $self->$level_name(@_);
}
sub _build_max_level {
- my ($self) = @_;
- return $self->level_names->[-1];
+ my ($self) = @_;
+ return $self->level_names->[-1];
}
sub _build__level_active {
- my ($self) = @_;
+ my ($self) = @_;
my $should_log = 0;
my $min_level = $self->min_level;
my $max_level = $self->max_level;
my %active;
-
+
foreach my $level (@{$self->level_names}) {
if($level eq $min_level) {
- $should_log = 1;
+ $should_log = 1;
}
$active{$level} = $should_log;
-
+
if (defined $max_level && $level eq $max_level) {
$should_log = 0;
}
return \%active;
}
-sub _install_methods {
- my ($self) = @_;
- my $should_log = 0;
- our $METHODS_INSTALLED = 1;
-
- no strict 'refs';
-
- foreach my $level (@{$self->level_names}) {
- *{"is_$level"} = sub { shift(@_)->_level_active->{$level} };
- *{$level} = sub { shift(@_)->_log($level, @_) };
- }
-}
-
sub _log {
my ($self, $level, $content, $metadata_in) = @_;
my %metadata = %$metadata_in;
sub _create_format_lookup {
my ($self, $level, $metadata, $content) = @_;
my $method = $metadata->{method};
-
+
$method = '(none)' unless defined $method;
-
- return {
- '%' => '%', t => $self->_render_time($metadata->{timestamp}),
+
+ return {
+ '%' => '%', 'n' => "\n",
+ t => $self->_render_time($metadata->{timestamp}),
r => $self->_render_remote($metadata->{object_remote}),
- s => $self->_render_log(@$content), l => $level,
- c => $metadata->{controller}, p => $metadata->{package}, m => $method,
- f => $metadata->{filename}, i => $metadata->{line},
+ s => $self->_render_log(@$content), l => $level,
+ c => $metadata->{exporter}, p => $metadata->{caller_package}, m => $method,
+ f => $metadata->{filename}, i => $metadata->{line},
h => $metadata->{hostname}, P => $metadata->{pid},
};
}
sub _render_remote {
my ($self, $remote) = @_;
- return 'local' if ! defined $remote || ! defined $remote->{connection_id};
- return 'remote #' . $remote->{connection_id};
+ return 'local' unless defined $remote;
+ my $conn_id = $remote->{connection_id};
+ $conn_id = '(uninit)' unless defined $conn_id;
+ return "remote #$conn_id";
}
sub _render_log {
my ($self, $level, $metadata, @content) = @_;
my $var_table = $self->_create_format_lookup($level, $metadata, [@content]);
my $template = $self->format;
-
+
$template =~ s/%([\w%])/$self->_get_format_var_value($1, $var_table)/ge;
-
+
chomp($template);
$template =~ s/\n/\n /g;
$template .= "\n";
use Object::Remote::Logging::Logger;
use Object::Remote::Logging qw( router arg_levels );
-
+
my $app_output = Object::Remote::Logging::Logger->new(
level_names => arg_levels, format => '%t %s',
min_level => 'verbose', max_level => 'info',
#objects that will receive the messages
my $selector = sub {
my ($generating_package, $metadata) = @_;
- return unless $metadata->{controller} eq 'App::Logging::Subclass';
+ return unless $metadata->{exporter} eq 'App::Logging::Subclass';
return $app_output;
};
#disconnect the selector from the router
undef($selector);
-
+
#router will hold this logger forever
#and send it all log messages
- router->connect(Object::Remote::Logging::WarnLogger->new(
+ router->connect(Object::Remote::Logging::Logger->new(
level_names => arg_levels, format => '%s at %f line %i, log level: %l'
min_level => 'warn', max_level => 'error',
));
=head1 DESCRIPTION
-This class receives log messages from an instance of L<Object::Remote::Log::Router>,
+This class receives log messages from an instance of L<Object::Remote::Logging::Router>,
formats them according to configuration, and then outputs them to STDERR. In between
the router and the logger is a selector method which inspects the log message metadata
and can return 0 or more loggers that should receive the log message.
A logger object receives the log messages that are generated and converts them to
formatted log entries then displays them to the end user. Each logger has a set
of active log levels and will only output a log entry if the log message is at
-an active log level.
+an active log level.
To gain access to the stream of log messages a connection is made to the log router.
A logger can directly connect to the router and receive an unfiltered stream of
log messages or a selector closure can be used instead. The selector will be executed
for each log message with the message metadata and returns a list of 0 or more loggers
that should receive the log message. When the selector is executed the first argument
-is the class name of the package that generated the log message and the second argument
+is the name of the package that generated the log message and the second argument
is a hash reference containing the message metadata.
=head1 METADATA
=over 4
-=item level
+=item message_level
Name of the log level of the message.
-=item controller
+=item exporter
-Name of the sub-class of Object::Remote::Logging in use by
-the generating package.
+Package name of the logging API that was used to generate the log message.
-=item package
+=item caller_package
Name of the package that generated the log message.
=item level_names
This is a required attribute. Must be an array ref with the list of log level names
-in it. The list must be ordered with the lowest level as element 0 and the highest
+in it. The list must be ordered with the lowest level as element 0 and the highest
level as the last element. There is no default value.
=item min_level
=item %s
-Log message rendered into a string with a leading space before any additional lines in a
+Log message rendered into a string with a leading space before any additional lines in a
multiple line message.
=item %t
=item %r
-Log::Remote connection information rendered into a string.
+Object::Remote connection information rendered into a string.
=item %c
-Name of the sub-class of Object::Remote::Logging that was used by the class
-that generated the log message. Can also be Object::Remote::Logging itself.
+Package name of the logging API that was used to generate the log message.
=item %p
-Package name of the class that generated the log message.
+Name of the package that generated the log message.
=item %m
A literal %.
+=item %n
+
+A newline.
+
=back
=back