Coverage Report

Created: 2026-03-07 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsdist-logging.cc
Line
Count
Source
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
#include "dnsdist-logging.hh"
23
24
#include <iomanip>
25
#include <set>
26
#include <stdexcept>
27
28
#include "ext/json11/json11.hpp"
29
30
#include "config.h"
31
#if defined(HAVE_SYSTEMD)
32
#include <systemd/sd-journal.h>
33
#endif /* HAVE_SYSTEMD */
34
35
#include "dnsdist-configuration.hh"
36
37
namespace dnsdist::logging
38
{
39
static const char* convertTime(const timeval& tval, std::array<char, 64>& buffer)
40
0
{
41
0
  auto format = dnsdist::configuration::getImmutableConfiguration().d_structuredLoggingTimeFormat;
42
0
  if (format == dnsdist::configuration::TimeFormat::ISO8601) {
43
0
    time_t now{};
44
0
    time(&now);
45
0
    struct tm localNow{};
46
0
    localtime_r(&now, &localNow);
47
48
0
    {
49
      // strftime is not thread safe, it can access locale information
50
0
      static std::mutex mutex;
51
0
      auto lock = std::scoped_lock(mutex);
52
53
0
      if (strftime(buffer.data(), buffer.size(), "%FT%H:%M:%S%z", &localNow) == 0) {
54
0
        buffer[0] = '\0';
55
0
      }
56
0
    }
57
58
0
    return buffer.data();
59
0
  }
60
0
  return Logging::toTimestampStringMilli(tval, buffer);
61
0
}
62
63
#if defined(HAVE_SYSTEMD)
64
static void loggerSDBackend(const Logging::Entry& entry)
65
{
66
  static const std::set<std::string, CIStringComparePOSIX> special{
67
    "message",
68
    "message_id",
69
    "priority",
70
    "code_file",
71
    "code_line",
72
    "code_func",
73
    "errno",
74
    "invocation_id",
75
    "user_invocation_id",
76
    "syslog_facility",
77
    "syslog_identifier",
78
    "syslog_pid",
79
    "syslog_timestamp",
80
    "syslog_raw",
81
    "documentation",
82
    "tid",
83
    "unit",
84
    "user_unit",
85
    "object_pid"};
86
87
  // We need to keep the string in mem until sd_journal_sendv has been called
88
  std::vector<std::string> strings;
89
  auto appendKeyAndVal = [&strings](const string& key, const string& value) {
90
    strings.emplace_back(key + "=" + value);
91
  };
92
  appendKeyAndVal("MESSAGE", entry.message);
93
  if (entry.error) {
94
    appendKeyAndVal("ERROR", entry.error.value());
95
  }
96
  appendKeyAndVal("LEVEL", std::to_string(entry.level));
97
  appendKeyAndVal("PRIORITY", std::to_string(entry.d_priority));
98
  if (dnsdist::configuration::getImmutableConfiguration().d_structuredLoggingUseServerID) {
99
    appendKeyAndVal("INSTANCE", dnsdist::configuration::getCurrentRuntimeConfiguration().d_server_id);
100
  }
101
  if (entry.name) {
102
    appendKeyAndVal("SUBSYSTEM", entry.name.value());
103
  }
104
  std::array<char, 64> timebuf{};
105
  appendKeyAndVal("TIMESTAMP", convertTime(entry.d_timestamp, timebuf));
106
  for (const auto& value : entry.values) {
107
    if (value.first.at(0) == '_' || special.count(value.first) != 0) {
108
      string key{"PDNS"};
109
      key.append(value.first);
110
      appendKeyAndVal(toUpper(key), value.second);
111
    }
112
    else {
113
      appendKeyAndVal(toUpper(value.first), value.second);
114
    }
115
  }
116
117
  std::vector<iovec> iov;
118
  iov.reserve(strings.size());
119
  for (const auto& str : strings) {
120
    // iovec has no 2 arg constructor, so make it explicit
121
    iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(str.data())), str.size()}); // NOLINT: it's the API
122
  }
123
  sd_journal_sendv(iov.data(), static_cast<int>(iov.size()));
