X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcommon%2Flogger.cpp;h=2c601fdbfedf0a9e5dd82ec77daebb1862919692;hp=73f6b50c4d8caa72fabcf5f481ad4bcecb6a23e4;hb=d9026a22f17d5f8b2734cbf0348ad35de5b13f0b;hpb=1cb02004ee5973b89368bd84f234d4652794690d diff --git a/src/common/logger.cpp b/src/common/logger.cpp index 73f6b50c..2c601fdb 100644 --- a/src/common/logger.cpp +++ b/src/common/logger.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2015 by the Quassel Project * + * Copyright (C) 2005-2019 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,123 +18,219 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#include -#include -#include +#include #ifdef HAVE_SYSLOG -# include +# include #endif +#include +#include +#include +#include + #include "logger.h" #include "quassel.h" +#include "types.h" + +namespace { + +QByteArray msgWithTime(const Logger::LogEntry& msg) +{ + return (msg.timeStamp.toString("yyyy-MM-dd hh:mm:ss ") + msg.message + "\n").toUtf8(); +} + +} // namespace + +Logger::Logger(QObject* parent) + : QObject(parent) +{ + static bool registered = []() { + qRegisterMetaType(); + return true; + }(); + Q_UNUSED(registered) + + connect(this, &Logger::messageLogged, this, &Logger::onMessageLogged); + + qInstallMessageHandler(Logger::messageHandler); +} Logger::~Logger() { - log(); + // If we're not initialized yet, output pending messages so they don't get lost + if (!_initialized) { + for (auto&& message : _messages) { + std::cerr << msgWithTime(message).constData(); + } + } } +std::vector Logger::messages() const +{ + return _messages; +} -void Logger::log() +void Logger::setup(bool keepMessages) { - if (_logLevel < Quassel::logLevel()) - return; + _keepMessages = keepMessages; - switch (_logLevel) { - case Quassel::DebugLevel: - _buffer.prepend("Debug: "); - break; - case Quassel::InfoLevel: - _buffer.prepend("Info: "); - break; - case Quassel::WarningLevel: - _buffer.prepend("Warning: "); - break; - case Quassel::ErrorLevel: - _buffer.prepend("Error: "); - break; - default: - break; + // Set maximum level for output (we still store/announce all messages for client-side filtering) + if (Quassel::isOptionSet("loglevel")) { + QString level = Quassel::optionValue("loglevel").toLower(); + if (level == "debug") + _outputLevel = LogLevel::Debug; + else if (level == "info") + _outputLevel = LogLevel::Info; + else if (level == "warning") + _outputLevel = LogLevel::Warning; + else if (level == "error") + _outputLevel = LogLevel::Error; + else { + throw ExitException{EXIT_FAILURE, qPrintable(tr("Invalid log level %1; supported are Debug|Info|Warning|Error").arg(level))}; + } } -#ifdef HAVE_SYSLOG - if (Quassel::logToSyslog()) { - int prio; - switch (_logLevel) { - case Quassel::DebugLevel: - prio = LOG_DEBUG; - break; - case Quassel::InfoLevel: - prio = LOG_INFO; - break; - case Quassel::WarningLevel: - prio = LOG_WARNING; - break; - case Quassel::ErrorLevel: - prio = LOG_ERR; - break; - default: - prio = LOG_INFO; - break; + QString logfilename = Quassel::optionValue("logfile"); + if (!logfilename.isEmpty()) { + _logFile.setFileName(logfilename); + if (!_logFile.open(QFile::Append | QFile::Unbuffered | QFile::Text)) { + qCritical() << qPrintable(tr("Could not open log file \"%1\": %2").arg(logfilename, _logFile.errorString())); + } + } + if (!_logFile.isOpen()) { + if (!_logFile.open(stderr, QFile::WriteOnly | QFile::Unbuffered | QFile::Text)) { + qCritical() << qPrintable(tr("Cannot write to stderr: %1").arg(_logFile.errorString())); } - syslog(prio|LOG_USER, "%s", qPrintable(_buffer)); } -#endif - // if we neither use syslog nor have a logfile we log to stdout +#ifdef HAVE_SYSLOG + _syslogEnabled = Quassel::isOptionSet("syslog"); - if (Quassel::logFile() || !Quassel::logToSyslog()) { - _buffer.prepend(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ")); + // set up options, program name, and facility for later calls to syslog(3) + if (_syslogEnabled) + openlog("quasselcore", LOG_PID, LOG_USER); +#endif - QTextStream out(stdout); - if (Quassel::logFile() && Quassel::logFile()->isOpen()) { - _buffer.remove(QChar('\n')); - out.setDevice(Quassel::logFile()); - } + _initialized = true; - out << _buffer << endl; + // Now that we've setup our logging backends, output pending messages + for (auto&& message : _messages) { + outputMessage(message); + } + if (!_keepMessages) { + _messages.clear(); } } +void Logger::messageHandler(QtMsgType type, const QMessageLogContext&, const QString& message) +{ + Quassel::instance()->logger()->handleMessage(type, message); +} -#if QT_VERSION < 0x050000 -void Logger::logMessage(QtMsgType type, const char *msg) +void Logger::handleMessage(QtMsgType type, const QString& msg) { switch (type) { case QtDebugMsg: - Logger(Quassel::DebugLevel) << msg; + handleMessage(LogLevel::Debug, msg); + break; + case QtInfoMsg: + handleMessage(LogLevel::Info, msg); break; case QtWarningMsg: - Logger(Quassel::WarningLevel) << msg; + handleMessage(LogLevel::Warning, msg); break; case QtCriticalMsg: - Logger(Quassel::ErrorLevel) << msg; + handleMessage(LogLevel::Error, msg); break; case QtFatalMsg: - Logger(Quassel::ErrorLevel) << msg; - Quassel::logFatalMessage(msg); - return; + handleMessage(LogLevel::Fatal, msg); + break; } } -#else -void Logger::logMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) + +void Logger::handleMessage(LogLevel level, const QString& msg) { - Q_UNUSED(context) + QString logString; - switch (type) { - case QtDebugMsg: - Logger(Quassel::DebugLevel) << msg.toLocal8Bit().constData(); - break; - case QtWarningMsg: - Logger(Quassel::WarningLevel) << msg.toLocal8Bit().constData(); - break; - case QtCriticalMsg: - Logger(Quassel::ErrorLevel) << msg.toLocal8Bit().constData(); - break; - case QtFatalMsg: - Logger(Quassel::ErrorLevel) << msg.toLocal8Bit().constData(); - Quassel::logFatalMessage(msg.toLocal8Bit().constData()); - return; + // Only add the log level to the message if we do not output to syslog + if (!_syslogEnabled) { + switch (level) { + case LogLevel::Debug: + logString = "[Debug] "; + break; + case LogLevel::Info: + logString = "[Info ] "; + break; + case LogLevel::Warning: + logString = "[Warn ] "; + break; + case LogLevel::Error: + logString = "[Error] "; + break; + case LogLevel::Fatal: + logString = "[FATAL] "; + break; + } + } + + // Use signal connection to make this method thread-safe + emit messageLogged({QDateTime::currentDateTime(), level, logString += msg}); +} + +void Logger::onMessageLogged(const LogEntry& message) +{ + if (_keepMessages) { + _messages.push_back(message); + } + + // If setup() wasn't called yet, just store the message - will be output later + if (_initialized) { + outputMessage(message); } } + +void Logger::outputMessage(const LogEntry& message) +{ + if (message.logLevel < _outputLevel) { + return; + } + +#ifdef HAVE_SYSLOG + if (_syslogEnabled) { + int prio{LOG_INFO}; + switch (message.logLevel) { + case LogLevel::Debug: + prio = LOG_DEBUG; + break; + case LogLevel::Info: + prio = LOG_INFO; + break; + case LogLevel::Warning: + prio = LOG_WARNING; + break; + case LogLevel::Error: + prio = LOG_ERR; + break; + case LogLevel::Fatal: + prio = LOG_CRIT; + } + syslog(prio, "%s", qPrintable(message.message)); + } #endif + + if (!_logFile.fileName().isEmpty() || !_syslogEnabled) { + _logFile.write(msgWithTime(message)); + } + +#ifndef Q_OS_MAC + // For fatal messages, write log to dump file + if (message.logLevel == LogLevel::Fatal) { + QFile dumpFile{Quassel::instance()->coreDumpFileName()}; + if (dumpFile.open(QIODevice::Append)) { + dumpFile.write(msgWithTime(message)); + dumpFile.close(); + } + } +#endif +}