]>
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> | |
11fdf7f2 TL |
27 | #include <unordered_map> |
28 | #include <exception> | |
29 | #include <iosfwd> | |
30 | #include <atomic> | |
31 | #include <mutex> | |
32 | #include <boost/lexical_cast.hpp> | |
f67539c2 | 33 | #include <fmt/format.h> |
11fdf7f2 TL |
34 | |
35 | ||
36 | /// \addtogroup logging | |
37 | /// @{ | |
38 | ||
39 | namespace seastar { | |
40 | ||
41 | /// \brief log level used with \see {logger} | |
42 | /// used with the logger.do_log method. | |
43 | /// Levels are in increasing order. That is if you want to see debug(3) logs you | |
44 | /// will also see error(0), warn(1), info(2). | |
45 | /// | |
46 | enum class log_level { | |
47 | error, | |
48 | warn, | |
49 | info, | |
50 | debug, | |
51 | trace, | |
52 | }; | |
53 | ||
54 | std::ostream& operator<<(std::ostream& out, log_level level); | |
55 | std::istream& operator>>(std::istream& in, log_level& level); | |
56 | } | |
57 | ||
58 | // Boost doesn't auto-deduce the existence of the streaming operators for some reason | |
59 | ||
60 | namespace boost { | |
61 | template<> | |
62 | seastar::log_level lexical_cast(const std::string& source); | |
63 | ||
64 | } | |
65 | ||
66 | namespace seastar { | |
67 | ||
68 | class logger; | |
69 | class logger_registry; | |
70 | ||
9f95a23c | 71 | /// \brief Logger class for ostream or syslog. |
11fdf7f2 TL |
72 | /// |
73 | /// Java style api for logging. | |
74 | /// \code {.cpp} | |
75 | /// static seastar::logger logger("lsa-api"); | |
76 | /// logger.info("Triggering compaction"); | |
77 | /// \endcode | |
78 | /// The output format is: (depending on level) | |
79 | /// DEBUG %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
80 | /// | |
f67539c2 | 81 | /// It is possible to rate-limit log messages, see \ref logger::rate_limit. |
11fdf7f2 TL |
82 | class logger { |
83 | sstring _name; | |
84 | std::atomic<log_level> _level = { log_level::info }; | |
9f95a23c TL |
85 | static std::ostream* _out; |
86 | static std::atomic<bool> _ostream; | |
11fdf7f2 | 87 | static std::atomic<bool> _syslog; |
f67539c2 TL |
88 | |
89 | public: | |
90 | class log_writer { | |
91 | public: | |
92 | virtual ~log_writer() = default; | |
93 | virtual internal::log_buf::inserter_iterator operator()(internal::log_buf::inserter_iterator) = 0; | |
11fdf7f2 | 94 | }; |
f67539c2 TL |
95 | template <typename Func> |
96 | SEASTAR_CONCEPT(requires requires (Func fn, internal::log_buf::inserter_iterator it) { | |
97 | it = fn(it); | |
98 | }) | |
99 | class lambda_log_writer : public log_writer { | |
100 | Func _func; | |
101 | public: | |
102 | lambda_log_writer(Func&& func) : _func(std::forward<Func>(func)) { } | |
103 | virtual ~lambda_log_writer() override = default; | |
104 | virtual internal::log_buf::inserter_iterator operator()(internal::log_buf::inserter_iterator it) override { return _func(it); } | |
11fdf7f2 | 105 | }; |
f67539c2 TL |
106 | |
107 | private: | |
108 | ||
109 | // We can't use an std::function<> as it potentially allocates. | |
110 | void do_log(log_level level, log_writer& writer); | |
111 | void failed_to_log(std::exception_ptr ex) noexcept; | |
112 | public: | |
113 | /// Apply a rate limit to log message(s) | |
114 | /// | |
115 | /// Pass this to \ref logger::log() to apply a rate limit to the message. | |
116 | /// The rate limit is applied to all \ref logger::log() calls this rate | |
117 | /// limit is passed to. Example: | |
118 | /// | |
119 | /// void handle_request() { | |
120 | /// static thread_local logger::rate_limit my_rl(std::chrono::seconds(10)); | |
121 | /// // ... | |
122 | /// 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"); | |
123 | /// // ... | |
124 | /// } | |
125 | /// | |
126 | /// The rate limit ensures that at most one message per interval will be | |
127 | /// logged. If there were messages dropped due to rate-limiting the | |
128 | /// following snippet will be prepended to the first non-dropped log | |
129 | /// messages: | |
130 | /// | |
131 | /// (rate limiting dropped $N similar messages) | |
132 | /// | |
133 | /// Where $N is the number of messages dropped. | |
134 | class rate_limit { | |
135 | friend class logger; | |
136 | ||
137 | using clock = lowres_clock; | |
138 | ||
139 | private: | |
140 | clock::duration _interval; | |
141 | clock::time_point _next; | |
142 | uint64_t _dropped_messages = 0; | |
143 | ||
144 | private: | |
145 | bool check(); | |
146 | bool has_dropped_messages() const { return bool(_dropped_messages); } | |
147 | uint64_t get_and_reset_dropped_messages() { | |
148 | return std::exchange(_dropped_messages, 0); | |
149 | } | |
150 | ||
151 | public: | |
152 | explicit rate_limit(std::chrono::milliseconds interval); | |
153 | }; | |
154 | ||
11fdf7f2 TL |
155 | public: |
156 | explicit logger(sstring name); | |
157 | logger(logger&& x); | |
158 | ~logger(); | |
159 | ||
f67539c2 | 160 | bool is_shard_zero() noexcept; |
11fdf7f2 TL |
161 | |
162 | /// Test if desired log level is enabled | |
163 | /// | |
164 | /// \param level - enum level value (info|error...) | |
165 | /// \return true if the log level has been enabled. | |
f67539c2 | 166 | bool is_enabled(log_level level) const noexcept { |
11fdf7f2 TL |
167 | return __builtin_expect(level <= _level.load(std::memory_order_relaxed), false); |
168 | } | |
169 | ||
170 | /// logs to desired level if enabled, otherwise we ignore the log line | |
171 | /// | |
f67539c2 TL |
172 | /// \param fmt - {fmt} style format |
173 | /// \param args - args to print string | |
174 | /// | |
175 | template <typename... Args> | |
176 | void log(log_level level, const char* fmt, Args&&... args) noexcept { | |
177 | if (is_enabled(level)) { | |
178 | try { | |
179 | lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { | |
180 | return fmt::format_to(it, fmt, std::forward<Args>(args)...); | |
181 | }); | |
182 | do_log(level, writer); | |
183 | } catch (...) { | |
184 | failed_to_log(std::current_exception()); | |
185 | } | |
186 | } | |
187 | } | |
188 | ||
189 | /// logs with a rate limit to desired level if enabled, otherwise we ignore the log line | |
190 | /// | |
191 | /// If there were messages dropped due to rate-limiting the following snippet | |
192 | /// will be prepended to the first non-dropped log messages: | |
193 | /// | |
194 | /// (rate limiting dropped $N similar messages) | |
195 | /// | |
196 | /// Where $N is the number of messages dropped. | |
197 | /// | |
198 | /// \param rl - the \ref rate_limit to apply to this log | |
199 | /// \param fmt - {fmt} style format | |
11fdf7f2 TL |
200 | /// \param args - args to print string |
201 | /// | |
202 | template <typename... Args> | |
f67539c2 TL |
203 | void log(log_level level, rate_limit& rl, const char* fmt, Args&&... args) noexcept { |
204 | if (is_enabled(level) && rl.check()) { | |
205 | try { | |
206 | lambda_log_writer writer([&] (internal::log_buf::inserter_iterator it) { | |
207 | if (rl.has_dropped_messages()) { | |
208 | it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); | |
209 | } | |
210 | return fmt::format_to(it, fmt, std::forward<Args>(args)...); | |
211 | }); | |
212 | do_log(level, writer); | |
213 | } catch (...) { | |
214 | failed_to_log(std::current_exception()); | |
215 | } | |
216 | } | |
217 | } | |
218 | ||
219 | /// \cond internal | |
220 | /// logs to desired level if enabled, otherwise we ignore the log line | |
221 | /// | |
222 | /// \param writer a function which writes directly to the underlying log buffer | |
223 | /// | |
224 | /// This is a low level method for use cases where it is very important to | |
225 | /// avoid any allocations. The \arg writer will be passed a | |
226 | /// internal::log_buf::inserter_iterator that allows it to write into the log | |
227 | /// buffer directly, avoiding the use of any intermediary buffers. | |
228 | void log(log_level level, log_writer& writer) noexcept { | |
11fdf7f2 TL |
229 | if (is_enabled(level)) { |
230 | try { | |
f67539c2 | 231 | do_log(level, writer); |
11fdf7f2 TL |
232 | } catch (...) { |
233 | failed_to_log(std::current_exception()); | |
234 | } | |
235 | } | |
236 | } | |
f67539c2 TL |
237 | /// logs to desired level if enabled, otherwise we ignore the log line |
238 | /// | |
239 | /// \param writer a function which writes directly to the underlying log buffer | |
240 | /// | |
241 | /// This is a low level method for use cases where it is very important to | |
242 | /// avoid any allocations. The \arg writer will be passed a | |
243 | /// internal::log_buf::inserter_iterator that allows it to write into the log | |
244 | /// buffer directly, avoiding the use of any intermediary buffers. | |
245 | /// This is rate-limited version, see \ref rate_limit. | |
246 | void log(log_level level, rate_limit& rl, log_writer& writer) noexcept { | |
247 | if (is_enabled(level) && rl.check()) { | |
248 | try { | |
249 | lambda_log_writer writer_wrapper([&] (internal::log_buf::inserter_iterator it) { | |
250 | if (rl.has_dropped_messages()) { | |
251 | it = fmt::format_to(it, "(rate limiting dropped {} similar messages) ", rl.get_and_reset_dropped_messages()); | |
252 | } | |
253 | return writer(it); | |
254 | }); | |
255 | do_log(level, writer_wrapper); | |
256 | } catch (...) { | |
257 | failed_to_log(std::current_exception()); | |
258 | } | |
259 | } | |
260 | } | |
261 | /// \endcond | |
11fdf7f2 TL |
262 | |
263 | /// Log with error tag: | |
264 | /// ERROR %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
265 | /// | |
f67539c2 | 266 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
267 | /// \param args - args to print string |
268 | /// | |
269 | template <typename... Args> | |
f67539c2 | 270 | void error(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
271 | log(log_level::error, fmt, std::forward<Args>(args)...); |
272 | } | |
273 | /// Log with warning tag: | |
274 | /// WARN %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
275 | /// | |
f67539c2 | 276 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
277 | /// \param args - args to print string |
278 | /// | |
279 | template <typename... Args> | |
f67539c2 | 280 | void warn(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
281 | log(log_level::warn, fmt, std::forward<Args>(args)...); |
282 | } | |
283 | /// Log with info tag: | |
284 | /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
285 | /// | |
f67539c2 | 286 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
287 | /// \param args - args to print string |
288 | /// | |
289 | template <typename... Args> | |
f67539c2 | 290 | void info(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
291 | log(log_level::info, fmt, std::forward<Args>(args)...); |
292 | } | |
293 | /// Log with info tag on shard zero only: | |
294 | /// INFO %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
295 | /// | |
f67539c2 | 296 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
297 | /// \param args - args to print string |
298 | /// | |
299 | template <typename... Args> | |
f67539c2 | 300 | void info0(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
301 | if (is_shard_zero()) { |
302 | log(log_level::info, fmt, std::forward<Args>(args)...); | |
303 | } | |
304 | } | |
f67539c2 | 305 | /// Log with debug tag: |
11fdf7f2 TL |
306 | /// DEBUG %Y-%m-%d %T,%03d [shard 0] - "your msg" \n |
307 | /// | |
f67539c2 | 308 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
309 | /// \param args - args to print string |
310 | /// | |
311 | template <typename... Args> | |
f67539c2 | 312 | void debug(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
313 | log(log_level::debug, fmt, std::forward<Args>(args)...); |
314 | } | |
315 | /// Log with trace tag: | |
316 | /// TRACE %Y-%m-%d %T,%03d [shard 0] - "your msg" \n | |
317 | /// | |
f67539c2 | 318 | /// \param fmt - {fmt} style format |
11fdf7f2 TL |
319 | /// \param args - args to print string |
320 | /// | |
321 | template <typename... Args> | |
f67539c2 | 322 | void trace(const char* fmt, Args&&... args) noexcept { |
11fdf7f2 TL |
323 | log(log_level::trace, fmt, std::forward<Args>(args)...); |
324 | } | |
325 | ||
326 | /// \return name of the logger. Usually one logger per module | |
327 | /// | |
f67539c2 | 328 | const sstring& name() const noexcept { |
11fdf7f2 TL |
329 | return _name; |
330 | } | |
331 | ||
332 | /// \return current log level for this logger | |
333 | /// | |
f67539c2 | 334 | log_level level() const noexcept { |
11fdf7f2 TL |
335 | return _level.load(std::memory_order_relaxed); |
336 | } | |
337 | ||
338 | /// \param level - set the log level | |
339 | /// | |
f67539c2 | 340 | void set_level(log_level level) noexcept { |
11fdf7f2 TL |
341 | _level.store(level, std::memory_order_relaxed); |
342 | } | |
343 | ||
9f95a23c | 344 | /// Set output stream, default is std::cerr |
f67539c2 | 345 | static void set_ostream(std::ostream& out) noexcept; |
9f95a23c TL |
346 | |
347 | /// Also output to ostream. default is true | |
f67539c2 | 348 | static void set_ostream_enabled(bool enabled) noexcept; |
9f95a23c | 349 | |
11fdf7f2 | 350 | /// Also output to stdout. default is true |
9f95a23c | 351 | [[deprecated("Use set_ostream_enabled instead")]] |
f67539c2 | 352 | static void set_stdout_enabled(bool enabled) noexcept; |
11fdf7f2 TL |
353 | |
354 | /// Also output to syslog. default is false | |
355 | /// | |
356 | /// NOTE: syslog() can block, which will stall the reactor thread. | |
357 | /// this should be rare (will have to fill the pipe buffer | |
358 | /// before syslogd can clear it) but can happen. | |
f67539c2 | 359 | static void set_syslog_enabled(bool enabled) noexcept; |
11fdf7f2 TL |
360 | }; |
361 | ||
362 | /// \brief used to keep a static registry of loggers | |
363 | /// since the typical use case is to do: | |
364 | /// \code {.cpp} | |
365 | /// static seastar::logger("my_module"); | |
366 | /// \endcode | |
367 | /// this class is used to wrap around the static map | |
368 | /// that holds pointers to all logs | |
369 | /// | |
370 | class logger_registry { | |
371 | mutable std::mutex _mutex; | |
372 | std::unordered_map<sstring, logger*> _loggers; | |
373 | public: | |
374 | /// loops through all registered loggers and sets the log level | |
375 | /// Note: this method locks | |
376 | /// | |
377 | /// \param level - desired level: error,info,... | |
378 | void set_all_loggers_level(log_level level); | |
379 | ||
380 | /// Given a name for a logger returns the log_level enum | |
381 | /// Note: this method locks | |
382 | /// | |
383 | /// \return log_level for the given logger name | |
384 | log_level get_logger_level(sstring name) const; | |
385 | ||
386 | /// Sets the log level for a given logger | |
387 | /// Note: this method locks | |
388 | /// | |
389 | /// \param name - name of logger | |
390 | /// \param level - desired level of logging | |
391 | void set_logger_level(sstring name, log_level level); | |
392 | ||
393 | /// Returns a list of registered loggers | |
394 | /// Note: this method locks | |
395 | /// | |
396 | /// \return all registered loggers | |
397 | std::vector<sstring> get_all_logger_names(); | |
398 | ||
399 | /// Registers a logger with the static map | |
400 | /// Note: this method locks | |
401 | /// | |
402 | void register_logger(logger* l); | |
403 | /// Unregisters a logger with the static map | |
404 | /// Note: this method locks | |
405 | /// | |
406 | void unregister_logger(logger* l); | |
407 | /// Swaps the logger given the from->name() in the static map | |
408 | /// Note: this method locks | |
409 | /// | |
410 | void moved(logger* from, logger* to); | |
411 | }; | |
412 | ||
413 | logger_registry& global_logger_registry(); | |
414 | ||
415 | enum class logger_timestamp_style { | |
416 | none, | |
417 | boot, | |
418 | real, | |
419 | }; | |
420 | ||
9f95a23c TL |
421 | enum class logger_ostream_type { |
422 | none, | |
423 | stdout, | |
424 | stderr, | |
425 | }; | |
426 | ||
11fdf7f2 TL |
427 | struct logging_settings final { |
428 | std::unordered_map<sstring, log_level> logger_levels; | |
429 | log_level default_level; | |
430 | bool stdout_enabled; | |
431 | bool syslog_enabled; | |
432 | logger_timestamp_style stdout_timestamp_style = logger_timestamp_style::real; | |
9f95a23c | 433 | logger_ostream_type logger_ostream = logger_ostream_type::stderr; |
11fdf7f2 TL |
434 | }; |
435 | ||
436 | /// Shortcut for configuring the logging system all at once. | |
437 | /// | |
438 | void apply_logging_settings(const logging_settings&); | |
439 | ||
440 | /// \cond internal | |
441 | ||
442 | extern thread_local uint64_t logging_failures; | |
443 | ||
444 | sstring pretty_type_name(const std::type_info&); | |
445 | ||
446 | sstring level_name(log_level level); | |
447 | ||
448 | template <typename T> | |
449 | class logger_for : public logger { | |
450 | public: | |
451 | logger_for() : logger(pretty_type_name(typeid(T))) {} | |
452 | }; | |
453 | ||
11fdf7f2 TL |
454 | /// \endcond |
455 | } // end seastar namespace | |
456 | ||
457 | // Pretty-printer for exceptions to be logged, e.g., std::current_exception(). | |
458 | namespace std { | |
459 | std::ostream& operator<<(std::ostream&, const std::exception_ptr&); | |
460 | std::ostream& operator<<(std::ostream&, const std::exception&); | |
461 | std::ostream& operator<<(std::ostream&, const std::system_error&); | |
462 | } | |
463 | ||
464 | /// @} |