1
# This program is copyright 2013 Percona Ireland Ltd.
2
# Feedback and improvements are welcome.
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
8
# This program is free software; you can redistribute it and/or modify it under
9
# the terms of the GNU General Public License as published by the Free Software
10
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16
# Place, Suite 330, Boston, MA 02111-1307 USA.
17
# ###########################################################################
18
# Percona::Agent::Logger package
19
# ###########################################################################
20
package Percona::Agent::Logger;
23
use warnings FATAL => 'all';
24
use English qw(-no_match_vars);
26
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
28
use POSIX qw(SIGALRM);
32
use Percona::WebAPI::Resource::LogEntry;
34
Transformers->import(qw(ts));
36
has 'exit_status' => (
52
default => sub { return; },
59
default => sub { return; },
62
has 'online_logging' => (
66
default => sub { return 1 },
69
has 'online_logging_enabled' => (
73
default => sub { return 0 },
80
default => sub { return 0 },
87
default => sub { return []; },
90
has '_pipe_write' => (
92
isa => 'Maybe[FileHandle]',
99
# Set the SIGALRM handler.
102
POSIX::SigAction->new(sub { die 'read timeout'; }),
103
) or die "Error setting SIGALRM handler: $OS_ERROR";
109
while(defined(my $line = <STDIN>)) {
115
PTDEBUG && _d('Read error:', $EVAL_ERROR);
116
die $EVAL_ERROR unless $EVAL_ERROR =~ m/read timeout/;
119
return unless scalar @lines || $timeout;
123
sub start_online_logging {
124
my ($self, %args) = @_;
125
my $client = $args{client};
126
my $log_link = $args{log_link};
127
my $read_timeout = $args{read_timeout} || 3;
129
return unless $self->online_logging;
131
my $pid = open(my $pipe_write, "|-");
136
$OUTPUT_AUTOFLUSH = 1;
137
$self->_pipe_write($pipe_write);
138
$self->online_logging_enabled(1);
147
my $lines = read_stdin($read_timeout);
148
last QUEUE unless $lines;
150
while ( defined(my $line = shift @$lines) ) {
151
# $line = ts,level,n_lines,message
152
my ($ts, $level, $n_lines, $msg) = $line =~ m/^([^,]+),([^,]+),([^,]+),(.+)/s;
153
if ( !$ts || !$level || !$n_lines || !$msg ) {
157
if ( $n_lines > 1 ) {
158
$n_lines--; # first line
159
for ( 1..$n_lines ) {
160
$msg .= shift @$lines;
164
push @log_entries, Percona::WebAPI::Resource::LogEntry->new(
169
($self->service ? (service => $self->service) : ()),
170
($self->data_ts ? (data_ts => $self->data_ts) : ()),
174
if ( scalar @log_entries ) {
178
resources => \@log_entries,
181
if ( my $e = $EVAL_ERROR ) {
182
# Safegaurd: don't spam the agent log file with errors.
183
if ( ++$n_errors <= 10 ) {
184
warn "Error sending log entry to API: $e";
185
if ( $n_errors == 10 ) {
186
my $ts = ts(time, 1); # 1=UTC
187
warn "$ts WARNING $n_errors consecutive errors, no more "
188
. "error messages will be printed until log entries "
189
. "are sent successfully again.\n";
199
# Safeguard: don't use too much memory if we lose connection
200
# to the API for a long time.
201
my $n_log_entries = scalar @log_entries;
202
if ( $n_log_entries > 1_000 ) {
203
warn "$n_log_entries log entries in send buffer, "
204
. "removing first 100 to avoid excessive usage.\n";
205
@log_entries = @log_entries[100..($n_log_entries-1)];
209
if ( scalar @log_entries ) {
210
my $ts = ts(time, 1); # 1=UTC
211
warn "$ts WARNING Failed to send these log entries "
212
. "(timestamps are UTC):\n";
213
foreach my $log ( @log_entries ) {
214
warn sprintf("%s %s %s\n",
216
level_name($log->log_level),
230
die "No log level name given" unless $name;
231
my $number = $name eq 'DEBUG' ? 1
232
: $name eq 'INFO' ? 2
233
: $name eq 'WARNING' ? 3
234
: $name eq 'ERROR' ? 4
235
: $name eq 'FATAL' ? 5
236
: die "Invalid log level name: $name";
241
die "No log level name given" unless $number;
242
my $name = $number == 1 ? 'DEBUG'
243
: $number == 2 ? 'INFO'
244
: $number == 3 ? 'WARNING'
245
: $number == 4 ? 'ERROR'
246
: $number == 5 ? 'FATAL'
247
: die "Invalid log level number: $number";
252
return if $self->online_logging;
253
return $self->_log(0, 'DEBUG', @_);
258
return $self->_log(1, 'INFO', @_);
263
$self->_set_exit_status();
264
return $self->_log(1, 'WARNING', @_);
269
$self->_set_exit_status();
270
return $self->_log(1, 'ERROR', @_);
275
$self->_set_exit_status();
276
$self->_log(1, 'FATAL', @_);
277
exit $self->exit_status;
280
sub _set_exit_status {
282
# exit_status is a scalar ref
283
my $exit_status = $self->exit_status; # get ref
284
$$exit_status |= 1; # deref to set
285
$self->exit_status($exit_status); # save back ref
290
my ($self, $online, $level, $msg) = @_;
292
my $ts = ts(time, 1); # 1=UTC
293
my $level_number = level_number($level);
295
return if $self->quiet && $level_number < $self->quiet;
299
$n_lines++ while $msg =~ m/\n/g;
301
if ( $online && $self->online_logging_enabled ) {
302
while ( defined(my $log_entry = shift @{$self->_buffer}) ) {
303
$self->_queue_log_entry(@$log_entry);
305
$self->_queue_log_entry($ts, $level_number, $n_lines, $msg);
308
if ( $online && $self->online_logging ) {
309
push @{$self->_buffer}, [$ts, $level_number, $n_lines, $msg];
312
if ( $level_number >= 3 ) { # warning
313
print STDERR "$ts $level $msg\n";
316
print STDOUT "$ts $level $msg\n";
323
sub _queue_log_entry {
324
my ($self, $ts, $log_level, $n_lines, $msg) = @_;
325
print "$ts,$log_level,$n_lines,$msg\n";
330
my ($package, undef, $line) = caller 0;
331
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
332
map { defined $_ ? $_ : 'undef' }
334
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
339
# ###########################################################################
340
# End Percona::Agent::Logger package
341
# ###########################################################################