The Battle for Wesnoth  1.19.5+dev
log.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2004 - 2024
3  by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file
19  * Standard logging facilities (interface).
20  *
21  * To use one of the standard log channels, put something like the following at the start
22  * of your .cpp file:
23  *
24  * static lg::log_domain log_display("display");
25  * \#define ERR_DP LOG_STREAM(err, log_display)
26  * \#define LOG_DP LOG_STREAM(info, log_display)
27  *
28  * Then stream logging info to ERR_DP, or LOG_DP, as if it were an ostream like std::cerr.
29  * (In general it will actually be std::cerr at runtime when logging is enabled.)
30  *
31  * LOG_DP << "Found a window resize event: ...";
32  *
33  * Please do not use iomanip features like std::hex directly on the logger. Because of the
34  * design of the logger, this will result in all of the loggers (in fact std::cerr) being
35  * imbued with std::hex. Please use a formatter instead.
36  *
37  * \#include "formatter.hpp"
38  *
39  * LOG_DP << (formatter() << "The random seed is: '" << std::hex << seed << "'\n").str();
40  *
41  * It might be nice if somehow the logger class / macros could support using iomanip
42  * things directly, but right now it doesn't, and it seems that it would complicate the
43  * design greatly enough that it doesn't seem worth it.
44  */
45 
46 #pragma once
47 
48 #ifndef __func__
49  #ifdef __FUNCTION__
50  #define __func__ __FUNCTION__
51  #endif
52 #endif
53 
54 #include <iosfwd> // needed else all files including log.hpp need to do it.
55 #include "utils/optional_fwd.hpp"
56 #include <string>
57 #include <utility>
58 #include <chrono>
59 #include <ctime>
60 #include <cstdint>
61 
62 #include "formatter.hpp"
63 
64 namespace lg {
65 
66 // Prefix and extension for log files.
67 // This is used to find old files to delete.
68 const std::string log_file_prefix = "wesnoth-";
69 const std::string log_file_suffix = ".log";
70 // stdout file for Windows; needs to end in log_file_suffix so log rotation works for both
71 const std::string out_log_file_suffix = ".out" + log_file_suffix;
72 
73 // Maximum number of older log files to keep intact. Other files are deleted.
74 // Note that this count does not include the current log file!
75 // double for Windows due to the separate .log and .out.log files
76 const unsigned max_logs = 8
77 #ifdef _WIN32
78 *2
79 #endif
80 ;
81 
82 enum class severity
83 {
84  LG_NONE=-1,
85  LG_ERROR=0,
86  LG_WARN=1,
87  LG_INFO=2,
88  LG_DEBUG=3
89 };
90 std::ostringstream& operator<<(std::ostringstream& oss, lg::severity severity);
91 
92 /**
93  * Helper class to redirect the output of the logger in a certain scope.
94  *
95  * The main usage of the redirection is for the unit tests to validate the
96  * output on the logger with the expected output.
97  */
99 {
100 public:
101 
102  /**
103  * Constructor.
104  *
105  * @param stream The stream to direct the output to.
106  */
107  explicit redirect_output_setter(std::ostream& stream);
108 
110 
111 private:
112 
113  /**
114  * The previously set redirection.
115  *
116  * This value is stored here to be restored in this destructor.
117  */
118  std::ostream* old_stream_;
119 };
120 
121 class logger;
122 
123 typedef std::pair<const std::string, severity> logd;
124 
125 class log_domain {
127 public:
128  explicit log_domain(char const *name, severity severity = severity::LG_WARN);
129  friend class logger;
130 };
131 
132 bool set_log_domain_severity(const std::string& name, severity severity);
133 bool set_log_domain_severity(const std::string& name, const logger &lg);
134 bool get_log_domain_severity(const std::string& name, severity &severity);
135 std::string list_log_domains(const std::string& filter);
136 
138 void set_strict_severity(const logger &lg);
139 bool broke_strict();
140 
141 /** toggle log sanitization */
142 void set_log_sanitize(bool sanitize);
143 
144 /**
145  * Do the initial redirection to a log file if the logs directory is writable.
146  * Also performs log rotation to delete old logs.
147  * NOTE: This runs before command line arguments are processed.
148  * Therefore the log file is initially written under the default userdata directory
149  */
150 void set_log_to_file();
151 /**
152  * Move the log file to another directory.
153  * Used if a custom userdata directory is given as a command line option to move it to the new location.
154  */
155 void move_log_file();
156 /**
157  * Checks that a dummy file can be written to and deleted from the logs directory.
158  */
160 /**
161  * Returns the result set by check_log_dir_writable().
162  * Will not be set if called before log redirection is done.
163  *
164  * @return true if the log directory is writable, false otherwise.
165  */
166 utils::optional<bool> log_dir_writable();
167 
168 /**
169  * Use the defined prefix and suffix to determine if a filename is a log file.
170  *
171  * @return true if it's a log file, false otherwise
172  */
173 bool is_not_log_file(const std::string& filename);
174 /**
175  * Check how many log files exist and delete the oldest when there's too many.
176  */
177 void rotate_logs(const std::string& log_dir);
178 /**
179  * Generate a unique file name using the current timestamp and a randomly generated number.
180  *
181  * @return A unique file name to use for the current log file.
182  */
183 std::string unique_log_filename();
184 
185 // A little "magic" to surround the logging operation in a mutex.
186 // This works by capturing the output first to a stringstream formatter, then
187 // locking a mutex and dumping it to the stream all in one go.
188 // By doing this we can avoid rare deadlocks if a function whose output is streamed
189 // calls logging of its own.
190 // We overload operator| only because it has lower precedence than operator<<
191 // Any other lower-precedence operator would have worked just as well.
193  std::ostream& stream_;
194  int indent_ = 0;
195  bool timestamp_ = false;
196  std::string prefix_;
197  bool auto_newline_ = true;
198 public:
199  log_in_progress(std::ostream& stream);
200  void operator|(formatter&& message);
201  void set_indent(int level);
202  void enable_timestamp();
203  void set_prefix(const std::string& prefix);
204  void set_auto_newline(bool enabled);
205 };
206 
207 class logger {
208  char const *name_;
210 public:
211  logger(char const *name, severity severity): name_(name), severity_(severity) {}
212  log_in_progress operator()(const log_domain& domain,
213  bool show_names = true, bool do_indent = false, bool show_timestamps = true, bool break_strict = true, bool auto_newline = true) const;
214 
215  bool dont_log(const log_domain& domain) const
216  {
217  return severity_ > domain.domain_->second;
218  }
219 
221  {
222  return severity_;
223  }
224 
225  std::string get_name() const
226  {
227  return name_;
228  }
229 };
230 
231 void timestamps(bool);
232 void precise_timestamps(bool);
233 /** TODO: we also have utils::format_timespan, which does something very similar... */
234 std::string format_timespan(const std::chrono::seconds& span);
235 std::string sanitize_log(const std::string& logstr);
236 std::string get_log_file_path();
237 
238 logger &err(), &warn(), &info(), &debug();
239 log_domain& general();
240 
242 {
243  std::chrono::steady_clock::time_point start_;
245  std::string str_;
246 public:
247  scope_logger(const log_domain& domain, const char* str)
248  : start_()
249  , domain_(domain)
250  , str_()
251  {
252  if (!debug().dont_log(domain)) do_log_entry(str);
253  }
254  scope_logger(const log_domain& domain, const std::string& str)
255  : start_()
256  , domain_(domain)
257  , str_()
258  {
259  if (!debug().dont_log(domain)) do_log_entry(str);
260  }
262  {
263  if (!str_.empty()) do_log_exit();
264  }
265 private:
266  void do_log_entry(const std::string& str) noexcept;
267  void do_log_exit() noexcept;
268 };
269 
270 /**
271  * Use this to show WML errors in the ingame chat.
272  * After every WML event the errors are shown to the user so they can inform the campaign maintainer.
273  */
274 std::stringstream& log_to_chat();
275 
276 } // namespace lg
277 
278 #define log_scope(description) lg::scope_logger scope_logging_object__(lg::general(), description);
279 #define log_scope2(domain,description) lg::scope_logger scope_logging_object__(domain, description);
280 
281 #define LOG_STREAM(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain) | formatter()
282 
283 // Don't prefix the logdomain to messages on this stream
284 #define LOG_STREAM_NAMELESS(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, false) | formatter()
285 
286 // Like LOG_STREAM_NAMELESS except doesn't add newlines automatically
287 #define LOG_STREAM_NAMELESS_STREAMING(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, false, false, true, true, false) | formatter()
288 
289 // When using log_scope/log_scope2 it is nice to have all output indented.
290 #define LOG_STREAM_INDENT(level,domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, true, true) | formatter()
291 
292 // If you have an explicit logger object and want to ignore the logging level, use this.
293 // Meant for cases where you explicitly call dont_log to avoid an expensive operation if the logging is disabled.
294 #define FORCE_LOG_TO(logger, domain) logger(domain) | formatter()
295 
296 // always log (since it's at the error level) to the general log stream
297 // outputting the log domain and timestamp is disabled
298 // meant as a replacement to using cerr/cout, but that goes through the same logging infrastructure as everything else
299 #define PLAIN_LOG lg::err()(lg::general(), false, false, false, false, true) | formatter()
300 #define STREAMING_LOG lg::err()(lg::general(), false, false, false, false, false) | formatter()
std::ostringstream wrapper.
Definition: formatter.hpp:40
logd * domain_
Definition: log.hpp:126
log_domain(char const *name, severity severity=severity::LG_WARN)
Definition: log.cpp:338
void operator|(formatter &&message)
Definition: log.cpp:488
std::string prefix_
Definition: log.hpp:196
std::ostream & stream_
Definition: log.hpp:193
void set_auto_newline(bool enabled)
Definition: log.cpp:520
void set_prefix(const std::string &prefix)
Definition: log.cpp:516
void enable_timestamp()
Definition: log.cpp:512
bool auto_newline_
Definition: log.hpp:197
void set_indent(int level)
Definition: log.cpp:508
log_in_progress(std::ostream &stream)
Definition: log.cpp:484
char const * name_
Definition: log.hpp:208
std::string get_name() const
Definition: log.hpp:225
logger(char const *name, severity severity)
Definition: log.hpp:211
log_in_progress operator()(const log_domain &domain, bool show_names=true, bool do_indent=false, bool show_timestamps=true, bool break_strict=true, bool auto_newline=true) const
Definition: log.cpp:454
severity get_severity() const
Definition: log.hpp:220
severity severity_
Definition: log.hpp:209
bool dont_log(const log_domain &domain) const
Definition: log.hpp:215
Helper class to redirect the output of the logger in a certain scope.
Definition: log.hpp:99
std::ostream * old_stream_
The previously set redirection.
Definition: log.hpp:118
redirect_output_setter(std::ostream &stream)
Constructor.
Definition: log.cpp:290
const log_domain & domain_
Definition: log.hpp:244
void do_log_entry(const std::string &str) noexcept
Definition: log.cpp:524
std::string str_
Definition: log.hpp:245
scope_logger(const log_domain &domain, const char *str)
Definition: log.hpp:247
std::chrono::steady_clock::time_point start_
Definition: log.hpp:243
void do_log_exit() noexcept
Definition: log.cpp:532
scope_logger(const log_domain &domain, const std::string &str)
Definition: log.hpp:254
Definition: pump.hpp:41
bool get_log_domain_severity(const std::string &name, severity &severity)
Definition: log.cpp:371
logger & err()
Definition: log.cpp:307
severity
Definition: log.hpp:83
std::string list_log_domains(const std::string &filter)
Definition: log.cpp:380
log_domain & general()
Definition: log.cpp:333
bool broke_strict()
Definition: log.cpp:400
logger & debug()
Definition: log.cpp:325
const std::string out_log_file_suffix
Definition: log.hpp:71
void rotate_logs(const std::string &log_dir)
Check how many log files exist and delete the oldest when there's too many.
Definition: log.cpp:102
std::string unique_log_filename()
Generate a unique file name using the current timestamp and a randomly generated number.
Definition: log.cpp:133
void set_log_to_file()
Do the initial redirection to a log file if the logs directory is writable.
Definition: log.cpp:234
void set_log_sanitize(bool sanitize)
toggle log sanitization
Definition: log.cpp:428
utils::optional< bool > log_dir_writable()
Returns the result set by check_log_dir_writable().
Definition: log.cpp:280
void move_log_file()
Move the log file to another directory.
Definition: log.cpp:178
logger & warn()
Definition: log.cpp:313
void timestamps(bool t)
Definition: log.cpp:304
std::string format_timespan(const std::chrono::seconds &span)
TODO: we also have utils::format_timespan, which does something very similar...
Definition: log.cpp:404
std::string sanitize_log(const std::string &logstr)
Definition: log.cpp:432
const unsigned max_logs
Definition: log.hpp:76
void check_log_dir_writable()
Checks that a dummy file can be written to and deleted from the logs directory.
Definition: log.cpp:146
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:543
const std::string log_file_prefix
Definition: log.hpp:68
std::pair< const std::string, severity > logd
Definition: log.hpp:121
bool is_not_log_file(const std::string &fn)
Use the defined prefix and suffix to determine if a filename is a log file.
Definition: log.cpp:96
void precise_timestamps(bool pt)
Definition: log.cpp:305
logger & info()
Definition: log.cpp:319
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:347
void set_strict_severity(severity severity)
Definition: log.cpp:390
std::string get_log_file_path()
Definition: log.cpp:285
const std::string log_file_suffix
Definition: log.hpp:69
std::ostringstream & operator<<(std::ostringstream &oss, const lg::severity severity)
Definition: log.cpp:90
std::string filename
Filename.