]> git.lizzy.rs Git - dragonfireclient.git/blob - src/log.h
Force-update shadows when the world is changed (#12364)
[dragonfireclient.git] / src / log.h
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #pragma once
21
22 #include <atomic>
23 #include <map>
24 #include <queue>
25 #include <string>
26 #include <fstream>
27 #include <thread>
28 #include <mutex>
29 #if !defined(_WIN32)  // POSIX
30         #include <unistd.h>
31 #endif
32 #include "threading/mutex_auto_lock.h"
33 #include "util/basic_macros.h"
34 #include "util/stream.h"
35 #include "irrlichttypes.h"
36
37 class ILogOutput;
38
39 enum LogLevel {
40         LL_NONE, // Special level that is always printed
41         LL_ERROR,
42         LL_WARNING,
43         LL_ACTION,  // In-game actions
44         LL_INFO,
45         LL_VERBOSE,
46         LL_TRACE,
47         LL_MAX,
48 };
49
50 enum LogColor {
51         LOG_COLOR_NEVER,
52         LOG_COLOR_ALWAYS,
53         LOG_COLOR_AUTO,
54 };
55
56 typedef u8 LogLevelMask;
57 #define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
58
59 class Logger {
60 public:
61         void addOutput(ILogOutput *out);
62         void addOutput(ILogOutput *out, LogLevel lev);
63         void addOutputMasked(ILogOutput *out, LogLevelMask mask);
64         void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
65         LogLevelMask removeOutput(ILogOutput *out);
66         void setLevelSilenced(LogLevel lev, bool silenced);
67
68         void registerThread(const std::string &name);
69         void deregisterThread();
70
71         void log(LogLevel lev, const std::string &text);
72         // Logs without a prefix
73         void logRaw(LogLevel lev, const std::string &text);
74
75         static LogLevel stringToLevel(const std::string &name);
76         static const std::string getLevelLabel(LogLevel lev);
77
78         bool hasOutput(LogLevel level) {
79                 return m_has_outputs[level].load(std::memory_order_relaxed);
80         }
81
82         static LogColor color_mode;
83
84 private:
85         void logToOutputsRaw(LogLevel, const std::string &line);
86         void logToOutputs(LogLevel, const std::string &combined,
87                 const std::string &time, const std::string &thread_name,
88                 const std::string &payload_text);
89
90         const std::string getThreadName();
91
92         std::vector<ILogOutput *> m_outputs[LL_MAX];
93         std::atomic<bool> m_has_outputs[LL_MAX];
94
95         // Should implement atomic loads and stores (even though it's only
96         // written to when one thread has access currently).
97         // Works on all known architectures (x86, ARM, MIPS).
98         volatile bool m_silenced_levels[LL_MAX];
99         std::map<std::thread::id, std::string> m_thread_names;
100         mutable std::mutex m_mutex;
101 };
102
103 class ILogOutput {
104 public:
105         virtual void logRaw(LogLevel, const std::string &line) = 0;
106         virtual void log(LogLevel, const std::string &combined,
107                 const std::string &time, const std::string &thread_name,
108                 const std::string &payload_text) = 0;
109 };
110
111 class ICombinedLogOutput : public ILogOutput {
112 public:
113         void log(LogLevel lev, const std::string &combined,
114                 const std::string &time, const std::string &thread_name,
115                 const std::string &payload_text)
116         {
117                 logRaw(lev, combined);
118         }
119 };
120
121 class StreamLogOutput : public ICombinedLogOutput {
122 public:
123         StreamLogOutput(std::ostream &stream) :
124                 m_stream(stream)
125         {
126 #if !defined(_WIN32)
127                 is_tty = isatty(fileno(stdout));
128 #else
129                 is_tty = false;
130 #endif
131         }
132
133         void logRaw(LogLevel lev, const std::string &line);
134
135 private:
136         std::ostream &m_stream;
137         bool is_tty;
138 };
139
140 class FileLogOutput : public ICombinedLogOutput {
141 public:
142         void setFile(const std::string &filename, s64 file_size_max);
143
144         void logRaw(LogLevel lev, const std::string &line)
145         {
146                 m_stream << line << std::endl;
147         }
148
149 private:
150         std::ofstream m_stream;
151 };
152
153 class LogOutputBuffer : public ICombinedLogOutput {
154 public:
155         LogOutputBuffer(Logger &logger) :
156                 m_logger(logger)
157         {
158                 updateLogLevel();
159         };
160
161         virtual ~LogOutputBuffer()
162         {
163                 m_logger.removeOutput(this);
164         }
165
166         void updateLogLevel();
167
168         void logRaw(LogLevel lev, const std::string &line);
169
170         void clear()
171         {
172                 MutexAutoLock lock(m_buffer_mutex);
173                 m_buffer = std::queue<std::string>();
174         }
175
176         bool empty() const
177         {
178                 MutexAutoLock lock(m_buffer_mutex);
179                 return m_buffer.empty();
180         }
181
182         std::string get()
183         {
184                 MutexAutoLock lock(m_buffer_mutex);
185                 if (m_buffer.empty())
186                         return "";
187                 std::string s = std::move(m_buffer.front());
188                 m_buffer.pop();
189                 return s;
190         }
191
192 private:
193         // g_logger serializes calls to logRaw() with a mutex, but that
194         // doesn't prevent get() / clear() from being called on top of it.
195         // This mutex prevents that.
196         mutable std::mutex m_buffer_mutex;
197         std::queue<std::string> m_buffer;
198         Logger &m_logger;
199 };
200
201 #ifdef __ANDROID__
202 class AndroidLogOutput : public ICombinedLogOutput {
203 public:
204         void logRaw(LogLevel lev, const std::string &line);
205 };
206 #endif
207
208 /*
209  * LogTarget
210  *
211  * This is the interface that sits between the LogStreams and the global logger.
212  * Primarily used to route streams to log levels, but could also enable other
213  * custom behavior.
214  *
215  */
216 class LogTarget {
217 public:
218         // Must be thread-safe. These can be called from any thread.
219         virtual bool hasOutput() = 0;
220         virtual void log(const std::string &buf) = 0;
221 };
222
223
224 /*
225  * StreamProxy
226  *
227  * An ostream-like object that can proxy to a real ostream or do nothing,
228  * depending on how it is configured. See LogStream below.
229  *
230  */
231 class StreamProxy {
232 public:
233         StreamProxy(std::ostream *os) : m_os(os) { }
234
235         template<typename T>
236         StreamProxy& operator<<(T&& arg) {
237                 if (m_os) {
238                         *m_os << std::forward<T>(arg);
239                 }
240                 return *this;
241         }
242
243         StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
244                 if (m_os) {
245                         *m_os << manip;
246                 }
247                 return *this;
248         }
249
250 private:
251         std::ostream *m_os;
252 };
253
254
255 /*
256  * LogStream
257  *
258  * The public interface for log streams (infostream, verbosestream, etc).
259  *
260  * LogStream minimizes the work done when a given stream is off. (meaning
261  * it has no output targets, so it goes to /dev/null)
262  *
263  * For example, consider:
264  *
265  *     verbosestream << "hello world" << 123 << std::endl;
266  *
267  * The compiler evaluates this as:
268  *
269  *   (((verbosestream << "hello world") << 123) << std::endl)
270  *      ^                            ^
271  *
272  * If `verbosestream` is on, the innermost expression (marked by ^) will return
273  * a StreamProxy that forwards to a real ostream, that feeds into the logger.
274  * However, if `verbosestream` is off, it will return a StreamProxy that does
275  * nothing on all later operations. Specifically, CPU time won't be wasted
276  * writing "hello world" and 123 into a buffer, or formatting the log entry.
277  *
278  * It is also possible to directly check if the stream is on/off:
279  *
280  *   if (verbosestream) {
281  *       auto data = ComputeExpensiveDataForTheLog();
282  *       verbosestream << data << endl;
283  *   }
284  *
285 */
286
287 class LogStream {
288 public:
289         LogStream() = delete;
290         DISABLE_CLASS_COPY(LogStream);
291
292         LogStream(LogTarget &target) :
293                 m_target(target),
294                 m_buffer(std::bind(&LogStream::internalFlush, this, std::placeholders::_1)),
295                 m_dummy_buffer(),
296                 m_stream(&m_buffer),
297                 m_dummy_stream(&m_dummy_buffer),
298                 m_proxy(&m_stream),
299                 m_dummy_proxy(nullptr) { }
300
301         template<typename T>
302         StreamProxy& operator<<(T&& arg) {
303                 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
304                 sp << std::forward<T>(arg);
305                 return sp;
306         }
307
308         StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
309                 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
310                 sp << manip;
311                 return sp;
312         }
313
314         operator bool() {
315                 return m_target.hasOutput();
316         }
317
318         void internalFlush(const std::string &buf) {
319                 m_target.log(buf);
320         }
321
322         operator std::ostream&() {
323                 return m_target.hasOutput() ? m_stream : m_dummy_stream;
324         }
325
326 private:
327         // 10 streams per thread x (256 + overhead) ~ 3K per thread
328         static const int BUFFER_LENGTH = 256;
329         LogTarget &m_target;
330         StringStreamBuffer<BUFFER_LENGTH> m_buffer;
331         DummyStreamBuffer m_dummy_buffer;
332         std::ostream m_stream;
333         std::ostream m_dummy_stream;
334         StreamProxy m_proxy;
335         StreamProxy m_dummy_proxy;
336
337 };
338
339 #ifdef __ANDROID__
340 extern AndroidLogOutput stdout_output;
341 extern AndroidLogOutput stderr_output;
342 #else
343 extern StreamLogOutput stdout_output;
344 extern StreamLogOutput stderr_output;
345 #endif
346
347 extern Logger g_logger;
348
349 /*
350  * By making the streams thread_local, each thread has its own
351  * private buffer. Two or more threads can write to the same stream
352  * simultaneously (lock-free), and there won't be any interference.
353  *
354  * The finished lines are sent to a LogTarget which is a global (not thread-local)
355  * object, and from there relayed to g_logger. The final writes are serialized
356  * by the mutex in g_logger.
357 */
358
359 extern thread_local LogStream dstream;
360 extern thread_local LogStream rawstream;  // Writes directly to all LL_NONE log outputs with no prefix.
361 extern thread_local LogStream errorstream;
362 extern thread_local LogStream warningstream;
363 extern thread_local LogStream actionstream;
364 extern thread_local LogStream infostream;
365 extern thread_local LogStream verbosestream;
366 extern thread_local LogStream tracestream;
367 // TODO: Search/replace these with verbose/tracestream
368 extern thread_local LogStream derr_con;
369 extern thread_local LogStream dout_con;
370
371 #define TRACESTREAM(x) do {     \
372         if (tracestream) {      \
373                 tracestream x;  \
374         }                       \
375 } while (0)