124
}
125
#endif /* HAVE_SYSTEMD */
126
127
static void loggerJSONBackend(const Logging::Entry& entry)
128
0
{
129
0
  std::array<char, 64> timebuf{};
130
0
  json11::Json::object json = {
131
0
    {"msg", entry.message},
132
0
    {"level", std::to_string(entry.level)},
133
0
    {"ts", convertTime(entry.d_timestamp, timebuf)},
134
0
  };
135
136
0
  if (entry.error) {
137
0
    json.emplace("error", entry.error.value());
138
0
  }
139
140
0
  if (entry.name) {
141
0
    json.emplace("subsystem", entry.name.value());
142
0
  }
143
144
0
  if (entry.d_priority != 0) {
145
0
    json.emplace("priority", std::to_string(entry.d_priority));
146
0
  }
147
148
0
  if (dnsdist::configuration::getImmutableConfiguration().d_structuredLoggingUseServerID) {
149
0
    json.emplace("instance", dnsdist::configuration::getCurrentRuntimeConfiguration().d_server_id);
150
0
  }
151
152
0
  for (auto const& value : entry.values) {
153
0
    json.emplace(value.first, value.second);
154
0
  }
155
156
0
  static thread_local std::string out;
157
0
  out.clear();
158
0
  json11::Json doc(std::move(json));
159
0
  doc.dump(out);
160
0
  std::cerr << out << std::endl;
161
0
}
162
163
static void loggerBackend(const Logging::Entry& entry)
164
0
{
165
0
  static thread_local std::stringstream buf;
166
167
0
  buf.str("");
168
0
  buf << "msg=" << std::quoted(entry.message);
169
0
  if (entry.error) {
170
0
    buf << " error=" << std::quoted(entry.error.value());
171
0
  }
172
173
0
  if (entry.name) {
174
0
    buf << " subsystem=" << std::quoted(entry.name.value());
175
0
  }
176
0
  buf << " level=" << std::quoted(std::to_string(entry.level));
177
0
  if (entry.d_priority != 0) {
178
0
    buf << " prio=" << std::quoted(Logr::Logger::toString(entry.d_priority));
179
0
  }
180
0
  if (dnsdist::configuration::getImmutableConfiguration().d_structuredLoggingUseServerID) {
181
0
    buf << " instance=" << std::quoted(dnsdist::configuration::getCurrentRuntimeConfiguration().d_server_id);
182
0
  }
183
184
0
  std::array<char, 64> timebuf{};
185
0
  buf << " ts=" << std::quoted(convertTime(entry.d_timestamp, timebuf));
186
0
  for (auto const& value : entry.values) {
187
0
    buf << " ";
188
0
    buf << value.first << "=" << std::quoted(value.second);
189
0
  }
190
191
0
  std::cout << buf.str() << endl;
192
0
}
193
194
static std::shared_ptr<Logging::Logger> s_topLogger{nullptr};
195
196
void setup(const std::string& backend)
197
0
{
198
0
  std::shared_ptr<Logging::Logger> logger;
199
0
  if (backend == "systemd-journal") {
200
#if defined(HAVE_SYSTEMD)
201
    if (int fileDesc = sd_journal_stream_fd("dnsdist", LOG_DEBUG, 0); fileDesc >= 0) {
202
      logger = Logging::Logger::create(loggerSDBackend);
203
      close(fileDesc);
204
    }
205
#endif
206
0
    if (logger == nullptr) {
207
0
      cerr << "Requested structured logging to systemd-journal, but it is not available" << endl;
208
0
    }
209
0
  }
210
0
  else if (backend == "json") {
211
0
    logger = Logging::Logger::create(loggerJSONBackend);
212
0
    if (logger == nullptr) {
213
0
      cerr << "JSON logging requested but it is not available" << endl;
214
0
    }
215
0
  }
216
217
0
  if (logger == nullptr) {
218
0
    logger = Logging::Logger::create(loggerBackend);
219
0
  }
220
221
0
  if (logger) {
222
0
    std::atomic_store_explicit(&s_topLogger, std::move(logger), std::memory_order_release);
223
0
  }
224
0
}
225
226
std::shared_ptr<const Logr::Logger> getTopLogger(const std::string_view& subsystem)
227
0
{
228
0
  auto topLogger = std::atomic_load_explicit(&s_topLogger, std::memory_order_acquire);
229
0
  if (!topLogger) {
230
0
    throw std::runtime_error("Trying to access the top-level logger before logging has been setup");
231
0
  }
232
233
0
  return topLogger->withName(std::string(subsystem));
234
0
}
235
236
bool doVerboseLogging()
237
0
{
238
0
  return dnsdist::configuration::getCurrentRuntimeConfiguration().d_verbose;
239
0
}
240
241
bool doStructuredLogging()
242
0
{
243
0
  return dnsdist::configuration::getImmutableConfiguration().d_structuredLogging;
244
0
}
245
246
}