3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
29 #if !defined(_WIN32) // POSIX
32 #include "threading/mutex_auto_lock.h"
33 #include "util/basic_macros.h"
34 #include "util/stream.h"
35 #include "irrlichttypes.h"
40 LL_NONE, // Special level that is always printed
43 LL_ACTION, // In-game actions
56 typedef u8 LogLevelMask;
57 #define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
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);
68 void registerThread(const std::string &name);
69 void deregisterThread();
71 void log(LogLevel lev, const std::string &text);
72 // Logs without a prefix
73 void logRaw(LogLevel lev, const std::string &text);
75 static LogLevel stringToLevel(const std::string &name);
76 static const std::string getLevelLabel(LogLevel lev);
78 bool hasOutput(LogLevel level) {
79 return m_has_outputs[level].load(std::memory_order_relaxed);
82 static LogColor color_mode;
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);
90 const std::string getThreadName();
92 std::vector<ILogOutput *> m_outputs[LL_MAX];
93 std::atomic<bool> m_has_outputs[LL_MAX];
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;
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;
111 class ICombinedLogOutput : public ILogOutput {
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)
117 logRaw(lev, combined);
121 class StreamLogOutput : public ICombinedLogOutput {
123 StreamLogOutput(std::ostream &stream) :
127 if (&stream == &std::cout)
128 is_tty = isatty(STDOUT_FILENO);
129 else if (&stream == &std::cerr)
130 is_tty = isatty(STDERR_FILENO);
134 void logRaw(LogLevel lev, const std::string &line);
137 std::ostream &m_stream;
141 class FileLogOutput : public ICombinedLogOutput {
143 void setFile(const std::string &filename, s64 file_size_max);
145 void logRaw(LogLevel lev, const std::string &line)
147 m_stream << line << std::endl;
151 std::ofstream m_stream;
154 class LogOutputBuffer : public ICombinedLogOutput {
156 LogOutputBuffer(Logger &logger) :
162 virtual ~LogOutputBuffer()
164 m_logger.removeOutput(this);
167 void updateLogLevel();
169 void logRaw(LogLevel lev, const std::string &line);
173 MutexAutoLock lock(m_buffer_mutex);
174 m_buffer = std::queue<std::string>();
179 MutexAutoLock lock(m_buffer_mutex);
180 return m_buffer.empty();
185 MutexAutoLock lock(m_buffer_mutex);
186 if (m_buffer.empty())
188 std::string s = std::move(m_buffer.front());
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;
203 class AndroidLogOutput : public ICombinedLogOutput {
205 void logRaw(LogLevel lev, const std::string &line);
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
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;
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.
234 StreamProxy(std::ostream *os) : m_os(os) { }
237 StreamProxy& operator<<(T&& arg) {
239 *m_os << std::forward<T>(arg);
244 StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
259 * The public interface for log streams (infostream, verbosestream, etc).
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)
264 * For example, consider:
266 * verbosestream << "hello world" << 123 << std::endl;
268 * The compiler evaluates this as:
270 * (((verbosestream << "hello world") << 123) << std::endl)
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.
279 * It is also possible to directly check if the stream is on/off:
281 * if (verbosestream) {
282 * auto data = ComputeExpensiveDataForTheLog();
283 * verbosestream << data << endl;
290 LogStream() = delete;
291 DISABLE_CLASS_COPY(LogStream);
293 LogStream(LogTarget &target) :
295 m_buffer(std::bind(&LogStream::internalFlush, this, std::placeholders::_1)),
298 m_dummy_stream(&m_dummy_buffer),
300 m_dummy_proxy(nullptr) { }
303 StreamProxy& operator<<(T&& arg) {
304 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
305 sp << std::forward<T>(arg);
309 StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) {
310 StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy;
316 return m_target.hasOutput();
319 void internalFlush(const std::string &buf) {
323 operator std::ostream&() {
324 return m_target.hasOutput() ? m_stream : m_dummy_stream;
328 // 10 streams per thread x (256 + overhead) ~ 3K per thread
329 static const int BUFFER_LENGTH = 256;
331 StringStreamBuffer<BUFFER_LENGTH> m_buffer;
332 DummyStreamBuffer m_dummy_buffer;
333 std::ostream m_stream;
334 std::ostream m_dummy_stream;
336 StreamProxy m_dummy_proxy;
341 extern AndroidLogOutput stdout_output;
342 extern AndroidLogOutput stderr_output;
344 extern StreamLogOutput stdout_output;
345 extern StreamLogOutput stderr_output;
348 extern Logger g_logger;
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.
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.
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;
372 #define TRACESTREAM(x) do { \