]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement 'reload()' to rotate Log(Response)Action's log file
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Jun 2021 09:51:46 +0000 (11:51 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Jun 2021 09:51:46 +0000 (11:51 +0200)
pdns/dnsdist-lua-actions.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/rules-actions.rst

index ce1e714be3e8ea22bb974fa8e3b3bdcc0d27dee3..754857e1ec9a3626600fd9bc9856341999631edc 100644 (file)
@@ -727,35 +727,26 @@ class LogAction : public DNSAction, public boost::noncopyable
 {
 public:
   // this action does not stop the processing
-  LogAction(): d_fp(nullptr, fclose)
+  LogAction()
   {
   }
 
-  LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp)
+  LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
   {
     if (str.empty()) {
       return;
     }
 
-    if(append) {
-      d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(str.c_str(), "a+"), fclose);
-    }
-    else {
-      d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(str.c_str(), "w"), fclose);
-    }
-
+    reopenLogFile();
     if (!d_fp) {
-      throw std::runtime_error("Unable to open file '"+str+"' for logging: "+stringerror());
-    }
-
-    if (!buffered) {
-      setbuf(d_fp.get(), 0);
+      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
     }
   }
 
   DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
   {
-    if (!d_fp) {
+    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+    if (!fp) {
       if (!d_verboseOnly || g_verbose) {
         if (d_includeTimestamp) {
           infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast<unsigned long long>(dq->queryTime->tv_sec), static_cast<unsigned long>(dq->queryTime->tv_nsec), dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).toString(), dq->getHeader()->id);
@@ -771,28 +762,28 @@ public:
         if (d_includeTimestamp) {
           uint64_t tv_sec = static_cast<uint64_t>(dq->queryTime->tv_sec);
           uint32_t tv_nsec = static_cast<uint32_t>(dq->queryTime->tv_nsec);
-          fwrite(&tv_sec, sizeof(tv_sec), 1, d_fp.get());
-          fwrite(&tv_nsec, sizeof(tv_nsec), 1, d_fp.get());
+          fwrite(&tv_sec, sizeof(tv_sec), 1, fp.get());
+          fwrite(&tv_nsec, sizeof(tv_nsec), 1, fp.get());
         }
         uint16_t id = dq->getHeader()->id;
-        fwrite(&id, sizeof(id), 1, d_fp.get());
-        fwrite(out.c_str(), 1, out.size(), d_fp.get());
-        fwrite(&dq->qtype, sizeof(dq->qtype), 1, d_fp.get());
-        fwrite(&dq->remote->sin4.sin_family, sizeof(dq->remote->sin4.sin_family), 1, d_fp.get());
+        fwrite(&id, sizeof(id), 1, fp.get());
+        fwrite(out.c_str(), 1, out.size(), fp.get());
+        fwrite(&dq->qtype, sizeof(dq->qtype), 1, fp.get());
+        fwrite(&dq->remote->sin4.sin_family, sizeof(dq->remote->sin4.sin_family), 1, fp.get());
         if (dq->remote->sin4.sin_family == AF_INET) {
-          fwrite(&dq->remote->sin4.sin_addr.s_addr, sizeof(dq->remote->sin4.sin_addr.s_addr), 1, d_fp.get());
+          fwrite(&dq->remote->sin4.sin_addr.s_addr, sizeof(dq->remote->sin4.sin_addr.s_addr), 1, fp.get());
         }
         else if (dq->remote->sin4.sin_family == AF_INET6) {
-          fwrite(&dq->remote->sin6.sin6_addr.s6_addr, sizeof(dq->remote->sin6.sin6_addr.s6_addr), 1, d_fp.get());
+          fwrite(&dq->remote->sin6.sin6_addr.s6_addr, sizeof(dq->remote->sin6.sin6_addr.s6_addr), 1, fp.get());
         }
-        fwrite(&dq->remote->sin4.sin_port, sizeof(dq->remote->sin4.sin_port), 1, d_fp.get());
+        fwrite(&dq->remote->sin4.sin_port, sizeof(dq->remote->sin4.sin_port), 1, fp.get());
       }
       else {
         if (d_includeTimestamp) {
-          fprintf(d_fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %d\n", static_cast<unsigned long long>(dq->queryTime->tv_sec), static_cast<unsigned long>(dq->queryTime->tv_nsec), dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
+          fprintf(fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %d\n", static_cast<unsigned long long>(dq->queryTime->tv_sec), static_cast<unsigned long>(dq->queryTime->tv_nsec), dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
         }
         else {
-          fprintf(d_fp.get(), "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
+          fprintf(fp.get(), "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id);
         }
       }
     }
@@ -806,46 +797,68 @@ public:
     }
     return "log";
   }
+
+  void reload() override
+  {
+    reopenLogFile();
+  }
+
 private:
+  void reopenLogFile()
+  {
+    std::shared_ptr<FILE> fp = nullptr;
+
+    if (d_append) {
+      fp = std::shared_ptr<FILE>(fopen(d_fname.c_str(), "a+"), fclose);
+    }
+    else {
+      fp = std::shared_ptr<FILE>(fopen(d_fname.c_str(), "w"), fclose);
+    }
+
+    if (!fp) {
+      /* don't fall on our sword when reopening */
+      return;
+    }
+
+    if (!d_buffered) {
+      setbuf(fp.get(), 0);
+    }
+
+    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
+  }
+
   std::string d_fname;
-  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+  std::shared_ptr<FILE> d_fp{nullptr};
   bool d_binary{true};
   bool d_verboseOnly{true};
   bool d_includeTimestamp{false};
+  bool d_append{false};
+  bool d_buffered{true};
 };
 
 class LogResponseAction : public DNSResponseAction, public boost::noncopyable
 {
 public:
-  LogResponseAction(): d_fp(nullptr, fclose)
+  LogResponseAction()
   {
   }
 
-  LogResponseAction(const std::string& str, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp)
+  LogResponseAction(const std::string& str, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
   {
     if (str.empty()) {
       return;
     }
 
-    if (append) {
-      d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(str.c_str(), "a+"), fclose);
-    }
-    else {
-      d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(str.c_str(), "w"), fclose);
-    }
-
+    reopenLogFile();
     if (!d_fp) {
-      throw std::runtime_error("Unable to open file '"+str+"' for logging: "+stringerror());
-    }
-
-    if (!buffered) {
-      setbuf(d_fp.get(), 0);
+      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
     }
   }
 
   DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
   {
-    if (!d_fp) {
+    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+    if (!fp) {
       if (!d_verboseOnly || g_verbose) {
         if (d_includeTimestamp) {
           infolog("[%u.%u] Answer to %s for %s %s (%s) with id %d", static_cast<unsigned long long>(dr->queryTime->tv_sec), static_cast<unsigned long>(dr->queryTime->tv_nsec), dr->remote->toStringWithPort(), dr->qname->toString(), QType(dr->qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
@@ -857,10 +870,10 @@ public:
     }
     else {
       if (d_includeTimestamp) {
-        fprintf(d_fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %d\n", static_cast<unsigned long long>(dr->queryTime->tv_sec), static_cast<unsigned long>(dr->queryTime->tv_nsec), dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
+        fprintf(fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %d\n", static_cast<unsigned long long>(dr->queryTime->tv_sec), static_cast<unsigned long>(dr->queryTime->tv_nsec), dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
       }
       else {
-        fprintf(d_fp.get(), "Answer to %s for %s %s (%s) with id %d\n", dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
+        fprintf(fp.get(), "Answer to %s for %s %s (%s) with id %d\n", dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
       }
     }
     return Action::None;
@@ -873,11 +886,42 @@ public:
     }
     return "log";
   }
+
+  void reload() override
+  {
+    reopenLogFile();
+  }
+
 private:
+  void reopenLogFile()
+  {
+    std::shared_ptr<FILE> fp = nullptr;
+
+    if (d_append) {
+      fp = std::shared_ptr<FILE>(fopen(d_fname.c_str(), "a+"), fclose);
+    }
+    else {
+      fp = std::shared_ptr<FILE>(fopen(d_fname.c_str(), "w"), fclose);
+    }
+
+    if (!fp) {
+      /* don't fall on our sword when reopening */
+      return;
+    }
+
+    if (!d_buffered) {
+      setbuf(fp.get(), 0);
+    }
+
+    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
+  }
+
   std::string d_fname;
-  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+  std::shared_ptr<FILE> d_fp{nullptr};
   bool d_verboseOnly{true};
   bool d_includeTimestamp{false};
+  bool d_append{false};
+  bool d_buffered{true};
 };
 
 
@@ -1681,6 +1725,8 @@ void setupLuaActions(LuaContext& luaCtx)
     });
 
   luaCtx.registerFunction("getStats", &DNSAction::getStats);
+  luaCtx.registerFunction("reload", &DNSAction::reload);
+  luaCtx.registerFunction("reload", &DNSResponseAction::reload);
 
   luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
       setLuaSideEffect();
index 5d6ed9401a321e96da194d96d6f21bd97e057792..46f40f407803fe9d3195b3657562f9f26bddd782 100644 (file)
@@ -223,6 +223,9 @@ public:
   {
     return {{}};
   }
+  virtual void reload()
+  {
+  }
 };
 
 class DNSResponseAction
@@ -234,6 +237,9 @@ public:
   {
   }
   virtual string toString() const = 0;
+  virtual void reload()
+  {
+  }
 };
 
 struct DynBlock
index 35aa1257566488ca8e7d4425da824ef141f244f2..3196db05b3507e35cdbdc8ffacd9136e8ae3e58d 100644 (file)
@@ -943,6 +943,9 @@ The following actions exist.
   .. versionchanged:: 1.4.0
     Added the optional parameters ``verboseOnly`` and ``includeTimestamp``, made ``filename`` optional.
 
+  .. versionchanged:: 1.7.0
+    Added the ``reload`` method.
+
   Log a line for each query, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
 
   If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
@@ -952,6 +955,8 @@ The following actions exist.
   The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
   The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
 
+  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
   Subsequent rules are processed after this action.
 
   :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
@@ -965,6 +970,9 @@ The following actions exist.
 
   .. versionadded:: 1.5.0
 
+  .. versionchanged:: 1.7.0
+    Added the ``reload`` method.
+
   Log a line for each response, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
 
   If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
@@ -972,6 +980,8 @@ The following actions exist.
   The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
   The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
 
+  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
   Subsequent rules are processed after this action.
 
   :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.