Merge pull request #9000 from haxtibal/feature/journaldlogger

JournaldLogger - log to systemd journal
This commit is contained in:
Alexander Aleksandrovič Klimov 2021-10-01 17:42:10 +02:00 committed by GitHub
commit 63fca8faa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 268 additions and 46 deletions

View File

@ -1754,6 +1754,33 @@ to InfluxDB. Experiment with the setting, if you are processing more than 1024 m
or similar. or similar.
### JournaldLogger <a id="objecttype-journaldlogger"></a>
Specifies Icinga 2 logging to the systemd journal using its native interface.
This configuration object is available as `journald` [logging feature](14-features.md#logging).
Resulting journal records have fields as described in
[journal fields](https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html),
and an additional custom field `ICINGA2_FACILITY` with the detailed message origin (e.g. "ApiListener").
Example:
```
object JournaldLogger "journald" {
severity = "warning"
}
```
Configuration Attributes:
Name | Type | Description
--------------------------|-----------------------|----------------------------------
severity | String | **Optional.** The minimum syslog compatible severity for this log. Can be "debug", "notice", "information", "warning" or "critical". Defaults to "information".
facility | String | **Optional.** Defines the syslog compatible facility to use for journal entries. This can be a facility constant like `FacilityDaemon`. Defaults to `FacilityUser`.
identifier | String | **Optional.** Defines the syslog compatible identifier (also known as "tag") to use for journal entries. If not given, systemd's default behavior is used and usually results in "icinga2".
Facility Constants are the same as for [SyslogLogger](09-object-types.md#objecttype-sysloglogger).
### LiveStatusListener <a id="objecttype-livestatuslistener"></a> ### LiveStatusListener <a id="objecttype-livestatuslistener"></a>

View File

@ -14,6 +14,7 @@ and `icinga2 feature disable` commands to configure loggers:
Feature | Description Feature | Description
----------------|------------ ----------------|------------
debuglog | Debug log (path: `/var/log/icinga2/debug.log`, severity: `debug` or higher) debuglog | Debug log (path: `/var/log/icinga2/debug.log`, severity: `debug` or higher)
journald | Systemd Journal (severity: `warning` or higher)
mainlog | Main log (path: `/var/log/icinga2/icinga2.log`, severity: `information` or higher) mainlog | Main log (path: `/var/log/icinga2/icinga2.log`, severity: `information` or higher)
syslog | Syslog (severity: `warning` or higher) syslog | Syslog (severity: `warning` or higher)
windowseventlog | Windows Event Log (severity: `information` or higher) windowseventlog | Windows Event Log (severity: `information` or higher)

View File

@ -39,6 +39,9 @@ install_if_not_exists(icinga2/features-available/debuglog.conf ${ICINGA2_CONFIGD
install_if_not_exists(icinga2/features-available/mainlog.conf ${ICINGA2_CONFIGDIR}/features-available) install_if_not_exists(icinga2/features-available/mainlog.conf ${ICINGA2_CONFIGDIR}/features-available)
if(NOT WIN32) if(NOT WIN32)
install_if_not_exists(icinga2/features-available/syslog.conf ${ICINGA2_CONFIGDIR}/features-available) install_if_not_exists(icinga2/features-available/syslog.conf ${ICINGA2_CONFIGDIR}/features-available)
if(HAVE_SYSTEMD)
install_if_not_exists(icinga2/features-available/journald.conf ${ICINGA2_CONFIGDIR}/features-available)
endif()
else() else()
install_if_not_exists(icinga2/features-available/windowseventlog.conf ${ICINGA2_CONFIGDIR}/features-available) install_if_not_exists(icinga2/features-available/windowseventlog.conf ${ICINGA2_CONFIGDIR}/features-available)
endif() endif()

View File

@ -0,0 +1,7 @@
/**
* The JournaldLogger type writes log information to the systemd journal.
*/
object JournaldLogger "journald" {
severity = "warning"
}

View File

@ -6,6 +6,7 @@ mkclass_target(configuration.ti configuration-ti.cpp configuration-ti.hpp)
mkclass_target(datetime.ti datetime-ti.cpp datetime-ti.hpp) mkclass_target(datetime.ti datetime-ti.cpp datetime-ti.hpp)
mkclass_target(filelogger.ti filelogger-ti.cpp filelogger-ti.hpp) mkclass_target(filelogger.ti filelogger-ti.cpp filelogger-ti.hpp)
mkclass_target(function.ti function-ti.cpp function-ti.hpp) mkclass_target(function.ti function-ti.cpp function-ti.hpp)
mkclass_target(journaldlogger.ti journaldlogger-ti.cpp journaldlogger-ti.hpp)
mkclass_target(logger.ti logger-ti.cpp logger-ti.hpp) mkclass_target(logger.ti logger-ti.cpp logger-ti.hpp)
mkclass_target(perfdatavalue.ti perfdatavalue-ti.cpp perfdatavalue-ti.hpp) mkclass_target(perfdatavalue.ti perfdatavalue-ti.cpp perfdatavalue-ti.hpp)
mkclass_target(streamlogger.ti streamlogger-ti.cpp streamlogger-ti.hpp) mkclass_target(streamlogger.ti streamlogger-ti.cpp streamlogger-ti.hpp)
@ -36,6 +37,7 @@ set(base_SOURCES
function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp
initialize.cpp initialize.hpp initialize.cpp initialize.hpp
io-engine.cpp io-engine.hpp io-engine.cpp io-engine.hpp
journaldlogger.cpp journaldlogger.hpp journaldlogger-ti.hpp
json.cpp json.hpp json-script.cpp json.cpp json.hpp json-script.cpp
lazy-init.hpp lazy-init.hpp
library.cpp library.hpp library.cpp library.hpp
@ -112,7 +114,10 @@ if(WIN32)
install(TARGETS eventprovider LIBRARY DESTINATION ${CMAKE_INSTALL_SBINDIR}) install(TARGETS eventprovider LIBRARY DESTINATION ${CMAKE_INSTALL_SBINDIR})
endif() endif()
set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/application-version.cpp PROPERTY EXCLUDE_UNITY_BUILD TRUE) set_property(
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/application-version.cpp ${CMAKE_CURRENT_SOURCE_DIR}/journaldlogger.cpp
PROPERTY EXCLUDE_UNITY_BUILD TRUE
)
if(ICINGA2_UNITY_BUILD) if(ICINGA2_UNITY_BUILD)
mkunity_target(base base base_SOURCES) mkunity_target(base base base_SOURCES)
@ -123,6 +128,11 @@ if(HAVE_SYSTEMD)
NAMES systemd/sd-daemon.h NAMES systemd/sd-daemon.h
HINTS ${SYSTEMD_ROOT_DIR}) HINTS ${SYSTEMD_ROOT_DIR})
include_directories(${SYSTEMD_INCLUDE_DIR}) include_directories(${SYSTEMD_INCLUDE_DIR})
set_property(
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/journaldlogger.cpp
APPEND PROPERTY COMPILE_DEFINITIONS
SD_JOURNAL_SUPPRESS_LOCATION
)
endif() endif()
add_library(base OBJECT ${base_SOURCES}) add_library(base OBJECT ${base_SOURCES})

View File

@ -0,0 +1,87 @@
/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
#include "base/i2-base.hpp"
#if !defined(_WIN32) && defined(HAVE_SYSTEMD)
#include "base/journaldlogger.hpp"
#include "base/journaldlogger-ti.cpp"
#include "base/configtype.hpp"
#include "base/statsfunction.hpp"
#include "base/sysloglogger.hpp"
#include <systemd/sd-journal.h>
using namespace icinga;
REGISTER_TYPE(JournaldLogger);
REGISTER_STATSFUNCTION(JournaldLogger, &JournaldLogger::StatsFunc);
void JournaldLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
{
DictionaryData nodes;
for (const JournaldLogger::Ptr& journaldlogger : ConfigType::GetObjectsByType<JournaldLogger>()) {
nodes.emplace_back(journaldlogger->GetName(), 1); //add more stats
}
status->Set("journaldlogger", new Dictionary(std::move(nodes)));
}
void JournaldLogger::OnConfigLoaded()
{
ObjectImpl<JournaldLogger>::OnConfigLoaded();
m_ConfiguredJournalFields.clear();
m_ConfiguredJournalFields.push_back(
String("SYSLOG_FACILITY=") + Value(SyslogHelper::FacilityToNumber(GetFacility())));
const String identifier = GetIdentifier();
if (!identifier.IsEmpty()) {
m_ConfiguredJournalFields.push_back(String("SYSLOG_IDENTIFIER=" + identifier));
}
}
void JournaldLogger::ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
ObjectImpl<JournaldLogger>::ValidateFacility(lvalue, utils);
if (!SyslogHelper::ValidateFacility(lvalue()))
BOOST_THROW_EXCEPTION(ValidationError(this, { "facility" }, "Invalid facility specified."));
}
/**
* Processes a log entry and outputs it to journald.
*
* @param entry The log entry.
*/
void JournaldLogger::ProcessLogEntry(const LogEntry& entry)
{
const std::vector<String> sdFields {
String("MESSAGE=") + entry.Message.GetData(),
String("PRIORITY=") + Value(SyslogHelper::SeverityToNumber(entry.Severity)),
String("ICINGA2_FACILITY=") + entry.Facility,
};
SystemdJournalSend(sdFields);
}
void JournaldLogger::Flush()
{
/* Nothing to do here. */
}
void JournaldLogger::SystemdJournalSend(const std::vector<String>& varJournalFields) const
{
struct iovec iovec[m_ConfiguredJournalFields.size() + varJournalFields.size()];
int iovecCount = 0;
for (const String& journalField: m_ConfiguredJournalFields) {
iovec[iovecCount] = IovecFromString(journalField);
iovecCount++;
}
for (const String& journalField: varJournalFields) {
iovec[iovecCount] = IovecFromString(journalField);
iovecCount++;
}
sd_journal_sendv(iovec, iovecCount);
}
struct iovec JournaldLogger::IovecFromString(const String& s) {
return { const_cast<char *>(s.CStr()), s.GetLength() };
}
#endif /* !_WIN32 && HAVE_SYSTEMD */

View File

@ -0,0 +1,44 @@
/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
#ifndef JOURNALDLOGGER_H
#define JOURNALDLOGGER_H
#include "base/i2-base.hpp"
#if !defined(_WIN32) && defined(HAVE_SYSTEMD)
#include "base/journaldlogger-ti.hpp"
#include <sys/uio.h>
namespace icinga
{
/**
* A logger that logs to systemd journald.
*
* @ingroup base
*/
class JournaldLogger final : public ObjectImpl<JournaldLogger>
{
public:
DECLARE_OBJECT(JournaldLogger);
DECLARE_OBJECTNAME(JournaldLogger);
static void StaticInitialize();
static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
void OnConfigLoaded() override;
void ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) override;
protected:
void SystemdJournalSend(const std::vector<String>& varJournalFields) const;
static struct iovec IovecFromString(const String& s);
std::vector<String> m_ConfiguredJournalFields;
void ProcessLogEntry(const LogEntry& entry) override;
void Flush() override;
};
}
#endif /* !_WIN32 && HAVE_SYSTEMD */
#endif /* JOURNALDLOGGER_H */

