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;
}