mirror of https://gitee.com/bigwinds/arangodb
466 lines
13 KiB
C++
466 lines
13 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
|
|
/// Copyright 2004-2013 triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Achim Brandt
|
|
/// @author Dr. Frank Celler
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Logger.h"
|
|
|
|
#include <cstring>
|
|
#include "Basics/ArangoGlobalContext.h"
|
|
#include "Basics/Common.h"
|
|
#include "Basics/ConditionLocker.h"
|
|
#include "Basics/Exceptions.h"
|
|
#include "Basics/MutexLocker.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/Thread.h"
|
|
#include "Basics/files.h"
|
|
#include "Logger/LogAppender.h"
|
|
#include "Logger/LogAppenderFile.h"
|
|
#include "Logger/LogThread.h"
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::basics;
|
|
|
|
namespace {
|
|
static std::string const DEFAULT = "DEFAULT";
|
|
static std::string const FATAL = "FATAL";
|
|
static std::string const ERR = "ERROR";
|
|
static std::string const WARN = "WARNING";
|
|
static std::string const INFO = "INFO";
|
|
static std::string const DEBUG = "DEBUG";
|
|
static std::string const TRACE = "TRACE";
|
|
static std::string const UNKNOWN = "UNKNOWN";
|
|
} // namespace
|
|
|
|
Mutex Logger::_initializeMutex;
|
|
|
|
std::atomic<bool> Logger::_active(false);
|
|
std::atomic<LogLevel> Logger::_level(LogLevel::INFO);
|
|
|
|
LogTimeFormats::TimeFormat Logger::_timeFormat(LogTimeFormats::TimeFormat::UTCDateString);
|
|
bool Logger::_showIds(false);
|
|
bool Logger::_showLineNumber(false);
|
|
bool Logger::_shortenFilenames(true);
|
|
bool Logger::_showThreadIdentifier(false);
|
|
bool Logger::_showThreadName(false);
|
|
bool Logger::_threaded(false);
|
|
bool Logger::_useColor(true);
|
|
bool Logger::_useEscaped(true);
|
|
bool Logger::_keepLogRotate(false);
|
|
bool Logger::_logRequestParameters(true);
|
|
bool Logger::_showRole(false);
|
|
char Logger::_role('\0');
|
|
TRI_pid_t Logger::_cachedPid(0);
|
|
std::string Logger::_outputPrefix("");
|
|
|
|
std::unique_ptr<LogThread> Logger::_loggingThread(nullptr);
|
|
|
|
LogLevel Logger::logLevel() { return _level.load(std::memory_order_relaxed); }
|
|
|
|
std::vector<std::pair<std::string, LogLevel>> Logger::logLevelTopics() {
|
|
return LogTopic::logLevelTopics();
|
|
}
|
|
|
|
void Logger::setShowIds(bool show) {
|
|
_showIds = show;
|
|
}
|
|
|
|
void Logger::setLogLevel(LogLevel level) {
|
|
_level.store(level, std::memory_order_relaxed);
|
|
}
|
|
|
|
void Logger::setLogLevel(std::string const& levelName) {
|
|
std::string l = StringUtils::tolower(levelName);
|
|
std::vector<std::string> v = StringUtils::split(l, '=');
|
|
|
|
if (v.empty() || v.size() > 2) {
|
|
Logger::setLogLevel(LogLevel::INFO);
|
|
LOG_TOPIC("b83c6", ERR, arangodb::Logger::FIXME)
|
|
<< "strange log level '" << levelName << "', using log level 'info'";
|
|
return;
|
|
}
|
|
|
|
// if log level is "foo = bar", we better get rid of the whitespace
|
|
StringUtils::trimInPlace(v[0]);
|
|
bool isGeneral = v.size() == 1;
|
|
|
|
if (!isGeneral) {
|
|
StringUtils::trimInPlace(v[1]);
|
|
l = v[1];
|
|
}
|
|
|
|
LogLevel level;
|
|
|
|
if (l == "fatal") {
|
|
level = LogLevel::FATAL;
|
|
} else if (l == "error" || l == "err") {
|
|
level = LogLevel::ERR;
|
|
} else if (l == "warning" || l == "warn") {
|
|
level = LogLevel::WARN;
|
|
} else if (l == "info") {
|
|
level = LogLevel::INFO;
|
|
} else if (l == "debug") {
|
|
level = LogLevel::DEBUG;
|
|
} else if (l == "trace") {
|
|
level = LogLevel::TRACE;
|
|
} else if (!isGeneral && (l.empty() || l == "default")) {
|
|
level = LogLevel::DEFAULT;
|
|
} else {
|
|
if (isGeneral) {
|
|
Logger::setLogLevel(LogLevel::INFO);
|
|
LOG_TOPIC("d880b", ERR, arangodb::Logger::FIXME)
|
|
<< "strange log level '" << levelName << "', using log level 'info'";
|
|
} else {
|
|
LOG_TOPIC("05367", ERR, arangodb::Logger::FIXME) << "strange log level '" << levelName << "'";
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (isGeneral) {
|
|
Logger::setLogLevel(level);
|
|
// setting the log level for topic "general" is required here, too,
|
|
// as "fixme" is the previous general log topic...
|
|
LogTopic::setLogLevel(std::string("general"), level);
|
|
} else {
|
|
LogTopic::setLogLevel(v[0], level);
|
|
}
|
|
}
|
|
|
|
void Logger::setLogLevel(std::vector<std::string> const& levels) {
|
|
for (auto level : levels) {
|
|
setLogLevel(level);
|
|
}
|
|
}
|
|
|
|
void Logger::setRole(char role) { _role = role; }
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setOutputPrefix(std::string const& prefix) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_outputPrefix = prefix;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setShowLineNumber(bool show) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_showLineNumber = show;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setShortenFilenames(bool shorten) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_shortenFilenames = shorten;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setShowThreadIdentifier(bool show) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_showThreadIdentifier = show;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setShowThreadName(bool show) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_showThreadName = show;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setUseColor(bool value) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_useColor = value;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setUseEscaped(bool value) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_useEscaped = value;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setShowRole(bool show) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_showRole = show;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setTimeFormat(LogTimeFormats::TimeFormat format) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_timeFormat = format;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setKeepLogrotate(bool keep) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_keepLogRotate = keep;
|
|
}
|
|
|
|
// NOTE: this function should not be called if the logging is active.
|
|
void Logger::setLogRequestParameters(bool log) {
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(
|
|
TRI_ERROR_INTERNAL, "cannot change settings once logging is active");
|
|
}
|
|
|
|
_logRequestParameters = log;
|
|
}
|
|
|
|
std::string const& Logger::translateLogLevel(LogLevel level) {
|
|
switch (level) {
|
|
case LogLevel::DEFAULT:
|
|
return DEFAULT;
|
|
case LogLevel::FATAL:
|
|
return FATAL;
|
|
case LogLevel::ERR:
|
|
return ERR;
|
|
case LogLevel::WARN:
|
|
return WARN;
|
|
case LogLevel::INFO:
|
|
return INFO;
|
|
case LogLevel::DEBUG:
|
|
return DEBUG;
|
|
case LogLevel::TRACE:
|
|
return TRACE;
|
|
}
|
|
|
|
return UNKNOWN;
|
|
}
|
|
|
|
void Logger::log(char const* function, char const* file, int line,
|
|
LogLevel level, size_t topicId, std::string const& message) {
|
|
#ifdef _WIN32
|
|
if (level == LogLevel::FATAL || level == LogLevel::ERR) {
|
|
if (ArangoGlobalContext::CONTEXT != nullptr &&
|
|
ArangoGlobalContext::CONTEXT->useEventLog()) {
|
|
TRI_LogWindowsEventlog(function, file, line, message);
|
|
}
|
|
|
|
// additionally log these errors to the debug output window in MSVC so
|
|
// we can see them during development
|
|
OutputDebugString(message.data());
|
|
OutputDebugString("\r\n");
|
|
}
|
|
#endif
|
|
|
|
if (!_active.load(std::memory_order_relaxed)) {
|
|
LogAppenderStdStream::writeLogMessage(STDERR_FILENO, (isatty(STDERR_FILENO) == 1),
|
|
level, message.data(), message.size(), true);
|
|
return;
|
|
}
|
|
|
|
std::stringstream out;
|
|
LogTimeFormats::writeTime(out, _timeFormat);
|
|
out << ' ';
|
|
|
|
// output prefix
|
|
if (!_outputPrefix.empty()) {
|
|
out << _outputPrefix << ' ';
|
|
}
|
|
|
|
// append the process / thread identifier
|
|
|
|
// we only determine our pid once, as currentProcessId() will
|
|
// likely do a syscall.
|
|
// this read-check-update sequence is not thread-safe, but this
|
|
// should not matter, as the pid value is only changed from 0 to the
|
|
// actual pid and never changes afterwards
|
|
if (_cachedPid == 0) {
|
|
_cachedPid = Thread::currentProcessId();
|
|
}
|
|
TRI_ASSERT(_cachedPid != 0);
|
|
out << '[' << _cachedPid;
|
|
|
|
if (_showThreadIdentifier) {
|
|
out << '-' << Thread::currentThreadNumber();
|
|
}
|
|
|
|
// log thread name
|
|
if (_showThreadName) {
|
|
char const* threadName = Thread::currentThreadName();
|
|
if (threadName == nullptr) {
|
|
threadName = "main";
|
|
}
|
|
|
|
out << '-' << threadName;
|
|
}
|
|
|
|
out << "] ";
|
|
|
|
if (_showRole && _role != '\0') {
|
|
out << _role << ' ';
|
|
}
|
|
|
|
// log level
|
|
out << Logger::translateLogLevel(level) << ' ';
|
|
|
|
// check if we must display the line number
|
|
if (_showLineNumber && file != nullptr && function != nullptr) {
|
|
char const* filename = file;
|
|
|
|
if (_shortenFilenames) {
|
|
// shorten file names from `/home/.../file.cpp` to just `file.cpp`
|
|
char const* shortened = strrchr(filename, TRI_DIR_SEPARATOR_CHAR);
|
|
if (shortened != nullptr) {
|
|
filename = shortened + 1;
|
|
}
|
|
}
|
|
out << '[' << function << "@" << filename << ':' << line << "] ";
|
|
}
|
|
|
|
// generate the complete message
|
|
out << message;
|
|
std::string ostreamContent = out.str();
|
|
size_t offset = ostreamContent.size() - message.size();
|
|
auto msg = std::make_unique<LogMessage>(level, topicId, std::move(ostreamContent), offset);
|
|
|
|
// now either queue or output the message
|
|
if (_threaded) {
|
|
try {
|
|
_loggingThread->log(msg);
|
|
bool const isDirectLogLevel =
|
|
(level == LogLevel::FATAL || level == LogLevel::ERR || level == LogLevel::WARN);
|
|
if (isDirectLogLevel) {
|
|
_loggingThread->flush();
|
|
}
|
|
return;
|
|
} catch (...) {
|
|
// fall-through to non-threaded logging
|
|
}
|
|
}
|
|
|
|
LogAppender::log(msg.get());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initializes the logging component
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Logger::initialize(bool threaded) {
|
|
MUTEX_LOCKER(locker, _initializeMutex);
|
|
|
|
if (_active) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
|
"Logger already initialized");
|
|
}
|
|
|
|
// logging is now active
|
|
_active.store(true);
|
|
|
|
// generate threaded logging?
|
|
_threaded = threaded;
|
|
|
|
if (threaded) {
|
|
_loggingThread = std::make_unique<LogThread>("Logging");
|
|
_loggingThread->start();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief shuts down the logging components
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Logger::shutdown() {
|
|
MUTEX_LOCKER(locker, _initializeMutex);
|
|
|
|
if (!_active) {
|
|
// if logging not activated or already shutdown, then we can abort here
|
|
return;
|
|
}
|
|
|
|
_active = false;
|
|
|
|
// logging is now inactive (this will terminate the logging thread)
|
|
// join with the logging thread
|
|
if (_threaded) {
|
|
// ignore all errors for now as we cannot log them anywhere...
|
|
int tries = 0;
|
|
while (_loggingThread->hasMessages() && ++tries < 1000) {
|
|
_loggingThread->wakeup();
|
|
std::this_thread::sleep_for(std::chrono::microseconds(10000));
|
|
}
|
|
_loggingThread->beginShutdown();
|
|
_loggingThread.reset();
|
|
}
|
|
|
|
// cleanup appenders
|
|
LogAppender::shutdown();
|
|
|
|
_cachedPid = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tries to flush the logging
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Logger::flush() {
|
|
MUTEX_LOCKER(locker, _initializeMutex);
|
|
|
|
if (!_active) {
|
|
// logging not (or not yet) initialized
|
|
return;
|
|
}
|
|
|
|
if (_threaded) {
|
|
LogThread::flush();
|
|
}
|
|
}
|