Translated using Weblate (Chinese (Simplified))
[oweals/minetest.git] / src / log.cpp
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 #include "log.h"
21
22 #include "threading/mutex_auto_lock.h"
23 #include "debug.h"
24 #include "gettime.h"
25 #include "porting.h"
26 #include "settings.h"
27 #include "config.h"
28 #include "exceptions.h"
29 #include "util/numeric.h"
30 #include "log.h"
31
32 #include <sstream>
33 #include <iostream>
34 #include <algorithm>
35 #include <cerrno>
36 #include <cstring>
37
38 const int BUFFER_LENGTH = 256;
39
40 class StringBuffer : public std::streambuf {
41 public:
42         StringBuffer() {
43                 buffer_index = 0;
44         }
45
46         int overflow(int c);
47         virtual void flush(const std::string &buf) = 0;
48         std::streamsize xsputn(const char *s, std::streamsize n);
49         void push_back(char c);
50
51 private:
52         char buffer[BUFFER_LENGTH];
53         int buffer_index;
54 };
55
56
57 class LogBuffer : public StringBuffer {
58 public:
59         LogBuffer(Logger &logger, LogLevel lev) :
60                 logger(logger),
61                 level(lev)
62         {}
63
64         void flush(const std::string &buffer);
65
66 private:
67         Logger &logger;
68         LogLevel level;
69 };
70
71
72 class RawLogBuffer : public StringBuffer {
73 public:
74         void flush(const std::string &buffer);
75 };
76
77 ////
78 //// Globals
79 ////
80
81 Logger g_logger;
82
83 StreamLogOutput stdout_output(std::cout);
84 StreamLogOutput stderr_output(std::cerr);
85 std::ostream null_stream(NULL);
86
87 RawLogBuffer raw_buf;
88
89 LogBuffer none_buf(g_logger, LL_NONE);
90 LogBuffer error_buf(g_logger, LL_ERROR);
91 LogBuffer warning_buf(g_logger, LL_WARNING);
92 LogBuffer action_buf(g_logger, LL_ACTION);
93 LogBuffer info_buf(g_logger, LL_INFO);
94 LogBuffer verbose_buf(g_logger, LL_VERBOSE);
95
96 // Connection
97 std::ostream *dout_con_ptr = &null_stream;
98 std::ostream *derr_con_ptr = &verbosestream;
99
100 // Server
101 std::ostream *dout_server_ptr = &infostream;
102 std::ostream *derr_server_ptr = &errorstream;
103
104 #ifndef SERVER
105 // Client
106 std::ostream *dout_client_ptr = &infostream;
107 std::ostream *derr_client_ptr = &errorstream;
108 #endif
109
110 std::ostream rawstream(&raw_buf);
111 std::ostream dstream(&none_buf);
112 std::ostream errorstream(&error_buf);
113 std::ostream warningstream(&warning_buf);
114 std::ostream actionstream(&action_buf);
115 std::ostream infostream(&info_buf);
116 std::ostream verbosestream(&verbose_buf);
117
118 // Android
119 #ifdef __ANDROID__
120
121 static unsigned int g_level_to_android[] = {
122         ANDROID_LOG_INFO,     // LL_NONE
123         //ANDROID_LOG_FATAL,
124         ANDROID_LOG_ERROR,    // LL_ERROR
125         ANDROID_LOG_WARN,     // LL_WARNING
126         ANDROID_LOG_WARN,     // LL_ACTION
127         //ANDROID_LOG_INFO,
128         ANDROID_LOG_DEBUG,    // LL_INFO
129         ANDROID_LOG_VERBOSE,  // LL_VERBOSE
130 };
131
132 class AndroidSystemLogOutput : public ICombinedLogOutput {
133         public:
134                 AndroidSystemLogOutput()
135                 {
136                         g_logger.addOutput(this);
137                 }
138                 ~AndroidSystemLogOutput()
139                 {
140                         g_logger.removeOutput(this);
141                 }
142                 void logRaw(LogLevel lev, const std::string &line)
143                 {
144                         STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX,
145                                 mismatch_between_android_and_internal_loglevels);
146                         __android_log_print(g_level_to_android[lev],
147                                 PROJECT_NAME_C, "%s", line.c_str());
148                 }
149 };
150
151 AndroidSystemLogOutput g_android_log_output;
152
153 #endif
154
155 ///////////////////////////////////////////////////////////////////////////////
156
157
158 ////
159 //// Logger
160 ////
161
162 LogLevel Logger::stringToLevel(const std::string &name)
163 {
164         if (name == "none")
165                 return LL_NONE;
166         else if (name == "error")
167                 return LL_ERROR;
168         else if (name == "warning")
169                 return LL_WARNING;
170         else if (name == "action")
171                 return LL_ACTION;
172         else if (name == "info")
173                 return LL_INFO;
174         else if (name == "verbose")
175                 return LL_VERBOSE;
176         else
177                 return LL_MAX;
178 }
179
180 void Logger::addOutput(ILogOutput *out)
181 {
182         addOutputMaxLevel(out, (LogLevel)(LL_MAX - 1));
183 }
184
185 void Logger::addOutput(ILogOutput *out, LogLevel lev)
186 {
187         m_outputs[lev].push_back(out);
188 }
189
190 void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask)
191 {
192         for (size_t i = 0; i < LL_MAX; i++) {
193                 if (mask & LOGLEVEL_TO_MASKLEVEL(i))
194                         m_outputs[i].push_back(out);
195         }
196 }
197
198 void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
199 {
200         assert(lev < LL_MAX);
201         for (size_t i = 0; i <= lev; i++)
202                 m_outputs[i].push_back(out);
203 }
204
205 LogLevelMask Logger::removeOutput(ILogOutput *out)
206 {
207         LogLevelMask ret_mask = 0;
208         for (size_t i = 0; i < LL_MAX; i++) {
209                 std::vector<ILogOutput *>::iterator it;
210
211                 it = std::find(m_outputs[i].begin(), m_outputs[i].end(), out);
212                 if (it != m_outputs[i].end()) {
213                         ret_mask |= LOGLEVEL_TO_MASKLEVEL(i);
214                         m_outputs[i].erase(it);
215                 }
216         }
217         return ret_mask;
218 }
219
220 void Logger::setLevelSilenced(LogLevel lev, bool silenced)
221 {
222         m_silenced_levels[lev] = silenced;
223 }
224
225 void Logger::registerThread(const std::string &name)
226 {
227         std::thread::id id = std::this_thread::get_id();
228         MutexAutoLock lock(m_mutex);
229         m_thread_names[id] = name;
230 }
231
232 void Logger::deregisterThread()
233 {
234         std::thread::id id = std::this_thread::get_id();
235         MutexAutoLock lock(m_mutex);
236         m_thread_names.erase(id);
237 }
238
239 const std::string Logger::getLevelLabel(LogLevel lev)
240 {
241         static const std::string names[] = {
242                 "",
243                 "ERROR",
244                 "WARNING",
245                 "ACTION",
246                 "INFO",
247                 "VERBOSE",
248         };
249         assert(lev < LL_MAX && lev >= 0);
250         STATIC_ASSERT(ARRLEN(names) == LL_MAX,
251                 mismatch_between_loglevel_names_and_enum);
252         return names[lev];
253 }
254
255 LogColor Logger::color_mode = LOG_COLOR_AUTO;
256
257 const std::string Logger::getThreadName()
258 {
259         std::map<std::thread::id, std::string>::const_iterator it;
260
261         std::thread::id id = std::this_thread::get_id();
262         it = m_thread_names.find(id);
263         if (it != m_thread_names.end())
264                 return it->second;
265
266         std::ostringstream os;
267         os << "#0x" << std::hex << id;
268         return os.str();
269 }
270
271 void Logger::log(LogLevel lev, const std::string &text)
272 {
273         if (m_silenced_levels[lev])
274                 return;
275
276         const std::string thread_name = getThreadName();
277         const std::string label = getLevelLabel(lev);
278         const std::string timestamp = getTimestamp();
279         std::ostringstream os(std::ios_base::binary);
280         os << timestamp << ": " << label << "[" << thread_name << "]: " << text;
281
282         logToOutputs(lev, os.str(), timestamp, thread_name, text);
283 }
284
285 void Logger::logRaw(LogLevel lev, const std::string &text)
286 {
287         if (m_silenced_levels[lev])
288                 return;
289
290         logToOutputsRaw(lev, text);
291 }
292
293 void Logger::logToOutputsRaw(LogLevel lev, const std::string &line)
294 {
295         MutexAutoLock lock(m_mutex);
296         for (size_t i = 0; i != m_outputs[lev].size(); i++)
297                 m_outputs[lev][i]->logRaw(lev, line);
298 }
299
300 void Logger::logToOutputs(LogLevel lev, const std::string &combined,
301         const std::string &time, const std::string &thread_name,
302         const std::string &payload_text)
303 {
304         MutexAutoLock lock(m_mutex);
305         for (size_t i = 0; i != m_outputs[lev].size(); i++)
306                 m_outputs[lev][i]->log(lev, combined, time, thread_name, payload_text);
307 }
308
309
310 ////
311 //// *LogOutput methods
312 ////
313
314 void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
315 {
316         // Only move debug.txt if there is a valid maximum file size
317         bool is_too_large = false;
318         if (file_size_max > 0) {
319                 std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
320                 is_too_large = ifile.tellg() > file_size_max;
321                 ifile.close();
322         }
323
324         if (is_too_large) {
325                 std::string filename_secondary = filename + ".1";
326                 actionstream << "The log file grew too big; it is moved to " <<
327                         filename_secondary << std::endl;
328                 remove(filename_secondary.c_str());
329                 rename(filename.c_str(), filename_secondary.c_str());
330         }
331         m_stream.open(filename, std::ios::app | std::ios::ate);
332
333         if (!m_stream.good())
334                 throw FileNotGoodException("Failed to open log file " +
335                         filename + ": " + strerror(errno));
336         m_stream << "\n\n"
337                 "-------------" << std::endl <<
338                 "  Separator" << std::endl <<
339                 "-------------\n" << std::endl;
340 }
341
342 void StreamLogOutput::logRaw(LogLevel lev, const std::string &line)
343 {
344         bool colored_message = (Logger::color_mode == LOG_COLOR_ALWAYS) ||
345                 (Logger::color_mode == LOG_COLOR_AUTO && is_tty);
346         if (colored_message) {
347                 switch (lev) {
348                 case LL_ERROR:
349                         // error is red
350                         m_stream << "\033[91m";
351                         break;
352                 case LL_WARNING:
353                         // warning is yellow
354                         m_stream << "\033[93m";
355                         break;
356                 case LL_INFO:
357                         // info is a bit dark
358                         m_stream << "\033[37m";
359                         break;
360                 case LL_VERBOSE:
361                         // verbose is darker than info
362                         m_stream << "\033[2m";
363                         break;
364                 default:
365                         // action is white
366                         colored_message = false;
367                 }
368         }
369
370         m_stream << line << std::endl;
371
372         if (colored_message) {
373                 // reset to white color
374                 m_stream << "\033[0m";
375         }
376 }
377
378 void LogOutputBuffer::updateLogLevel()
379 {
380         const std::string &conf_loglev = g_settings->get("chat_log_level");
381         LogLevel log_level = Logger::stringToLevel(conf_loglev);
382         if (log_level == LL_MAX) {
383                 warningstream << "Supplied unrecognized chat_log_level; "
384                         "showing none." << std::endl;
385                 log_level = LL_NONE;
386         }
387
388         m_logger.removeOutput(this);
389         m_logger.addOutputMaxLevel(this, log_level);
390 }
391
392 void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line)
393 {
394         std::string color;
395
396         if (!g_settings->getBool("disable_escape_sequences")) {
397                 switch (lev) {
398                 case LL_ERROR: // red
399                         color = "\x1b(c@#F00)";
400                         break;
401                 case LL_WARNING: // yellow
402                         color = "\x1b(c@#EE0)";
403                         break;
404                 case LL_INFO: // grey
405                         color = "\x1b(c@#BBB)";
406                         break;
407                 case LL_VERBOSE: // dark grey
408                         color = "\x1b(c@#888)";
409                         break;
410                 default: break;
411                 }
412         }
413
414         m_buffer.push(color.append(line));
415 }
416
417 ////
418 //// *Buffer methods
419 ////
420
421 int StringBuffer::overflow(int c)
422 {
423         push_back(c);
424         return c;
425 }
426
427
428 std::streamsize StringBuffer::xsputn(const char *s, std::streamsize n)
429 {
430         for (int i = 0; i < n; ++i)
431                 push_back(s[i]);
432         return n;
433 }
434
435 void StringBuffer::push_back(char c)
436 {
437         if (c == '\n' || c == '\r') {
438                 if (buffer_index)
439                         flush(std::string(buffer, buffer_index));
440                 buffer_index = 0;
441         } else {
442                 buffer[buffer_index++] = c;
443                 if (buffer_index >= BUFFER_LENGTH) {
444                         flush(std::string(buffer, buffer_index));
445                         buffer_index = 0;
446                 }
447         }
448 }
449
450
451 void LogBuffer::flush(const std::string &buffer)
452 {
453         logger.log(level, buffer);
454 }
455
456 void RawLogBuffer::flush(const std::string &buffer)
457 {
458         g_logger.logRaw(LL_NONE, buffer);
459 }