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