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
|
|
|
|
|
|
|
[![Test status](https://circleci.com/gh/binary-com/perl-Log-Any-Adapter-DERIV.svg?style=shield&circle-token=bed2af8f8e388746eafbbf905cf6990f84dbd69e)](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. |