1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Log::Log4perl::Layout::Mozilla;
use 5.10.1;
use Moo;
use Sys::Hostname;
use JSON::MaybeXS ();
use English qw(-no_match_vars $PID);
use constant LOGGING_FORMAT_VERSION => 2.0;
extends 'Log::Log4perl::Layout';
has 'name' => (
is => 'ro',
default => 'Bugzilla',
);
has 'max_json_length' => (
is => 'ro',
isa => sub { die "must be at least 1024\n" if $_[0] < 1024 },
default => 4096,
);
sub BUILDARGS {
my ($class, $params) = @_;
delete $params->{value};
foreach my $key (keys %$params) {
if (ref $params->{$key} eq 'HASH') {
$params->{$key} = $params->{$key}{value};
}
}
return $params;
}
sub render {
my ( $self, $msg, $category, $priority, $caller_level ) = @_;
state $HOSTNAME = hostname();
state $JSON = JSON::MaybeXS->new(
indent => 0, # to prevent newlines (and save space)
ascii => 1, # to avoid encoding issues downstream
allow_unknown => 1, # encode null on bad value (instead of exception)
convert_blessed => 1, # call TO_JSON on blessed ref, if it exists
allow_blessed => 1, # encode null on blessed ref that can't be converted
);
my $mdc = Log::Log4perl::MDC->get_context;
my $fields = $mdc->{fields} // {};
if ($mdc->{request_id}) {
$fields->{request_id} = $mdc->{request_id}
}
my %out = (
EnvVersion => LOGGING_FORMAT_VERSION,
Hostname => $HOSTNAME,
Logger => $self->name,
Pid => $PID,
Severity => $Log::Log4perl::Level::SYSLOG{$priority},
Timestamp => time() * 1e9,
Type => $category,
Fields => { msg => $msg, %$fields },
);
my $json_text = $JSON->encode(\%out) . "\n";
if (length($json_text) > $self->max_json_length) {
my $scary_msg = sprintf 'DANGER! LOG MESSAGE TOO BIG %d > %d', length($json_text), $self->max_json_length;
$out{Fields} = { remote_ip => $mdc->{remote_ip}, msg => $scary_msg };
$out{Severity} = 1; # alert
$json_text = $JSON->encode(\%out) . "\n";
}
return $json_text;
}
1;
__END__
=head1 NAME
Log::Log4perl::Layout::Mozilla - Implement the mozilla-services json log format
=head1 SYNOPSIS
Example configuration:
log4perl.appender.Example.layout = Log::Log4perl::Layout::Mozilla
log4perl.appender.Example.layout.max_json_length = 16384
log4perl.appender.Example.layout.name = Bugzilla
=head1 DESCRIPTION
This class implements a C<Log::Log4perl> layout format
that implements the recommend json format using in Mozilla services.
L<https://wiki.mozilla.org/Firefox/Services/Logging#MozLog_JSON_schema>.
The JSON hash is ASCII encoded, with no newlines or other whitespace, and is
suitable for output, via Log::Log4perl appenders, to files and syslog etc.
Contextual data in the L<Log::Log4perl::MDC> hash will be put into the Fields
hash.
=head1 LAYOUT CONFIGURATION
=head2 name
Data source, server that is doing the logging, e.g. "Sync-1_5".
Use the server's name, and avoid implementation details. "FxaAuthWebserver", not "NginxLogs".
=head2 max_json_length
Set the maximum JSON length in bytes. The default is 4096,
and it cannot be smaller than 1024.
log4perl.appender.Example.layout.max_json_length = 16384
This is useful where some downstream system has a limit on the maximum size of
a message.
If the message is larger than this limit, the message will be replaced
with a scary message at a severity level of ALERT.
|