Fix logging under systemd

icinga2.service used `-e ${ICINGA2_ERROR_LOG}`, but this is documented
as having no effect without `-d`.  Furthermore, icinga2 under systemd
unconditionally logged everything to the system log (but without setting
the log level etc), which contradicted the documentation.  (Issue #6339)

Stop icinga2 on systemd from logging to stdout - and hence the system log -
once it has finished starting up.  Just like when you start icinga2 from a
terminal using `-d`.  And just like -d, we stop logging fatal errors to
stderr, and instead write to the log file passed with `-e`.

As per docs, mainlog (icinga2.log) is already enabled by default.  And
pre-startup messages including config errors will still appear in the
system log.

This uses a new option --close-stdio, which has the same effect on logging as
--daemonize, but does not fork or call setsid().

For this purpose, I moved setsid() up and into Daemonize().

Consequence of that last point: if anyone is weird enough to specify a TTY
device file as the fatal error log (-e option), that will become icinga's
controlling terminal, which you generally don't want as a daemon.  This
makes it consistent with the existing behaviour for icinga mainlog.  For
this reason you're supposed to use O_NOCTTY in Linux daemons.  But I wasn't
sure where icinga would want to put the ugly `#ifdef _WIN32 ... #else ...`.
This commit is contained in:
Alan Jenkins 2018-05-29 17:24:05 +01:00
parent 50463a6a10
commit a21166dcf8
3 changed files with 21 additions and 13 deletions

View File

@ -388,8 +388,10 @@ Command options:
-z [ --no-config ] start without a configuration file -z [ --no-config ] start without a configuration file
-C [ --validate ] exit after validating the configuration -C [ --validate ] exit after validating the configuration
-e [ --errorlog ] arg log fatal errors to the specified log file (only -e [ --errorlog ] arg log fatal errors to the specified log file (only
works in combination with --daemonize) works in combination with --daemonize or
--close-stdio)
-d [ --daemonize ] detach from the controlling terminal -d [ --daemonize ] detach from the controlling terminal
--close-stdio do not log to stdout (or stderr) after startup
Report bugs at <https://github.com/Icinga/icinga2> Report bugs at <https://github.com/Icinga/icinga2>
Icinga home page: <https://www.icinga.com/> Icinga home page: <https://www.icinga.com/>

View File

@ -6,7 +6,7 @@ After=syslog.target network-online.target postgresql.service mariadb.service car
Type=notify Type=notify
EnvironmentFile=@ICINGA2_SYSCONFIGFILE@ EnvironmentFile=@ICINGA2_SYSCONFIGFILE@
ExecStartPre=@CMAKE_INSTALL_PREFIX@/lib/icinga2/prepare-dirs @ICINGA2_SYSCONFIGFILE@ ExecStartPre=@CMAKE_INSTALL_PREFIX@/lib/icinga2/prepare-dirs @ICINGA2_SYSCONFIGFILE@
ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2 daemon -e ${ICINGA2_ERROR_LOG} ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2 daemon --close-stdio -e ${ICINGA2_ERROR_LOG}
PIDFile=@ICINGA2_INITRUNDIR@/icinga2.pid PIDFile=@ICINGA2_INITRUNDIR@/icinga2.pid
ExecReload=@CMAKE_INSTALL_PREFIX@/lib/icinga2/safe-reload @ICINGA2_SYSCONFIGFILE@ ExecReload=@CMAKE_INSTALL_PREFIX@/lib/icinga2/safe-reload @ICINGA2_SYSCONFIGFILE@
TimeoutStartSec=30m TimeoutStartSec=30m

View File

@ -105,6 +105,14 @@ static void Daemonize() noexcept
Log(LogDebug, "Daemonize()") Log(LogDebug, "Daemonize()")
<< "Child process with PID " << Utility::GetPid() << " continues; re-initializing base."; << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
// Detach from controlling terminal
pid_t sid = setsid();
if (sid == -1) {
Log(LogCritical, "cli")
<< "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
exit(EXIT_FAILURE);
}
try { try {
Application::InitializeBase(); Application::InitializeBase();
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
@ -115,7 +123,7 @@ static void Daemonize() noexcept
#endif /* _WIN32 */ #endif /* _WIN32 */
} }
static bool SetDaemonIO(const String& stderrFile) static void CloseStdIO(const String& stderrFile)
{ {
#ifndef _WIN32 #ifndef _WIN32
int fdnull = open("/dev/null", O_RDWR); int fdnull = open("/dev/null", O_RDWR);
@ -147,14 +155,7 @@ static bool SetDaemonIO(const String& stderrFile)
if (fderr > 2) if (fderr > 2)
close(fderr); close(fderr);
} }
pid_t sid = setsid();
if (sid == -1) {
return false;
}
#endif #endif
return true;
} }
String DaemonCommand::GetDescription() const String DaemonCommand::GetDescription() const
@ -174,9 +175,10 @@ void DaemonCommand::InitParameters(boost::program_options::options_description&
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file") ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
("no-config,z", "start without a configuration file") ("no-config,z", "start without a configuration file")
("validate,C", "exit after validating the configuration") ("validate,C", "exit after validating the configuration")
("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize)") ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio)")
#ifndef _WIN32 #ifndef _WIN32
("daemonize,d", "detach from the controlling terminal") ("daemonize,d", "detach from the controlling terminal")
("close-stdio", "do not log to stdout (or stderr) after startup")
#endif /* _WIN32 */ #endif /* _WIN32 */
; ;
@ -290,12 +292,16 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::strin
} }
} }
if (vm.count("daemonize")) { if (vm.count("daemonize") || vm.count("close-stdio")) {
// After disabling the console log, any further errors will go to the configured log only.
// Let's try to make this clear and say good bye.
Log(LogInformation, "cli", "Closing console log.");
String errorLog; String errorLog;
if (vm.count("errorlog")) if (vm.count("errorlog"))
errorLog = vm["errorlog"].as<std::string>(); errorLog = vm["errorlog"].as<std::string>();
SetDaemonIO(errorLog); CloseStdIO(errorLog);
Logger::DisableConsoleLog(); Logger::DisableConsoleLog();
} }