View File

@ -0,0 +1,21 @@
/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
#include "base/logger.hpp"
library base;
namespace icinga
{
class JournaldLogger : Logger
{
activation_priority -100;
[config] String facility {
default {{{ return "LOG_USER"; }}}
};
[config] String identifier;
};
}

View File

@ -5,6 +5,7 @@
#include "base/sysloglogger-ti.cpp" #include "base/sysloglogger-ti.cpp"
#include "base/configtype.hpp" #include "base/configtype.hpp"
#include "base/statsfunction.hpp" #include "base/statsfunction.hpp"
#include <syslog.h>
using namespace icinga; using namespace icinga;
@ -12,11 +13,11 @@ REGISTER_TYPE(SyslogLogger);
REGISTER_STATSFUNCTION(SyslogLogger, &SyslogLogger::StatsFunc); REGISTER_STATSFUNCTION(SyslogLogger, &SyslogLogger::StatsFunc);
INITIALIZE_ONCE(&SyslogLogger::StaticInitialize); INITIALIZE_ONCE(&SyslogHelper::StaticInitialize);
std::map<String, int> SyslogLogger::m_FacilityMap; std::map<String, int> SyslogHelper::m_FacilityMap;
void SyslogLogger::StaticInitialize() void SyslogHelper::StaticInitialize()
{ {
ScriptGlobal::Set("System.FacilityAuth", "LOG_AUTH", true); ScriptGlobal::Set("System.FacilityAuth", "LOG_AUTH", true);
ScriptGlobal::Set("System.FacilityAuthPriv", "LOG_AUTHPRIV", true); ScriptGlobal::Set("System.FacilityAuthPriv", "LOG_AUTHPRIV", true);
@ -61,6 +62,44 @@ void SyslogLogger::StaticInitialize()
m_FacilityMap["LOG_UUCP"] = LOG_UUCP; m_FacilityMap["LOG_UUCP"] = LOG_UUCP;
} }
bool SyslogHelper::ValidateFacility(const String& facility)
{
if (m_FacilityMap.find(facility) == m_FacilityMap.end()) {
try {
Convert::ToLong(facility);
} catch (const std::exception&) {
return false;
}
}
return true;
}
int SyslogHelper::SeverityToNumber(LogSeverity severity)
{
switch (severity) {
case LogDebug:
return LOG_DEBUG;
case LogNotice:
return LOG_NOTICE;
case LogWarning:
return LOG_WARNING;
case LogCritical:
return LOG_CRIT;
case LogInformation:
default:
return LOG_INFO;
}
}
int SyslogHelper::FacilityToNumber(const String& facility)
{
auto it = m_FacilityMap.find(facility);
if (it != m_FacilityMap.end())
return it->second;
else
return Convert::ToLong(facility);
}
void SyslogLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) void SyslogLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
{ {
DictionaryData nodes; DictionaryData nodes;
@ -75,28 +114,14 @@ void SyslogLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&)
void SyslogLogger::OnConfigLoaded() void SyslogLogger::OnConfigLoaded()
{ {
ObjectImpl<SyslogLogger>::OnConfigLoaded(); ObjectImpl<SyslogLogger>::OnConfigLoaded();
m_Facility = SyslogHelper::FacilityToNumber(GetFacility());
String facilityString = GetFacility();
auto it = m_FacilityMap.find(facilityString);
if (it != m_FacilityMap.end())
m_Facility = it->second;
else
m_Facility = Convert::ToLong(facilityString);
} }
void SyslogLogger::ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) void SyslogLogger::ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils)
{ {
ObjectImpl<SyslogLogger>::ValidateFacility(lvalue, utils); ObjectImpl<SyslogLogger>::ValidateFacility(lvalue, utils);
if (!SyslogHelper::ValidateFacility(lvalue()))
if (m_FacilityMap.find(lvalue()) == m_FacilityMap.end()) { BOOST_THROW_EXCEPTION(ValidationError(this, { "facility" }, "Invalid facility specified."));
try {
Convert::ToLong(lvalue());
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ValidationError(this, { "facility" }, "Invalid facility specified."));
}
}
} }
/** /**
@ -106,27 +131,8 @@ void SyslogLogger::ValidateFacility(const Lazy<String>& lvalue, const Validation
*/ */
void SyslogLogger::ProcessLogEntry(const LogEntry& entry) void SyslogLogger::ProcessLogEntry(const LogEntry& entry)
{ {
int severity; syslog(SyslogHelper::SeverityToNumber(entry.Severity) | m_Facility,
switch (entry.Severity) { "%s", entry.Message.CStr());
case LogDebug:
severity = LOG_DEBUG;
break;
case LogNotice:
severity = LOG_NOTICE;
break;
case LogWarning:
severity = LOG_WARNING;
break;
case LogCritical:
severity = LOG_CRIT;
break;
case LogInformation:
default:
severity = LOG_INFO;
break;
}
syslog(severity | m_Facility, "%s", entry.Message.CStr());
} }
void SyslogLogger::Flush() void SyslogLogger::Flush()

