]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * This file is open source software, licensed to you under the terms | |
3 | * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | |
4 | * distributed with this work for additional information regarding copyright | |
5 | * ownership. You may not use this file except in compliance with the License. | |
6 | * | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | /* | |
19 | * Copyright (C) 2015 Cloudius Systems, Ltd. | |
20 | */ | |
21 | #pragma once | |
22 | ||
23 | #include <seastar/core/sstring.hh> | |
f67539c2 TL |
24 | #include <seastar/util/concepts.hh> |
25 | #include <seastar/util/log-impl.hh> | |
26 | #include <seastar/core/lowres_clock.hh> | |
20effc67 TL |
27 | #include <seastar/util/std-compat.hh> |
28 | ||
11fdf7f2 TL |
29 | #include <unordered_map> |
30 | #include <exception> | |
31 | #include <iosfwd> | |
32 | #include <atomic> | |
33 | #include <mutex> | |
34 | #include <boost/lexical_cast.hpp> | |
f67539c2 | 35 | #include <fmt/format.h> |
11fdf7f2 TL |
36 | |
37 | ||
38 | /// \addtogroup logging | |
39 | /// @{ | |
40 | ||
41 | namespace seastar { | |
42 | ||
43 | /// \brief log level used with \see {logger} | |
44 | /// used with the logger.do_log method. | |
45 | /// Levels are in increasing order. That is if you want to see debug(3) logs you | |
46 | /// will also see error(0), warn(1), info(2). | |
47 | /// | |
48 | enum class log_level { | |
49 | error, | |
50 | warn, | |
51 | info, | |
52 | debug, | |
53 | trace, | |
54 | }; | |
55 | ||
56 | std::ostream& operator<<(std::ostream& out, log_level level); | |
57 | std::istream& operator>>(std::istream& in, log_level& level); | |
58 | } | |
59 | ||
60 | // Boost doesn't auto-deduce the existence of the streaming operators for some reason | |
61 | ||
62 | namespace boost { | |
63 | template<> | |
64 | seastar::log_level lexical_cast(const std::string& source); | |
65 | ||
66 | } | |
67 | ||
68 | namespace seastar { | |
69 | ||
70 | class logger; | |
71 | class logger_registry; | |
72 | ||
9f95a23c | 73 | /// \brief Logger class for ostream or syslog. |
11fdf7f2 TL |
74 | /// |
75 | /// Java style api for logging. | |
76 | /// \code {.cpp} | |
77 | /// static seastar::logger logger("lsa-api"); | |
78 | /// logger.info("Triggering compaction"); | |
79 | /// \endcode | |
80 | /// The output format is: (depending on level) | |
81 | /// DEBUG %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
82 | /// | |
f67539c2 | 83 | /// It is possible to rate-limit log messages, see \ref logger::rate_limit. |
11fdf7f2 TL |
84 | class logger { |
85 | sstring _name; | |
86 | std::atomic<log_level> _level = { log_level::info }; | |
9f95a23c TL |
87 | static std::ostream* _out; |
88 | static std::atomic<bool> _ostream; | |
11fdf7f2 | 89 | static std::atomic<bool> _syslog; |
1e59de90 TL |
90 | static unsigned _shard_field_width; |
91 | static inline thread_local bool silent = false; | |
f67539c2 TL |
92 | |
93 | public: | |
94 | class log_writer { | |
95 | public: | |
96 | virtual ~log_writer() = default; | |
97 | virtual internal::log_buf::inserter_iterator operator()(internal::log_buf::inserter_iterator) = 0; | |
11fdf7f2 | 98 | }; |
f67539c2 TL |
99 | template <typename Func> |
100 | SEASTAR_CONCEPT(requires requires (Func fn, internal::log_buf::inserter_iterator it) { | |
101 | it = fn(it); | |
102 | }) | |
103 | class lambda_log_writer : public log_writer { | |
104 | Func _func; | |
105 | public: | |
106 | lambda_log_writer(Func&& func) : _func(std::forward<Func>(func)) { } | |
107 | virtual ~lambda_log_writer() override = default; | |
108 | virtual internal::log_buf::inserter_iterator operator()(internal::log_buf::inserter_iterator it) override { return _func(it); } | |
11fdf7f2 | 109 | }; |
f67539c2 | 110 | |
20effc67 TL |
111 | /// \cond internal |
112 | /// \brief used to hold the log format string and the caller's source_location. | |
113 | struct format_info { | |
114 | /// implicitly construct format_info from a const char* format string. | |
115 | /// \param fmt - {fmt} style format string | |
116 | format_info(const char* format, compat::source_location loc = compat::source_location::current()) noexcept | |
117 | : format(format) | |
118 | , loc(loc) | |
119 | {} | |
120 | /// implicitly construct format_info from a std::string_view format string. | |
121 | /// \param fmt - {fmt} style format string_view | |
122 | format_info(std::string_view format, compat::source_location loc = compat::source_location::current()) noexcept | |
123 | : format(format) | |
124 | , loc(loc) | |
125 | {} | |
126 | /// implicitly construct format_info with no format string. | |
127 | format_info(compat::source_location loc = compat::source_location::current()) noexcept | |
128 | : format() | |
129 | , loc(loc) | |
130 | {} | |
131 | std::string_view format; | |
132 | compat::source_location loc; | |
133 | }; | |
134 | ||
f67539c2 TL |
135 | private: |
136 | ||
137 | // We can't use an std::function<> as it potentially allocates. | |
138 | void do_log(log_level level, log_writer& writer); | |
20effc67 TL |
139 | void failed_to_log(std::exception_ptr ex, format_info fmt) noexcept; |
140 | ||
141 | class silencer { | |
20effc67 | 142 | public: |
1e59de90 TL |
143 | silencer() noexcept { |
144 | silent = true; | |
145 | } | |
20effc67 TL |
146 | |
147 | ~silencer() { | |
1e59de90 | 148 | silent = false; |
20effc67 TL |
149 | } |
150 | }; | |
151 | ||
f67539c2 TL |
152 | public: |
153 | /// Apply a rate limit to log message(s) | |
154 | /// | |
155 | /// Pass this to \ref logger::log() to apply a rate limit to the message. | |
156 | /// The rate limit is applied to all \ref logger::log() calls this rate | |
157 | /// limit is passed to. Example: | |
158 | /// | |
159 | /// void handle_request() { | |
160 | /// static thread_local logger::rate_limit my_rl(std::chrono::seconds(10)); | |
161 | /// // ... | |
162 | /// my_log.log(log_level::info, my_rl, "a message we don't want to log on every request, only at most once each 10 seconds"); | |
163 | /// // ... | |
164 | /// } | |
165 | /// | |
166 | /// The rate limit ensures that at most one message per interval will be | |
167 | /// logged. If there were messages dropped due to rate-limiting the | |
168 | /// following snippet will be prepended to the first non-dropped log | |
169 | /// messages: | |
170 | /// | |
171 | /// (rate limiting dropped $N similar messages) | |
172 | /// | |
173 | /// Where $N is the number of messages dropped. | |
174 | class rate_limit { | |
175 | friend class logger; | |
176 | ||
177 | using clock = lowres_clock; | |
178 | ||
179 | private: | |
180 | clock::duration _interval; | |
181 | clock::time_point _next; | |
182 | uint64_t _dropped_messages = 0; | |
183 | ||
184 | private: | |
185 | bool check(); | |
186 | bool has_dropped_messages() const { return bool(_dropped_messages); } | |
187 | uint64_t get_and_reset_dropped_messages() { | |
188 | return std::exchange(_dropped_messages, 0); | |
189 | } | |
190 | ||
191 | public: | |
192 | explicit rate_limit(std::chrono::milliseconds interval); | |
193 | }; | |
194 | ||
11fdf7f2 TL |
195 | public: |
196 | explicit logger(sstring name); | |
197 | logger(logger&& x); | |
198 | ~logger(); | |
199 | ||
f67539c2 | 200 | bool is_shard_zero() noexcept; |
11fdf7f2 TL |
201 | |
202 | /// Test if desired log level is enabled | |
203 | /// | |
204 | /// \param level - enum level value (info|error...) | |
205 | /// \return true if the log level has been enabled. | |
f67539c2 | 206 | bool is_enabled(log_level level) const noexcept { |
1e59de90 | 207 | return __builtin_expect(level <= _level.load(std::memory_order_relaxed), false) && !silent; |
11fdf7f2 TL |
208 | } |
209 | ||
210 | /// logs to desired level if enabled, otherwise we ignore the log line | |
211 | /// | |
20effc67 TL |
212 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
213 | /// or a logger::format_info passed down the call chain. | |
f67539c2 TL |
214 | /// \param args - args to print string |
215 | /// | |
216 | template <typename... Args> | |
20effc67 | 217 | void log(log_level level, format_info fmt, Args&&... args) noexcept { |
f67539c2 TL |
218 | if (is_enabled(level)) { |
219 | try { | |
220 | lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { | |
20effc67 TL |
221 | #if FMT_VERSION >= 80000 |
222 | return fmt::format_to(it, fmt::runtime(fmt.format), std::forward<Args>(args)...); | |
223 | #else | |
224 | return fmt::format_to(it, fmt.format, std::forward<Args>(args)...); | |
225 | #endif | |
f67539c2 TL |
226 | }); |
227 | do_log(level, writer); | |
228 | } catch (...) { | |
20effc67 | 229 | failed_to_log(std::current_exception(), std::move(fmt)); |
f67539c2 TL |
230 | } |
231 | } | |
232 | } | |
233 | ||
234 | /// logs with a rate limit to desired level if enabled, otherwise we ignore the log line | |
235 | /// | |
236 | /// If there were messages dropped due to rate-limiting the following snippet | |
237 | /// will be prepended to the first non-dropped log messages: | |
238 | /// | |
239 | /// (rate limiting dropped $N similar messages) | |
240 | /// | |
241 | /// Where $N is the number of messages dropped. | |
242 | /// | |
243 | /// \param rl - the \ref rate_limit to apply to this log | |
20effc67 TL |
244 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
245 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
246 | /// \param args - args to print string |
247 | /// | |
248 | template <typename... Args> | |
20effc67 | 249 | void log(log_level level, rate_limit& rl, format_info fmt, Args&&... args) noexcept { |
f67539c2 TL |
250 | if (is_enabled(level) && rl.check()) { |
251 | try { | |
252 | lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { | |
253 | if (rl.has_dropped_messages()) { | |
254 | it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); | |
255 | } | |
20effc67 TL |
256 | #if FMT_VERSION >= 80000 |
257 | return fmt::format_to(it, fmt::runtime(fmt.format), std::forward<Args>(args)...); | |
258 | #else | |
259 | return fmt::format_to(it, fmt.format, std::forward<Args>(args)...); | |
260 | #endif | |
f67539c2 TL |
261 | }); |
262 | do_log(level, writer); | |
263 | } catch (...) { | |
20effc67 | 264 | failed_to_log(std::current_exception(), std::move(fmt)); |
f67539c2 TL |
265 | } |
266 | } | |
267 | } | |
268 | ||
269 | /// \cond internal | |
270 | /// logs to desired level if enabled, otherwise we ignore the log line | |
271 | /// | |
272 | /// \param writer a function which writes directly to the underlying log buffer | |
20effc67 | 273 | /// \param fmt - optional logger::format_info passed down the call chain. |
f67539c2 TL |
274 | /// |
275 | /// This is a low level method for use cases where it is very important to | |
276 | /// avoid any allocations. The \arg writer will be passed a | |
277 | /// internal::log_buf::inserter_iterator that allows it to write into the log | |
278 | /// buffer directly, avoiding the use of any intermediary buffers. | |
20effc67 | 279 | void log(log_level level, log_writer& writer, format_info fmt = {}) noexcept { |
11fdf7f2 TL |
280 | if (is_enabled(level)) { |
281 | try { | |
f67539c2 | 282 | do_log(level, writer); |
11fdf7f2 | 283 | } catch (...) { |
20effc67 | 284 | failed_to_log(std::current_exception(), std::move(fmt)); |
11fdf7f2 TL |
285 | } |
286 | } | |
287 | } | |
f67539c2 TL |
288 | /// logs to desired level if enabled, otherwise we ignore the log line |
289 | /// | |
290 | /// \param writer a function which writes directly to the underlying log buffer | |
20effc67 | 291 | /// \param fmt - optional logger::format_info passed down the call chain. |
f67539c2 TL |
292 | /// |
293 | /// This is a low level method for use cases where it is very important to | |
294 | /// avoid any allocations. The \arg writer will be passed a | |
295 | /// internal::log_buf::inserter_iterator that allows it to write into the log | |
296 | /// buffer directly, avoiding the use of any intermediary buffers. | |
297 | /// This is rate-limited version, see \ref rate_limit. | |
20effc67 | 298 | void log(log_level level, rate_limit& rl, log_writer& writer, format_info fmt = {}) noexcept { |
f67539c2 TL |
299 | if (is_enabled(level) && rl.check()) { |
300 | try { | |
301 | lambda_log_writer writer_wrapper([&] (internal::log_buf::inserter_iterator it) { | |
302 | if (rl.has_dropped_messages()) { | |
303 | it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); | |
304 | } | |
305 | return writer(it); | |
306 | }); | |
307 | do_log(level, writer_wrapper); | |
308 | } catch (...) { | |
20effc67 | 309 | failed_to_log(std::current_exception(), std::move(fmt)); |
f67539c2 TL |
310 | } |
311 | } | |
312 | } | |
313 | /// \endcond | |
11fdf7f2 TL |
314 | |
315 | /// Log with error tag: | |
316 | /// ERROR %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
317 | /// | |
20effc67 TL |
318 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
319 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
320 | /// \param args - args to print string |
321 | /// | |
322 | template <typename... Args> | |
20effc67 TL |
323 | void error(format_info fmt, Args&&... args) noexcept { |
324 | log(log_level::error, std::move(fmt), std::forward<Args>(args)...); | |
11fdf7f2 TL |
325 | } |
326 | /// Log with warning tag: | |
327 | /// WARN %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
328 | /// | |
20effc67 TL |
329 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
330 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
331 | /// \param args - args to print string |
332 | /// | |
333 | template <typename... Args> | |
20effc67 TL |
334 | void warn(format_info fmt, Args&&... args) noexcept { |
335 | log(log_level::warn, std::move(fmt), std::forward<Args>(args)...); | |
11fdf7f2 TL |
336 | } |
337 | /// Log with info tag: | |
338 | /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
339 | /// | |
20effc67 TL |
340 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
341 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
342 | /// \param args - args to print string |
343 | /// | |
344 | template <typename... Args> | |
20effc67 TL |
345 | void info(format_info fmt, Args&&... args) noexcept { |
346 | log(log_level::info, std::move(fmt), std::forward<Args>(args)...); | |
11fdf7f2 TL |
347 | } |
348 | /// Log with info tag on shard zero only: | |
349 | /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
350 | /// | |
20effc67 TL |
351 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
352 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
353 | /// \param args - args to print string |
354 | /// | |
355 | template <typename... Args> | |
20effc67 | 356 | void info0(format_info fmt, Args&&... args) noexcept { |
11fdf7f2 | 357 | if (is_shard_zero()) { |
20effc67 | 358 | log(log_level::info, std::move(fmt), std::forward<Args>(args)...); |
11fdf7f2 TL |
359 | } |
360 | } | |
f67539c2 | 361 | /// Log with debug tag: |
11fdf7f2 TL |
362 | /// DEBUG %Y-%m-%d %T,%03d [shard 0] - "your msg" \n |
363 | /// | |
20effc67 TL |
364 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
365 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
366 | /// \param args - args to print string |
367 | /// | |
368 | template <typename... Args> | |
20effc67 TL |
369 | void debug(format_info fmt, Args&&... args) noexcept { |
370 | log(log_level::debug, std::move(fmt), std::forward<Args>(args)...); | |
11fdf7f2 TL |
371 | } |
372 | /// Log with trace tag: | |
373 | /// TRACE %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
374 | /// | |
20effc67 TL |
375 | /// \param fmt - {fmt} style format string (implictly converted to struct logger::format_info) |
376 | /// or a logger::format_info passed down the call chain. | |
11fdf7f2 TL |
377 | /// \param args - args to print string |
378 | /// | |
379 | template <typename... Args> | |
20effc67 TL |
380 | void trace(format_info fmt, Args&&... args) noexcept { |
381 | log(log_level::trace, std::move(fmt), std::forward<Args>(args)...); | |
11fdf7f2 TL |
382 | } |
383 | ||
384 | /// \return name of the logger. Usually one logger per module | |
385 | /// | |
f67539c2 | 386 | const sstring& name() const noexcept { |
11fdf7f2 TL |
387 | return _name; |
388 | } | |
389 | ||
390 | /// \return current log level for this logger | |
391 | /// | |
f67539c2 | 392 | log_level level() const noexcept { |
11fdf7f2 TL |
393 | return _level.load(std::memory_order_relaxed); |
394 | } | |
395 | ||
396 | /// \param level - set the log level | |
397 | /// | |
f67539c2 | 398 | void set_level(log_level level) noexcept { |
11fdf7f2 TL |
399 | _level.store(level, std::memory_order_relaxed); |
400 | } | |
401 | ||
9f95a23c | 402 | /// Set output stream, default is std::cerr |
f67539c2 | 403 | static void set_ostream(std::ostream& out) noexcept; |
9f95a23c TL |
404 | |
405 | /// Also output to ostream. default is true | |
f67539c2 | 406 | static void set_ostream_enabled(bool enabled) noexcept; |
9f95a23c | 407 | |
11fdf7f2 | 408 | /// Also output to stdout. default is true |
9f95a23c | 409 | [[deprecated("Use set_ostream_enabled instead")]] |
f67539c2 | 410 | static void set_stdout_enabled(bool enabled) noexcept; |
11fdf7f2 TL |
411 | |
412 | /// Also output to syslog. default is false | |
413 | /// | |
414 | /// NOTE: syslog() can block, which will stall the reactor thread. | |
415 | /// this should be rare (will have to fill the pipe buffer | |
416 | /// before syslogd can clear it) but can happen. | |
f67539c2 | 417 | static void set_syslog_enabled(bool enabled) noexcept; |
1e59de90 TL |
418 | |
419 | /// Set the width of shard id field in log messages | |
420 | /// | |
421 | /// \c this_shard_id() is printed as a part of the prefix in logging | |
422 | /// messages, like "[shard 42]", where \c 42 is the decimal number of the | |
423 | /// current shard id printed with a minimal width. | |
424 | /// | |
425 | /// \param width the minimal width of the shard id field | |
426 | static void set_shard_field_width(unsigned width) noexcept; | |
427 | ||
428 | /// enable/disable the colored tag in ostream | |
429 | /// | |
430 | /// \note this is a noop if fmtlib's version is less than 6.0 | |
431 | static void set_with_color(bool enabled) noexcept; | |
11fdf7f2 TL |
432 | }; |
433 | ||
434 | /// \brief used to keep a static registry of loggers | |
435 | /// since the typical use case is to do: | |
436 | /// \code {.cpp} | |
437 | /// static seastar::logger("my_module"); | |
438 | /// \endcode | |
439 | /// this class is used to wrap around the static map | |
440 | /// that holds pointers to all logs | |
441 | /// | |
442 | class logger_registry { | |
443 | mutable std::mutex _mutex; | |
444 | std::unordered_map<sstring, logger*> _loggers; | |
445 | public: | |
446 | /// loops through all registered loggers and sets the log level | |
447 | /// Note: this method locks | |
448 | /// | |
449 | /// \param level - desired level: error,info,... | |
450 | void set_all_loggers_level(log_level level); | |
451 | ||
452 | /// Given a name for a logger returns the log_level enum | |
453 | /// Note: this method locks | |
454 | /// | |
455 | /// \return log_level for the given logger name | |
456 | log_level get_logger_level(sstring name) const; | |
457 | ||
458 | /// Sets the log level for a given logger | |
459 | /// Note: this method locks | |
460 | /// | |
461 | /// \param name - name of logger | |
462 | /// \param level - desired level of logging | |
463 | void set_logger_level(sstring name, log_level level); | |
464 | ||
465 | /// Returns a list of registered loggers | |
466 | /// Note: this method locks | |
467 | /// | |
468 | /// \return all registered loggers | |
469 | std::vector<sstring> get_all_logger_names(); | |
470 | ||
471 | /// Registers a logger with the static map | |
472 | /// Note: this method locks | |
473 | /// | |
474 | void register_logger(logger* l); | |
475 | /// Unregisters a logger with the static map | |
476 | /// Note: this method locks | |
477 | /// | |
478 | void unregister_logger(logger* l); | |
479 | /// Swaps the logger given the from->name() in the static map | |
480 | /// Note: this method locks | |
481 | /// | |
482 | void moved(logger* from, logger* to); | |
483 | }; | |
484 | ||
485 | logger_registry& global_logger_registry(); | |
486 | ||
20effc67 | 487 | /// \brief Timestamp style. |
11fdf7f2 TL |
488 | enum class logger_timestamp_style { |
489 | none, | |
490 | boot, | |
491 | real, | |
492 | }; | |
493 | ||
20effc67 | 494 | /// \brief Output stream to use for logging. |
9f95a23c TL |
495 | enum class logger_ostream_type { |
496 | none, | |
497 | stdout, | |
498 | stderr, | |
499 | }; | |
500 | ||
11fdf7f2 TL |
501 | struct logging_settings final { |
502 | std::unordered_map<sstring, log_level> logger_levels; | |
503 | log_level default_level; | |
504 | bool stdout_enabled; | |
505 | bool syslog_enabled; | |
1e59de90 | 506 | bool with_color; |
11fdf7f2 | 507 | logger_timestamp_style stdout_timestamp_style = logger_timestamp_style::real; |
9f95a23c | 508 | logger_ostream_type logger_ostream = logger_ostream_type::stderr; |
11fdf7f2 TL |
509 | }; |
510 | ||
511 | /// Shortcut for configuring the logging system all at once. | |
512 | /// | |
513 | void apply_logging_settings(const logging_settings&); | |
514 | ||
515 | /// \cond internal | |
516 | ||
517 | extern thread_local uint64_t logging_failures; | |
518 | ||
519 | sstring pretty_type_name(const std::type_info&); | |
520 | ||
521 | sstring level_name(log_level level); | |
522 | ||
523 | template <typename T> | |
524 | class logger_for : public logger { | |
525 | public: | |
526 | logger_for() : logger(pretty_type_name(typeid(T))) {} | |
527 | }; | |
528 | ||
11fdf7f2 TL |
529 | /// \endcond |
530 | } // end seastar namespace | |
531 | ||
532 | // Pretty-printer for exceptions to be logged, e.g., std::current_exception(). | |
533 | namespace std { | |
534 | std::ostream& operator<<(std::ostream&, const std::exception_ptr&); | |
535 | std::ostream& operator<<(std::ostream&, const std::exception&); | |
536 | std::ostream& operator<<(std::ostream&, const std::system_error&); | |
537 | } | |
538 | ||
1e59de90 TL |
539 | #if FMT_VERSION >= 90000 |
540 | template <> struct fmt::formatter<std::exception_ptr> : fmt::ostream_formatter {}; | |
541 | template <> struct fmt::formatter<std::exception> : fmt::ostream_formatter {}; | |
542 | template <> struct fmt::formatter<std::system_error> : fmt::ostream_formatter {}; | |
543 | #endif | |
544 | ||
11fdf7f2 | 545 | /// @} |