//// LogHandler.cpp// libraries/shared/src//// Created by Stephen Birarda on 2014-10-28.// Migrated from Logging.cpp created on 6/11/13// Copyright 2014 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//#include "LogHandler.h"#include <mutex>#include <QtCore/QCoreApplication>#include <QtCore/QDateTime>#include <QtCore/QDebug>#include <QtCore/QMutexLocker>#include <QtCore/QRegExp>#include <QtCore/QThread>#include <QtCore/QTimer>QMutex LogHandler::_mutex;LogHandler& LogHandler::getInstance() { static LogHandler staticInstance; return staticInstance;}LogHandler::LogHandler() { // when the log handler is first setup we should print our timezone QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t"); printMessage(LogMsgType::LogInfo, QMessageLogContext(), timezoneString);}LogHandler::~LogHandler() { flushRepeatedMessages(); printMessage(LogMsgType::LogDebug, QMessageLogContext(), "LogHandler shutdown.");}const char* stringForLogType(LogMsgType msgType) { switch (msgType) { case LogInfo: return "INFO"; case LogDebug: return "DEBUG"; case LogWarning: return "WARNING"; case LogCritical: return "CRITICAL"; case LogFatal: return "FATAL"; case LogSuppressed: return "SUPPRESS"; default: return "UNKNOWN"; }}// the following will produce 11/18 13:55:36const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";// the following will produce 11/18 13:55:36.999const QString DATE_STRING_FORMAT_WITH_MILLISECONDS = "MM/dd hh:mm:ss.zzz";void LogHandler::setTargetName(const QString& targetName) { QMutexLocker lock(&_mutex); _targetName = targetName;}void LogHandler::setShouldOutputProcessID(bool shouldOutputProcessID) { QMutexLocker lock(&_mutex); _shouldOutputProcessID = shouldOutputProcessID;}void LogHandler::setShouldOutputThreadID(bool shouldOutputThreadID) { QMutexLocker lock(&_mutex); _shouldOutputThreadID = shouldOutputThreadID;}void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { QMutexLocker lock(&_mutex); _shouldDisplayMilliseconds = shouldDisplayMilliseconds;}void LogHandler::flushRepeatedMessages() { QMutexLocker lock(&_mutex); QHash<QString, int>::iterator message = _repeatMessageCountHash.begin(); while (message != _repeatMessageCountHash.end()) { if (message.value() > 0) { QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); QMessageLogContext emptyContext; lock.unlock(); printMessage(LogSuppressed, emptyContext, repeatMessage); lock.relock(); } _lastRepeatedMessage.remove(message.key()); message = _repeatMessageCountHash.erase(message); }}QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) { QMutexLocker lock(&_mutex); if (message.isEmpty()) { return QString(); } if (type == LogDebug) { // for debug messages, check if this matches any of our regexes for repeated log messages foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { QRegExp repeatRegex(regexString); if (repeatRegex.indexIn(message) != -1) { if (!_repeatMessageCountHash.contains(regexString)) { // we have a match but didn't have this yet - output the first one _repeatMessageCountHash[regexString] = 0; // break the foreach so we output the first match break; } else { // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message _repeatMessageCountHash[regexString] += 1; _lastRepeatedMessage[regexString] = message; // return out, we're not printing this one return QString(); } } } } if (type == LogDebug) { // see if this message is one we should only print once foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) { QRegExp onlyOnceRegex(regexString); if (onlyOnceRegex.indexIn(message) != -1) { if (!_onlyOnceMessageCountHash.contains(message)) { // we have a match and haven't yet printed this message. _onlyOnceMessageCountHash[message] = 1; // break the foreach so we output the first match break; } else { // We've already printed this message, don't print it again. return QString(); } } } } // log prefix is in the following format // [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string const QString* dateFormatPtr = &DATE_STRING_FORMAT; if (_shouldDisplayMilliseconds) { dateFormatPtr = &DATE_STRING_FORMAT_WITH_MILLISECONDS; } QString prefixString = QString("[%1] [%2] [%3]").arg(QDateTime::currentDateTime().toString(*dateFormatPtr), stringForLogType(type), context.category); if (_shouldOutputProcessID) { prefixString.append(QString(" [%1]").arg(QCoreApplication::applicationPid())); } if (_shouldOutputThreadID) { size_t threadID = (size_t)QThread::currentThreadId(); prefixString.append(QString(" [%1]").arg(threadID)); } if (!_targetName.isEmpty()) { prefixString.append(QString(" [%1]").arg(_targetName)); } QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); fprintf(stdout, "%s\n", qPrintable(logMessage)); return logMessage;}void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { getInstance().printMessage((LogMsgType) type, context, message);}void LogHandler::setupRepeatedMessageFlusher() { static std::once_flag once; std::call_once(once, [&] { // setup our timer to flush the verbose logs every 5 seconds QTimer* logFlushTimer = new QTimer(this); connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages); logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000); });}const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) { // make sure we setup the repeated message flusher, but do it on the LogHandler thread QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); QMutexLocker lock(&_mutex); return *_repeatedMessageRegexes.insert(regexString);}const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) { QMutexLocker lock(&_mutex); return *_onlyOnceMessageRegexes.insert(regexString);}
//
// LogHandler.cpp
// libraries/shared/src
//
// Created by Stephen Birarda on 2014-10-28.
// Migrated from Logging.cpp created on 6/11/13
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LogHandler.h"
#include <mutex>
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QMutexLocker>
#include <QtCore/QRegExp>
#include <QtCore/QThread>
#include <QtCore/QTimer>
QMutex LogHandler::_mutex;
LogHandler& LogHandler::getInstance() {
static LogHandler staticInstance;
return staticInstance;
}
LogHandler::LogHandler() {
// when the log handler is first setup we should print our timezone
QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t");
printMessage(LogMsgType::LogInfo, QMessageLogContext(), timezoneString);
}
LogHandler::~LogHandler() {
flushRepeatedMessages();
printMessage(LogMsgType::LogDebug, QMessageLogContext(), "LogHandler shutdown.");
}
const char* stringForLogType(LogMsgType msgType) {
switch (msgType) {
case LogInfo:
return "INFO";
case LogDebug:
return "DEBUG";
case LogWarning:
return "WARNING";
case LogCritical:
return "CRITICAL";
case LogFatal:
return "FATAL";
case LogSuppressed:
return "SUPPRESS";
default:
return "UNKNOWN";
}
}
// the following will produce 11/18 13:55:36
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
// the following will produce 11/18 13:55:36.999
const QString DATE_STRING_FORMAT_WITH_MILLISECONDS = "MM/dd hh:mm:ss.zzz";
void LogHandler::setTargetName(const QString& targetName) {
QMutexLocker lock(&_mutex);
_targetName = targetName;
}
void LogHandler::setShouldOutputProcessID(bool shouldOutputProcessID) {
QMutexLocker lock(&_mutex);
_shouldOutputProcessID = shouldOutputProcessID;
}
void LogHandler::setShouldOutputThreadID(bool shouldOutputThreadID) {
QMutexLocker lock(&_mutex);
_shouldOutputThreadID = shouldOutputThreadID;
}
void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) {
QMutexLocker lock(&_mutex);
_shouldDisplayMilliseconds = shouldDisplayMilliseconds;
}
void LogHandler::flushRepeatedMessages() {
QMutexLocker lock(&_mutex);
QHash<QString, int>::iterator message = _repeatMessageCountHash.begin();
while (message != _repeatMessageCountHash.end()) {
if (message.value() > 0) {
QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"")
.arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key()));
QMessageLogContext emptyContext;
lock.unlock();
printMessage(LogSuppressed, emptyContext, repeatMessage);
lock.relock();
}
_lastRepeatedMessage.remove(message.key());
message = _repeatMessageCountHash.erase(message);
}
}
QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) {
QMutexLocker lock(&_mutex);
if (message.isEmpty()) {
return QString();
}
if (type == LogDebug) {
// for debug messages, check if this matches any of our regexes for repeated log messages
foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) {
QRegExp repeatRegex(regexString);
if (repeatRegex.indexIn(message) != -1) {
if (!_repeatMessageCountHash.contains(regexString)) {
// we have a match but didn't have this yet - output the first one
_repeatMessageCountHash[regexString] = 0;
// break the foreach so we output the first match
break;
} else {
// we have a match - add 1 to the count of repeats for this message and set this as the last repeated message
_repeatMessageCountHash[regexString] += 1;
_lastRepeatedMessage[regexString] = message;
// return out, we're not printing this one
return QString();
}
}
}
}
if (type == LogDebug) {
// see if this message is one we should only print once
foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) {
QRegExp onlyOnceRegex(regexString);
if (onlyOnceRegex.indexIn(message) != -1) {
if (!_onlyOnceMessageCountHash.contains(message)) {
// we have a match and haven't yet printed this message.
_onlyOnceMessageCountHash[message] = 1;
// break the foreach so we output the first match
break;
} else {
// We've already printed this message, don't print it again.
return QString();
}
}
}
}
// log prefix is in the following format
// [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string
const QString* dateFormatPtr = &DATE_STRING_FORMAT;
if (_shouldDisplayMilliseconds) {
dateFormatPtr = &DATE_STRING_FORMAT_WITH_MILLISECONDS;
}
QString prefixString = QString("[%1] [%2] [%3]").arg(QDateTime::currentDateTime().toString(*dateFormatPtr),
stringForLogType(type), context.category);
if (_shouldOutputProcessID) {
prefixString.append(QString(" [%1]").arg(QCoreApplication::applicationPid()));
}
if (_shouldOutputThreadID) {
size_t threadID = (size_t)QThread::currentThreadId();
prefixString.append(QString(" [%1]").arg(threadID));
}
if (!_targetName.isEmpty()) {
prefixString.append(QString(" [%1]").arg(_targetName));
}
QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " "));
fprintf(stdout, "%s\n", qPrintable(logMessage));
return logMessage;
}
void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
getInstance().printMessage((LogMsgType) type, context, message);
}
void LogHandler::setupRepeatedMessageFlusher() {
static std::once_flag once;
std::call_once(once, [&] {
// setup our timer to flush the verbose logs every 5 seconds
QTimer* logFlushTimer = new QTimer(this);
connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages);
logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000);
});
}
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
// make sure we setup the repeated message flusher, but do it on the LogHandler thread
QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher");
QMutexLocker lock(&_mutex);
return *_repeatedMessageRegexes.insert(regexString);
}
const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) {
QMutexLocker lock(&_mutex);
return *_onlyOnceMessageRegexes.insert(regexString);
}