/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 | | } |