]> git.lizzy.rs Git - minetest.git/blob - src/log.h
Add callback on_mapblocks_changed
[minetest.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                 if (&stream == &std::cout)
128                         is_tty = isatty(STDOUT_FILENO);
129                 else if (&stream == &std::cerr)
130                         is_tty = isatty(STDERR_FILENO);
131 #endif
132         }
133
134         void logRaw(LogLevel lev, const std::string &line);
135
136 private:
137         std::ostream &m_stream;
138         bool is_tty = false;
139 };
140
141 class FileLogOutput : public ICombinedLogOutput {
142 public:
143         void setFile(const std::string &filename, s64 file_size_max);
144
145         void logRaw(LogLevel lev, const std::string &line)
146         {
147                 m_stream << line << std::endl;
148         }
149
150 private:
151         std::ofstream m_stream;
152 };
153
154 class LogOutputBuffer : public ICombinedLogOutput {
155 public:
156         LogOutputBuffer(Logger &logger) :
157                 m_logger(logger)
158         {
159                 updateLogLevel();
160         };
161
162         virtual ~LogOutputBuffer()
163         {
164                 m_logger.removeOutput(this);
165         }
166
167         void updateLogLevel();
168
169         void logRaw(LogLevel lev, const std::string &line);
170
171         void clear()
172         {
173                 MutexAutoLock lock(m_buffer_mutex);
174                 m_buffer = std::queue<std::string>();
175         }
176
177         bool empty() const
178         {
179                 MutexAutoLock lock(m_buffer_mutex);
180                 return m_buffer.empty();
181         }
182
183         std::string get()
184         {
185                 MutexAutoLock lock(m_buffer_mutex);
186                 if (m_buffer.empty())
187                         return "";
188                 std::string s = std::move(m_buffer.front());
189                 m_buffer.pop();
190                 return s;
191         }
192
193 private:
194         // g_logger serializes calls to logRaw() with a mutex, but that
195         // doesn't prevent get() / clear() from being called on top of it.
196         // This mutex prevents that.
197         mutable std::mutex m_buffer_mutex;
198         std::queue<std::string> m_buffer;
199         Logger &m_logger;
200 };
201
202 #ifdef __ANDROID__
203 class AndroidLogOutput : public ICombinedLogOutput {
204 public:
205         void logRaw(LogLevel lev, const std::string &line);
206 };
207 #endif
208
209 /*
210  * LogTarget
211  *
212  * This is the interface that sits between the LogStreams and the global logger.
213  * Primarily used to route streams to log levels, but could also enable other
214  * custom behavior.
215  *
216  */
217 class LogTarget {
218 public:
219         // Must be thread-safe. These can be called from any thread.
220         virtual bool hasOutput() = 0;
221         virtual void log(const std::string &buf) = 0;
222 };
223
224
225 /*
226  * StreamProxy
227  *
228  * An ostream-like object that can proxy to a real ostream or do nothing,
229  * depending on how it is configured. See LogStream below.
230  *
231  */
232 class StreamProxy {
233 public:
234         StreamProxy(std::ostream *os) : m_os(os) { }
235
236         template<typename T>
237         StreamProxy& operator<<(T&& arg) {
238                 if (m_os) {
239                         *m_os << std::forward<T>(arg);
240                 }
241                 return *this;
242         }
243
244         StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
245                 if (m_os) {
246                         *m_os << manip;
247                 }
248                 return *this;
249         }
250
251 private:
252         std::ostream *m_os;
253 };
254
255
256 /*
257  * LogStream
258  *
259  * The public interface for log streams (infostream, verbosestream, etc).
260  *
261  * LogStream minimizes the work done when a given stream is off. (meaning
262  * it has no output targets, so it goes to /dev/null)
263  *
264  * For example, consider:
265  *
266  *     verbosestream << "hello world" << 123 << std::endl;
267  *
268  * The compiler evaluates this as:
269  *
270  *   (((verbosestream << "hello world") << 123) << std::endl)
271  *      ^                            ^
272  *
273  * If `verbosestream` is on, the innermost expression (marked by ^) will return
274  * a StreamProxy that forwards to a real ostream, that feeds into the logger.
275  * However, if `verbosestream` is off, it will return a StreamProxy that does
276  * nothing on all later operations. Specifically, CPU time won't be wasted
277  * writing "hello world" and 123 into a buffer, or formatting the log entry.
278  *
279  * It is also possible to directly check if the stream is on/off:
280  *
281  *   if (verbosestream) {
282  *       auto data = ComputeExpensiveDataForTheLog();
283  *       verbosestream << data << endl;
284  *   }
285  *
286 */
287
288 class LogStream {
289 public:
290         LogStream() = delete;
291         DISABLE_CLASS_COPY(LogStream);
292
293         LogStream(LogTarget &target) :
294                 m_target(target),
295                 m_buffer(std::bind(&LogStream::internalFlush, this, std::placeholders::_1)),
296                 m_dummy_buffer(),
297                 m_stream(&m_buffer),
298                 m_dummy_stream(&m_dummy_buffer),
299                 m_proxy(&m_stream),
300                 m_dummy_proxy(nullptr) { }
301
302         template<typename T>
303         StreamProxy& operator<<(T&& arg) {
304                 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
305                 sp << std::forward<T>(arg);
306                 return sp;
307         }
308
309         StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
310                 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
311                 sp << manip;
312                 return sp;
313         }
314
315         operator bool() {
316                 return m_target.hasOutput();
317         }
318
319         void internalFlush(const std::string &buf) {
320                 m_target.log(buf);
321         }
322
323         operator std::ostream&() {
324                 return m_target.hasOutput() ? m_stream : m_dummy_stream;
325         }
326
327 private:
328         // 10 streams per thread x (256 + overhead) ~ 3K per thread
329         static const int BUFFER_LENGTH = 256;
330         LogTarget &m_target;
331         StringStreamBuffer<BUFFER_LENGTH> m_buffer;
332         DummyStreamBuffer m_dummy_buffer;
333         std::ostream m_stream;
334         std::ostream m_dummy_stream;
335         StreamProxy m_proxy;
336         StreamProxy m_dummy_proxy;
337
338 };
339
340 #ifdef __ANDROID__
341 extern AndroidLogOutput stdout_output;
342 extern AndroidLogOutput stderr_output;
343 #else
344 extern StreamLogOutput stdout_output;
345 extern StreamLogOutput stderr_output;
346 #endif
347
348 extern Logger g_logger;
349
350 /*
351  * By making the streams thread_local, each thread has its own
352  * private buffer. Two or more threads can write to the same stream
353  * simultaneously (lock-free), and there won't be any interference.
354  *
355  * The finished lines are sent to a LogTarget which is a global (not thread-local)
356  * object, and from there relayed to g_logger. The final writes are serialized
357  * by the mutex in g_logger.
358 */
359
360 extern thread_local LogStream dstream;
361 extern thread_local LogStream rawstream;  // Writes directly to all LL_NONE log outputs with no prefix.
362 extern thread_local LogStream errorstream;
363 extern thread_local LogStream warningstream;
364 extern thread_local LogStream actionstream;
365 extern thread_local LogStream infostream;
366 extern thread_local LogStream verbosestream;
367 extern thread_local LogStream tracestream;
368 // TODO: Search/replace these with verbose/tracestream
369 extern thread_local LogStream derr_con;
370 extern thread_local LogStream dout_con;
371
372 #define TRACESTREAM(x) do {     \
373         if (tracestream) {      \
374                 tracestream x;  \
375         }                       \
376 } while (0)