The Battle for Wesnoth  1.17.14+dev
log.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2004 - 2022
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 <sstream> // as above. iostream (actually, iosfwd) declares stringstream as an incomplete type, but does not define it
56 #include <string>
57 #include <utility>
58 #include <ctime>
59 
60 #include "formatter.hpp"
61 
62 namespace lg {
63 
64 // Prefix and extension for log files. This is used both to generate the unique
65 // log file name during startup and to find old files to delete.
66 const std::string log_file_prefix = "wesnoth-";
67 const std::string log_file_suffix = ".log";
68 
69 // Maximum number of older log files to keep intact. Other files are deleted.
70 // Note that this count does not include the current log file!
71 const unsigned max_logs = 8;
72 
74 {
79 };
80 
81 /**
82  * Helper class to redirect the output of the logger in a certain scope.
83  *
84  * The main usage of the redirection is for the unit tests to validate the
85  * output on the logger with the expected output.
86  */
88 {
89 public:
90 
91  /**
92  * Constructor.
93  *
94  * @param stream The stream to direct the output to.
95  */
96  explicit redirect_output_setter(std::ostream& stream);
97 
99 
100 private:
101 
102  /**
103  * The previously set redirection.
104  *
105  * This value is stored here to be restored in this destructor.
106  */
107  std::ostream* old_stream_;
108 };
109 
110 class logger;
111 
112 typedef std::pair<const std::string, int> logd;
113 
114 class log_domain {
115  logd *domain_;
116 public:
117  explicit log_domain(char const *name, int severity = 1);
118  friend class logger;
119 };
120 
121 bool set_log_domain_severity(const std::string& name, int severity);
122 bool set_log_domain_severity(const std::string& name, const logger &lg);
123 bool get_log_domain_severity(const std::string& name, int &severity);
124 std::string list_logdomains(const std::string& filter);
125 
126 void set_strict_severity(int severity);
127 void set_strict_severity(const logger &lg);
128 bool broke_strict();
129 void set_log_to_file();
131 bool log_dir_writable();
132 
133 bool is_not_log_file(const std::string& filename);
134 void rotate_logs(const std::string& log_dir);
135 std::string unique_log_filename();
136 
137 // A little "magic" to surround the logging operation in a mutex.
138 // This works by capturing the output first to a stringstream formatter, then
139 // locking a mutex and dumping it to the stream all in one go.
140 // By doing this we can avoid rare deadlocks if a function whose output is streamed
141 // calls logging of its own.
142 // We overload operator| only because it has lower precedence than operator<<
143 // Any other lower-precedence operator would have worked just as well.
145  std::ostream& stream_;
146  int indent_ = 0;
147  bool timestamp_ = false;
148  std::string prefix_;
149  bool auto_newline_ = true;
150 public:
151  log_in_progress(std::ostream& stream);
152  void operator|(formatter&& message);
153  void set_indent(int level);
154  void enable_timestamp();
155  void set_prefix(const std::string& prefix);
156  void set_auto_newline(bool enabled);
157 };
158 
159 class logger {
160  char const *name_;
162 public:
163  logger(char const *name, int severity): name_(name), severity_(severity) {}
164  log_in_progress operator()(const log_domain& domain,
165  bool show_names = true, bool do_indent = false, bool show_timestamps = true, bool break_strict = true, bool auto_newline = true) const;
166 
167  bool dont_log(const log_domain& domain) const
168  {
169  return severity_ > domain.domain_->second;
170  }
171 
172  /**
173  * Returns following values depending on the logger:
174  * error: 0
175  * warn: 1
176  * info: 2
177  * debug: 3
178  * See also the lg::severity enum.
179  */
180  int get_severity() const
181  {
182  return severity_;
183  }
184 
185  std::string get_name() const
186  {
187  return name_;
188  }
189 };
190 
191 void timestamps(bool);
192 void precise_timestamps(bool);
193 std::string get_timestamp(const std::time_t& t, const std::string& format="%Y%m%d %H:%M:%S ");
194 std::string get_timespan(const std::time_t& t);
195 std::string sanitize_log(const std::string& logstr);
196 std::string& get_log_file_path();
197 void set_log_file_path(const std::string& path);
198 
199 logger &err(), &warn(), &info(), &debug();
201 
203 {
204  int64_t ticks_;
206  std::string str_;
207 public:
208  scope_logger(const log_domain& domain, const char* str)
209  : ticks_(0)
210  , domain_(domain)
211  , str_()
212  {
213  if (!debug().dont_log(domain)) do_log_entry(str);
214  }
215  scope_logger(const log_domain& domain, const std::string& str)
216  : ticks_(0)
217  , domain_(domain)
218  , str_()
219  {
220  if (!debug().dont_log(domain)) do_log_entry(str);
221  }
223  {
224  if (!str_.empty()) do_log_exit();
225  }
226 private:
227  void do_log_entry(const std::string& str) noexcept;
228  void do_log_exit() noexcept;
229 };
230 
231 /**
232  * Use this to show WML errors in the ingame chat.
233  * After every WML event the errors are shown to the user so they can inform the campaign maintainer.
234  */
235 std::stringstream& log_to_chat();
236 
237 } // namespace lg
238 
239 #define log_scope(description) lg::scope_logger scope_logging_object__(lg::general(), description);
240 #define log_scope2(domain,description) lg::scope_logger scope_logging_object__(domain, description);
241 
242 #define LOG_STREAM(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain) | formatter()
243 
244 // Don't prefix the logdomain to messages on this stream
245 #define LOG_STREAM_NAMELESS(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, false) | formatter()
246 
247 // Like LOG_STREAM_NAMELESS except doesn't add newlines automatically
248 #define LOG_STREAM_NAMELESS_STREAMING(level, domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, false, false, true, true, false) | formatter()
249 
250 // When using log_scope/log_scope2 it is nice to have all output indented.
251 #define LOG_STREAM_INDENT(level,domain) if (lg::level().dont_log(domain)) ; else lg::level()(domain, true, true) | formatter()
252 
253 // If you have an explicit logger object and want to ignore the logging level, use this.
254 // Meant for cases where you explicitly call dont_log to avoid an expensive operation if the logging is disabled.
255 #define FORCE_LOG_TO(logger, domain) logger(domain) | formatter()
256 
257 // always log (since it's at the error level) to the general log stream
258 // outputting the log domain and timestamp is disabled
259 // meant as a replacement to using cerr/cout, but that goes through the same logging infrastructure as everything else
260 #define PLAIN_LOG lg::err()(lg::general(), false, false, false, false, true) | formatter()
261 #define STREAMING_LOG lg::err()(lg::general(), false, false, false, false, false) | formatter()
std::string get_timestamp(const std::time_t &t, const std::string &format)
Definition: log.cpp:313
char const * name_
Definition: log.hpp:160
int64_t ticks_
Definition: log.hpp:204
std::string get_timespan(const std::time_t &t)
Definition: log.cpp:320
logger(char const *name, int severity)
Definition: log.hpp:163
logger & info()
Definition: log.cpp:228
void timestamps(bool t)
Definition: log.cpp:213
std::string str_
Definition: log.hpp:206
redirect_output_setter(std::ostream &stream)
Constructor.
Definition: log.cpp:199
Helper class to redirect the output of the logger in a certain scope.
Definition: log.hpp:87
const std::string log_file_suffix
Definition: log.hpp:67
std::pair< const std::string, int > logd
Definition: log.hpp:110
bool is_not_log_file(const std::string &fn)
Helper function for rotate_logs.
Definition: log.cpp:84
bool broke_strict()
Definition: log.cpp:309
scope_logger(const log_domain &domain, const char *str)
Definition: log.hpp:208
std::string & get_log_file_path()
Definition: log.cpp:190
bool get_log_domain_severity(const std::string &name, int &severity)
Definition: log.cpp:280
std::ostringstream wrapper.
Definition: formatter.hpp:39
std::string sanitize_log(const std::string &logstr)
Definition: log.cpp:349
bool log_dir_writable()
Definition: log.cpp:185
void rotate_logs(const std::string &log_dir)
Deletes old log files from the log directory.
Definition: log.cpp:93
std::string path
Definition: game_config.cpp:39
int severity_
Definition: log.hpp:161
logger & debug()
Definition: log.cpp:234
bool dont_log(const log_domain &domain) const
Definition: log.hpp:167
severity
Definition: log.hpp:73
log_domain & general()
Definition: log.cpp:242
std::string unique_log_filename()
Generates a unique log file name.
Definition: log.cpp:127
void check_log_dir_writable()
Definition: log.cpp:141
Definition: pump.hpp:40
const log_domain & domain_
Definition: log.hpp:205
std::ostream * old_stream_
The previously set redirection.
Definition: log.hpp:107
logger & err()
Definition: log.cpp:216
void set_strict_severity(int severity)
Definition: log.cpp:299
terrain_code operator|(const terrain_code &a, const terrain_code &b)
Definition: translation.hpp:72
std::string get_name() const
Definition: log.hpp:185
const unsigned max_logs
Definition: log.hpp:71
bool set_log_domain_severity(const std::string &name, int severity)
Definition: log.cpp:256
logger & warn()
Definition: log.cpp:222
double t
Definition: astarsearch.cpp:65
std::string list_logdomains(const std::string &filter)
Definition: log.cpp:289
std::string prefix_
Definition: log.hpp:148
void set_log_to_file()
Definition: log.cpp:173
void set_log_file_path(const std::string &path)
Definition: log.cpp:194
int get_severity() const
Returns following values depending on the logger: error: 0 warn: 1 info: 2 debug: 3 See also the lg::...
Definition: log.hpp:180
std::ostream & stream_
Definition: log.hpp:145
scope_logger(const log_domain &domain, const std::string &str)
Definition: log.hpp:215
const std::string log_file_prefix
Definition: log.hpp:66
void precise_timestamps(bool pt)
Definition: log.cpp:214
logd * domain_
Definition: log.hpp:115
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:458