library/base/log.cpp (251 lines of code) (raw):

/* * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <string> #include <stdio.h> #include <stdarg.h> #include <time.h> #include <string.h> #include <vector> #include <glib/gstdio.h> #include "base/c++helpers.h" #include "base/log.h" #include "base/wb_memory.h" #include "base/file_utilities.h" #include "base/file_functions.h" // TODO: these two file libs should really be only one. #include "base/string_utilities.h" using namespace base; static const char* LevelText[] = {"", "ERR", "WRN", "INF", "DB1", "DB2", "DB3"}; /*static*/ const std::string Logger::_logLevelNames[] = {"none", "error", "warning", "info", "debug1", "debug2", "debug3"}; /*static*/ bool Logger::_logLevelSpecifiedByUser = false; //-------------------------------------------------------------------------------------------------- struct Logger::LoggerImpl { LoggerImpl() { // Default values for all available log levels. _levels[enumIndex(Logger::LogLevel::Disabled)] = false; // Disable None level. _levels[enumIndex(Logger::LogLevel::Error)] = true; _levels[enumIndex(Logger::LogLevel::Warning)] = true; _levels[enumIndex(Logger::LogLevel::Info)] = true; // Includes all g_message calls. #if !defined(DEBUG) && !defined(_DEBUG) _levels[enumIndex(Logger::LogLevel::Debug)] = false; // General debug messages. _levels[enumIndex(Logger::LogLevel::Debug2)] = false; // Verbose debug messages. #else _levels[enumIndex(Logger::LogLevel::Debug)] = true; _levels[enumIndex(Logger::LogLevel::Debug2)] = true; #endif _levels[enumIndex(Logger::LogLevel::Debug3)] = false; // Really chatty, should be switched on only on demand. } bool level_is_enabled(const Logger::LogLevel level) const { return _levels[enumIndex(level)]; } std::string _dir; std::string _filename; bool _levels[Logger::logLevelCount]; bool _new_line_pending; // Set to true when the last logged entry ended with a new line. bool _std_err_log; }; Logger::LoggerImpl* Logger::_impl = nullptr; //-------------------------------------------------------------------------------------------------- std::string Logger::log_filename() { return _impl ? _impl->_filename : ""; } //-------------------------------------------------------------------------------------------------- std::string Logger::log_dir() { return _impl ? _impl->_dir : ""; } //-------------------------------------------------------------------------------------------------- Logger::Logger(const bool stderr_log, const std::string& target_file) { if (!_impl) _impl = new Logger::LoggerImpl(); _impl->_std_err_log = stderr_log; if (!target_file.empty()) { _impl->_filename = target_file; FILE_scope_ptr fp = base_fopen(_impl->_filename.c_str(), "w"); } } //-------------------------------------------------------------------------------------------------- Logger::Logger(const std::string& dir, const bool stderr_log, const std::string& file_name, int limit) { std::vector<std::string> filenames; // Creates the file names array filenames.push_back(strfmt("%s.log", file_name.data())); for (int index = 1; index < limit; index++) filenames.push_back(strfmt("%s.%d.log", file_name.data(), index)); if (!_impl) _impl = new Logger::LoggerImpl(); _impl->_std_err_log = stderr_log; _impl->_new_line_pending = true; if (!dir.empty() && !file_name.empty()) { _impl->_dir = base::joinPath(dir.c_str(), "log", ""); _impl->_filename = base::joinPath(_impl->_dir.c_str(), filenames[0].c_str(), ""); try { create_directory(_impl->_dir, 0700, true); } catch (const file_error& e) { // We need to catch the exception here, otherwise WB will be aborted fprintf(stderr, "Exception in logger: %s\n", e.what()); } // Rotate log files: wb.log -> wb.1.log, wb.1.log -> wb.2.log, ... for (int i = limit - 1; i > 0; --i) { try { std::string filename = base::joinPath(_impl->_dir.c_str(), filenames[i].c_str(), ""); if (file_exists(filename)) remove(filename); std::string filename2 = base::joinPath(_impl->_dir.c_str(), filenames[i - 1].c_str(), ""); if (file_exists(filename2)) rename(filename2, filename); } catch (...) { // we do not care for rename exceptions here! } } // truncate log file we do not need gigabytes of logs FILE_scope_ptr fp = base_fopen(_impl->_filename.c_str(), "w"); } } //-------------------------------------------------------------------------------------------------- void Logger::enable_level(const LogLevel level) { if (enumIndex(level) < logLevelCount) _impl->_levels[enumIndex(level)] = true; } //-------------------------------------------------------------------------------------------------- void Logger::disable_level(const LogLevel level) { if (enumIndex(level) < logLevelCount) _impl->_levels[enumIndex(level)] = false; } //-------------------------------------------------------------------------------------------------- void local_free(char* d) { g_free(d); } //-------------------------------------------------------------------------------------------------- /** * Logs the given text with the given domain to the current log file. * Note: it should be pretty safe to use utf-8 encoded text too here, though avoid log messages * which are several thousands of chars long. */ void Logger::logv(LogLevel level, const char* const domain, const char* format, va_list args) { scope_ptr<char, local_free> buffer(g_strdup_vprintf(format, args)); // Print to stderr if no logger is created (yet). if (!_impl) { fprintf(stderr, "%s", buffer.get()); fflush(stderr); return; } const time_t t = time(NULL); struct tm tm; #ifdef _MSC_VER localtime_s(&tm, &t); #else localtime_r(&t, &tm); #endif FILE_scope_ptr fp = _impl->_filename.empty() ? NULL : base_fopen(_impl->_filename.c_str(), "a"); if (fp) { if (_impl->_new_line_pending) fprintf(fp, "%02u:%02u:%02u [%3s][%15s]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, LevelText[enumIndex(level)], domain); fwrite(buffer, 1, strlen(buffer.get()), fp); } // No explicit newline here. If messages are composed (e.g. python errors) // they get split over several lines and lose all their formatting. // Additionally for messages with implicit newline (which are most) we get many empty lines. if (_impl->_std_err_log) { #if defined(_MSC_VER) HANDLE hConsole = 0; WORD wOldColorAttrs; CONSOLE_SCREEN_BUFFER_INFO csbiInfo; if ((level == LogLevel::Error) || (level == LogLevel::Warning)) { hConsole = GetStdHandle(STD_ERROR_HANDLE); GetConsoleScreenBufferInfo(hConsole, &csbiInfo); wOldColorAttrs = csbiInfo.wAttributes; SetConsoleTextAttribute(hConsole, FOREGROUND_RED); if (level == LogLevel::Error) SetConsoleTextAttribute(hConsole, FOREGROUND_RED); else if (level == LogLevel::Warning) SetConsoleTextAttribute(hConsole, FOREGROUND_INTENSITY); } #elif !defined(__APPLE__) if (level == LogLevel::Error) fprintf(stderr, "\e[1;31m"); else if (level == LogLevel::Warning) fprintf(stderr, "\e[1m"); #endif #ifdef _MSC_VER if (_impl->_new_line_pending) { char* tmp = g_strdup_printf("%02u:%02u:%02u [%3s][%15s]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, LevelText[enumIndex(level)], domain); OutputDebugStringA(tmp); g_free(tmp); } // if you want the program to stop when a specific log msg is printed, put a bp in the next line and set condition // to log_msg_serial==# OutputDebugStringA(buffer.get()); #endif // We need the data in stderr even in Windows, so that the output can be read from other tools. if (_impl->_new_line_pending) fprintf(stderr, "%02u:%02u:%02u [%3s][%15s]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, LevelText[enumIndex(level)], domain); // If you want the program to stop when a specific log msg is printed, put a bp in the next line // and set condition to log_msg_serial==# fprintf(stderr, "%s", buffer.get()); #if defined(_MSC_VER) if ((level == LogLevel::Error) || (level == LogLevel::Warning)) SetConsoleTextAttribute(hConsole, wOldColorAttrs); #elif !defined(__APPLE__) if (level == LogLevel::Error || level == LogLevel::Warning) fprintf(stderr, "\e[0m"); #endif } const char ending_char = buffer[strlen(buffer) - 1]; _impl->_new_line_pending = (ending_char == '\n') || (ending_char == '\r'); } //-------------------------------------------------------------------------------------------------- void Logger::log(const Logger::LogLevel level, const char* const domain, const char* format, ...) { if (_impl->level_is_enabled(level)) { va_list args; va_start(args, format); logv(level, domain, format, args); va_end(args); } } //-------------------------------------------------------------------------------------------------- void Logger::log_exc(const LogLevel level, const char* const domain, const char* msg, const std::exception& exc) { log(level, domain, "%s: Exception: %s\n", msg, exc.what()); } //-------------------------------------------------------------------------------------------------- void Logger::log_throw(const LogLevel level, const char* const domain, const char* format, ...) { if (_impl->level_is_enabled(level)) { va_list args; va_start(args, format); logv(level, domain, format, args); va_end(args); throw std::logic_error(""); } } //-------------------------------------------------------------------------------------------------- std::string Logger::get_state() { std::string state = ""; if (_impl) { for (std::size_t i = 0; i < logLevelCount; ++i) { state += _impl->level_is_enabled((Logger::LogLevel)i) ? "1" : "0"; } } return state; } //-------------------------------------------------------------------------------------------------- void Logger::set_state(const std::string& state) { if (_impl && state.length() >= logLevelCount) { for (std::size_t i = 0; i < logLevelCount; ++i) { const char level = state[i]; if (level == '1') { Logger::enable_level((Logger::LogLevel)i); } else if (level == '0') { Logger::disable_level((Logger::LogLevel)i); } } } } //-------------------------------------------------------------------------------------------------- /** * Returns the most chatty log level which is currently active. */ std::string Logger::active_level() { if (_impl == NULL) return "none"; int i; for (i = logLevelCount - 1; i >= 0; i--) if (_impl->level_is_enabled((LogLevel)i)) break; switch (i) { case 0: return "none"; case 1: return "error"; case 2: return "warning"; case 3: return "info"; case 4: return "debug1"; case 5: return "debug2"; case 6: return "debug3"; } return "none"; } //-------------------------------------------------------------------------------------------------- /** * Used to set a log level, which implicitly enables all less-chatty levels too. * E.g. setting "info" not only enables info, but also warning and error etc. */ bool Logger::active_level(const std::string& value) { if (_impl == NULL) return false; int levelIndex = logLevelCount - 1; while (levelIndex >= 0 && !same_string(value, _logLevelNames[levelIndex])) levelIndex--; if (levelIndex < 0) return false; // Invalid value given. Ignore it. for (int i = 0; i < int(logLevelCount); ++i) { if (levelIndex >= i) enable_level((LogLevel)i); else disable_level((LogLevel)i); } return true; } //-------------------------------------------------------------------------------------------------- void Logger::log_to_stderr(bool value) { _impl->_std_err_log = value; }