View File

@ -10,6 +10,23 @@
namespace icinga namespace icinga
{ {
/**
* Helper class to handle syslog facility strings and numbers.
*
* @ingroup base
*/
class SyslogHelper final
{
public:
static void StaticInitialize();
static bool ValidateFacility(const String& facility);
static int SeverityToNumber(LogSeverity severity);
static int FacilityToNumber(const String& facility);
private:
static std::map<String, int> m_FacilityMap;
};
/** /**
* A logger that logs to syslog. * A logger that logs to syslog.
* *
@ -21,14 +38,12 @@ public:
DECLARE_OBJECT(SyslogLogger); DECLARE_OBJECT(SyslogLogger);
DECLARE_OBJECTNAME(SyslogLogger); DECLARE_OBJECTNAME(SyslogLogger);
static void StaticInitialize();
static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
void OnConfigLoaded() override; void OnConfigLoaded() override;
void ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) override; void ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) override;
protected: protected:
static std::map<String, int> m_FacilityMap;
int m_Facility; int m_Facility;
void ProcessLogEntry(const LogEntry& entry) override; void ProcessLogEntry(const LogEntry& entry) override;

View File

@ -10,7 +10,7 @@ icolor brightgreen "object[ \t]+(timeperiod|scheduleddowntime|dependency|perfd
icolor brightgreen "object[ \t]+(graphitewriter|idomysqlconnection|idomysqlconnection)" icolor brightgreen "object[ \t]+(graphitewriter|idomysqlconnection|idomysqlconnection)"
icolor brightgreen "object[ \t]+(livestatuslistener|statusdatawriter|externalcommandlistener)" icolor brightgreen "object[ \t]+(livestatuslistener|statusdatawriter|externalcommandlistener)"
icolor brightgreen "object[ \t]+(compatlogger|checkresultreader|checkcomponent|notificationcomponent)" icolor brightgreen "object[ \t]+(compatlogger|checkresultreader|checkcomponent|notificationcomponent)"
icolor brightgreen "object[ \t]+(filelogger|sysloglogger|apilistener|endpoint|zone)" icolor brightgreen "object[ \t]+(filelogger|sysloglogger|journaldlogger|apilistener|endpoint|zone)"
## apply def ## apply def
icolor brightgreen "apply[ \t]+(Service|Dependency|Notification|ScheduledDowntime)" icolor brightgreen "apply[ \t]+(Service|Dependency|Notification|ScheduledDowntime)"

View File

@ -57,7 +57,8 @@ syn keyword icinga2ObjType Comment Dependency Downtime ElasticsearchWriter
syn keyword icinga2ObjType Endpoint EventCommand ExternalCommandListener syn keyword icinga2ObjType Endpoint EventCommand ExternalCommandListener
syn keyword icinga2ObjType FileLogger GelfWriter GraphiteWriter Host HostGroup syn keyword icinga2ObjType FileLogger GelfWriter GraphiteWriter Host HostGroup
syn keyword icinga2ObjType IcingaApplication IdoMysqlConnection IdoPgsqlConnection syn keyword icinga2ObjType IcingaApplication IdoMysqlConnection IdoPgsqlConnection
syn keyword icinga2ObjType InfluxdbWriter Influxdb2Writer LivestatusListener Notification NotificationCommand syn keyword icinga2ObjType InfluxdbWriter Influxdb2Writer JournaldLogger
syn keyword icinga2ObjType LivestatusListener Notification NotificationCommand
syn keyword icinga2ObjType NotificationComponent OpenTsdbWriter PerfdataWriter syn keyword icinga2ObjType NotificationComponent OpenTsdbWriter PerfdataWriter
syn keyword icinga2ObjType ScheduledDowntime Service ServiceGroup SyslogLogger syn keyword icinga2ObjType ScheduledDowntime Service ServiceGroup SyslogLogger
syn keyword icinga2ObjType TimePeriod User UserGroup WindowsEventLogLogger Zone syn keyword icinga2ObjType TimePeriod User UserGroup WindowsEventLogLogger Zone