Skip to content

Commit b6204f1

Browse files
committed
fix log rotation issues
1 parent 0d5814d commit b6204f1

File tree

4 files changed

+631
-66
lines changed

4 files changed

+631
-66
lines changed

lib/LANraragi/Utils/Logging.pm

Lines changed: 128 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,32 @@ use utf8;
77
use feature 'say';
88
use POSIX;
99
use FindBin;
10+
use Time::HiRes;
11+
use Config;
1012

1113
use Encode;
1214
use File::ReadBackwards;
1315
use Compress::Zlib;
16+
use Mojo::Log;
1417
use LANraragi::Model::Config;
18+
use LANraragi::Utils::RotatingLog;
1519
use LANraragi::Utils::Redis qw(redis_decode);
20+
use LANraragi::Utils::TempFolder qw(get_temp);
21+
22+
use constant IS_UNIX => ( $Config{osname} ne 'MSWin32' );
1623

1724
# Contains all functions related to logging.
1825
use Exporter 'import';
1926
our @EXPORT_OK = qw(get_logger get_plugin_logger get_logdir get_lines_from_file);
2027

28+
BEGIN {
29+
if ( !IS_UNIX ) {
30+
require Win32API::File;
31+
}
32+
}
33+
34+
our %LOGGER_CACHE;
35+
2136
# Get the Log folder.
2237
sub get_logdir {
2338

@@ -38,80 +53,80 @@ sub get_logger {
3853
my $pgname = $_[0];
3954
my $logfile = $_[1];
4055

41-
my $logpath = get_logdir . "/$logfile.log";
42-
43-
if ( -e $logpath && -s $logpath > 1048576 ) {
44-
45-
# Rotate log if it's > 1MB
46-
say "Rotating logfile $logfile";
47-
48-
# Based on Logfile::Rotate
49-
# Rotate existing logs
50-
for ( my $i = 7; $i > 1; $i-- ) {
51-
my $j = $i - 1;
52-
my $next = "$logpath.$i.gz";
53-
my $prev = "$logpath.$j.gz";
54-
if ( -r $prev && -f $prev ) {
55-
rename( $prev, $next ) or die "error: rename failed: ($prev,$next)";
56-
}
56+
my $logpath = get_logdir . "/$logfile.log";
57+
my $cache_key = "$logfile|$pgname";
58+
my $log;
59+
60+
# Reuse cached logger if exists, otherwise clean cache and recreate
61+
my $cache_refresh_error;
62+
if ( exists $LOGGER_CACHE{$cache_key} && -e $logpath ) {
63+
$log = $LOGGER_CACHE{$cache_key};
64+
65+
my $ok;
66+
{
67+
local $@;
68+
$ok = eval {
69+
LANraragi::Utils::RotatingLog::refresh_logger_handle($log);
70+
1;
71+
};
72+
$cache_refresh_error = $@ unless $ok;
5773
}
5874

59-
# Rotate current log and Gzip-it
60-
my $gz = gzopen( "$logpath.1.gz", "wb" ) or die "error: could not gzopen $logpath: $!";
61-
62-
open( my $handle, '<', $logpath ) or die "Couldn't open $logpath :" . $!;
63-
my $buffer;
64-
$gz->gzwrite($buffer) while read( $handle, $buffer, 4096 ) > 0;
65-
$gz->gzclose();
66-
close $handle;
67-
68-
unlink $logpath or die "error: could not delete $logpath: $!";
75+
if ($ok) {
76+
return $log;
77+
} else {
78+
delete $LOGGER_CACHE{$cache_key};
79+
undef $log; # avoid returning a stale handle
80+
}
6981
}
7082

71-
my $log = Mojo::Log->new(
72-
path => $logpath,
73-
level => 'info'
74-
);
75-
76-
my $devmode = LANraragi::Model::Config->enable_devmode;
77-
78-
#Tell logger to store debug logs as well in debug mode
79-
if ($devmode) {
80-
$log->level('debug');
83+
# Create and cache logger with retry + backoff + jitter
84+
# Report the first logger init failure if exists
85+
my $tries = 0;
86+
my $first_error;
87+
while ( $tries < 3 ) {
88+
my $ok;
89+
my $err;
90+
{
91+
local $@;
92+
$ok = eval {
93+
$log = LANraragi::Utils::RotatingLog->new(
94+
path => $logpath,
95+
level => 'info',
96+
logfile => $logfile,
97+
tempdir => get_temp()
98+
);
99+
1;
100+
};
101+
$err = $@ unless $ok;
102+
}
103+
if ($ok) {
104+
configure_logger($log, $pgname);
105+
$LOGGER_CACHE{$cache_key} = $log;
106+
last;
107+
} else {
108+
$first_error //= $err;
109+
Time::HiRes::sleep(rand());
110+
$tries++;
111+
}
81112
}
82113

83-
# Step down into trace if we're launched from npm run dev-server-verbose
84-
if ( $ENV{LRR_DEVSERVER} ) {
85-
$log->level('trace');
114+
# Fall back to Mojo::Log if retry doesn't work
115+
if ( !$log ) {
116+
$log = Mojo::Log->new(
117+
path => $logpath,
118+
level => 'info'
119+
);
120+
configure_logger( $log, $pgname );
121+
$log->error("RotatingLog init failed, falling back to Mojo::Log. First error: $first_error");
122+
} elsif ( $tries > 0 ) {
123+
$log->warn("RotatingLog initialized after $tries failures. First error: $first_error");
86124
}
87125

88-
#Copy logged messages to STDOUT with the matching name
89-
$log->on(
90-
message => sub {
91-
my ( $time, $level, @lines ) = @_;
92-
93-
#Like with logging to file, debug logs are only printed in debug mode
94-
unless ( $devmode == 0 && ( $level eq 'debug' || $level eq 'trace' ) ) {
95-
print "[$pgname] [$level] ";
96-
say $lines[0];
97-
}
98-
}
99-
);
100-
101-
$log->format(
102-
sub {
103-
my ( $time, $level, @lines ) = @_;
104-
my $time2 = strftime( "%Y-%m-%d %H:%M:%S", localtime($time) );
105-
106-
my $logstring = join( "\n", @lines );
107-
108-
# We'd like to make sure we always show proper UTF-8.
109-
# redis_decode, while not initially designed for this, does the job.
110-
$logstring = redis_decode($logstring);
111-
112-
return "[$time2] [$pgname] [$level] $logstring\n";
113-
}
114-
);
126+
# Report cache refresh error if exists
127+
if ( $cache_refresh_error ) {
128+
$log->error($cache_refresh_error);
129+
}
115130

116131
return $log;
117132
}
@@ -151,4 +166,51 @@ sub get_lines_from_file {
151166

152167
}
153168

169+
# Provide logger with the required configs and formatting
170+
sub configure_logger {
171+
172+
my $logger = shift;
173+
my $pgname = shift;
174+
175+
my $devmode = LANraragi::Model::Config->enable_devmode;
176+
177+
#Tell logger to store debug logs as well in debug mode
178+
if ($devmode) {
179+
$logger->level('debug');
180+
}
181+
182+
# Step down into trace if we're launched from npm run dev-server-verbose
183+
if ( $ENV{LRR_DEVSERVER} ) {
184+
$logger->level('trace');
185+
}
186+
187+
# Copy logged messages to STDOUT with the matching name
188+
$logger->on(
189+
message => sub {
190+
my ( $log, $level, @lines ) = @_;
191+
192+
# Like with logging to file, debug logs are only printed in debug mode
193+
unless ( $devmode == 0 && ( $level eq 'debug' || $level eq 'trace' ) ) {
194+
print "[$pgname] [$level] ";
195+
say $lines[0];
196+
}
197+
}
198+
);
199+
200+
$logger->format(
201+
sub {
202+
my ( $time, $level, @lines ) = @_;
203+
my $time2 = strftime( "%Y-%m-%d %H:%M:%S", localtime($time) );
204+
205+
my $logstring = join( "\n", @lines );
206+
207+
# We'd like to make sure we always show proper UTF-8.
208+
# redis_decode, while not initially designed for this, does the job.
209+
$logstring = redis_decode($logstring);
210+
211+
return "[$time2] [$pgname] [$level] $logstring\n";
212+
}
213+
);
214+
}
215+
154216
1;

0 commit comments

Comments
 (0)