File Coverage

blib/lib/Trickster/Logger.pm
Criterion Covered Total %
statement 16 50 32.0
branch 1 10 10.0
condition 3 6 50.0
subroutine 7 15 46.6
pod 5 8 62.5
total 32 89 35.9


line stmt bran cond sub pod time code
1             package Trickster::Logger;
2              
3 5     5   34 use strict;
  5         10  
  5         207  
4 5     5   22 use warnings;
  5         9  
  5         221  
5 5     5   57 use v5.14;
  5         14  
6              
7 5     5   3032 use Time::Piece;
  5         69795  
  5         24  
8              
9             our %LEVELS = (
10             debug => 0,
11             info => 1,
12             warn => 2,
13             error => 3,
14             fatal => 4,
15             );
16              
17             sub new {
18 7     7 0 2986 my ($class, %opts) = @_;
19            
20             return bless {
21             level => $opts{level} || 'info',
22             output => $opts{output} || \*STDERR,
23 7   50     155 formatter => $opts{formatter} || \&_default_formatter,
      50        
      50        
24             }, $class;
25             }
26              
27 0     0 1 0 sub debug { shift->_log('debug', @_) }
28 0     0 1 0 sub info { shift->_log('info', @_) }
29 0     0 1 0 sub warn { shift->_log('warn', @_) }
30 1     1 1 5 sub error { shift->_log('error', @_) }
31 0     0 1 0 sub fatal { shift->_log('fatal', @_) }
32              
33             sub _log {
34 1     1   4 my ($self, $level, $message, %context) = @_;
35            
36 1 50       23 return if $LEVELS{$level} < $LEVELS{$self->{level}};
37            
38 0           my $formatted = $self->{formatter}->($level, $message, \%context);
39            
40 0           my $fh = $self->{output};
41 0           print $fh $formatted, "\n";
42             }
43              
44             sub _default_formatter {
45 0     0     my ($level, $message, $context) = @_;
46            
47 0           my $timestamp = localtime->strftime('%Y-%m-%d %H:%M:%S');
48 0           my $level_str = uc($level);
49            
50 0           my $line = "[$timestamp] [$level_str] $message";
51            
52 0 0         if (%$context) {
53 0           my @parts;
54 0           for my $key (sort keys %$context) {
55 0           push @parts, "$key=$context->{$key}";
56             }
57 0           $line .= " {" . join(', ', @parts) . "}";
58             }
59            
60 0           return $line;
61             }
62              
63             sub set_level {
64 0     0 0   my ($self, $level) = @_;
65 0           $self->{level} = $level;
66             }
67              
68             sub middleware {
69 0     0 0   my ($self) = @_;
70            
71             return sub {
72 0     0     my $app = shift;
73            
74             return sub {
75 0           my $env = shift;
76            
77 0           my $start = time;
78 0           my $method = $env->{REQUEST_METHOD};
79 0           my $path = $env->{PATH_INFO};
80            
81             $self->info("Request started",
82             method => $method,
83             path => $path,
84             remote_addr => $env->{REMOTE_ADDR},
85 0           );
86            
87 0           my $res = $app->($env);
88            
89 0           my $duration = time - $start;
90 0 0         my $status = ref($res) eq 'ARRAY' ? $res->[0] : 'unknown';
91            
92 0 0         my $level = $status >= 500 ? 'error' :
    0          
93             $status >= 400 ? 'warn' : 'info';
94            
95 0           $self->$level("Request completed",
96             method => $method,
97             path => $path,
98             status => $status,
99             duration => sprintf('%.3fs', $duration),
100             );
101            
102 0           return $res;
103 0           };
104 0           };
105             }
106              
107             1;
108              
109             __END__