| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Log::Any::Adapter::DERIV; |
|
2
|
|
|
|
|
|
|
# ABSTRACT: one company's example of a standardised logging setup |
|
3
|
|
|
|
|
|
|
|
|
4
|
4
|
|
|
4
|
|
163879
|
use strict; |
|
|
4
|
|
|
|
|
30
|
|
|
|
4
|
|
|
|
|
113
|
|
|
5
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
215
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:DERIV'; # AUTHORITY |
|
8
|
|
|
|
|
|
|
our $VERSION = '0.003'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
4
|
|
|
4
|
|
27
|
use feature qw(state); |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
452
|
|
|
11
|
4
|
|
|
4
|
|
1791
|
use parent qw(Log::Any::Adapter::Coderef); |
|
|
4
|
|
|
|
|
1259
|
|
|
|
4
|
|
|
|
|
36
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
4
|
|
|
4
|
|
19732
|
use utf8; |
|
|
4
|
|
|
|
|
61
|
|
|
|
4
|
|
|
|
|
19
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=encoding utf8 |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 NAME |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Log::Any::Adapter::DERIV - standardised logging to STDERR and JSON file |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=begin markdown |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
[](https://app.circleci.com/pipelines/github/binary-com/perl-Log-Any-Adapter-DERIV) |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=end markdown |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Log::Any; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# print text log to STDERR, json format when inside docker container, |
|
32
|
|
|
|
|
|
|
# colored text format when STDERR is a tty, non-colored text format when |
|
33
|
|
|
|
|
|
|
# STDERR is redirected. |
|
34
|
|
|
|
|
|
|
use Log::Any::Adapter ('DERIV'); |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
#specify STDERR directly |
|
37
|
|
|
|
|
|
|
use Log::Any::Adapter ('DERIV', stderr => 1) |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
#specify STDERR's format |
|
40
|
|
|
|
|
|
|
use Log::Any::Adapter ('DERIV', stderr => 'json') |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
#specify the json log name |
|
43
|
|
|
|
|
|
|
use Log::Any::Adapter ('DERIV', json_log_file => '/var/log/program.json.log'); |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Applies some opinionated log handling rules for L. |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
B. It does the following, affecting global state |
|
50
|
|
|
|
|
|
|
in various ways: |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=over 4 |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=item * applies UTF-8 encoding to STDERR |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=item * writes to a C<.json.log> file. |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=item * overrides the default L formatter to provide data as JSON |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=item * when stringifying, may replace some problematic objects with simplified versions |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=back |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
An example of the string-replacement approach would be the event loop in asynchronous code: |
|
65
|
|
|
|
|
|
|
it's likely to have many components attached to it, and dumping that would effectively end up |
|
66
|
|
|
|
|
|
|
dumping the entire tree of useful objects in the process. This is a planned future extension, |
|
67
|
|
|
|
|
|
|
not currently implemented. |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 Why |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
This is provided as a CPAN module as an example for dealing with multiple outputs and formatting. |
|
72
|
|
|
|
|
|
|
The existing L modules tend to cover one thing, and it's |
|
73
|
|
|
|
|
|
|
not immediately obvious how to extend formatting, or send data to multiple logging mechanisms at once. |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Although the module may not be directly useful, it is hoped that other teams may find |
|
76
|
|
|
|
|
|
|
parts of the code useful for their own logging requirements. |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
There is a public repository on Github, anyone is welcome to fork that and implement |
|
79
|
|
|
|
|
|
|
their own version or make feature/bug fix suggestions if they seem generally useful: |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
L |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head2 PARAMETERS |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=over 4 |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=item * json_log_file |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Specify a file name to which you want the json formatted logs printed into. |
|
90
|
|
|
|
|
|
|
If not given, then it prints the logs to STDERR. |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item * STDERR |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
If it is true, then print logs to STDERR |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
If the value is json or text, then print logs with that format |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
If the value is just a true value other than `json` or `text`, |
|
99
|
|
|
|
|
|
|
then if it is running in a container, then it prints the logs in `json` format. |
|
100
|
|
|
|
|
|
|
Else if STDERR is a tty, then it prints `colored text` format. |
|
101
|
|
|
|
|
|
|
Else it prints non-color text format. |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=back |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
If no parameters provided, then default `stderr => 1`; |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head1 METHODS |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=cut |
|
112
|
|
|
|
|
|
|
|
|
113
|
4
|
|
|
4
|
|
1806
|
use Time::Moment; |
|
|
4
|
|
|
|
|
5706
|
|
|
|
4
|
|
|
|
|
142
|
|
|
114
|
4
|
|
|
4
|
|
2592
|
use Path::Tiny; |
|
|
4
|
|
|
|
|
38149
|
|
|
|
4
|
|
|
|
|
218
|
|
|
115
|
4
|
|
|
4
|
|
2620
|
use curry; |
|
|
4
|
|
|
|
|
1361
|
|
|
|
4
|
|
|
|
|
146
|
|
|
116
|
4
|
|
|
4
|
|
1286
|
use JSON::MaybeUTF8 qw(:v1); |
|
|
4
|
|
|
|
|
17601
|
|
|
|
4
|
|
|
|
|
520
|
|
|
117
|
4
|
|
|
4
|
|
29
|
use PerlIO; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
42
|
|
|
118
|
4
|
|
|
4
|
|
105
|
use Config; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
138
|
|
|
119
|
4
|
|
|
4
|
|
1913
|
use Term::ANSIColor; |
|
|
4
|
|
|
|
|
27612
|
|
|
|
4
|
|
|
|
|
256
|
|
|
120
|
4
|
|
|
4
|
|
29
|
use Log::Any qw($log); |
|
|
4
|
|
|
|
|
39
|
|
|
|
4
|
|
|
|
|
31
|
|
|
121
|
4
|
|
|
4
|
|
1003
|
use Fcntl qw(:DEFAULT :seek :flock); |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
1619
|
|
|
122
|
4
|
|
|
4
|
|
33
|
use Log::Any::Adapter::Util qw(numeric_level logging_methods); |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
300
|
|
|
123
|
4
|
|
|
4
|
|
1772
|
use Clone qw(clone); |
|
|
4
|
|
|
|
|
9502
|
|
|
|
4
|
|
|
|
|
766
|
|
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# Used for stringifying data more neatly than Data::Dumper might offer |
|
126
|
|
|
|
|
|
|
our $JSON = JSON::MaybeXS->new( |
|
127
|
|
|
|
|
|
|
# Multi-line for terminal output, single line if redirecting somewhere |
|
128
|
|
|
|
|
|
|
pretty => _fh_is_tty(\*STDERR), |
|
129
|
|
|
|
|
|
|
# Be consistent |
|
130
|
|
|
|
|
|
|
canonical => 1, |
|
131
|
|
|
|
|
|
|
# Try a bit harder to give useful output |
|
132
|
|
|
|
|
|
|
convert_blessed => 1, |
|
133
|
|
|
|
|
|
|
); |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# Simple mapping from severity levels to Term::ANSIColor definitions. |
|
136
|
|
|
|
|
|
|
our %SEVERITY_COLOUR = ( |
|
137
|
|
|
|
|
|
|
trace => [qw(grey12)], |
|
138
|
|
|
|
|
|
|
debug => [qw(grey18)], |
|
139
|
|
|
|
|
|
|
info => [qw(green)], |
|
140
|
|
|
|
|
|
|
warning => [qw(bright_yellow)], |
|
141
|
|
|
|
|
|
|
error => [qw(red bold)], |
|
142
|
|
|
|
|
|
|
fatal => [qw(red bold)], |
|
143
|
|
|
|
|
|
|
critical => [qw(red bold)], |
|
144
|
|
|
|
|
|
|
); |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
my @methods = reverse logging_methods(); |
|
147
|
|
|
|
|
|
|
my %num_to_name = map { $_ => $methods[$_] } 0 .. $#methods; |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# The obvious way to handle this might be to provide our own proxy class: |
|
150
|
|
|
|
|
|
|
# $Log::Any::OverrideDefaultProxyClass = 'Log::Any::Proxy::DERIV'; |
|
151
|
|
|
|
|
|
|
# but the handling for proxy classes is somewhat opaque - and there's an ordering problem |
|
152
|
|
|
|
|
|
|
# where `use Log::Any` before the adapter is loaded means we end up with some classes having |
|
153
|
|
|
|
|
|
|
# the default anyway. |
|
154
|
|
|
|
|
|
|
# Rather than trying to deal with that, we just provide our own default: |
|
155
|
|
|
|
|
|
|
{ |
|
156
|
4
|
|
|
4
|
|
36
|
no warnings 'redefine'; ## no critic (ProhibitNoWarnings) |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
738
|
|
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# We expect this to be loaded, but be explicit just in case - we'll be overriding |
|
159
|
|
|
|
|
|
|
# one of the methods, so let's at least make sure it exists first |
|
160
|
|
|
|
|
|
|
require Log::Any::Proxy; |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Mostly copied from Log::Any::Proxy |
|
163
|
|
|
|
|
|
|
*Log::Any::Proxy::_default_formatter = sub { |
|
164
|
0
|
|
|
0
|
|
0
|
my ($cat, $lvl, $format, @params) = @_; |
|
165
|
0
|
0
|
|
|
|
0
|
return $format->() if ref($format) eq 'CODE'; |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
chomp( |
|
168
|
|
|
|
|
|
|
my @new_params = map { |
|
169
|
0
|
|
0
|
|
|
0
|
eval { $JSON->encode($_) } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
170
|
|
|
|
|
|
|
// Log::Any::Proxy::_stringify_params($_) |
|
171
|
|
|
|
|
|
|
} @params |
|
172
|
|
|
|
|
|
|
); |
|
173
|
0
|
|
|
|
|
0
|
s{\n}{\n }g for @new_params; |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Perl 5.22 adds a 'redundant' warning if the number parameters exceeds |
|
176
|
|
|
|
|
|
|
# the number of sprintf placeholders. If a user does this, the warning |
|
177
|
|
|
|
|
|
|
# is issued from here, which isn't very helpful. Doing something |
|
178
|
|
|
|
|
|
|
# clever would be expensive, so instead we just disable warnings for |
|
179
|
|
|
|
|
|
|
# the final line of this subroutine. |
|
180
|
4
|
|
|
4
|
|
30
|
no warnings; ## no critic (ProhibitNoWarnings) |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
7529
|
|
|
181
|
0
|
|
|
|
|
0
|
return sprintf($format, @new_params); |
|
182
|
|
|
|
|
|
|
}; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Upgrade any `warn ...` lines to send through Log::Any. |
|
186
|
|
|
|
|
|
|
$SIG{__WARN__} = sub { ## no critic (RequireLocalizedPunctuationVars) |
|
187
|
|
|
|
|
|
|
# We don't expect anything called from here to raise further warnings, but |
|
188
|
|
|
|
|
|
|
# let's be safe and try to avoid any risk of recursion |
|
189
|
|
|
|
|
|
|
local $SIG{__WARN__} = undef; |
|
190
|
|
|
|
|
|
|
chomp(my $msg = shift); |
|
191
|
|
|
|
|
|
|
$log->warn($msg); |
|
192
|
|
|
|
|
|
|
}; |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub new { |
|
195
|
180
|
|
|
180
|
0
|
301515
|
my ($class, %args) = @_; |
|
196
|
180
|
|
|
0
|
|
1084
|
my $self = $class->SUPER::new(sub { }, %args); |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# if there is json_log_file, then print json to that file |
|
199
|
180
|
100
|
|
|
|
6763
|
if ($self->{json_log_file}) { |
|
200
|
106
|
50
|
|
|
|
283
|
$self->{json_fh} = path($self->{json_log_file})->opena_utf8 or die 'unable to open log file - ' . $!; |
|
201
|
106
|
|
|
|
|
58506
|
$self->{json_fh}->autoflush(1); |
|
202
|
|
|
|
|
|
|
} |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# if there is stderr, then print log to stderr also |
|
205
|
|
|
|
|
|
|
# if stderr is json or text, then use that format |
|
206
|
|
|
|
|
|
|
# else, if it is in_container, then json, else text |
|
207
|
180
|
100
|
100
|
|
|
4741
|
if (!$self->{json_log_file} && !$self->{stderr}) { |
|
208
|
50
|
|
|
|
|
89
|
$self->{stderr} = 1; |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
|
|
211
|
180
|
|
|
|
|
613
|
for my $stdfile (['stderr', \*STDERR], ['stdout', \*STDOUT]) { |
|
212
|
360
|
|
|
|
|
1084
|
my ($name, $fh) = $stdfile->@*; |
|
213
|
360
|
100
|
|
|
|
1656
|
if ($self->{$name}) { |
|
214
|
102
|
50
|
|
|
|
361
|
$self->{$name} = {format => $self->{$name}} if ref($self->{$name}) ne 'HASH'; |
|
215
|
|
|
|
|
|
|
# docker tends to prefer JSON |
|
216
|
|
|
|
|
|
|
$self->{$name}{format} = _in_container() ? 'json' : 'text' |
|
217
|
102
|
100
|
100
|
|
|
574
|
if (!$self->{$name}{format} || $self->{$name}{format} ne 'json' && $self->{$name}{format} ne 'text'); |
|
|
|
100
|
66
|
|
|
|
|
|
218
|
102
|
|
|
|
|
424
|
$self->apply_filehandle_utf8($fh); |
|
219
|
102
|
|
|
|
|
5530
|
$self->{$name}{fh} = $fh; |
|
220
|
102
|
|
66
|
|
|
352
|
$self->{$name}{color} //= _fh_is_tty($fh); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Keep a strong reference to this, since we expect to stick around until exit anyway |
|
225
|
180
|
|
|
|
|
1352
|
$self->{code} = $self->curry::log_entry; |
|
226
|
180
|
|
|
|
|
3223
|
return $self; |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head2 apply_filehandle_utf8 |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Applies UTF-8 to filehandle if it is not utf-flavoured already |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
$object->apply_filehandle_utf8($fh); |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=over 4 |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=item * C<$fh> file handle |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=back |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub apply_filehandle_utf8 { |
|
244
|
102
|
|
|
102
|
1
|
188
|
my ($class, $fh) = @_; |
|
245
|
|
|
|
|
|
|
# We'd expect `encoding(utf-8-strict)` and `utf8` if someone's already applied binmode |
|
246
|
|
|
|
|
|
|
# for us, but implementation details in Perl may change those names slightly, and on |
|
247
|
|
|
|
|
|
|
# some platforms (Windows?) there's also a chance of one of the UTF16LE/BE variants, |
|
248
|
|
|
|
|
|
|
# so we make this check quite lax and skip binmode if there's anything even slightly |
|
249
|
|
|
|
|
|
|
# utf-flavoured in the mix. |
|
250
|
|
|
|
|
|
|
$fh->binmode(':encoding(UTF-8)') |
|
251
|
102
|
100
|
|
|
|
509
|
unless grep { /utf/i } PerlIO::get_layers($fh, output => 1); |
|
|
238
|
|
|
|
|
1446
|
|
|
252
|
102
|
|
|
|
|
23622
|
$fh->autoflush(1); |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head2 format_line |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
Formatting the log entry with timestamp, from which the message populated, |
|
258
|
|
|
|
|
|
|
severity and message. |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
If color/colour param passed it adds appropriate color code for timestamp, |
|
261
|
|
|
|
|
|
|
log level, from which this log message populated and actual message. |
|
262
|
|
|
|
|
|
|
For non-color mode, it just returns the formatted message. |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
$object->format_line($data, {color => $color}); |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=over 4 |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=item * C<$data> hashref - The data with stack info like package method from |
|
269
|
|
|
|
|
|
|
which the message populated, timestamp, severity and message |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=item * C<$opts> hashref - the options color |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=back |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Returns only formatted string if non-color mode. Otherwise returns formatted |
|
276
|
|
|
|
|
|
|
string with embedded ANSI color code using L |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=cut |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub format_line { |
|
281
|
18
|
|
|
18
|
1
|
37
|
my ($class, $data, $opts) = @_; |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# With international development teams, no matter which spelling we choose |
|
284
|
|
|
|
|
|
|
# someone's going to get this wrong sooner or later... or to put another |
|
285
|
|
|
|
|
|
|
# way, we got country *and* western. |
|
286
|
18
|
|
66
|
|
|
60
|
$opts->{colour} = $opts->{color} || $opts->{colour}; |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Expand formatting if necessary: it's not immediately clear how to defer |
|
289
|
|
|
|
|
|
|
# handling of structured data, the ->structured method doesn't have a way |
|
290
|
|
|
|
|
|
|
# to return the stringified data back to the caller for example |
|
291
|
|
|
|
|
|
|
# for edge cases like `my $msg = $log->debug(...);` so we're still working |
|
292
|
|
|
|
|
|
|
# on how best to handle this: |
|
293
|
|
|
|
|
|
|
# https://metacpan.org/release/Log-Any/source/lib/Log/Any/Proxy.pm#L105 |
|
294
|
|
|
|
|
|
|
# $_ = sprintf $_->@* for grep ref, $data->{message}; |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# If we have a stack entry, report the context - default to "main" if we're at top level |
|
297
|
18
|
50
|
|
|
|
42
|
my $from = $data->{stack}[-1] ? join '->', @{$data->{stack}[-1]}{qw(package method)} : 'main'; |
|
|
18
|
|
|
|
|
57
|
|
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# Start with the plain-text details |
|
300
|
|
|
|
|
|
|
my @details = ( |
|
301
|
|
|
|
|
|
|
Time::Moment->from_epoch($data->{epoch})->strftime('%Y-%m-%dT%H:%M:%S%3f'), |
|
302
|
|
|
|
|
|
|
uc(substr $data->{severity}, 0, 1), |
|
303
|
18
|
|
|
|
|
222
|
"[$from]", $data->{message}); |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# This is good enough if we're in non-colour mode |
|
306
|
18
|
100
|
|
|
|
111
|
return join ' ', @details unless $opts->{colour}; |
|
307
|
|
|
|
|
|
|
|
|
308
|
7
|
50
|
|
|
|
26
|
my @colours = ($SEVERITY_COLOUR{$data->{severity}} || die 'no severity definition found for ' . $data->{severity})->@*; |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# Colour formatting codes applied at the start and end of each line, in case something else |
|
311
|
|
|
|
|
|
|
# gets inbetween us and the output |
|
312
|
7
|
|
|
|
|
16
|
local $Term::ANSIColor::EACHLINE = "\n"; |
|
313
|
7
|
|
|
|
|
19
|
my ($ts, $level) = splice @details, 0, 2; |
|
314
|
7
|
|
|
|
|
12
|
$from = shift @details; |
|
315
|
|
|
|
|
|
|
|
|
316
|
7
|
|
|
|
|
28
|
return join ' ', colored($ts, qw(bright_blue)), colored($level, @colours), colored($from, qw(grey10)), map { colored($_, @colours) } @details; |
|
|
7
|
|
|
|
|
931
|
|
|
317
|
|
|
|
|
|
|
} |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=head2 log_entry |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
Add format and add color code using C and writes the log entry |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
$object->log_entry($data); |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=over 4 |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=item *C<$data> hashref - The log data |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=back |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=cut |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
sub log_entry { |
|
334
|
81
|
|
|
81
|
1
|
12246
|
my ($self, $data) = @_; |
|
335
|
81
|
|
|
|
|
200
|
$data = $self->_process_data($data); |
|
336
|
81
|
|
|
|
|
112
|
my $json_data; |
|
337
|
81
|
|
|
|
|
143
|
my %text_data = (); |
|
338
|
81
|
|
66
|
75
|
|
286
|
my $get_json = sub { $json_data //= encode_json_text($data) . "\n"; return $json_data; }; |
|
|
75
|
|
|
|
|
332
|
|
|
|
75
|
|
|
|
|
1888
|
|
|
339
|
|
|
|
|
|
|
my $get_text = |
|
340
|
81
|
|
100
|
20
|
|
278
|
sub { my $color = shift // 0; $text_data{$color} //= $self->format_line($data, {color => $color}) . "\n"; return $text_data{$color}; }; |
|
|
20
|
|
66
|
|
|
56
|
|
|
|
20
|
|
|
|
|
103
|
|
|
|
20
|
|
|
|
|
345
|
|
|
341
|
|
|
|
|
|
|
|
|
342
|
81
|
100
|
|
|
|
244
|
if ($self->{json_fh}) { |
|
343
|
53
|
|
|
|
|
148
|
_lock($self->{json_fh}); |
|
344
|
53
|
|
|
|
|
179
|
$self->{json_fh}->print($get_json->()); |
|
345
|
53
|
|
|
|
|
3309
|
_unlock($self->{json_fh}); |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
81
|
|
|
|
|
194
|
for my $stdfile (qw(stderr stdout)) { |
|
349
|
162
|
100
|
|
|
|
1073
|
next unless $self->{$stdfile}; |
|
350
|
|
|
|
|
|
|
my $txt = |
|
351
|
|
|
|
|
|
|
$self->{$stdfile}{format} eq 'json' |
|
352
|
|
|
|
|
|
|
? $get_json->() |
|
353
|
42
|
100
|
|
|
|
127
|
: $get_text->($self->{$stdfile}{color}); |
|
354
|
42
|
|
|
|
|
84
|
my $fh = $self->{$stdfile}{fh}; |
|
355
|
|
|
|
|
|
|
|
|
356
|
42
|
|
|
|
|
121
|
_lock($fh); |
|
357
|
42
|
|
|
|
|
182
|
$fh->print($txt); |
|
358
|
42
|
|
|
|
|
760
|
_unlock($fh); |
|
359
|
|
|
|
|
|
|
} |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=head2 _process_data |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
Process the data before printing out. Reduce the continues L stack |
|
365
|
|
|
|
|
|
|
messages and filter the messages based on log level. |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
$object->_process_data($data); |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=over 4 |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=item * C<$data> hashref - The log data. |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=back |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
Returns a hashref - the processed data |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=cut |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub _process_data { |
|
380
|
81
|
|
|
81
|
|
138
|
my ($self, $data) = @_; |
|
381
|
|
|
|
|
|
|
|
|
382
|
81
|
|
|
|
|
2308
|
$data = clone($data); |
|
383
|
81
|
|
|
|
|
292
|
$data = $self->_collapse_future_stack($data); |
|
384
|
81
|
|
|
|
|
188
|
$data = $self->_filter_stack($data); |
|
385
|
|
|
|
|
|
|
|
|
386
|
81
|
|
|
|
|
1040
|
return $data; |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head2 _filter_stack |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
Filter the stack message based on log level. |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
$object->_filter_stack($data); |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=over 4 |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item * C<$data> hashref - Log stack data |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=back |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Returns hashref - the filtered data |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=cut |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
sub _filter_stack { |
|
406
|
81
|
|
|
81
|
|
140
|
my ($self, $data) = @_; |
|
407
|
|
|
|
|
|
|
|
|
408
|
81
|
100
|
|
|
|
233
|
return $data if (numeric_level($data->{severity}) <= numeric_level('warn')); |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
# now severity > warn |
|
411
|
12
|
100
|
|
|
|
178
|
return $data if $self->{log_level} >= numeric_level('debug'); |
|
412
|
|
|
|
|
|
|
|
|
413
|
3
|
|
|
|
|
23
|
delete $data->{stack}; |
|
414
|
|
|
|
|
|
|
|
|
415
|
3
|
|
|
|
|
4
|
return $data; |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head2 _collapse_future_stack |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
Go through the caller stack and if continuous L messages then keep |
|
421
|
|
|
|
|
|
|
only one at the first. |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
$object->_collapse_future_stack($data); |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
=over 4 |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=item * C<$data> hashref - Log stack data |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=back |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
Returns a hashref - the reduced log data |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=cut |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
sub _collapse_future_stack { |
|
436
|
83
|
|
|
83
|
|
1266
|
my ($self, $data) = @_; |
|
437
|
83
|
|
|
|
|
136
|
my $stack = $data->{stack}; |
|
438
|
83
|
|
|
|
|
138
|
my @new_stack; |
|
439
|
|
|
|
|
|
|
my $previous_is_future; |
|
440
|
|
|
|
|
|
|
|
|
441
|
83
|
|
|
|
|
176
|
for my $frame ($stack->@*) { |
|
442
|
214
|
100
|
100
|
|
|
646
|
if ($frame->{package} eq 'Future' || $frame->{package} eq 'Future::PP') { |
|
443
|
29
|
100
|
|
|
|
55
|
next if ($previous_is_future); |
|
444
|
6
|
|
|
|
|
12
|
push @new_stack, $frame; |
|
445
|
6
|
|
|
|
|
10
|
$previous_is_future = 1; |
|
446
|
|
|
|
|
|
|
} else { |
|
447
|
185
|
|
|
|
|
278
|
push @new_stack, $frame; |
|
448
|
185
|
|
|
|
|
276
|
$previous_is_future = 0; |
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
} |
|
451
|
83
|
|
|
|
|
153
|
$data->{stack} = \@new_stack; |
|
452
|
|
|
|
|
|
|
|
|
453
|
83
|
|
|
|
|
214
|
return $data; |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=head2 _fh_is_tty |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
Check the filehandle opened to tty |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=over 4 |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item * C<$fh> file handle |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=back |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
Returns boolean |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
sub _fh_is_tty { |
|
471
|
21
|
|
|
21
|
|
40
|
my $fh = shift; |
|
472
|
|
|
|
|
|
|
|
|
473
|
21
|
|
|
|
|
226
|
return -t $fh; ## no critic (ProhibitInteractiveTest) |
|
474
|
|
|
|
|
|
|
} |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=head2 _in_container |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
Returns true if we think we are currently running in a container. |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
At the moment this only looks for a C<.dockerenv> file in the root directory; |
|
481
|
|
|
|
|
|
|
future versions may expand this to provide a more accurate check covering |
|
482
|
|
|
|
|
|
|
other container systems such as `runc`. |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
Returns boolean |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=cut |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub _in_container { |
|
489
|
18
|
|
|
18
|
|
394
|
return -r '/.dockerenv'; |
|
490
|
|
|
|
|
|
|
} |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=head2 _linux_flock_data |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
Based on the type of lock requested, it packs into linux binary flock structure |
|
495
|
|
|
|
|
|
|
and return the string of that structure. |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
Linux struct flock: "s s l l i" |
|
498
|
|
|
|
|
|
|
short l_type short - Possible values: F_RDLCK(0) - read lock, F_WRLCK(1) - write lock, F_UNLCK(2) - unlock |
|
499
|
|
|
|
|
|
|
short l_whence - starting offset |
|
500
|
|
|
|
|
|
|
off_t l_start - relative offset |
|
501
|
|
|
|
|
|
|
off_t l_len - number of consecutive bytes to lock |
|
502
|
|
|
|
|
|
|
pid_t l_pid - process ID |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=over 4 |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=item * C<$type> integer lock type - F_WRLCK or F_UNLCK |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=back |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
Returns a string of the linux flock structure |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=cut |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
sub _linux_flock_data { |
|
515
|
102
|
|
|
102
|
|
159
|
my ($type) = @_; |
|
516
|
102
|
|
|
|
|
181
|
my $FLOCK_STRUCT = "s s l l i"; |
|
517
|
|
|
|
|
|
|
|
|
518
|
102
|
|
|
|
|
451
|
return pack($FLOCK_STRUCT, $type, SEEK_SET, 0, 0, 0); |
|
519
|
|
|
|
|
|
|
} |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=head2 _flock |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
call fcntl to lock or unlock a file handle |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=over 4 |
|
526
|
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
=item * C<$fh> file handle |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=item * C<$type> lock type, either F_WRLCK or F_UNLCK |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=back |
|
532
|
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
Returns boolean or undef |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=cut |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# We don't use `flock` function directly here |
|
538
|
|
|
|
|
|
|
# In some cases the program will do fork after the log file opened. |
|
539
|
|
|
|
|
|
|
# In such case every subprocess can get lock of the log file at the same time. |
|
540
|
|
|
|
|
|
|
# Using fcntl to lock a file can avoid this problem |
|
541
|
|
|
|
|
|
|
sub _flock { |
|
542
|
102
|
|
|
102
|
|
184
|
my ($fh, $type) = @_; |
|
543
|
102
|
|
|
|
|
178
|
my $lock = _linux_flock_data($type); |
|
544
|
102
|
|
|
|
|
1123
|
my $result = fcntl($fh, F_SETLKW, $lock); |
|
545
|
|
|
|
|
|
|
|
|
546
|
102
|
50
|
|
|
|
394
|
return $result if $result; |
|
547
|
|
|
|
|
|
|
|
|
548
|
0
|
|
|
|
|
0
|
return undef; |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=head2 _lock |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
Lock a file handler with fcntl. |
|
554
|
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
=over 4 |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
=item * C<$fh> File handle |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=back |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
Returns boolean |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=cut |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub _lock { |
|
566
|
51
|
|
|
51
|
|
87
|
my ($fh) = @_; |
|
567
|
|
|
|
|
|
|
|
|
568
|
51
|
|
|
|
|
98
|
return _flock($fh, F_WRLCK); |
|
569
|
|
|
|
|
|
|
} |
|
570
|
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
=head2 _unlock |
|
572
|
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
Unlock a file handler locked by fcntl |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
=over 4 |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
=item * C<$fh> File handle |
|
578
|
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=back |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
Returns boolean |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=cut |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
sub _unlock { |
|
586
|
51
|
|
|
51
|
|
115
|
my ($fh) = @_; |
|
587
|
|
|
|
|
|
|
|
|
588
|
51
|
|
|
|
|
112
|
return _flock($fh, F_UNLCK); |
|
589
|
|
|
|
|
|
|
} |
|
590
|
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=head2 level |
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Return the current log level name. |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=cut |
|
596
|
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub level { |
|
598
|
9
|
|
|
9
|
1
|
264
|
my $self = shift; |
|
599
|
9
|
|
|
|
|
43
|
return $num_to_name{$self->{log_level}}; |
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
1; |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=head1 AUTHOR |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
Deriv Group Services Ltd. C<< DERIV@cpan.org >> |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=head1 LICENSE |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
Copyright Deriv Group Services Ltd 2020-2021. Licensed under the same terms as Perl itself. |