File Coverage

blib/lib/Log/ger/Layout/LTSV.pm
Criterion Covered Total %
statement 88 89 98.8
branch 43 48 89.5
condition 5 18 27.7
subroutine 12 12 100.0
pod 0 2 0.0
total 148 169 87.5


line stmt bran cond sub pod time code
1             package Log::ger::Layout::LTSV;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-03-11'; # DATE
5             our $DIST = 'Log-ger-Layout-LTSV'; # DIST
6             our $VERSION = '0.006'; # VERSION
7              
8 1     1   5188 use 5.010001;
  1         3  
9 1     1   5 use strict;
  1         2  
  1         20  
10 1     1   5 use warnings;
  1         2  
  1         25  
11              
12 1     1   498 use Devel::Caller::Util;
  1         406  
  1         43  
13 1     1   6 use Log::ger ();
  1         3  
  1         18  
14 1     1   532 use Time::HiRes qw(time);
  1         1442  
  1         4  
15              
16             our $time_start = time();
17             our $time_now = $time_start;
18             our $time_last = $time_start;
19              
20             sub meta { +{
21 5     5 0 13738 v => 2,
22             } }
23              
24             sub _encode {
25 5     5   10 my ($pkg, $msg) = @_;
26              
27 5         7 my @res;
28 5         23 for my $l (sort keys %$msg) {
29 25         40 my $val = $msg->{$l};
30 25         43 $l =~ s/[:\t\n]+/ /g;
31 25         47 $val =~ s/[\t\n]+/ /g;
32 25         62 push @res, "$l:$val";
33             }
34 5         44 join("\t", @res);
35             }
36              
37             sub _layout {
38 5     5   10 my $pkg = shift;
39 5         38 my ($plugin_conf, $msg0, $per_target_conf, $lnum, $level) = @_;
40              
41 5         18 ($time_last, $time_now) = ($time_now, time());
42 5         10 my %per_message_data;
43              
44             my $msg;
45 5 100       20 if (ref $msg0 eq 'HASH') {
46 3         11 $msg = {%$msg0};
47             } else {
48 2         6 $msg = {message => $msg0};
49             }
50              
51 5 100       14 if ($plugin_conf->{delete_fields}) {
52 1         2 for my $f (@{ $plugin_conf->{delete_fields} }) {
  1         3  
53 2 100       5 if (ref $f eq 'Regexp') {
54 1         3 for my $k (keys %$msg) {
55 2 100       11 delete $msg->{$k} if $k =~ $f;
56             }
57             } else {
58 1         3 delete $msg->{$f};
59             }
60             }
61             }
62              
63 5 100       11 if (my $ff = $plugin_conf->{add_fields}) {
64 1         6 for my $f (keys %$ff) {
65 1         2 $msg->{$f} = $ff->{$f};
66             }
67             }
68              
69 5 100       12 if (my $ff = $plugin_conf->{add_special_fields}) {
70 1         1 my %mentioned_specials;
71 1         6 for my $f (keys %$ff) {
72 15         26 $mentioned_specials{ $ff->{$f} }++;
73             }
74              
75 1 0 33     5 if (
      0        
      0        
76             $mentioned_specials{Class} ||
77             $mentioned_specials{File} ||
78             $mentioned_specials{Line} ||
79             $mentioned_specials{Location}
80             ) {
81             $per_message_data{caller0} =
82 1         5 [Devel::Caller::Util::caller (0, 0, $plugin_conf->{packages_to_ignore}, $plugin_conf->{subroutines_to_ignore})];
83             }
84 1 50 33     86 if (
85             $mentioned_specials{Location} ||
86             $mentioned_specials{Method}
87             ) {
88             $per_message_data{caller1} =
89 1         4 [Devel::Caller::Util::caller (1, 0, $plugin_conf->{packages_to_ignore}, $plugin_conf->{subroutines_to_ignore})];
90             }
91 1 50       106 if ($mentioned_specials{Stack_Trace}) {
92             $per_message_data{callers} =
93 1         6 [Devel::Caller::Util::callers(0, 0, $plugin_conf->{packages_to_ignore}, $plugin_conf->{subroutines_to_ignore})];
94             }
95              
96 1         172 for my $f (keys %$ff) {
97 15         24 my $sf = $ff->{$f};
98 15         19 my $val;
99 15 100       70 if ($sf eq 'Category') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
100 1         2 $val = $per_target_conf->{category};
101             } elsif ($sf eq 'Class') {
102 1         3 $val = $per_message_data{caller0}[0];
103             } elsif ($sf eq 'Date_Local') {
104 1         52 my @t = localtime($time_now);
105 1         10 $val = sprintf(
106             "%04d-%02d-%02dT%02d:%02d:%02d",
107             $t[5]+1900, $t[4]+1, $t[3],
108             $t[2], $t[1], $t[0],
109             );
110             } elsif ($sf eq 'Date_GMT') {
111 1         10 my @t = gmtime($time_now);
112 1         6 $val = sprintf(
113             "%04d-%02d-%02dT%02d:%02d:%02d",
114             $t[5]+1900, $t[4]+1, $t[3],
115             $t[2], $t[1], $t[0],
116             );
117             } elsif ($sf eq 'File') {
118 1         1 $val = $per_message_data{caller0}[1];
119             } elsif ($sf eq 'Hostname') {
120 1         546 require Sys::Hostname;
121 1         1090 $val = Sys::Hostname::hostname();
122             } elsif ($sf eq 'Location') {
123             $val = sprintf(
124             "%s (%s:%d)",
125             $per_message_data{caller1}[3] // '',
126             $per_message_data{caller0}[1],
127 1   50     16 $per_message_data{caller0}[2],
128             );
129             } elsif ($sf eq 'Line') {
130 1         2 $val = $per_message_data{caller0}[2];
131             } elsif ($sf eq 'Message') {
132 1         1 $val = $msg0;
133             } elsif ($sf eq 'Method') {
134 1   50     4 $val = $per_message_data{caller1}[3] // '';
135             } elsif ($sf eq 'Level') {
136 1         2 $val = $level;
137             } elsif ($sf eq 'PID') {
138 1         3 $val = $$;
139             } elsif ($sf eq 'Elapsed_Start') {
140 1         3 $val = $time_now - $time_start;
141             } elsif ($sf eq 'Elapsed_Last') {
142 1         2 $val = $time_now - $time_last;
143             } elsif ($sf eq 'Stack_Trace') {
144 6         21 $val = join(", ", map { "$_->[3] called at $_->[1] line $_->[2]" }
145 1         1 @{ $per_message_data{callers} });
  1         3  
146 0         0 } else { die "Unknown special field '$f'" }
147 15         63 $msg->{$f} = $val;
148             }
149             }
150 5         18 $pkg->_encode($msg);
151             }
152              
153             sub _get_hooks {
154 5     5   10 my $pkg = shift;
155 5         12 my %plugin_conf = @_;
156              
157             $plugin_conf{packages_to_ignore} //= [
158 5   50     32 "Log::ger",
159             "Log::ger::Layout::LTSV",
160             "Try::Tiny",
161             ];
162              
163             return {
164             create_layouter => [
165             $pkg, # key
166             50, # priority
167             sub { # hook
168 10     10   7414 my %hook_args = @_; # see Log::ger::Manual::Internals/"Arguments passed to hook"
169              
170 10         30 my $layouter = sub { $pkg->_layout(\%plugin_conf, @_) };
  5         5666  
171 10         30 [$layouter];
172 5         36 }],
173             };
174             }
175              
176             sub get_hooks {
177 5     5 0 62 __PACKAGE__->_get_hooks(@_);
178             }
179              
180             1;
181             # ABSTRACT: Layout log message as LTSV
182              
183             __END__
184              
185             =pod
186              
187             =encoding UTF-8
188              
189             =head1 NAME
190              
191             Log::ger::Layout::LTSV - Layout log message as LTSV
192              
193             =head1 VERSION
194              
195             This document describes version 0.006 of Log::ger::Layout::LTSV (from Perl distribution Log-ger-Layout-LTSV), released on 2020-03-11.
196              
197             =head1 SYNOPSIS
198              
199             use Log::ger::Layout LTSV => (
200             add_fields => {key3 => 'value', key4 => 'value', ...}, # optional
201             add_special_fields => {_date => 'Date_GMT', _host => 'Hostname', ...}, # optional
202             delete_fields => ['key1', 'key2', qr/some-regex/, ...], # optional
203             );
204             use Log::ger;
205              
206             # if you use it together with Log::ger::Format::None:
207             log_warn({key1 => 'val1', key2 => 'val2', foo => 'bar', ...);
208              
209             # otherwise, using the standard formatter:
210             log_warn("blah %s", ['some', 'data']);
211              
212             The final message will be something like:
213              
214             _date:2017-06-28T14:08:22 _host:example.com foo:bar key3:value key4:value
215              
216             or:
217              
218             _date:2017-06-28T14:08:22 _host:example.com message:blah ["some","data"]
219              
220             =head1 DESCRIPTION
221              
222             This layouter allows you to log message as LTSV row. If you use
223             L<Log::ger::Format::None>, you can pass a hashref. Otherwise, the message will
224             be put in C<message> label. You can then delete keys then add additional
225             fields/keys (including special fields, a la L<Log::ger::Layout::Pattern>).
226              
227             =for Pod::Coverage ^(.+)$
228              
229             =head1 CONFIGURATION
230              
231             =head2 add_fields => hash
232              
233             =head2 add_special_fields => hash
234              
235             Known special fields:
236              
237             Category: Category of the logging event
238             Class: Fully qualified package [or class] name of the caller
239             Date_Local: Current date in ISO8601 format (YYYY-MM-DD<T>hh:mm:ss) (localtime)
240             Date_GMT: Current date in ISO8601 format (YYYY-MM-DD<T>hh:mm:ss) (GMT)
241             File: File where the logging event occurred
242             Hostname: (if Sys::Hostname is available)
243             Location: Fully qualified name of the calling method followed by the
244             callers source the file name and line number between parentheses.
245             Line: Line number within the file where the log statement was issued
246             Message: The message to be logged
247             Method: Method or function where the logging request was issued
248             Level: Level ("priority") of the logging event
249             PID: PID of the current process
250             Elapsed_Start: Number of seconds elapsed from program start to logging event
251             Elapsed_Last: Number of seconds elapsed from last logging event to current
252             logging event
253             Stack_Trace: stack trace of functions called
254              
255             Unknown special fields will cause the layouter to die.
256              
257             =head2 delete_fields
258              
259             =head2 packages_to_ignore
260              
261             Regex or arrayref. When producing caller or stack trace information, will pass
262             this to L<Devel::Caller::Util>'s C<caller()> or C<callers()>.
263              
264             =head2 subroutines_to_ignore
265              
266             Regex or arrayref. When producing caller or stack trace information, will pass
267             this to L<Devel::Caller::Util>'s C<caller()> or C<callers()>.
268              
269             =head1 HOMEPAGE
270              
271             Please visit the project's homepage at L<https://metacpan.org/release/Log-ger-Layout-LTSV>.
272              
273             =head1 SOURCE
274              
275             Source repository is at L<https://github.com/perlancar/perl-Log-ger-Layout-LTSV>.
276              
277             =head1 BUGS
278              
279             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Log-ger-Layout-LTSV>
280              
281             When submitting a bug or request, please include a test-file or a
282             patch to an existing test-file that illustrates the bug or desired
283             feature.
284              
285             =head1 SEE ALSO
286              
287             More about LTSV format: L<http://ltsv.org>
288              
289             L<Log::ger>
290              
291             L<Log::ger::Layout::Pattern>
292              
293             =head1 AUTHOR
294              
295             perlancar <perlancar@cpan.org>
296              
297             =head1 COPYRIGHT AND LICENSE
298              
299             This software is copyright (c) 2020, 2019, 2017 by perlancar@cpan.org.
300              
301             This is free software; you can redistribute it and/or modify it under
302             the same terms as the Perl 5 programming language system itself.
303              
304             =cut