Merge pull request #2821 from webmin/dev/miniserv-logrotate
Add an option to rotate Miniserv logs using logrotate
This commit is contained in:
+21
-3
@@ -13,14 +13,24 @@ my $journal_changed = defined($in{'error_journal'}) &&
|
||||
&webmin::miniserv_systemd_journal_available("usermin.service") &&
|
||||
($miniserv{'errorlog'} eq '-' ? 1 : 0) != ($in{'error_journal'} ? 1 : 0);
|
||||
|
||||
# Either Miniserv clears the logs itself, or logrotate takes them over. There
|
||||
# is no setting for the latter, so it is on when clearing is off and a section
|
||||
# already rotates the logs.
|
||||
my $logrotate = $in{'logclear'} == 2 ? 1 : 0;
|
||||
!$logrotate || &webmin::miniserv_logrotate_available() ||
|
||||
&error($text{'log_elogrotate'});
|
||||
my $was_logrotate = !int($miniserv{'logclear'}) &&
|
||||
&webmin::miniserv_logrotate_available() &&
|
||||
&webmin::get_miniserv_logrotate_section(\%miniserv) ? 1 : 0;
|
||||
|
||||
# Validate and save the access-log settings.
|
||||
!$in{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
|
||||
&error(&text('log_ehours', $in{'logtime'}));
|
||||
$miniserv{'log'} = $in{'log'};
|
||||
$miniserv{'loghost'} = $in{'loghost'};
|
||||
$miniserv{'logtrust'} = $in{'logtrust'};
|
||||
$miniserv{'logclf'} = $in{'logclf'};
|
||||
$miniserv{'logclear'} = $in{'logclear'};
|
||||
$miniserv{'logclear'} = $logrotate ? 0 : $in{'logclear'};
|
||||
!$miniserv{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
|
||||
&error(&text('log_ehours', $in{'logtime'}));
|
||||
$miniserv{'logtime'} = $in{'logtime'};
|
||||
|
||||
# Save the error destination and matching systemd drop-in when supported.
|
||||
@@ -35,6 +45,14 @@ else {
|
||||
}
|
||||
&unlock_file($usermin_miniserv_config);
|
||||
|
||||
# Hand the Miniserv logs to logrotate, or take them back from it
|
||||
if ($logrotate) {
|
||||
&webmin::setup_miniserv_logrotate(\%miniserv, "usermin");
|
||||
}
|
||||
elsif ($was_logrotate) {
|
||||
&webmin::remove_miniserv_logrotate(\%miniserv);
|
||||
}
|
||||
|
||||
# Restart through systemd when stderr needs to be re-attached.
|
||||
if ($journal_changed) {
|
||||
&webmin::restart_miniserv_systemd_service("usermin.service", 2);
|
||||
|
||||
+10
-4
@@ -21,11 +21,17 @@ print &ui_table_row($text{'log_trust'},
|
||||
&ui_yesno_radio("logtrust", int($miniserv{'logtrust'})));
|
||||
print &ui_table_row($text{'log_clf'},
|
||||
&ui_yesno_radio("logclf", int($miniserv{'logclf'})));
|
||||
my @clear_opts = ( [ 1, &text('log_period',
|
||||
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ] );
|
||||
my $logclear = int($miniserv{'logclear'});
|
||||
if (&webmin::miniserv_logrotate_available()) {
|
||||
push(@clear_opts, [ 2, $text{'log_logrotate'} ]);
|
||||
$logclear = 2 if (!$logclear &&
|
||||
&webmin::get_miniserv_logrotate_section(\%miniserv));
|
||||
}
|
||||
push(@clear_opts, [ 0, $text{'no'} ]);
|
||||
print &ui_table_row($text{'log_clear'},
|
||||
&ui_radio("logclear", int($miniserv{'logclear'}),
|
||||
[ [ 1, &text('log_period',
|
||||
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
&ui_radio("logclear", $logclear, \@clear_opts));
|
||||
|
||||
# Only systemd services can safely inherit stderr into the journal.
|
||||
if (&webmin::miniserv_systemd_journal_available("usermin.service")) {
|
||||
|
||||
@@ -10,6 +10,8 @@ log_trust=Log client IP address when behind proxy?
|
||||
log_clf=Use combined log format (including referrer and user agent)?
|
||||
log_clear=Periodically clear log files?
|
||||
log_period=Yes, every $1 hours
|
||||
log_logrotate=Yes, rotate using <tt>logrotate</tt>
|
||||
log_elogrotate=The <tt>logrotate</tt> configuration is not available on this system
|
||||
log_error=Error log destination
|
||||
log_error_file=Write to <tt>miniserv.error</tt>
|
||||
log_error_journal=Write to the <tt>systemd journal</tt>
|
||||
|
||||
@@ -7621,6 +7621,14 @@ my ($param_action,
|
||||
|
||||
my $m = $param_module ? $param_module : &get_module_name();
|
||||
|
||||
# When logrotate has emptied the actions log, throw away the file changes
|
||||
# and annotations that belonged to the rotated entries
|
||||
if ($gconfig{'logrotate'} && -e $webmin_logfile && !-s _) {
|
||||
&unlink_file("$ENV{'WEBMIN_VAR'}/diffs");
|
||||
&unlink_file("$ENV{'WEBMIN_VAR'}/files");
|
||||
&unlink_file("$ENV{'WEBMIN_VAR'}/annotations");
|
||||
}
|
||||
|
||||
if ($gconfig{'logclear'}) {
|
||||
# check if it is time to clear the log
|
||||
my @st = stat("$webmin_logfile.time");
|
||||
|
||||
+22
-4
@@ -20,8 +20,18 @@ $miniserv{'log'} = $in{'log'};
|
||||
$miniserv{'loghost'} = $in{'loghost'};
|
||||
$miniserv{'logtrust'} = $in{'logtrust'};
|
||||
$miniserv{'logclf'} = $in{'logclf'};
|
||||
$miniserv{'logclear'} = $in{'logclear'};
|
||||
!$in{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
|
||||
|
||||
# Either Miniserv clears the logs itself, or logrotate takes them over. There
|
||||
# is no setting for the latter, so it is on when clearing is off and a section
|
||||
# already rotates the logs.
|
||||
my $logrotate = $in{'logclear'} == 2 ? 1 : 0;
|
||||
!$logrotate || &miniserv_logrotate_available() ||
|
||||
&error($text{'log_elogrotate'});
|
||||
my $was_logrotate = !int($miniserv{'logclear'}) &&
|
||||
&miniserv_logrotate_available() &&
|
||||
&get_miniserv_logrotate_section(\%miniserv) ? 1 : 0;
|
||||
$miniserv{'logclear'} = $logrotate ? 0 : $in{'logclear'};
|
||||
!$miniserv{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
|
||||
&error(&text('log_ehours', $in{'logtime'}));
|
||||
$miniserv{'logtime'} = $in{'logtime'};
|
||||
if ($in{'perms_def'}) {
|
||||
@@ -60,9 +70,18 @@ else {
|
||||
}
|
||||
&unlock_file($ENV{'MINISERV_CONFIG'});
|
||||
|
||||
# Hand the Miniserv and actions logs to logrotate, or take them back from it
|
||||
if ($logrotate) {
|
||||
&setup_miniserv_logrotate(\%miniserv, "webmin", [ $webmin_logfile ]);
|
||||
}
|
||||
elsif ($was_logrotate) {
|
||||
&remove_miniserv_logrotate(\%miniserv, [ $webmin_logfile ]);
|
||||
}
|
||||
|
||||
$gconfig{'log'} = $in{'log'};
|
||||
$gconfig{'logtime'} = $in{'logtime'};
|
||||
$gconfig{'logclear'} = $in{'logclear'};
|
||||
$gconfig{'logclear'} = $miniserv{'logclear'};
|
||||
$gconfig{'logrotate'} = $logrotate;
|
||||
$gconfig{'logusers'} =
|
||||
$in{'uall'} ? '' : join(" ", split(/\0/, $in{'users'}));
|
||||
$gconfig{'logmodules'} =
|
||||
@@ -95,4 +114,3 @@ else {
|
||||
&show_restart_page();
|
||||
}
|
||||
&webmin_log("log", undef, undef, \%in);
|
||||
|
||||
|
||||
+11
-5
@@ -31,12 +31,18 @@ print &ui_table_row($text{'log_trust'},
|
||||
print &ui_table_row($text{'log_clf'},
|
||||
&ui_yesno_radio("logclf", int($miniserv{'logclf'})));
|
||||
|
||||
# Clear logs regularly
|
||||
# Clear logs regularly, or hand them over to logrotate when available
|
||||
my @clear_opts = ( [ 1, &text('log_period',
|
||||
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ] );
|
||||
my $logclear = int($miniserv{'logclear'});
|
||||
if (&miniserv_logrotate_available()) {
|
||||
push(@clear_opts, [ 2, $text{'log_logrotate'} ]);
|
||||
$logclear = 2 if (!$logclear &&
|
||||
&get_miniserv_logrotate_section(\%miniserv));
|
||||
}
|
||||
push(@clear_opts, [ 0, $text{'no'} ]);
|
||||
print &ui_table_row($text{'log_clear2'},
|
||||
&ui_radio("logclear", int($miniserv{'logclear'}),
|
||||
[ [ 1, &text('log_period',
|
||||
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
&ui_radio("logclear", $logclear, \@clear_opts));
|
||||
|
||||
# A systemd service can keep Miniserv errors in the journal instead.
|
||||
if (&miniserv_systemd_journal_available("webmin.service")) {
|
||||
|
||||
@@ -93,6 +93,8 @@ log_trust=Log client IP address when behind proxy?
|
||||
log_clf=Use combined log format (including referrer and user agent)?
|
||||
log_clear2=Periodically clear log files?
|
||||
log_period=Yes, every $1 hours
|
||||
log_logrotate=Yes, rotate using <tt>logrotate</tt>
|
||||
log_elogrotate=The <tt>logrotate</tt> configuration is not available on this system
|
||||
log_error=Error log destination
|
||||
log_error_file=Write to <tt>miniserv.error</tt>
|
||||
log_error_journal=Write to the <tt>systemd journal</tt>
|
||||
|
||||
@@ -83,6 +83,8 @@ else {
|
||||
$logfile =~ s![^/]+$!miniserv.error!;
|
||||
$miniserv->{'errorlog'} = $logfile;
|
||||
&unlink_file($dropin) if (-e $dropin);
|
||||
# Take the directory too, unless other drop-ins are still using it
|
||||
rmdir($dropin_dir) if (-d $dropin_dir);
|
||||
}
|
||||
|
||||
# Apply the Miniserv and service-manager settings as one UI operation.
|
||||
@@ -110,6 +112,142 @@ else {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 miniserv_logrotate_available()
|
||||
|
||||
Returns 1 if logrotate is installed and can be configured from Webmin.
|
||||
|
||||
=cut
|
||||
sub miniserv_logrotate_available
|
||||
{
|
||||
return &foreign_available("logrotate") && &foreign_installed("logrotate")
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
=head2 miniserv_log_files(&miniserv)
|
||||
|
||||
Returns the Miniserv access and error log paths that rotation applies to.
|
||||
|
||||
=cut
|
||||
sub miniserv_log_files
|
||||
{
|
||||
my ($miniserv) = @_;
|
||||
my $errorlog = $miniserv->{'logfile'};
|
||||
$errorlog =~ s![^/]+$!miniserv.error!;
|
||||
$errorlog = $miniserv->{'errorlog'} if ($miniserv->{'errorlog'} =~ /^\//);
|
||||
return &unique($miniserv->{'logfile'}, $errorlog);
|
||||
}
|
||||
|
||||
=head2 get_miniserv_logrotate_section(&miniserv)
|
||||
|
||||
Returns the logrotate section that covers the Miniserv access log, if any.
|
||||
|
||||
=cut
|
||||
sub get_miniserv_logrotate_section
|
||||
{
|
||||
my ($miniserv) = @_;
|
||||
&foreign_require("logrotate");
|
||||
my ($logfile) = &miniserv_log_files($miniserv);
|
||||
# Global directives like weekly and rotate share the list with log sections,
|
||||
# but their name is a plain string instead of a list of log files
|
||||
foreach my $c (@{&logrotate::get_config()}) {
|
||||
next if (ref($c->{'name'}) ne 'ARRAY');
|
||||
return $c if (&indexof($logfile, @{$c->{'name'}}) >= 0);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
=head2 setup_miniserv_logrotate(&miniserv, name, [&extra-logs])
|
||||
|
||||
Adds any missing Miniserv and extra logs to the access-log section, or creates
|
||||
a section when none exists. Copy-truncate rotation lets Miniserv keep its open
|
||||
error log handle, as it only re-opens that log when the file disappears.
|
||||
|
||||
=cut
|
||||
sub setup_miniserv_logrotate
|
||||
{
|
||||
my ($miniserv, $name, $extra) = @_;
|
||||
my @logs = &unique(&miniserv_log_files($miniserv), @{$extra || [ ]});
|
||||
my $lconf = &get_miniserv_logrotate_section($miniserv);
|
||||
|
||||
# Skip any log that some other section already rotates
|
||||
my %covered;
|
||||
foreach my $c (@{&logrotate::get_config()}) {
|
||||
next if (ref($c->{'name'}) ne 'ARRAY');
|
||||
foreach my $log (@{$c->{'name'}}) {
|
||||
$covered{$log} = 1;
|
||||
}
|
||||
}
|
||||
my @missing = grep { !$covered{$_} } @logs;
|
||||
return 0 if ($lconf && !@missing);
|
||||
|
||||
# Vendor files cannot be edited in place, so copy first on systems that
|
||||
# keep their logrotate config under /usr
|
||||
my $file = $lconf ? $lconf->{'file'} : &logrotate::get_add_file($name);
|
||||
&logrotate::ensure_writable_config_file($file);
|
||||
my $parent = &logrotate::get_config_parent();
|
||||
if ($lconf) {
|
||||
# Copying re-reads the config, so look the section up again
|
||||
$lconf = &get_miniserv_logrotate_section($miniserv);
|
||||
push(@{$lconf->{'name'}}, @missing);
|
||||
}
|
||||
else {
|
||||
$lconf = { 'file' => &logrotate::get_add_file($name),
|
||||
'name' => [ grep { !$covered{$_} } @logs ],
|
||||
'members' => [ { 'name' => 'weekly' },
|
||||
{ 'name' => 'rotate', 'value' => 7 },
|
||||
{ 'name' => 'missingok' },
|
||||
{ 'name' => 'notifempty' },
|
||||
{ 'name' => 'compress' },
|
||||
{ 'name' => 'copytruncate' } ] };
|
||||
}
|
||||
&lock_file($lconf->{'file'});
|
||||
&logrotate::save_directive($parent,
|
||||
defined($lconf->{'index'}) ? $lconf : undef,
|
||||
$lconf);
|
||||
&flush_file_lines($lconf->{'file'});
|
||||
&unlock_file($lconf->{'file'});
|
||||
&logrotate::flush_logrotate_config_cache();
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 remove_miniserv_logrotate(&miniserv, [&extra-logs])
|
||||
|
||||
Removes the Miniserv logs and any extra logs from their logrotate section. The
|
||||
section and its file are deleted when they cover nothing else.
|
||||
|
||||
=cut
|
||||
sub remove_miniserv_logrotate
|
||||
{
|
||||
my ($miniserv, $extra) = @_;
|
||||
my $lconf = &get_miniserv_logrotate_section($miniserv);
|
||||
return 0 if (!$lconf);
|
||||
&logrotate::ensure_writable_config_file($lconf->{'file'});
|
||||
|
||||
# Copying re-reads the config, so look the section up again
|
||||
$lconf = &get_miniserv_logrotate_section($miniserv);
|
||||
my %logs = map { $_, 1 } (&miniserv_log_files($miniserv), @{$extra || [ ]});
|
||||
my @leftover = grep { !$logs{$_} } @{$lconf->{'name'}};
|
||||
my $parent = &logrotate::get_config_parent();
|
||||
&lock_file($lconf->{'file'});
|
||||
if (@leftover) {
|
||||
# Other logs share the section, so only drop ours
|
||||
$lconf->{'name'} = \@leftover;
|
||||
&logrotate::save_directive($parent, $lconf, $lconf);
|
||||
&flush_file_lines($lconf->{'file'});
|
||||
&unlock_file($lconf->{'file'});
|
||||
&logrotate::flush_logrotate_config_cache();
|
||||
}
|
||||
else {
|
||||
&logrotate::save_directive($parent, $lconf, undef);
|
||||
&flush_file_lines($lconf->{'file'});
|
||||
&unlock_file($lconf->{'file'});
|
||||
&logrotate::flush_logrotate_config_cache();
|
||||
&logrotate::delete_if_empty($lconf->{'file'});
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
our $primary_port = $primary_ssl ? 443 : 80;
|
||||
|
||||
our $webmin_key_email = "jcameron\@webmin.com";
|
||||
|
||||
Reference in New Issue
Block a user