/src/pdns/pdns/logging.hh
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, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | |
23 | | #pragma once |
24 | | |
25 | | #include "config.h" |
26 | | |
27 | | #include <map> |
28 | | #include <memory> |
29 | | #include <string> |
30 | | #include <sstream> |
31 | | |
32 | | #include "logr.hh" |
33 | | #include "dnsname.hh" |
34 | | #include "iputils.hh" |
35 | | |
36 | | namespace Logging |
37 | | { |
38 | | |
39 | | struct Entry |
40 | | { |
41 | | std::optional<std::string> name; // name parts joined with '.' |
42 | | std::string message; // message as send to log call |
43 | | std::optional<std::string> error; // error if .Error() was called |
44 | | struct timeval d_timestamp; // time of entry generation |
45 | | std::map<std::string, std::string> values; // key-value pairs |
46 | | size_t level; // level at which this was logged |
47 | | Logr::Priority d_priority; // (syslog) priority) |
48 | | }; |
49 | | |
50 | | // Warning: some meta-programming is going on. We define helper |
51 | | // templates that can be used to see if specific string output |
52 | | // functions are available. If so, we use those instead of << into an |
53 | | // ostringstream. Note that this decision happens compile time. |
54 | | // Some hints taken from https://www.cppstories.com/2019/07/detect-overload-from-chars/ |
55 | | // (I could not get function templates with enabled_if<> to work in this case) |
56 | | // |
57 | | // Default: std::string(T) is not available |
58 | | template <typename T, typename = void> |
59 | | struct is_to_string_available : std::false_type |
60 | | { |
61 | | }; |
62 | | |
63 | | // If std::string(T) is available this template is used |
64 | | template <typename T> |
65 | | struct is_to_string_available<T, std::void_t<decltype(std::to_string(std::declval<T>()))>> : std::true_type |
66 | | { |
67 | | }; |
68 | | |
69 | | // Same mechanism for t.toLogString() and t.toStructuredLogString() |
70 | | template <typename T, typename = void> |
71 | | struct is_toLogString_available : std::false_type |
72 | | { |
73 | | }; |
74 | | |
75 | | template <typename T> |
76 | | struct is_toLogString_available<T, std::void_t<decltype(std::declval<T>().toLogString())>> : std::true_type |
77 | | { |
78 | | }; |
79 | | |
80 | | template <typename T, typename = void> |
81 | | struct is_toStructuredLogString_available : std::false_type |
82 | | { |
83 | | }; |
84 | | |
85 | | template <typename T> |
86 | | struct is_toStructuredLogString_available<T, std::void_t<decltype(std::declval<T>().toStructuredLogString())>> : std::true_type |
87 | | { |
88 | | }; |
89 | | |
90 | | template <typename T, typename = void> |
91 | | struct is_toString_available : std::false_type |
92 | | { |
93 | | }; |
94 | | |
95 | | template <typename T> |
96 | | struct is_toString_available<T, std::void_t<decltype(std::declval<T>().toString())>> : std::true_type |
97 | | { |
98 | | }; |
99 | | |
100 | | const char* toTimestampStringMilli(const struct timeval& tval, std::array<char, 64>& buf, const std::string& format = "%s"); |
101 | | |
102 | | template <typename T> |
103 | | struct Loggable : public Logr::Loggable |
104 | | { |
105 | | const T& _t; |
106 | | Loggable(const T& v) : |
107 | 0 | _t(v) |
108 | 0 | { |
109 | 0 | } |
110 | | std::string to_string() const |
111 | 0 | { |
112 | | if constexpr (std::is_same_v<T, std::string>) { |
113 | | return _t; |
114 | | } |
115 | | else if constexpr (is_toStructuredLogString_available<T>::value) { |
116 | | return _t.toStructuredLogString(); |
117 | | } |
118 | | else if constexpr (is_toLogString_available<T>::value) { |
119 | | return _t.toLogString(); |
120 | | } |
121 | | else if constexpr (is_toString_available<T>::value) { |
122 | | return _t.toString(); |
123 | | } |
124 | 0 | else if constexpr (is_to_string_available<T>::value) { |
125 | 0 | return std::to_string(_t); |
126 | | } |
127 | | else { |
128 | | std::ostringstream oss; |
129 | | oss << _t; |
130 | | return oss.str(); |
131 | | } |
132 | 0 | } |
133 | | }; |
134 | | |
135 | | template <typename T> |
136 | | struct IterLoggable : public Logr::Loggable |
137 | | { |
138 | | const T& _t1; |
139 | | const T& _t2; |
140 | | IterLoggable(const T& v1, const T& v2) : |
141 | | _t1(v1), _t2(v2) |
142 | | { |
143 | | } |
144 | | std::string to_string() const |
145 | | { |
146 | | std::ostringstream oss; |
147 | | bool first = true; |
148 | | for (auto i = _t1; i != _t2; i++) { |
149 | | if (!first) { |
150 | | oss << ' '; |
151 | | } |
152 | | else { |
153 | | first = false; |
154 | | } |
155 | | if constexpr (std::is_same_v<typename T::value_type, std::string>) { |
156 | | oss << *i; |
157 | | } |
158 | | else if constexpr (is_toStructuredLogString_available<typename T::value_type>::value) { |
159 | | oss << i->toStructuredLogString(); |
160 | | } |
161 | | else if constexpr (is_toLogString_available<typename T::value_type>::value) { |
162 | | oss << i->toLogString(); |
163 | | } |
164 | | else if constexpr (is_toString_available<typename T::value_type>::value) { |
165 | | oss << i->toString(); |
166 | | } |
167 | | else if constexpr (is_to_string_available<typename T::value_type>::value) { |
168 | | oss << std::to_string(*i); |
169 | | } |
170 | | else { |
171 | | oss << *i; |
172 | | } |
173 | | } |
174 | | return oss.str(); |
175 | | } |
176 | | }; |
177 | | |
178 | | using EntryLogger = void (*)(const Entry&); |
179 | | |
180 | | class Logger : public Logr::Logger, public std::enable_shared_from_this<const Logger> |
181 | | { |
182 | | public: |
183 | | void info(Logr::Priority, const std::string& msg) const override; |
184 | | void error(Logr::Priority, int err, const std::string& msg) const override; |
185 | | void error(Logr::Priority, const std::string& err, const std::string& msg) const override; |
186 | | |
187 | | std::shared_ptr<Logr::Logger> v(size_t level) const override; |
188 | | std::shared_ptr<Logr::Logger> withValues(const std::map<std::string, std::string>& values) const override; |
189 | | std::shared_ptr<Logr::Logger> withName(const std::string& name) const override; |
190 | | |
191 | | static std::shared_ptr<Logger> create(EntryLogger callback); |
192 | | static std::shared_ptr<Logger> create(EntryLogger callback, const std::string& name); |
193 | | |
194 | | Logger(EntryLogger callback); |
195 | | Logger(EntryLogger callback, std::optional<std::string> name); |
196 | | Logger(std::shared_ptr<const Logger> parent, std::optional<std::string> name, size_t lvl, EntryLogger callback); |
197 | | ~Logger() override; |
198 | | |
199 | | private: |
200 | | void logMessage(const std::string& msg, Logr::Priority prio, const std::optional<std::string>& err) const; |
201 | | std::shared_ptr<const Logger> getptr() const; |
202 | | |
203 | | std::shared_ptr<const Logger> _parent{nullptr}; |
204 | | EntryLogger _callback; |
205 | | std::optional<std::string> _name; |
206 | | std::map<std::string, std::string> _values; |
207 | | // current Logger's level. the higher the more verbose. |
208 | | size_t _level{0}; |
209 | | }; |
210 | | } |
211 | | |
212 | | extern std::shared_ptr<Logging::Logger> g_slog; |
213 | | |
214 | | #ifdef RECURSOR // [ |
215 | | |
216 | | // Prefer structured logging? Since Recursor 5.1.0, we always do. We keep a const, to allow for |
217 | | // step-by-step removal of old style logging code (for recursor-only code). Note that code shared |
218 | | // with auth still uses old-style, so the SLOG calls should remain for shared code. |
219 | | constexpr bool g_slogStructured = true; |
220 | | |
221 | | // A helper macro to switch between old-style logging and new-style (structured logging) |
222 | | // A typical use: |
223 | | // |
224 | | // SLOG(g_log<<Logger::Warning<<"Unable to parse configuration file '"<<configname<<"'"<<endl, |
225 | | // startupLog->error(Logr::Warning, "No such file", "Unable to parse configuration file", "config_file", Logging::Loggable(configname)); |
226 | | // |
227 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
228 | | #define SLOG(oldStyle, slogCall) \ |
229 | | do { \ |
230 | | slogCall; \ |
231 | | } while (0) |
232 | | |
233 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
234 | | #define VERBOSESLOG(nonStructured, structured) |
235 | | |
236 | | #elif defined(DNSDIST) // ] [ |
237 | | |
238 | | // Still able to choose between old-style and structured logging |
239 | | |
240 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
241 | | #define SLOG(nonStructured, structured) \ |
242 | | do { \ |
243 | | if (dnsdist::logging::doStructuredLogging()) { \ |
244 | | structured; \ |
245 | | } \ |
246 | | else { \ |
247 | | nonStructured; \ |
248 | | } \ |
249 | | } while (0) |
250 | | |
251 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
252 | | #define VERBOSESLOG(nonStructured, structured) \ |
253 | | do { \ |
254 | | if (dnsdist::logging::doVerboseLogging()) { \ |
255 | | SLOG(nonStructured, structured); \ |
256 | | } \ |
257 | | } while (0) |
258 | | |
259 | | #else // ] [ PDNS_AUTH |
260 | | |
261 | | // Still able to choose between old-style and structured logging |
262 | | extern bool g_slogStructured; |
263 | | |
264 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
265 | | #define SLOG(oldStyle, slogCall) \ |
266 | 0 | do { \ |
267 | 0 | if (g_slogStructured) { \ |
268 | 0 | slogCall; \ |
269 | 0 | } \ |
270 | 0 | else { \ |
271 | 0 | oldStyle; \ |
272 | 0 | } \ |
273 | 0 | } while (0) |
274 | | |
275 | | // VERBOSESLOG is not used in Auth. |
276 | | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) |
277 | | #define VERBOSESLOG(nonStructured, structured) |
278 | | |
279 | | #endif // ] |