]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Reinstate support for ISO8601 with structured logging
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 29 Dec 2025 16:01:06 +0000 (17:01 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 19 Jan 2026 10:01:26 +0000 (11:01 +0100)
Signed-off-by: Remi Gacogne <remi.gacogne@powerdns.com>
pdns/dnsdistdist/dnsdist-configuration-yaml.cc
pdns/dnsdistdist/dnsdist-configuration.hh
pdns/dnsdistdist/dnsdist-logging.cc
pdns/dnsdistdist/dnsdist-lua.cc
pdns/dnsdistdist/dnsdist-settings-definitions.yml
pdns/dnsdistdist/docs/reference/config.rst

index cf2b8e5303ce93190d38537afb9afadc2f4894ea..0c0cd4ed468a4c6fd1bc584f95a59ae12085415e 100644 (file)
@@ -991,9 +991,27 @@ static void handleLoggingConfiguration(const Context& context, const dnsdist::ru
     }
   }
 
-  dnsdist::configuration::updateImmutableConfiguration([settings](dnsdist::configuration::ImmutableConfiguration& config) {
+  std::optional<dnsdist::configuration::TimeFormat> timeFormat{};
+  const auto timeFormatStr = std::string(settings.structured.time_format);
+  if (!timeFormatStr.empty()) {
+    if (timeFormatStr == "numeric") {
+      timeFormat = dnsdist::configuration::TimeFormat::Numeric;
+    }
+    else if (timeFormatStr == "ISO8601") {
+      timeFormat = dnsdist::configuration::TimeFormat::ISO8601;
+    }
+    else {
+      SLOG(warnlog("Unknown value '%s' passed to logging.structured.time_format parameter", timeFormatStr),
+             context.logger->info(Logr::Warning, "Unknown value passed to logging.structured.time_format parameter", "value", Logging::Loggable(timeFormatStr)));
+      }
+  }
+
+  dnsdist::configuration::updateImmutableConfiguration([settings, timeFormat](dnsdist::configuration::ImmutableConfiguration& config) {
     config.d_loggingBackend = std::string(settings.structured.backend);
     config.d_structuredLogging = settings.structured.enabled;
+    if (timeFormat) {
+      config.d_structuredLoggingTimeFormat = *timeFormat;
+    }
   });
 }
 
index 28c1484bbd3857145794fc9a3abb0ff3936e06d9..df8dbcdee5bdd7178904b688950869e4712be250 100644 (file)
@@ -53,6 +53,12 @@ static constexpr uint16_t s_defaultPayloadSizeSelfGenAnswers = 1232;
 static constexpr uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
 static_assert(s_defaultPayloadSizeSelfGenAnswers < s_udpIncomingBufferSize, "The UDP responder's payload size should be smaller or equal to our incoming buffer size");
 
+enum class TimeFormat: uint8_t
+{
+  Numeric,
+  ISO8601
+};
+
 /* this part of the configuration can only be updated at configuration
    time, and is immutable once the configuration phase is over */
 struct ImmutableConfiguration
@@ -98,6 +104,7 @@ struct ImmutableConfiguration
   uint32_t d_tcpBanDurationForExceedingMaxReadIOsPerQuery{60};
   uint32_t d_tcpBanDurationForExceedingTCPTLSRate{10};
   uint16_t d_maxUDPOutstanding{std::numeric_limits<uint16_t>::max()};
+  TimeFormat d_structuredLoggingTimeFormat{TimeFormat::Numeric};
   uint8_t d_udpTimeout{2};
   uint8_t d_tcpConnectionsOverloadThreshold{90};
   uint8_t d_tcpConnectionsMaskV4{32};
index db1aa907fd8e3331f025dbdf30a2c0a2f5f9e21f..ce6237395f6811afec16f289d63ee364688be8d0 100644 (file)
 
 namespace dnsdist::logging
 {
+static const char* convertTime(const timeval& tval, std::array<char, 64>& buffer)
+{
+  auto format = dnsdist::configuration::getImmutableConfiguration().d_structuredLoggingTimeFormat;
+  if (format == dnsdist::configuration::TimeFormat::ISO8601) {
+    time_t now{};
+    time(&now);
+    struct tm localNow{};
+    localtime_r(&now, &localNow);
+
+    {
+      // strftime is not thread safe, it can access locale information
+      static std::mutex mutex;
+      auto lock = std::scoped_lock(mutex);
+
+      if (strftime(buffer.data(), buffer.size(), "%FT%H:%M:%S%z", &localNow) == 0) {
+        buffer[0] = '\0';
+      }
+    }
+
+    return buffer.data();
+  }
+  return Logging::toTimestampStringMilli(tval, buffer);
+}
+
 #if defined(HAVE_SYSTEMD)
 static void loggerSDBackend(const Logging::Entry& entry)
 {
@@ -75,7 +99,7 @@ static void loggerSDBackend(const Logging::Entry& entry)
     appendKeyAndVal("SUBSYSTEM", entry.name.value());
   }
   std::array<char, 64> timebuf{};
-  appendKeyAndVal("TIMESTAMP", Logging::toTimestampStringMilli(entry.d_timestamp, timebuf));
+  appendKeyAndVal("TIMESTAMP", convertTime(entry.d_timestamp, timebuf));
   for (const auto& value : entry.values) {
     if (value.first.at(0) == '_' || special.count(value.first) != 0) {
       string key{"PDNS"};
@@ -103,7 +127,7 @@ static void loggerJSONBackend(const Logging::Entry& entry)
   json11::Json::object json = {
     {"msg", entry.message},
     {"level", std::to_string(entry.level)},
-    {"ts", Logging::toTimestampStringMilli(entry.d_timestamp, timebuf)},
+    {"ts", convertTime(entry.d_timestamp, timebuf)},
   };
 
   if (entry.error) {
@@ -148,7 +172,7 @@ static void loggerBackend(const Logging::Entry& entry)
   }
 
   std::array<char, 64> timebuf{};
-  buf << " ts=" << std::quoted(Logging::toTimestampStringMilli(entry.d_timestamp, timebuf));
+  buf << " ts=" << std::quoted(convertTime(entry.d_timestamp, timebuf));
   for (auto const& value : entry.values) {
     buf << " ";
     buf << value.first << "=" << std::quoted(value.second);
index 1856d010cf49551a6465e41b09e6d3393299c346..6e9560034293cd3f2fee9ae40b04d51e2dd6be5f 100644 (file)
@@ -1762,16 +1762,33 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
   luaCtx.writeFunction("setStructuredLogging", [](bool enable, std::optional<LuaAssociativeTable<std::string>> options) {
+    std::optional<dnsdist::configuration::TimeFormat> format{};
+    std::string timeFormat;
     std::string backend;
     if (options) {
+      if (getOptionalValue<std::string>(options, "timeFormat", timeFormat) == 1) {
+        if (timeFormat == "numeric") {
+          format = dnsdist::configuration::TimeFormat::Numeric;
+        }
+        else if (timeFormat == "ISO8601") {
+          format = dnsdist::configuration::TimeFormat::ISO8601;
+        }
+        else {
+          SLOG(warnlog("Unknown value '%s' to setStructuredLogging's 'timeFormat' parameter", timeFormat),
+               getLogger("setStructuredLogging")->info(Logr::Warning, "Unknown value passed to setStructuredLogging's 'timeFormat' parameter", "value", Logging::Loggable(timeFormat)));
+        }
+      }
       getOptionalValue<std::string>(options, "backend", backend);
       checkAllParametersConsumed("setStructuredLogging", options);
     }
 
-    dnsdist::configuration::updateImmutableConfiguration([enable, &backend](dnsdist::configuration::ImmutableConfiguration& config) {
+    dnsdist::configuration::updateImmutableConfiguration([enable, &backend, format](dnsdist::configuration::ImmutableConfiguration& config) {
       if (enable && !backend.empty()) {
         config.d_loggingBackend = backend;
       }
+      if (format) {
+        config.d_structuredLoggingTimeFormat = *format;
+      }
       config.d_structuredLogging = enable;
     });
   });
index ac82b339599f29456b114eef02be69dc1fb3f337..dde42e4cbb5985fdacca585d5fa02023cd9d560a 100644 (file)
@@ -1777,7 +1777,7 @@ structured_logging:
   parameters:
     - name: "enabled"
       type: "bool"
-      default: "true"
+      default: "false"
       description: |
         Set whether log messages should be in structured-logging format."
       changes:
@@ -1789,7 +1789,7 @@ structured_logging:
       description: "Set the key name for the log level. There is unfortunately no standard name for this key, so in some setups it might be useful to set this value to a different name to have consistency across products"
       changes:
         - version: "2.1.0"
-          content: "This setting has been removed"
+          content: "This setting has no longer any effect because it was confusing. The log level is now always logged as ``level`` and the syslog priority, if any, as ``priority`` in all backends except the default one where it is named ``prio``"
     - name: "time_format"
       type: "String"
       default: "numeric"
@@ -1797,9 +1797,6 @@ structured_logging:
       supported-values:
         - "ISO8601"
         - "numeric"
-      changes:
-        - version: "2.1.0"
-          content: "This setting has been removed"
     - name: "backend"
       type: "String"
       default: ""
index d9eab1ec97bc651bb4cfdf9ef3ff20cdef91eafb..2c9ab633760cb149347e9ac3eae329a448f395e6 100644 (file)
@@ -1402,12 +1402,15 @@ Status, Statistics and More
 
   .. versionadded:: 1.9.0
 
+  .. versionchanged:: 2.1.0
+    The ``level_prefix`` option has no longer any effect because it was confusing. The log level is now always logged as ``level`` and the syslog priority, if any, as ``priority`` in all backends except the default one where it is named ``prio``
+
   Set whether log messages should be in a structured-logging-like format. This is turned off by default.
-  The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and with ``levelPrefix="prio"`` and ``timeFormat="ISO8601"``)::
+  The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and ``timeFormat="ISO8601"``)::
 
-    ts="2023-11-06T12:04:58+0100" prio="Info" msg="Added downstream server 127.0.0.1:53"
+    ts="2023-11-06T12:04:58+0100" level="Info" msg="Added downstream server 127.0.0.1:53"
 
-  And with ``levelPrefix="level"`` and ``timeFormat="numeric"``)::
+  And with ``timeFormat="numeric"`` instead)::
 
     ts="1699268815.133" level="Info" msg="Added downstream server 127.0.0.1:53"
 
@@ -1416,7 +1419,7 @@ Status, Statistics and More
 
   Options:
 
-  * ``levelPrefix=prefix``: string - Set the prefix for the log level. Default is ``prio``.
+  * ``levelPrefix=prefix``: string - Set the prefix for the log level. Default is ``prio``. Not supported since 2.1.0.
   * ``timeFormat=format``: string - Set the time format. Supported values are ``ISO8601`` and ``numeric``. Default is ``numeric``.
 
 .. function:: setOpenTelemetryTracing(value)