File Coverage

blib/lib/Class/Easy/Log.pm
Criterion Covered Total %
statement 87 103 84.4
branch 25 34 73.5
condition 19 30 63.3
subroutine 15 17 88.2
pod 0 8 0.0
total 146 192 76.0


line stmt bran cond sub pod time code
1             package Class::Easy::Log;
2             # $Id: Log.pm,v 1.3 2009/07/20 18:00:10 apla Exp $
3              
4 6     6   31 use Class::Easy::Import;
  6         39  
  6         38  
5 6     6   5867 use Class::Easy::Log::Tie;
  6         13  
  6         175  
6 6     6   31 use Class::Easy ();
  6         11  
  6         14439  
7              
8             # log4perl has categories, layouts and appenders
9             our $default_layout = '[%P] [%M(%L)] [%c] %m%n';
10              
11             Class::Easy::make_accessor (__PACKAGE__, 'category');
12             Class::Easy::make_accessor (__PACKAGE__, 'tied');
13             Class::Easy::make_accessor (__PACKAGE__, 'layout');
14              
15             my $driver_config = {};
16             our $int_loggers = {
17             default => bless {
18             category => 'default', broker => '', tied => 0
19             }, __PACKAGE__
20             };
21              
22             my $java_mappings = {
23             L => 'line',
24             P => 'pid',
25             r => 'ts_start',
26             R => 'ts_log',
27             c => 'category',
28             C => 'package',
29             d => 'date',
30             F => 'file',
31             H => 'hostname',
32             l => 'where',
33             m => 'message',
34             M => 'method',
35             n => 'newline',
36             p => 'priority',
37             T => 'stack',
38             };
39              
40             our $hostname;
41             if (Class::Easy::try_to_use ('Sys::Hostname')) {
42             $hostname = Sys::Hostname->can('hostname')->();
43             }
44              
45             Class::Easy::Log->configure_driver (
46             id => 'log4perl', package => 'Log::Log4perl', constructor => 'get_logger',
47             log => 'debug', # default logging level
48            
49             );
50              
51             # basic logger: logger ('sql');
52             # log4perl logger: logger (log4perl => 'sql');
53             # also you'll need to configure log4perl somewhere:
54             # Log::Log4perl::init (...);
55             # Class::Easy::Log->configure_driver (
56             # type => 'log4perl', package => 'Log::Log4perl', constructor => 'get_logger'
57             # );
58              
59             sub configure_driver {
60 6     6 0 14 my $class = shift;
61 6         27 my $params = {@_};
62            
63 6 50       27 if (Class::Easy::try_to_use ($params->{package})) {
64 0         0 $driver_config->{$params->{id}} = $params;
65             }
66             }
67              
68             sub logger { # create logger
69            
70 14     14 0 2984 my $driver_id;
71             my $category;
72 0         0 my $appender;
73            
74 0         0 my $ref;
75            
76 14 100       51 if (defined $_[1]) {
77 2         6 $ref = ref \$_[1];
78             }
79            
80 14 100 66     207 unless (@_) { # if type omitted, we use current package name as type
  2 100 66     13  
    50 33        
    50 33        
81 1         4 $category = (caller)[0];
82             } elsif (scalar (@_) == 2 and $ref eq 'GLOB' and defined *{$_[1]}{IO}) {
83 2         4 $category = $_[0];
84 2         5 $appender = $_[1];
85             } elsif ((@_ == 2 or @_ == 1) and exists $driver_config->{$_[0]}) {
86 0         0 $driver_id = $_[0];
87 0 0       0 $category = @_ == 1 ? (caller)[0] : $_[1];
88             } elsif (@_ == 1) {
89 11         30 $category = $_[0];
90             } else {
91 0         0 die "you must use logger (), logger (driver), logger (category) or logger (driver => category)";
92             }
93            
94 14         18 my $self;
95            
96 14 50       31 unless (defined $driver_id) { # basic internal driver require no processing
    0          
97            
98 14         36 my $existing_logger = $int_loggers->{$category};
99            
100 14   100     88 $self = $existing_logger || bless {
101             category => $category,
102             broker => '',
103             }, 'Class::Easy::Log';
104            
105 14 100       40 unless (defined $existing_logger) {
106 5         12 $int_loggers->{$category} = $self;
107            
108             Class::Easy::make_accessor ((caller)[0], 'log_'.$category, default => sub {
109 7     7   19 my $caller1 = [caller (1)];
110 7         24 my $caller0 = [caller];
111              
112 7         21 unshift @_, $category, $self, $caller1, $caller0;
113 7         21 goto &_wrapper;
114 5         81 });
115              
116             Class::Easy::make_accessor ((caller)[0], 'timer_'.$category, default => sub {
117 1     1   11 Class::Easy::Timer->new (@_, $self)
118 5         94 });
119             }
120              
121             } elsif (defined $driver_config->{$driver_id}) { # driver defined
122 0         0 my $driver = $driver_config->{$driver_id};
123 0         0 $self = $driver->{package}->can ($driver->{constructor})->($driver->{package}, $category);
124              
125             Class::Easy::make_accessor ((caller)[0], 'log_'.$category, default => sub {
126 0     0   0 goto &{$self->can ($driver->{log})};
  0         0  
127 0         0 });
128            
129             # make_accessor ((caller)[0], 'log_'.$type, default => \&Class::Easy::Log::message);
130             }
131            
132 14 100       40 if ($appender) {
133 2         4 $self->appender ($appender);
134             }
135            
136 14         93 return $self;
137             }
138              
139             sub appender {
140 13     13 0 34 my $self = shift;
141             # my $appender = shift;
142            
143 13 100       78 if (@_) {
144 9         21 $self->{tied} = 1;
145 9         63 tie $self->{broker} => 'Class::Easy::Log::Tie', $_[0];
146             } else {
147 4         9 $self->{tied} = 0;
148 4         46 untie $self->{broker};
149             }
150              
151             }
152              
153             # example usage:
154             # logger (sql); # create sub log_sql
155             # log_sql ('message'); # log message, but nobody receive this message
156             # logger (sql => 'STDERR'); # now any log messages go to the STDERR
157              
158             sub _parse_layout {
159 24     24   873 my $logger = shift;
160            
161 24   66     139 $logger->{layout} ||= $default_layout;
162            
163 24 100 66     150 return $logger
164             if defined $logger->{_layout} and $logger->{layout} eq $logger->{_layout};
165            
166 10         16 my $layout = $logger->{layout};
167            
168 10         18 my $layout_format = '';
169 10         20 my @layout_fields = ();
170 10         75 while ($layout =~ /([^\%]*)\%([^\%cCdFHlLmMnpPrRTxX]*)([\%cCdFHlLmMnpPrRTxX])/g) {
171            
172 58         133 $layout_format .= "$1\%$2";
173 58 100 100     417 if ($3 eq 'L' or $3 eq 'P') {
    50 33        
    100          
174 18         29 $layout_format .= 'd';
175             } elsif ($3 eq 'r' or $3 eq 'R') {
176 0         0 $layout_format .= 'd';
177             } elsif ($3 eq '%') {
178 1         2 $layout_format .= '%';
179             } else {
180 39         49 $layout_format .= 's';
181             }
182 58 100       335 push @layout_fields, $java_mappings->{$3}
183             unless $3 eq '%';
184             }
185             # TODO: create more failsafe solution
186 10         47 $layout_format .= substr ($layout, length($layout_format));
187            
188 10         22 $logger->{_layout_format} = $layout_format;
189 10         23 $logger->{_layout_fields} = \@layout_fields;
190 10         24 $logger->{_layout} = $layout;
191            
192 10         33 return $logger;
193             }
194              
195             sub _format_log {
196 22     22   32 my $self = shift;
197            
198 22         69 my $time = time;
199            
200 22         394 my $values = {
201             pid => $$,
202             category => $self->{category},
203             newline => "\n",
204             ts_start => $time - $^T,
205             hostname => $hostname, # doesn't reflect hostname changes in runtime
206             date => $time,
207             @_
208             };
209              
210             # TODO: make sure all these values supported
211             # R => 'ts_log', # use timer_${logger} instead
212             # C => 'package', # useless, because we have %M = method
213             # F => 'file', # who cares about script files?
214             # l => 'where', # wtf?
215             # p => 'priority', # log level, if written not for robots
216             # T => 'stack', # everything loves java stacks
217             # TODO: add date formatting support
218            
219             # use Data::Dumper;
220             # warn Dumper $self->{_layout_fields};
221             # warn Dumper [map {$values->{$_}} @{$self->{_layout_fields}}];
222            
223             # warn $self->{_layout_format}, join (', ', @{$self->{_layout_fields}}), (join ', ', map {
224             # $values->{$_}
225             # } @{$self->{_layout_fields}});
226            
227 132         462 return sprintf ($self->{_layout_format}, (map {
228 22         57 $values->{$_}
229 22         47 } @{$self->{_layout_fields}}));
230            
231             }
232              
233             sub _wrapper {
234 21     21   49 my $category = shift;
235 21         35 my $logger = shift;
236 21         37 my $caller1 = shift;
237 21         33 my $caller0 = shift;
238            
239 21   100     110 my $sub = $caller1->[3] || 'main';
240 21         39 my $line = $caller0->[2];
241            
242             # my ($package, $filename, $line, $subroutine, $hasargs, $wantarray,
243             # $evaltext, $is_require, $hints, $bitmask)
244            
245 21         245 $logger->_parse_layout;
246            
247 21         118 $logger->{broker} = $logger->_format_log (
248             message => join ('', @_),
249             method => $sub,
250             line => $line
251             );
252            
253 21         109 return 1;
254             }
255              
256             sub debug {
257 10     10 0 152 my $caller1 = [caller (1)];
258 10         41 my $caller0 = [caller];
259              
260 10         107 unshift @_, 'default', $int_loggers->{default}, $caller1, $caller0;
261              
262 10         52 goto &_wrapper;
263             }
264              
265             sub debug_depth {
266 0     0 0 0 my $caller1 = [caller (2)];
267 0         0 my $caller0 = [caller (1)];
268              
269 0         0 unshift @_, 'default', $int_loggers->{default}, $caller1, $caller0;
270              
271 0         0 goto &_wrapper;
272             }
273              
274             sub critical {
275 1   50 1 0 8 my $sub = (caller (1))[3] || 'main';
276 1         3 my $line = (caller)[2];
277            
278 1         6 my $logger = logger ('DIE')->_parse_layout;
279            
280 1         7 die $logger->_format_log (
281             message => join ('', @_),
282             method => $sub,
283             line => $line
284             );
285             }
286              
287             sub catch_stderr {
288 2     2 0 570 my $ref = shift;
289 2         22 tie *STDERR => 'Class::Easy::Log::Tie', $ref;
290             }
291              
292             sub release_stderr {
293 1     1 0 6 untie *STDERR;
294             }
295              
296             1;