--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-logging.hh"
+
+#include <iomanip>
+#include <set>
+#include <stdexcept>
+
+#include "ext/json11/json11.hpp"
+
+#include "config.h"
+#if defined(HAVE_SYSTEMD)
+#include <systemd/sd-journal.h>
+#endif /* HAVE_SYSTEMD */
+
+namespace dnsdist::logging
+{
+#if defined(HAVE_SYSTEMD)
+static void loggerSDBackend(const Logging::Entry& entry)
+{
+ static const std::set<std::string, CIStringComparePOSIX> special{
+ "message",
+ "message_id",
+ "priority",
+ "code_file",
+ "code_line",
+ "code_func",
+ "errno",
+ "invocation_id",
+ "user_invocation_id",
+ "syslog_facility",
+ "syslog_identifier",
+ "syslog_pid",
+ "syslog_timestamp",
+ "syslog_raw",
+ "documentation",
+ "tid",
+ "unit",
+ "user_unit",
+ "object_pid"};
+
+ // We need to keep the string in mem until sd_journal_sendv has been called
+ std::vector<std::string> strings;
+ auto appendKeyAndVal = [&strings](const string& key, const string& value) {
+ strings.emplace_back(key + "=" + value);
+ };
+ appendKeyAndVal("MESSAGE", entry.message);
+ if (entry.error) {
+ appendKeyAndVal("ERROR", entry.error.value());
+ }
+ appendKeyAndVal("LEVEL", std::to_string(entry.level));
+ appendKeyAndVal("PRIORITY", std::to_string(entry.d_priority));
+ if (entry.name) {
+ appendKeyAndVal("SUBSYSTEM", entry.name.value());
+ }
+ std::array<char, 64> timebuf{};
+ appendKeyAndVal("TIMESTAMP", Logging::toTimestampStringMilli(entry.d_timestamp, timebuf));
+ for (const auto& value : entry.values) {
+ if (value.first.at(0) == '_' || special.count(value.first) != 0) {
+ string key{"PDNS"};
+ key.append(value.first);
+ appendKeyAndVal(toUpper(key), value.second);
+ }
+ else {
+ appendKeyAndVal(toUpper(value.first), value.second);
+ }
+ }
+
+ std::vector<iovec> iov;
+ iov.reserve(strings.size());
+ for (const auto& str : strings) {
+ // iovec has no 2 arg constructor, so make it explicit
+ iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(str.data())), str.size()}); // NOLINT: it's the API
+ }
+ sd_journal_sendv(iov.data(), static_cast<int>(iov.size()));
+}
+#endif /* HAVE_SYSTEMD */
+
+static void loggerJSONBackend(const Logging::Entry& entry)
+{
+ std::array<char, 64> timebuf{};
+ json11::Json::object json = {
+ {"msg", entry.message},
+ {"level", std::to_string(entry.level)},
+ {"ts", Logging::toTimestampStringMilli(entry.d_timestamp, timebuf)},
+ };
+
+ if (entry.error) {
+ json.emplace("error", entry.error.value());
+ }
+
+ if (entry.name) {
+ json.emplace("subsystem", entry.name.value());
+ }
+
+ if (entry.d_priority != 0) {
+ json.emplace("priority", std::to_string(entry.d_priority));
+ }
+
+ for (auto const& value : entry.values) {
+ json.emplace(value.first, value.second);
+ }
+
+ static thread_local std::string out;
+ out.clear();
+ json11::Json doc(std::move(json));
+ doc.dump(out);
+ std::cerr << out << std::endl;
+}
+
+static void loggerBackend(const Logging::Entry& entry)
+{
+ static thread_local std::stringstream buf;
+
+ buf.str("");
+ buf << "msg=" << std::quoted(entry.message);
+ if (entry.error) {
+ buf << " error=" << std::quoted(entry.error.value());
+ }
+
+ if (entry.name) {
+ buf << " subsystem=" << std::quoted(entry.name.value());
+ }
+ buf << " level=" << std::quoted(std::to_string(entry.level));
+ if (entry.d_priority != 0) {
+ buf << " prio=" << std::quoted(Logr::Logger::toString(entry.d_priority));
+ }
+
+ std::array<char, 64> timebuf{};
+ buf << " ts=" << std::quoted(Logging::toTimestampStringMilli(entry.d_timestamp, timebuf));
+ for (auto const& value : entry.values) {
+ buf << " ";
+ buf << value.first << "=" << std::quoted(value.second);
+ }
+
+ std::cout << buf.str() << endl;
+}
+
+static std::shared_ptr<Logging::Logger> s_topLogger{nullptr};
+
+void setup(const std::string& backend)
+{
+ if (backend == "systemd-journal") {
+#if defined(HAVE_SYSTEMD)
+ if (int fileDesc = sd_journal_stream_fd("dnsdist", LOG_DEBUG, 0); fileDesc >= 0) {
+ s_topLogger = Logging::Logger::create(loggerSDBackend);
+ close(fileDesc);
+ }
+#endif
+ if (s_topLogger == nullptr) {
+ cerr << "Requested structured logging to systemd-journal, but it is not available" << endl;
+ }
+ }
+ else if (backend == "json") {
+ s_topLogger = Logging::Logger::create(loggerJSONBackend);
+ if (s_topLogger == nullptr) {
+ cerr << "JSON logging requested but it is not available" << endl;
+ }
+ }
+
+ if (s_topLogger == nullptr) {
+ s_topLogger = Logging::Logger::create(loggerBackend);
+ }
+}
+
+std::shared_ptr<const Logging::Logger> getTopLogger()
+{
+ if (!s_topLogger) {
+ throw std::runtime_error("Trying to access the top-level logger before logging has been setup");
+ }
+
+ return s_topLogger;
+}
+
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "logging.hh"
+
+namespace dnsdist::logging
+{
+void setup(const std::string& backend);
+std::shared_ptr<const Logging::Logger> getTopLogger();
+}
--- /dev/null
+../logging.cc
\ No newline at end of file
--- /dev/null
+../logr.hh
\ No newline at end of file
src_dir / 'dnsdist-ipcrypt2.cc',
src_dir / 'dnsdist-kvs.cc',
src_dir / 'dnsdist-lbpolicies.cc',
+ src_dir / 'dnsdist-logging.cc',
src_dir / 'dnsdist-lua-actions.cc',
src_dir / 'dnsdist-lua-bindings.cc',
src_dir / 'dnsdist-lua-bindings-dnscrypt.cc',
src_dir / 'gettime.cc',
src_dir / 'iputils.cc',
src_dir / 'libssl.cc',
+ src_dir / 'logging.cc',
src_dir / 'misc.cc',
src_dir / 'protozero.cc',
src_dir / 'protozero-trace.cc',
--- /dev/null
+/**
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "logging.hh"
+#include <string>
+#include <mutex>
+
+namespace Logging
+{
+
+std::shared_ptr<const Logger> Logger::getptr() const
+{
+ return shared_from_this();
+}
+
+bool Logger::enabled(Logr::Priority prio) const
+{
+ return _level <= _verbosity || prio != Logr::Absent;
+}
+
+void Logger::info(const std::string& msg) const
+{
+ logMessage(msg, Logr::Absent, std::nullopt);
+}
+
+void Logger::info(Logr::Priority prio, const std::string& msg) const
+{
+ logMessage(msg, prio, std::nullopt);
+}
+
+void Logger::logMessage(const std::string& msg, const std::optional<std::string>& err) const
+{
+ logMessage(msg, Logr::Absent, err);
+}
+
+void Logger::logMessage(const std::string& msg, Logr::Priority prio, const std::optional<std::string>& err) const
+{
+ if (!enabled(prio)) {
+ return;
+ }
+ Entry entry;
+ entry.level = _level;
+ entry.d_priority = prio;
+ ::gettimeofday(&entry.d_timestamp, nullptr);
+ entry.name = _name;
+ entry.message = msg;
+ entry.error = err;
+ auto parent = _parent;
+ entry.values.insert(_values.begin(), _values.end());
+ while (parent) {
+ entry.values.insert(parent->_values.begin(), parent->_values.end());
+ parent = parent->_parent;
+ }
+ _callback(entry);
+}
+
+void Logger::error(Logr::Priority prio, int err, const std::string& msg) const
+{
+ logMessage(msg, prio, std::string(stringerror(err)));
+}
+
+void Logger::error(Logr::Priority prio, const std::string& err, const std::string& msg) const
+{
+ logMessage(msg, prio, err);
+}
+
+void Logger::error(int err, const std::string& msg) const
+{
+ logMessage(msg, Logr::Absent, std::string(stringerror(err)));
+}
+
+void Logger::error(const std::string& err, const std::string& msg) const
+{
+ logMessage(msg, Logr::Absent, err);
+}
+
+std::shared_ptr<Logr::Logger> Logger::v(size_t level) const
+{
+ auto res = std::make_shared<Logger>(getptr(), _name, getVerbosity(), level + _level, _callback);
+ return res;
+}
+
+std::shared_ptr<Logr::Logger> Logger::withValues(const std::map<std::string, std::string>& values) const
+{
+ auto res = std::make_shared<Logger>(getptr(), _name, getVerbosity(), _level, _callback);
+ res->_values = values;
+ return res;
+}
+
+std::shared_ptr<Logr::Logger> Logger::withName(const std::string& name) const
+{
+ std::shared_ptr<Logger> res;
+ if (_name) {
+ res = std::make_shared<Logger>(getptr(), _name.value() + "." + name, getVerbosity(), _level, _callback);
+ }
+ else {
+ res = std::make_shared<Logger>(getptr(), name, getVerbosity(), _level, _callback);
+ }
+ res->setVerbosity(getVerbosity());
+ return res;
+}
+std::shared_ptr<Logger> Logger::create(EntryLogger callback)
+{
+ return std::make_shared<Logger>(callback);
+}
+std::shared_ptr<Logger> Logger::create(EntryLogger callback, const std::string& name)
+{
+ return std::make_shared<Logger>(callback, name);
+}
+
+size_t Logger::getVerbosity() const
+{
+ return _verbosity;
+}
+
+void Logger::setVerbosity(size_t verbosity)
+{
+ _verbosity = verbosity;
+}
+
+Logger::Logger(EntryLogger callback) :
+ _callback(callback)
+{
+}
+Logger::Logger(EntryLogger callback, std::optional<std::string> name) :
+ _callback(callback), _name(std::move(name))
+{
+}
+Logger::Logger(std::shared_ptr<const Logger> parent, std::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback) :
+ _parent(std::move(parent)), _callback(callback), _name(std::move(name)), _level(lvl), _verbosity(verbosity)
+{
+}
+
+Logger::~Logger() = default;
+};
+
+std::shared_ptr<Logging::Logger> g_slog{nullptr};
+
+const char* Logging::toTimestampStringMilli(const struct timeval& tval, std::array<char, 64>& buf, const std::string& format)
+{
+ size_t len = 0;
+ if (format != "%s") {
+ // strftime is not thread safe, it can access locale information
+ static std::mutex mutex;
+ auto lock = std::scoped_lock(mutex);
+ struct tm theTime // clang-format insists on formatting it like this
+ {};
+ len = strftime(buf.data(), buf.size(), format.c_str(), localtime_r(&tval.tv_sec, &theTime));
+ }
+ if (len == 0) {
+ len = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
+ }
+
+ snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
+ return buf.data();
+}
#include "config.h"
-#ifdef RECURSOR
+#if defined(RECURSOR) || defined(DNSDIST)
#include <map>
#include <memory>
}
};
-typedef void (*EntryLogger)(const Entry&);
+using EntryLogger = void (*)(const Entry&);
class Logger : public Logr::Logger, public std::enable_shared_from_this<const Logger>
{
};
}
+#if !defined(DNSDIST)
extern std::shared_ptr<Logging::Logger> g_slog;
// Prefer structured logging? Since Recursor 5.1.0, we always do. We keep a const, to allow for
do { \
slogCall; \
} while (0)
-
#else // No structured logging (e.g. auth)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SLOG(oldStyle, slogCall) \
do { \
oldStyle; \
} while (0)
-#endif // RECURSOR
+#endif /* ! DNSDIST */
+
+#endif // RECURSOR || DNSDIST
+++ /dev/null
-/**
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "logging.hh"
-#include <string>
-#include <mutex>
-#include "utility.hh"
-
-namespace Logging
-{
-
-std::shared_ptr<const Logger> Logger::getptr() const
-{
- return shared_from_this();
-}
-
-bool Logger::enabled(Logr::Priority prio) const
-{
- return _level <= _verbosity || prio != Logr::Absent;
-}
-
-void Logger::info(const std::string& msg) const
-{
- logMessage(msg, Logr::Absent, std::nullopt);
-}
-
-void Logger::info(Logr::Priority prio, const std::string& msg) const
-{
- logMessage(msg, prio, std::nullopt);
-}
-
-void Logger::logMessage(const std::string& msg, const std::optional<std::string>& err) const
-{
- logMessage(msg, Logr::Absent, err);
-}
-
-void Logger::logMessage(const std::string& msg, Logr::Priority prio, const std::optional<std::string>& err) const
-{
- if (!enabled(prio)) {
- return;
- }
- Entry entry;
- entry.level = _level;
- entry.d_priority = prio;
- Utility::gettimeofday(&entry.d_timestamp);
- entry.name = _name;
- entry.message = msg;
- entry.error = err;
- auto parent = _parent;
- entry.values.insert(_values.begin(), _values.end());
- while (parent) {
- entry.values.insert(parent->_values.begin(), parent->_values.end());
- parent = parent->_parent;
- }
- _callback(entry);
-}
-
-void Logger::error(Logr::Priority prio, int err, const std::string& msg) const
-{
- logMessage(msg, prio, std::string(stringerror(err)));
-}
-
-void Logger::error(Logr::Priority prio, const std::string& err, const std::string& msg) const
-{
- logMessage(msg, prio, err);
-}
-
-void Logger::error(int err, const std::string& msg) const
-{
- logMessage(msg, Logr::Absent, std::string(stringerror(err)));
-}
-
-void Logger::error(const std::string& err, const std::string& msg) const
-{
- logMessage(msg, Logr::Absent, err);
-}
-
-std::shared_ptr<Logr::Logger> Logger::v(size_t level) const
-{
- auto res = std::make_shared<Logger>(getptr(), _name, getVerbosity(), level + _level, _callback);
- return res;
-}
-
-std::shared_ptr<Logr::Logger> Logger::withValues(const std::map<std::string, std::string>& values) const
-{
- auto res = std::make_shared<Logger>(getptr(), _name, getVerbosity(), _level, _callback);
- res->_values = values;
- return res;
-}
-
-std::shared_ptr<Logr::Logger> Logger::withName(const std::string& name) const
-{
- std::shared_ptr<Logger> res;
- if (_name) {
- res = std::make_shared<Logger>(getptr(), _name.value() + "." + name, getVerbosity(), _level, _callback);
- }
- else {
- res = std::make_shared<Logger>(getptr(), name, getVerbosity(), _level, _callback);
- }
- res->setVerbosity(getVerbosity());
- return res;
-}
-std::shared_ptr<Logger> Logger::create(EntryLogger callback)
-{
- return std::make_shared<Logger>(callback);
-}
-std::shared_ptr<Logger> Logger::create(EntryLogger callback, const std::string& name)
-{
- return std::make_shared<Logger>(callback, name);
-}
-
-size_t Logger::getVerbosity() const
-{
- return _verbosity;
-}
-
-void Logger::setVerbosity(size_t verbosity)
-{
- _verbosity = verbosity;
-}
-
-Logger::Logger(EntryLogger callback) :
- _callback(callback)
-{
-}
-Logger::Logger(EntryLogger callback, std::optional<std::string> name) :
- _callback(callback), _name(std::move(name))
-{
-}
-Logger::Logger(std::shared_ptr<const Logger> parent, std::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback) :
- _parent(std::move(parent)), _callback(callback), _name(std::move(name)), _level(lvl), _verbosity(verbosity)
-{
-}
-
-Logger::~Logger() = default;
-};
-
-std::shared_ptr<Logging::Logger> g_slog{nullptr};
-
-const char* Logging::toTimestampStringMilli(const struct timeval& tval, std::array<char, 64>& buf, const std::string& format)
-{
- size_t len = 0;
- if (format != "%s") {
- // strftime is not thread safe, it can access locale information
- static std::mutex mutex;
- auto lock = std::scoped_lock(mutex);
- struct tm theTime // clang-format insists on formatting it like this
- {};
- len = strftime(buf.data(), buf.size(), format.c_str(), localtime_r(&tval.tv_sec, &theTime));
- }
- if (len == 0) {
- len = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
- }
-
- snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
- return buf.data();
-}
--- /dev/null
+../logging.cc
\ No newline at end of file