Version 0.2.0 - major refactoring of code, and new features.
This commit is contained in:
parent
80c08901e6
commit
4576619ae7
2 changed files with 468 additions and 193 deletions
3
TODO.md
3
TODO.md
|
|
@ -1,5 +1,2 @@
|
||||||
* Write a man page.
|
* Write a man page.
|
||||||
* Add a regex filter (read from a file) to decide what to log and what to drop.
|
* Add a regex filter (read from a file) to decide what to log and what to drop.
|
||||||
* Instead of requiring a fifo already exist, if the file doesn't already exist create the fifo. Would need to modify the trap for SIGTERM in order to clean up the file.
|
|
||||||
* Figure out a way to check if the program is respawning too offten - this would indicate an error in the calling process and we don't want to just keep looping forever.
|
|
||||||
* Have an option to change UID and/or GID when running. Alternatively, use setpriv to drop capabilities.
|
|
||||||
|
|
|
||||||
652
lumberjack
652
lumberjack
|
|
@ -3,83 +3,167 @@
|
||||||
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
||||||
# Licensed under the terms of the GNU General Public License version 3.
|
# Licensed under the terms of the GNU General Public License version 3.
|
||||||
#
|
#
|
||||||
#........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1.........2.........3.....:...4
|
#........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1..:......2.........3.........4.........5.........:
|
||||||
|
|
||||||
|
|
||||||
# Script details.
|
# Script details.
|
||||||
LJ_NAME="${0##*/}"
|
NAME="${0##*/}"
|
||||||
LJ_VERSION="0.1.7"
|
VERSION="0.2.0"
|
||||||
|
|
||||||
|
|
||||||
|
# Functions.
|
||||||
|
check_leading_dirs() {
|
||||||
|
# $1 The virtual host being processed.
|
||||||
|
# $2 The path of which to validate the leading directories.
|
||||||
|
|
||||||
|
[[ -z "$1" || -z "$2" ]] && return 1
|
||||||
|
|
||||||
|
if ! is_dir "$(remove_expansions "$2")"; then
|
||||||
|
(( FLAGS[${1}-template_prefix] == 0 )) && {
|
||||||
|
syslog "warn" "prefix directories of template do not exist: $(remove_expansions "$2")"
|
||||||
|
FLAGS[${1}-template_prefix]=1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
(( FLAGS[${1}-template_prefix] == 1 )) && {
|
||||||
|
syslog "info" "prefix directories of template reappeared: $(remove_expansions "$2")"
|
||||||
|
FLAGS[${1}-template_prefix]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
close_fd() {
|
||||||
|
# $1 The site of which to close the file descriptor.
|
||||||
|
|
||||||
|
[[ -z "$1" ]] && return 1
|
||||||
|
|
||||||
|
# shellcheck disable=SC1083
|
||||||
|
{ exec {FDS[$1]}>&-; } 2>/dev/null || syslog "warn" "failed to close FD ${FDS[$1]} for $1"
|
||||||
|
unset "FDS[$1]" "FLAGS[${1}-template_prefix]" "FLAGS[${1}-make_dir_fail]" "FLAGS[${1}-fix_link]"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_missing_dirs() {
|
||||||
|
# $1 The virtual host being processed.
|
||||||
|
# $2 The directory to insure exists.
|
||||||
|
|
||||||
|
[[ -z "$1" || -z "$2" ]] && return 1
|
||||||
|
|
||||||
|
if ! make_dir "$2"; then
|
||||||
|
(( FLAGS[${1}-make_dir_fail] == 0 )) && {
|
||||||
|
syslog "warn" "error creating log file's directory: $2"
|
||||||
|
FLAGS[${1}-make_dir_fail]=1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
(( FLAGS[${1}-make_dir_fail] == 1 )) && {
|
||||||
|
syslog "info" "created log file's directory: $2"
|
||||||
|
FLAGS[${1}-make_dir_fail]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
# $1 The text of the error message to display on stderr.
|
# $1 The text of the error message to display on stderr.
|
||||||
printf "%s: %s\n" "$LJ_NAME" "$1" >&2
|
|
||||||
|
printf "%s: %s\\n" "$NAME" "$1" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
display_help() {
|
display_help() {
|
||||||
# |........1.........2.........3.........4.........5.........6.........7.........8
|
# |........1.........2.........3.........4.........5.........6.........7.........8
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
Usage: $LJ_NAME [options] <basedir> <template>
|
Usage: $NAME [options] <basedir> <template>
|
||||||
Process input (possibly including an httpd VirtualHost site identifier) from
|
Process input (possibly including an httpd VirtualHost site identifier) from
|
||||||
stdin or a FIFO and write a log line to a log file based upon the <basedir> and
|
stdin or a pipe/FIFO and write a log line to a log file based upon the <basedir>
|
||||||
<template>.
|
and <template>.
|
||||||
|
|
||||||
Options (all of which are optional):
|
Options (all of which are optional):
|
||||||
-ca <arg> Set the compression command arguments. Default: ${LJ_COMPRESSOR_ARGS[@]}.
|
-ca <arg> Set the compression command arguments. Default: ${COMPRESSOR_ARGS[@]}.
|
||||||
Quotes are required if more than one <arg> is supplied.
|
Quotes are required if more than one <arg> is supplied.
|
||||||
-cc <util> Set the compression command to use. Default: $LJ_COMPRESSOR.
|
-cc <util> Set the compression command to use. Default: $COMPRESSOR.
|
||||||
-f Request flushing of the log file to disk after every write.
|
-f Request flushing of the log file to disk after every write.
|
||||||
This may significantly reduce performance and result in a lot of
|
This may significantly reduce performance and result in a lot of
|
||||||
disk writes. Best to let the kernel do appropriate buffering.
|
disk writes. Best to let the kernel do appropriate buffering.
|
||||||
|
-g <group> Set name of the group to run with. Override the usual
|
||||||
|
behaviour of using the primary group membership of the user
|
||||||
|
specified with -u and run with this GID. All files created by
|
||||||
|
lumberjack will be owned by this group. The default is to run
|
||||||
|
with the primary group that executed lumberjack, which is
|
||||||
|
usually root.
|
||||||
-h Display this help.
|
-h Display this help.
|
||||||
-i <fifo> Read input from the FIFO at <fifo>, rather than stdin.
|
-i <pipe> Read input from the pipe/FIFO at <pipe>, rather than stdin.
|
||||||
|
If the pipe/FIFO does not exist, it will be created.
|
||||||
-j <jobs> Maximum number of compression jobs to have active at once.
|
-j <jobs> Maximum number of compression jobs to have active at once.
|
||||||
Default: $LJ_MAXJOBS. Don't set this too high.
|
Default: $MAXJOBS. Don't set this too high.
|
||||||
-l <link> Create a symlink named <link> to the currently active log file.
|
-l <link> Create a symlink named <link> to the currently active log file.
|
||||||
The <link> is created relative to <basedir>. In normal mode,
|
The <link> is created relative to <basedir>. In normal mode,
|
||||||
the link name may include the same '{}' sequence and %-escaped
|
the link name may include the same '{}' sequence and %-escaped
|
||||||
formatting as the <template> (see below). In raw mode (-r), the
|
formatting as the <template> (see below). In raw mode (-r), the
|
||||||
'{}' is not allowed, but % escape sequences can still be used.
|
'{}' is not allowed, but % escape sequences can still be used.
|
||||||
WARNING: The (expanded) location of this link will be WIPED OUT!
|
WARNING: The (expanded) location of this link will be WIPED OUT!
|
||||||
|
-md <umask> Set the umask used when creating directories. Default: $DIR_UMASK.
|
||||||
|
Useful umasks are: 077, 066, 026 and 022.
|
||||||
|
-mf <umask> Set the umask used when creating files. Default: $FILE_UMASK.
|
||||||
|
Useful umasks are: 066 and 022.
|
||||||
|
-mp <umask> Set the umask used when creating the pipe. Default: $PIPE_UMASK.
|
||||||
|
Useful umasks are: 066 and 006.
|
||||||
|
-p Make all parents. Normally, all directories up to - but not
|
||||||
|
including - the first directory with non-escaped %-format
|
||||||
|
strings of the <template> (see below) must already exist for the
|
||||||
|
log lines to be written to the file. With this option, the
|
||||||
|
parent directories of the logfile will be created automatically.
|
||||||
|
WARNING: This option can be unsafe with certain <template>
|
||||||
|
formats - it can result in creation of arbitrary directories
|
||||||
|
based on unclean input from outside sources.
|
||||||
-r Raw logging mode. In this mode, no processing of the log line
|
-r Raw logging mode. In this mode, no processing of the log line
|
||||||
for an httpd VirtualHost site identifier is performed - log
|
for an httpd VirtualHost site identifier is performed - log
|
||||||
lines are written verbatim to the log filename constructed from
|
lines are written verbatim to the log filename constructed from
|
||||||
<basedir> and <template>.
|
<basedir> and <template>.
|
||||||
-ud <umask> Set the umask used when creating directories. Default: $LJ_DIR_UMASK.
|
-s <facility> Set the syslog facility to be used for logging. Default: $SYSLOG_FACILITY.
|
||||||
Useful umasks are: 077, 066, 026 and 022.
|
-u <user> Set name of the user to run with. With this option, as soon as
|
||||||
-uf <umask> Set the umask used when creating files. Default: $LJ_FILE_UMASK.
|
lumberjack starts it will re-exec itself, running as this user.
|
||||||
Useful umasks are: 077 and 022.
|
Without the -g option, the primary group of <user> is used for
|
||||||
|
the running GID. All files created by lumberjack will be owned
|
||||||
|
by this user. The default is to run as the user that executed
|
||||||
|
lumberjack, which is usually root.
|
||||||
-v Display version and copyright information.
|
-v Display version and copyright information.
|
||||||
-z Enable compression of the old log files.
|
-z Enable compression of the old log files.
|
||||||
-- Cease option processing and begin argument parsing.
|
-- Cease option processing and begin argument parsing.
|
||||||
Option processing ceases with the first non-option argument or --.
|
Option processing ceases with the first non-option argument or --.
|
||||||
|
|
||||||
Arguments (all of which are mandatory):
|
Arguments (all of which are mandatory):
|
||||||
<basedir> The base directory of where to write the log files.
|
<basedir> The base directory of where to write the log files. The base
|
||||||
|
directory must exist.
|
||||||
<template> The filename template. When in normal mode, the template must
|
<template> The filename template. When in normal mode, the template must
|
||||||
include at least one occurrance of '{}', which is replaced with
|
include at least one occurrance of '{}', which is replaced with
|
||||||
the site name from the VirtualHost identifier. In raw mode
|
the site name from the VirtualHost identifier. In raw mode
|
||||||
(-r), the '{}' should not be included in the template. The
|
(-r), the '{}' should not be included in the template. The
|
||||||
template may also include any %-escaped format strings
|
template may also include any %-prefixed format strings
|
||||||
recognised by the strftime(3) function. See below for examples.
|
recognised by the strftime(3) function. See below for examples.
|
||||||
|
The template can not start with a / - it is relative to the
|
||||||
|
<basedir>. Unless the -p option is used, all directories up to
|
||||||
|
- but not including - the first directory with non-escaped
|
||||||
|
%-format strings of the <template> must already exist for the
|
||||||
|
log lines to be written to the file.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
When used with the httpd CustomLog directive, using %v as the first log format
|
When used with the httpd CustomLog directive, using %v as the first log format
|
||||||
string:
|
string:
|
||||||
"|$LJ_NAME '/path/to/logsdir' '{}/logs/access-log-%Y-%m'"
|
"|$NAME '/path/to/logsdir' '{}/logs/access-log-%Y-%m'"
|
||||||
Where the httpd VirtualHost identifier is 'example.com', would write logs
|
Where the httpd VirtualHost identifier is 'example.com', would write logs
|
||||||
(with the site identifier stripped) to the filename:
|
(with the site identifier stripped) to the filename:
|
||||||
/path/to/logsdir/example.com/logs/access-log-<year>-<month>
|
/path/to/logsdir/example.com/logs/access-log-<year>-<month>
|
||||||
"|$LJ_NAME '/path/to/logsdir' '{}/logs/()-access-log-%Y-%m'"
|
"|$NAME '/path/to/logsdir' '{}/logs/()-access-log-%Y-%m'"
|
||||||
Where the httpd VirtualHost identifier is 'example.com', would write logs
|
Where the httpd VirtualHost identifier is 'example.com', would write logs
|
||||||
(with the site identifier steipped) to the filename:
|
(with the site identifier steipped) to the filename:
|
||||||
/path/to/logsdir/example.com/logs/example.com-access-log-<year>-<month>
|
/path/to/logsdir/example.com/logs/example.com-access-log-<year>-<month>
|
||||||
When used with the httpd ErrorLog directive (both examples are equilivent):
|
When used with the httpd ErrorLog directive (both examples are equilivent):
|
||||||
"|$LJ_NAME -r '/path/to/logsdir' 'logs/error-log-%Y-%m'"
|
"|$NAME -r '/path/to/logsdir' 'logs/error-log-%Y-%m'"
|
||||||
Would write raw log lines to the filename:
|
Would write raw log lines to the filename:
|
||||||
/path/to/logsdir/logs/error-log-<year>-<month>
|
/path/to/logsdir/logs/error-log-<year>-<month>
|
||||||
"|$LJ_NAME -r '/path/to/logsdir/logs' 'error-log-%Y-%m'"
|
"|$NAME -r '/path/to/logsdir/logs' 'error-log-%Y-%m'"
|
||||||
Equilivant to the above; would write raw log lines to the filename:
|
Equilivant to the above; would write raw log lines to the filename:
|
||||||
/path/to/logsdir/logs/error-log-<year>-<month>
|
/path/to/logsdir/logs/error-log-<year>-<month>
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -88,7 +172,7 @@ EOF
|
||||||
display_version() {
|
display_version() {
|
||||||
# |........1.........2.........3.........4.........5.........6.........7.........8
|
# |........1.........2.........3.........4.........5.........6.........7.........8
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
$LJ_NAME v$LJ_VERSION.
|
$NAME v$VERSION.
|
||||||
Copyright (c) 2018-2020 Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>.
|
Copyright (c) 2018-2020 Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>.
|
||||||
Licensed under the terms of the GNU GPL v3 <http://gnu.org/licenses/gpl.html>.
|
Licensed under the terms of the GNU GPL v3 <http://gnu.org/licenses/gpl.html>.
|
||||||
This program is free software; you can modify or redistribute it in accordence
|
This program is free software; you can modify or redistribute it in accordence
|
||||||
|
|
@ -97,76 +181,123 @@ display_version() {
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit_handler() {
|
||||||
|
(( FLAGS[created_fifo] == 1 )) && {
|
||||||
|
rm -f "$INPUT" 2>/dev/null || syslog "warn" "failed to remove pipe/fifo: $INPUT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is_dir() {
|
is_dir() {
|
||||||
# $1 The path to verify is a directory.
|
# $1 The path to verify is a directory.
|
||||||
[[ ! "$1" ]] && return 1
|
|
||||||
[[ ! -d "$1" ]] && {
|
[[ -z "$1" ]] && return 1
|
||||||
syslog "error" "not a directory: $1"
|
|
||||||
return 1
|
[[ ! -d "$1" ]] && return 1
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
make_dir() {
|
make_dir() {
|
||||||
# $1 The directory to create.
|
# $1 The directory to create.
|
||||||
[[ ! "$1" ]] && return 1
|
|
||||||
[[ ! -e "$1" ]] && {
|
[[ -z "$1" ]] && return 1
|
||||||
umask "$LJ_DIR_UMASK"
|
|
||||||
mkdir -p "$1" 2>/dev/null || {
|
if [[ ! -e "$1" ]]; then
|
||||||
syslog "error" "failed to create directory: $1"
|
umask "$DIR_UMASK"
|
||||||
return 1
|
mkdir -p "$1" 2>/dev/null || return 1
|
||||||
}
|
else
|
||||||
}
|
is_dir "$1" || return 1
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
open_fd() {
|
open_fd() {
|
||||||
# $1 The site identifier in the array.
|
# $1 The site/vhost identifier in the array.
|
||||||
# $2 The log file path to open.
|
# $2 The log file path to open.
|
||||||
|
|
||||||
[[ ! "$1" || ! "$2" ]] && return 1
|
[[ -z "$1" || -z "$2" ]] && return 1
|
||||||
|
umask "$FILE_UMASK"
|
||||||
umask "$LJ_FILE_UMASK"
|
# shellcheck disable=SC1083
|
||||||
exec {LJ_FDS[$1]}>>"$2" || {
|
if ! { exec {FDS[$1]}>>"$2"; } 2>/dev/null; then
|
||||||
|
(( FLAGS[${1}-open_fd_fail] == 0 )) && {
|
||||||
syslog "error" "failed to open log file for writing: $2"
|
syslog "error" "failed to open log file for writing: $2"
|
||||||
return 1
|
FLAGS[${1}-open_fd_fail]=1
|
||||||
}
|
}
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
(( FLAGS[${1}-open_fd_fail] == 1 )) && {
|
||||||
|
syslog "info" "opened log file for writing: $2"
|
||||||
|
FLAGS[${1}-open_fd_fail]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove_expansions() {
|
||||||
|
# This function takes a template path as input and will output a path with elements
|
||||||
|
# starting with the first (non-escaped) %-sequence to the end of the path, removed.
|
||||||
|
# That is, it will return the path with the variable parts removed.
|
||||||
|
# $1 The path to parse.
|
||||||
|
|
||||||
|
local IFS='/' ITEMS INDEX
|
||||||
|
|
||||||
|
read -r -a ITEMS <<<"${1//+(\/)/\/}"
|
||||||
|
|
||||||
|
for INDEX in "${!ITEMS[@]}"; do
|
||||||
|
# Thanks to Marc Eberhard for helping with the regex that I couldn't quite get right.
|
||||||
|
if [[ "${ITEMS[INDEX]}" =~ ^((%%)*[^%]*)*[%]?$ ]]; then
|
||||||
|
printf "%s" "${ITEMS[INDEX]}"
|
||||||
|
[[ ! -z "${ITEMS[INDEX+1]}" ]] && [[ "${ITEMS[INDEX+1]}" =~ ^((%%)*[^%]*)*[%]?$ ]] && printf "%s" "/"
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
sigchld_handler() {
|
sigchld_handler() {
|
||||||
local LJ_JOB
|
local JOB
|
||||||
for LJ_JOB in "${!LJ_JOBS[@]}"; do
|
|
||||||
[[ "${LJ_JOBS[$LJ_JOB]}" ]] && {
|
for JOB in "${!JOBS[@]}"; do
|
||||||
! kill -0 "${LJ_JOBS[$LJ_JOB]}" >/dev/null 2>&1 && {
|
[[ "${JOBS[$JOB]}" ]] && {
|
||||||
wait "${LJ_JOBS[$LJ_JOB]}"
|
! kill -0 "${JOBS[$JOB]}" >/dev/null 2>&1 && {
|
||||||
unset "LJ_JOBS[$LJ_JOB]"
|
wait "${JOBS[$JOB]}"
|
||||||
(( LJ_RUNNING-- ))
|
unset "JOBS[$JOB]"
|
||||||
|
(( RUNNING_JOBS-- ))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
start_compression_jobs
|
start_compression_jobs
|
||||||
(( LJ_RUNNING == 0 )) && set +bm
|
(( RUNNING_JOBS == 0 )) && set +bm
|
||||||
|
}
|
||||||
|
|
||||||
|
sighup_handler() {
|
||||||
|
local SITE
|
||||||
|
|
||||||
|
syslog "info" "closing all file descriptors"
|
||||||
|
for SITE in "${!FDS[@]}"; do
|
||||||
|
close_fd "$SITE"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
sigterm_handler() {
|
sigterm_handler() {
|
||||||
local LJ_SITE LJ_JOB
|
local SITE
|
||||||
for LJ_SITE in "${!LJ_FDS[@]}"; do
|
|
||||||
{ exec {LJ_FDS[$LJ_SITE]}>&-; } 2>/dev/null
|
for SITE in "${!FDS[@]}"; do
|
||||||
|
close_fd "$SITE"
|
||||||
done
|
done
|
||||||
disown -a
|
disown -a
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
start_compression_jobs() {
|
start_compression_jobs() {
|
||||||
local LJ_JOB
|
local JOB
|
||||||
while (( LJ_RUNNING < LJ_MAXJOBS )); do
|
|
||||||
for LJ_JOB in "${!LJ_JOBS[@]}"; do
|
while (( RUNNING_JOBS < MAXJOBS )); do
|
||||||
[[ ! "${LJ_JOBS[$LJ_JOB]}" ]] && {
|
for JOB in "${!JOBS[@]}"; do
|
||||||
|
[[ ! "${JOBS[$JOB]}" ]] && {
|
||||||
set -bm
|
set -bm
|
||||||
"$LJ_COMPRESSOR" "${LJ_COMPRESSOR_ARGS[@]}" "$LJ_JOB" >/dev/null 2>&1 &
|
"$COMPRESSOR" "${COMPRESSOR_ARGS[@]}" "$JOB" >/dev/null 2>&1 &
|
||||||
LJ_JOBS[$LJ_JOB]="$!"
|
JOBS[$JOB]="$!"
|
||||||
(( LJ_RUNNING++ ))
|
(( RUNNING_JOBS++ ))
|
||||||
continue 2
|
continue 2
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
|
|
@ -177,28 +308,51 @@ start_compression_jobs() {
|
||||||
syslog() {
|
syslog() {
|
||||||
# $1 The syslog level at which to log the message.
|
# $1 The syslog level at which to log the message.
|
||||||
# $2 The text of the message to log.
|
# $2 The text of the message to log.
|
||||||
[[ ! "$1" || ! "$2" ]] && return 1
|
|
||||||
logger --id="$$" -p "user.$1" -t "$LJ_NAME" "$1: $2" 2>/dev/null
|
[[ -z "$1" || -z "$2" ]] && return 1
|
||||||
|
|
||||||
|
logger --id="$$" -p "$SYSLOG_FACILITY.$1" -t "$NAME" "$1: $2" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Extended globs are required.
|
||||||
|
shopt -s extglob
|
||||||
|
|
||||||
|
# Some variables.
|
||||||
|
# The array of file descriptors corresponding to each path.
|
||||||
|
declare -A FDS
|
||||||
|
# The array of jobs needing to be compressed.
|
||||||
|
declare -A JOBS
|
||||||
|
# The array of flags.
|
||||||
|
declare -A FLAGS
|
||||||
|
# The number of compression jobs currently active.
|
||||||
|
RUNNING_JOBS=0
|
||||||
|
# The original arguments to the script.
|
||||||
|
ORIG_ARGS=()
|
||||||
|
|
||||||
# Some detaults.
|
# Some detaults.
|
||||||
LJ_COMPRESSOR_ARGS=( "-9" )
|
COMPRESSOR_ARGS=( "-9" )
|
||||||
LJ_COMPRESSOR="gzip" # Use gzip by default as log processing utils can often natively read gzipped files.
|
COMPRESSOR="gzip" # Use gzip by default as log processing utils can often natively read gzipped files.
|
||||||
LJ_FLUSH=0
|
INPUT="/dev/stdin"
|
||||||
LJ_INPUT="/dev/stdin"
|
MAXJOBS="4"
|
||||||
LJ_MAXJOBS="4"
|
LINKFILE=""
|
||||||
LJ_LINKFILE=""
|
DIR_UMASK="022"
|
||||||
LJ_RAW=0
|
FILE_UMASK="022"
|
||||||
LJ_DIR_UMASK="022"
|
PIPE_UMASK="066"
|
||||||
LJ_FILE_UMASK="022"
|
SYSLOG_FACILITY="user"
|
||||||
LJ_COMPRESS=0
|
RUNAS_USER=""
|
||||||
|
RUNAS_GROUP=""
|
||||||
|
FLAGS=([flush]=0 [raw]=0 [compress]=0 [make_parents]=0 [created_fifo]=0 [timed_out]=0 [basedir_vanished]=0 [basedir_notdir]=0)
|
||||||
|
|
||||||
# trap signals.
|
# trap signals.
|
||||||
trap 'sigchld_handler' SIGCHLD
|
trap 'sigchld_handler' SIGCHLD
|
||||||
trap '' SIGHUP
|
trap 'sighup_handler' SIGHUP
|
||||||
trap 'syslog "info" "received SIGUSR1 ping request"' SIGUSR1
|
trap 'syslog "info" "received SIGUSR1 ping request"' SIGUSR1
|
||||||
trap 'sigterm_handler' SIGTERM
|
trap 'sigterm_handler' SIGTERM
|
||||||
|
trap 'exit_handler' EXIT
|
||||||
|
|
||||||
|
# Retain the copy of the original arguments.
|
||||||
|
#read -r -a ORIG_ARGS <<<"$@"
|
||||||
|
|
||||||
# Parse command line options.
|
# Parse command line options.
|
||||||
while :; do
|
while :; do
|
||||||
|
|
@ -206,75 +360,121 @@ while :; do
|
||||||
-ca)
|
-ca)
|
||||||
# Set the compression command arguments.
|
# Set the compression command arguments.
|
||||||
[[ ! "$2" ]] && die "missing argument to -ca"
|
[[ ! "$2" ]] && die "missing argument to -ca"
|
||||||
LJ_COMPRESSOR_ARGS=( $2 )
|
read -r -a COMPRESSOR_ARGS <<<"$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-cc)
|
-cc)
|
||||||
# Set the compression command to use.
|
# Set the compression command to use.
|
||||||
[[ ! "$2" ]] && die "missing argument to -cc"
|
[[ ! "$2" ]] && die "missing argument to -cc"
|
||||||
"$2" --help >/dev/null 2>&1 || die "$2: invalid compressor command"
|
"$2" --help >/dev/null 2>&1 || die "invalid compressor command: $2"
|
||||||
LJ_COMPRESSOR="$2"
|
COMPRESSOR="$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-f)
|
-f)
|
||||||
# Flush files after every write.
|
# Flush files after every write.
|
||||||
LJ_FLUSH=1
|
FLAGS[flush]=1
|
||||||
|
ORIG_ARGS+=("$1")
|
||||||
shift
|
shift
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
|
-g)
|
||||||
|
# Set the group to run as.
|
||||||
|
(( UID != 0 )) && die "only root can use -g"
|
||||||
|
getent group "$2" >/dev/null 2>&1 || die "invalid group: $2"
|
||||||
|
RUNAS_GROUP="$2"
|
||||||
|
shift 2
|
||||||
|
continue
|
||||||
|
;;
|
||||||
-h|-help|--help)
|
-h|-help|--help)
|
||||||
# Show the help screen and exit.
|
# Show the help screen and exit.
|
||||||
display_help
|
display_help
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-i)
|
-i)
|
||||||
# Use a FIFO instead of stdin - the FIFO must already exist (use 'mkfifo' first).
|
# Use a pipe/FIFO instead of stdin.
|
||||||
[[ ! "$2" ]] && die "missing argument to -f"
|
[[ ! "$2" ]] && die "missing argument to -i"
|
||||||
[[ "${2:0:1}" != "/" ]] && die "$2: must be an absolute path"
|
[[ "${2:0:1}" != "/" ]] && die "must be an absolute path: $2"
|
||||||
[[ ! -e "$2" ]] && die "$2: no such file"
|
INPUT="$2"
|
||||||
[[ ! -p "$2" ]] && due "$2: not a FIFO"
|
ORIG_ARGS+=("$1" "$2")
|
||||||
LJ_INPUT="$2"
|
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-j)
|
-j)
|
||||||
# Set the maximum number of concurrent compression jobs to have active at once.
|
# Set the maximum number of concurrent compression jobs to have active at once.
|
||||||
[[ ! "$2" =~ [0-9]+ ]] && die "$2: invalid number of jobs"
|
[[ ! "$2" =~ [0-9]+ ]] && die "invalid number of jobs: $2"
|
||||||
(( $2 == 0 )) && die "$2: invalid number of jobs"
|
(( $2 == 0 )) && die "invalid number of jobs: $2"
|
||||||
LJ_MAXJOBS="$2"
|
MAXJOBS="$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-l)
|
-l)
|
||||||
# Set the link name to use.
|
# Set the link name to use.
|
||||||
[[ ! "$2" ]] && die "missing argument to -l"
|
[[ ! "$2" ]] && die "missing argument to -l"
|
||||||
[[ "${2:0:1}" == "/" ]] && die "$2: link name cannot begin with '/'"
|
[[ "${2:0:1}" == "/" ]] && die "link name cannot begin with '/': $2"
|
||||||
[[ "${2: -1:1}" == "/" ]] && die "$2: link name cannot end with '/'"
|
[[ "${2: -1:1}" == "/" ]] && die "link name cannot end with '/': $2"
|
||||||
LJ_LINKFILE="$2"
|
LINKFILE="$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
|
-md)
|
||||||
|
# Set the directory umask.
|
||||||
|
[[ ! "$2" ]] && die "missing argument to -md"
|
||||||
|
[[ ! "$2" =~ [0-7]{3,4} ]] && die "invalid umask: $2"
|
||||||
|
DIR_UMASK="$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
-mf)
|
||||||
|
# Set the file umask.
|
||||||
|
[[ ! "$2" ]] && die "missing argument to -mf"
|
||||||
|
[[ ! "$2" =~ [0-7]{3} ]] && die "invalid umask: $2"
|
||||||
|
FILE_UMASK="$2"
|
||||||
|
ORIG_ARGS+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
-mp)
|
||||||
|
# Set the pipe umask.
|
||||||
|
[[ ! "$2" ]] && die "missing argument to -mp"
|
||||||
|
[[ ! "$2" =~ [0-7]{3} ]] && die "invalid umask: $2"
|
||||||
|
PIPE_UMASK="$2"
|
||||||
|
shift 2
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
-p)
|
||||||
|
# Create parent directories.
|
||||||
|
FLAGS[make_parents]=1
|
||||||
|
ORIG_ARGS+=("$1")
|
||||||
|
shift
|
||||||
|
continue
|
||||||
|
;;
|
||||||
-r)
|
-r)
|
||||||
# Set raw mode.
|
# Set raw mode.
|
||||||
LJ_RAW=1
|
FLAGS[raw]=1
|
||||||
|
ORIG_ARGS+=("$1")
|
||||||
shift
|
shift
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-ud)
|
-s)
|
||||||
# Set the directory umask.
|
# Set the syslog facility.
|
||||||
[[ ! "$2" ]] && die "missing argument to -ud"
|
[[ ! "${2,,}" =~ (auth|authpriv|cron|daemon|ftp|kern|lpr|mail|news|syslog|user|uucp|local[0-7]) ]] && die "invalid syslog facility: $2"
|
||||||
[[ ! "$2" =~ [0-7]{3} ]] && die "$2: invalid umask"
|
SYSLOG_FACILITY="${2,,}"
|
||||||
LJ_DIR_UMASK="$2"
|
ORIG_ARGS+=("$1" "$2")
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
-uf)
|
-u)
|
||||||
# Set the file umask.
|
# Set the user to run as.
|
||||||
[[ ! "$2" ]] && die "missing argument to -uf"
|
(( UID != 0 )) && die "only root can use -u"
|
||||||
[[ ! "$2" =~ [0-7]{3} ]] && die "$2: invalid umask"
|
getent passwd "$2" >/dev/null 2>&1 || die "invalid user: $2"
|
||||||
LJ_FILE_UMASK="$2"
|
RUNAS_USER="$2"
|
||||||
shift 2
|
shift 2
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
|
|
@ -285,7 +485,8 @@ while :; do
|
||||||
;;
|
;;
|
||||||
-z)
|
-z)
|
||||||
# Compress logs once they are rotated.
|
# Compress logs once they are rotated.
|
||||||
LJ_COMPRESS=1
|
FLAGS[compress]=1
|
||||||
|
ORIG_ARGS+=("$1")
|
||||||
shift
|
shift
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
|
|
@ -302,35 +503,52 @@ done
|
||||||
|
|
||||||
# If there isn't 2 arguments left, exit.
|
# If there isn't 2 arguments left, exit.
|
||||||
(( $# != 2 )) && {
|
(( $# != 2 )) && {
|
||||||
printf "%s\n" "$LJ_NAME: incorrect number of non-option arguments" >&2
|
printf "%s\\n" "$NAME: incorrect number of non-option arguments" >&2
|
||||||
printf "%s\n" "Try: $LJ_NAME -h" >&2
|
printf "%s\\n" "Try: $NAME -h" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# The remaining arguments should be the base directory and the template.
|
# The remaining arguments should be the base directory and the template.
|
||||||
LJ_BASEDIR="$1"
|
BASEDIR="${1/%\//}"
|
||||||
LJ_TEMPLATE="$2"
|
TEMPLATE="$2"
|
||||||
|
|
||||||
|
# Apply user and group settings.
|
||||||
|
if [[ ! -z "$RUNAS_USER" ]]; then
|
||||||
|
if [[ ! -z "$RUNAS_GROUP" ]]; then
|
||||||
|
exec su -g "$RUNAS_GROUP" -- "$RUNAS_USER" "$0" "${ORIG_ARGS[@]}" "$BASEDIR" "$TEMPLATE"
|
||||||
|
else
|
||||||
|
exec su -- "$RUNAS_USER" "$@" "${ORIG_ARGS[@]}" "$BASEDIR" "$TEMPLATE"
|
||||||
|
fi
|
||||||
|
elif [[ ! -z "$RUNAS_GROUP" ]]; then
|
||||||
|
exec sg -- "$RUNAS_GROUP" "$0" "${ORIG_ARGS[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Santy checking.
|
# Santy checking.
|
||||||
[[ "${LJ_BASEDIR:0:1}" != "/" ]] && die "$LJ_BASEDIR: must be an absolute path"
|
[[ "${BASEDIR:0:1}" != "/" ]] && die "must be an absolute path: $BASEDIR"
|
||||||
[[ ! -e "$LJ_BASEDIR" ]] && die "$LJ_BASEDIR: base directory does not exist"
|
[[ ! -e "$BASEDIR" ]] && die "base directory does not exist: $BASEDIR"
|
||||||
[[ ! -d "$LJ_BASEDIR" ]] && die "$LJ_BASEDIR: not a directory"
|
[[ ! -d "$BASEDIR" ]] && die "not a directory: $BASEDIR"
|
||||||
[[ "${LJ_TEMPLATE: -1:1}" == "/" ]] && die "$LJ_TEMPLATE: template cannot end with '/'"
|
[[ ! -w "$BASEDIR" ]] && die "no write permission: $BASEDIR"
|
||||||
(( LJ_RAW == 0 )) && [[ ! "$LJ_TEMPLATE" =~ .*\{\} ]] && die "$LJ_TEMPLATE: template must include at least one '{}'"
|
[[ "${TEMPLATE: 0:1}" == "/" ]] && die "template cannot start with '/' - must be a relative path: $TEMPLATE"
|
||||||
(( LJ_RAW != 0 )) && [[ "$LJ_TEMPLATE" =~ .*\{\} ]] && die "$LJ_TEMPLATE: template cannot include '{}'"
|
[[ "${TEMPLATE: -1:1}" == "/" ]] && die "template cannot end with '/' - path must be a filename: $TEMPLATE"
|
||||||
(( LJ_RAW != 0 )) && [[ "$LJ_LINKFILE" =~ .*\{\} ]] && die "$LJ_LINKFILE: link name cannot include '{}'"
|
(( FLAGS[raw] == 0 )) && [[ ! "$TEMPLATE" =~ .*\{\} ]] && die "template must include at least one '{}': $TEMPLATE"
|
||||||
|
(( FLAGS[raw] != 0 )) && [[ "$TEMPLATE" =~ .*\{\} ]] && die "template cannot include '{}': $TEMPLATE"
|
||||||
|
(( FLAGS[raw] != 0 )) && [[ "$LINKFILE" =~ .*\{\} ]] && die "link name cannot include '{}': $LINKFILE"
|
||||||
|
|
||||||
# The array of file descriptors corresponding to each path.
|
# If input is to be a pipe/FIFO, create it if necessary.
|
||||||
declare -A LJ_FDS
|
[[ "$INPUT" != "/dev/stdin" ]] && {
|
||||||
# The array of jobs needing to be compressed.
|
if [[ ! -e "$INPUT" ]]; then
|
||||||
declare -A LJ_JOBS
|
mkfifo "$INPUT" 2>/dev/null || die "failed to create pipe/FIFO: $INPUT"
|
||||||
# The number of compression jobs currently active.
|
FLAGS[created_fifo]=1
|
||||||
LJ_RUNNING=0
|
elif [[ ! -p "$INPUT" ]]; then
|
||||||
|
die "not a pipe/FIFO: $INPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main loop
|
||||||
while :; do
|
while :; do
|
||||||
# Reset used variables.
|
# Reset used variables.
|
||||||
unset LJ_LOG_VHOST LJ_LOG_DATA
|
unset LOG_VHOST LOG_DATA
|
||||||
LJ_TIMED_OUT=0
|
FLAGS[timed_out]=0
|
||||||
|
|
||||||
# Start compression jobs if there's any in the queue.
|
# Start compression jobs if there's any in the queue.
|
||||||
start_compression_jobs
|
start_compression_jobs
|
||||||
|
|
@ -338,132 +556,192 @@ while :; do
|
||||||
# The time until the top of the next minute - this is used for the 'read' timeout so that
|
# The time until the top of the next minute - this is used for the 'read' timeout so that
|
||||||
# closing log files and compression can still occur even if no log lines are written.
|
# closing log files and compression can still occur even if no log lines are written.
|
||||||
# Note: This does mean we can't have per second log files, but I can't see that being a requirement.
|
# Note: This does mean we can't have per second log files, but I can't see that being a requirement.
|
||||||
LJ_TTNM="$(( 60 - 10#$(printf "%(%S)T") ))"
|
# shellcheck disable=SC2183
|
||||||
|
TTNM="$(( 60 - 10#$(printf "%(%S)T") ))"
|
||||||
|
|
||||||
# Read the log line.
|
# Read the log line.
|
||||||
# Note: The $(...) expansion should *not* be quoted in this instance.
|
# Note: The $(...) expansion should *not* be quoted in this instance, and the space between
|
||||||
read -r -t "$LJ_TTNM" $((( LJ_RAW == 0 )) && printf "%s" "LJ_LOG_VHOST") LJ_LOG_DATA <"$LJ_INPUT"
|
# $( and (( is necessary to quiet shellcheck.
|
||||||
LJ_ERR="$?"
|
# shellcheck disable=SC2046
|
||||||
if (( LJ_ERR > 128 )); then
|
read -r -t "$TTNM" $( (( FLAGS[raw] == 0 )) && printf "%s" "LOG_VHOST") LOG_DATA <"$INPUT"
|
||||||
|
ERR="$?"
|
||||||
|
|
||||||
|
# Determine how the read above was exited.
|
||||||
|
if (( ERR > 128 )); then
|
||||||
# If 'read' timed out, set a marker.
|
# If 'read' timed out, set a marker.
|
||||||
LJ_TIMED_OUT=1
|
FLAGS[timed_out]=1
|
||||||
elif (( LJ_ERR == 1 )); then
|
elif (( ERR == 1 )); then
|
||||||
[[ "$LJ_INPUT" == "/dev/stdin" ]] && {
|
[[ "$INPUT" == "/dev/stdin" ]] && {
|
||||||
# stdin has been closed by the parent, quit gracefully by raising a SIGTERM.
|
# stdin has been closed by the parent, quit gracefully by raising a SIGTERM.
|
||||||
kill -TERM "$$"
|
kill -TERM "$$"
|
||||||
}
|
}
|
||||||
elif (( LJ_ERR != 0 )); then
|
elif (( ERR != 0 )); then
|
||||||
# Unhandled error - sleep for a second and try again.
|
# Unhandled error - log the issue and continue.
|
||||||
syslog "error" "unhandled return code from 'read': $LJ_ERR"
|
syslog "error" "unhandled return code from 'read': $ERR"
|
||||||
sleep 1
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Make sure the base path still exists - it could have disappeared while we were blocked in 'read'.
|
||||||
|
# Note: We won't make this directory ourselves - as it's the base directory it should exist on the system to start with.
|
||||||
|
if [[ ! -e "$BASEDIR" ]]; then
|
||||||
|
(( FLAGS[basedir_vanished] == 0 )) && {
|
||||||
|
syslog "error" "base directory has vanished"
|
||||||
|
FLAGS[basedir_vanished]=1
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
(( FLAGS[basedir_vanished] == 1 )) && {
|
||||||
|
syslog "info" "base directory has reappeared"
|
||||||
|
FLAGS[basedir_vanished]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the base path is a directory.
|
||||||
|
if ! is_dir "$BASEDIR"; then
|
||||||
|
(( FLAGS[basedir_notdir] == 0 )) && {
|
||||||
|
syslog "error" "base path is no longer a directory"
|
||||||
|
FLAGS[basedir_notdir]=1
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
(( FLAGS[basedir_notdir] == 1 )) && {
|
||||||
|
syslog "info" "base path has become directory again"
|
||||||
|
FLAGS[basedir_notdir]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Expand the strftime-encoded strings in the template.
|
# Expand the strftime-encoded strings in the template.
|
||||||
LJ_EXPANDED_TEMPLATE="$(printf "%($LJ_TEMPLATE)T")"
|
EXPANDED_TEMPLATE="$(printf "%($TEMPLATE)T")"
|
||||||
|
|
||||||
# The old expanded template needs to be seeded if it's not already set from a previous loop.
|
# The old expanded template needs to be seeded if it's not already set from a previous loop.
|
||||||
# Set it to the same as the current expanded template so that no rotation is done the first time around.
|
# Set it to the same as the current expanded template so that no rotation is done the first time around.
|
||||||
[[ -z "$LJ_OLD_TEMPLATE" ]] && LJ_OLD_TEMPLATE="$LJ_EXPANDED_TEMPLATE"
|
[[ -z "$OLD_TEMPLATE" ]] && OLD_TEMPLATE="$EXPANDED_TEMPLATE"
|
||||||
|
|
||||||
# If the 'read' timed out and the exapnded template is the same as the old expanded template, there is no need to do anything.
|
# If the 'read' timed out and the exapnded template is the same as the old expanded template, there is no need to do anything.
|
||||||
(( LJ_TIMED_OUT == 1 )) && [[ "$LJ_EXPANDED_TEMPLATE" == "$LJ_OLD_TEMPLATE" ]] && continue
|
(( FLAGS[timed_out] == 1 )) && [[ "$EXPANDED_TEMPLATE" == "$OLD_TEMPLATE" ]] && continue
|
||||||
|
|
||||||
# If the 'read' did not time out but the line read is empty, don't do anything.
|
# If the 'read' did not time out but the line read is empty, don't do anything.
|
||||||
(( LJ_TIMED_OUT != 1 )) && [[ "$LJ_LOG_DATA" =~ ^[[:space:]]*$ ]] && continue
|
(( FLAGS[timed_out] == 0 )) && [[ "$LOG_DATA" =~ ^[[:space:]]*$ ]] && continue
|
||||||
|
|
||||||
# Make sure the base directory still exists - it could have disappeared while we were blocked in 'read'.
|
|
||||||
# Note: We won't make this directory ourselves - as it's the base directory it should exist on the system to start with.
|
|
||||||
[[ ! -e "$LJ_BASEDIR" ]] && {
|
|
||||||
syslog "error" "directory no longer exists: $LJ_BASEDIR"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
is_dir "$LJ_BASEDIR" || continue
|
|
||||||
|
|
||||||
# If the new expanded template is different from the old, close and reopen all the logs and queue for compression (if required).
|
# If the new expanded template is different from the old, close and reopen all the logs and queue for compression (if required).
|
||||||
[[ "$LJ_EXPANDED_TEMPLATE" != "$LJ_OLD_TEMPLATE" ]] && {
|
[[ "$EXPANDED_TEMPLATE" != "$OLD_TEMPLATE" ]] && {
|
||||||
# Loop through all the open FDs.
|
# Loop through all the open FDs.
|
||||||
for LJ_SITE in "${!LJ_FDS[@]}"; do
|
for SITE in "${!FDS[@]}"; do
|
||||||
# Generate the fully expanded filename from the strftime-expanded template and the site name from the array.
|
# Generate the fully expanded filename from the strftime-expanded template and the site name from the array.
|
||||||
LJ_FILENAME="$LJ_BASEDIR/${LJ_EXPANDED_TEMPLATE//\{\}/$LJ_SITE}"
|
FILENAME="$BASEDIR/${EXPANDED_TEMPLATE//\{\}/$SITE}"
|
||||||
|
|
||||||
# Close the file descriptor for the old log file path.
|
# Close the file descriptor for the old log file path.
|
||||||
{ exec {LJ_FDS[$LJ_SITE]}>&-; } 2>/dev/null || {
|
close_fd "$SITE"
|
||||||
syslog "warn" "failed to close FD ${LJ_FDS[$LJ_SITE]} for $LJ_SITE"
|
|
||||||
# Don't 'continue' here as we should still be able to open the new log file. But, it'll leave an FD open indefinitely...
|
# Make sure the directory leading up to the expanded part of the template exists.
|
||||||
}
|
# Note: We don't create this part of the template's path tree as doing so would
|
||||||
unset "LJ_FDS[$LJ_SITE]"
|
# potentially allow any path to be created by use of a custom Host: header.
|
||||||
# Create (if necessary) and verify new log file dir.
|
check_leading_dirs "$SITE" "$BASEDIR/${TEMPLATE//\{\}/$SITE}" || continue
|
||||||
make_dir "${LJ_FILENAME%/*}" || continue
|
|
||||||
is_dir "${LJ_FILENAME%/*}" || continue
|
# Create what's missing from the full log file's directory based on the expanded template.
|
||||||
|
create_missing_dirs "$SITE" "${FILENAME%/*}" || continue
|
||||||
|
|
||||||
# Open the new log file.
|
# Open the new log file.
|
||||||
open_fd "$LJ_SITE" "$LJ_FILENAME" || continue
|
open_fd "$SITE" "$FILENAME" || continue
|
||||||
|
|
||||||
# Fix the now broken symlink - point it to the currently active log file.
|
# Fix the now broken symlink - point it to the currently active log file.
|
||||||
[[ "$LJ_LINKFILE" ]] && {
|
[[ "$LINKFILE" ]] && {
|
||||||
LJ_LINKFILE_EXPANDED="$(printf "%($LJ_LINKFILE)T")"
|
LINKFILE_EXPANDED="$(printf "%($LINKFILE)T")"
|
||||||
# Note: This will clobber anything that already exists with the link name.
|
# Note: This will clobber anything that already exists with the link name.
|
||||||
rm -rf "$LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_SITE}"
|
rm -rf "${BASEDIR:?}/${LINKFILE_EXPANDED//\{\}/$SITE}"
|
||||||
ln -sfr "$LJ_FILENAME" "$LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_SITE}" 2>/dev/null || {
|
if ! ln -sfr "$FILENAME" "$BASEDIR/${LINKFILE_EXPANDED//\{\}/$SITE}"; then
|
||||||
syslog "error" "failed to fix link: $LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_SITE}"
|
(( FLAGS[${SITE}-fix_link] == 0 )) && {
|
||||||
|
syslog "error" "failed to fix link: $BASEDIR/${LINKFILE_EXPANDED//\{\}/$SITE}"
|
||||||
|
FLAGS[${SITE}-fix_link]=1
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
(( FLAGS[${SITE}-fix_link] == 1 )) && {
|
||||||
|
syslog "info" "fixed link: $BASEDIR/${LINKFILE_EXPANDED//\{\}/$SITE}"
|
||||||
|
FLAGS[${SITE}-fix_link]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add the old log file to the compression jobs task list.
|
# Add the old log file to the compression jobs task list.
|
||||||
(( LJ_COMPRESS != 0 )) && {
|
(( FLAGS[compress] != 0 )) && {
|
||||||
LJ_JOBS+=([$LJ_BASEDIR/${LJ_OLD_TEMPLATE//\{\}/$LJ_SITE}]="")
|
JOBS+=([$BASEDIR/${OLD_TEMPLATE//\{\}/$SITE}]="")
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the 'read' did not time out, there must be a log line to write.
|
# If the 'read' did not time out, there must be a log line to write.
|
||||||
(( LJ_TIMED_OUT == 0 )) && {
|
(( FLAGS[timed_out] == 0 )) && {
|
||||||
# If not in raw mode, an unset LJ_LOG_VHOST is an error.
|
# If not in raw mode, an unset LOG_VHOST is an error.
|
||||||
# If in raw mode, we need a placeholder for the LJ_FDS array element as LJ_LOG_VHOST would normally be unset.
|
# If in raw mode, we need a placeholder for the FDS array element as LOG_VHOST would normally be unset.
|
||||||
if (( LJ_RAW == 0 )); then
|
if (( FLAGS[raw] == 0 )); then
|
||||||
[[ ! "$LJ_LOG_VHOST" ]] && {
|
[[ ! "$LOG_VHOST" ]] && {
|
||||||
syslog "error" "empty VirtualHost site identifier"
|
syslog "error" "empty VirtualHost site identifier"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LJ_LOG_VHOST="*raw*"
|
LOG_VHOST="_raw_"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate the fully expanded filename from the strftime-expanded template.
|
# Generate the fully expanded filename from the strftime-expanded template.
|
||||||
LJ_FILENAME="$LJ_BASEDIR/${LJ_EXPANDED_TEMPLATE//\{\}/$LJ_LOG_VHOST}"
|
FILENAME="$BASEDIR/${EXPANDED_TEMPLATE//\{\}/$LOG_VHOST}"
|
||||||
|
|
||||||
# Create/check the log file directory.
|
# Unless the -p option has been used, make sure the directory leading up to the
|
||||||
make_dir "${LJ_FILENAME%/*}" || continue
|
# expanded part of the template exists.
|
||||||
is_dir "${LJ_FILENAME%/*}" || continue
|
(( FLAGS[make_parents] == 0 )) && {
|
||||||
|
check_leading_dirs "$LOG_VHOST" "$BASEDIR/${TEMPLATE//\{\}/$LOG_VHOST}" || continue
|
||||||
|
}
|
||||||
|
|
||||||
# If no FD is open for the VHOST, open it.
|
# Create what's missing from the full log file's directory based on the expanded template.
|
||||||
[[ ! "${LJ_FDS[$LJ_LOG_VHOST]}" ]] && {
|
create_missing_dirs "$LOG_VHOST" "${FILENAME%/*}" || continue
|
||||||
open_fd "$LJ_LOG_VHOST" "$LJ_FILENAME" || continue
|
|
||||||
|
# If no FD is open for the LOG_VHOST, open it.
|
||||||
|
[[ -z "${FDS[$LOG_VHOST]}" ]] && {
|
||||||
|
open_fd "$LOG_VHOST" "$FILENAME" || continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write the log entry.
|
# Write the log entry.
|
||||||
printf "%s\n" "$LJ_LOG_DATA" >&"${LJ_FDS[$LJ_LOG_VHOST]}"
|
printf "%s\\n" "$LOG_DATA" >&"${FDS[$LOG_VHOST]}"
|
||||||
|
|
||||||
# Flush data to disk if requested.
|
# Flush data to disk if requested.
|
||||||
(( LJ_FLUSH == 1 )) && {
|
(( FLAGS[flush] == 1 )) && {
|
||||||
sync "$LJ_FILENAME" 2>/dev/null || syslog "warn" "failed to sync: $LJ_FILENAME"
|
if ! sync "$FILENAME" 2>/dev/null; then
|
||||||
|
(( FLAGS[sync_fail] == 0 )) && {
|
||||||
|
syslog "warn" "failed to sync: $FILENAME"
|
||||||
|
FLAGS[sync_fail]=1
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
(( FLAGS[sync_fail] == 1 )) && {
|
||||||
|
syslog "info" "sync successful: $FILENAME"
|
||||||
|
FLAGS[sync_fail]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create symlink to the currently active log file.
|
# Create symlink to the currently active log file.
|
||||||
[[ "$LJ_LINKFILE" ]] && {
|
[[ "$LINKFILE" ]] && {
|
||||||
LJ_LINKFILE_EXPANDED="$(printf "%($LJ_LINKFILE)T")"
|
LINKFILE_EXPANDED="$(printf "%($LINKFILE)T")"
|
||||||
[[ "$(stat -L --printf="%d:%i" "$LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_LOG_VHOST}" 2>/dev/null)" != \
|
[[ "$(stat -L --printf="%d:%i" "$BASEDIR/${LINKFILE_EXPANDED//\{\}/$LOG_VHOST}" 2>/dev/null)" != \
|
||||||
"$(stat --printf="%d:%i" "$LJ_FILENAME" 2>/dev/null)" ]] && {
|
"$(stat --printf="%d:%i" "$FILENAME" 2>/dev/null)" ]] && {
|
||||||
# Note: This will clobber anything that already exists with the link name.
|
# Note: This will clobber anything that already exists with the link name.
|
||||||
rm -rf "$LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_LOG_VHOST}"
|
rm -rf "${BASEDIR:?}/${LINKFILE_EXPANDED//\{\}/$LOG_VHOST}"
|
||||||
ln -sfr "$LJ_FILENAME" "$LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_LOG_VHOST}" 2>/dev/null || {
|
if ! ln -sfr "$FILENAME" "$BASEDIR/${LINKFILE_EXPANDED//\{\}/$LOG_VHOST}" 2>/dev/null; then
|
||||||
syslog "error" "failed to create link: $LJ_BASEDIR/${LJ_LINKFILE_EXPANDED//\{\}/$LJ_LOG_VHOST}"
|
(( FLAGS[${LOG_VHOST}-create_link] == 0 )) && {
|
||||||
|
syslog "error" "failed to create link: $BASEDIR/${LINKFILE_EXPANDED//\{\}/$LOG_VHOST}"
|
||||||
|
FLAGS[${LOG_VHOST}-create_link]=1
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
(( FLAGS[${LOG_VHOST}-create_link] == 1 )) && {
|
||||||
|
syslog "info" "created link: $BASEDIR/${LINKFILE_EXPANDED//\{\}/$LOG_VHOST}"
|
||||||
|
FLAGS[${LOG_VHOST}-create_link]=0
|
||||||
|
}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Store the last used filename.
|
# Store the last used filename.
|
||||||
LJ_OLD_TEMPLATE="$LJ_EXPANDED_TEMPLATE"
|
OLD_TEMPLATE="$EXPANDED_TEMPLATE"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue