From be98fa32c7d56ea91359b647a329356fa44eca04 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Fri, 3 Feb 2012 09:35:22 +0100 Subject: Allow customization of qDebug output at runtime Check the QT_OUTPUT_PATTERN environment variable in the default message handler to customize the output of messages. Following place holders are right now supported: %{message}, %{type}, %{file}, %{line}, %{function} The original cleanupFuncinfo was written by Thiago Macieira. Change-Id: I6ad25baaa0e6a1c9f886105d2a93ef3310e512a9 Reviewed-by: Olivier Goffart Reviewed-by: David Faure --- src/corelib/global/qlogging.cpp | 313 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) (limited to 'src/corelib/global/qlogging.cpp') diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index bc26c9b6b7..39b3205571 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -39,7 +39,13 @@ ** ****************************************************************************/ -#include +#include "qlogging.h" +#include "qlist.h" +#include "qbytearray.h" +#include "qstring.h" +#include "qvarlengtharray.h" + +#include QT_BEGIN_NAMESPACE @@ -73,4 +79,309 @@ QT_BEGIN_NAMESPACE \sa QMessageLogContext, qDebug(), qWarning(), qCritical(), qFatal() */ +/*! + \internal +*/ +Q_AUTOTEST_EXPORT QByteArray qCleanupFuncinfo(QByteArray info) +{ + // Strip the function info down to the base function name + // note that this throws away the template definitions, + // the parameter types (overloads) and any const/volatile qualifiers. + + if (info.isEmpty()) + return info; + + int pos; + + // skip trailing [with XXX] for templates (gcc) + pos = info.size() - 1; + if (info.endsWith(']')) { + while (--pos) { + if (info.at(pos) == '[') + info.truncate(pos); + } + } + + // operator names with '(', ')', '<', '>' in it + static const char operator_call[] = "operator()"; + static const char operator_lessThan[] = "operator<"; + static const char operator_greaterThan[] = "operator>"; + static const char operator_lessThanEqual[] = "operator<="; + static const char operator_greaterThanEqual[] = "operator>="; + + // canonize operator names + info.replace("operator ", "operator"); + + // remove argument list + forever { + int parencount = 0; + pos = info.lastIndexOf(')'); + if (pos == -1) { + // Don't know how to parse this function name + return info; + } + + // find the beginning of the argument list + --pos; + ++parencount; + while (pos && parencount) { + if (info.at(pos) == ')') + ++parencount; + else if (info.at(pos) == '(') + --parencount; + --pos; + } + if (parencount != 0) + return info; + + info.truncate(++pos); + + if (info.at(pos - 1) == ')') { + if (info.indexOf(operator_call) == pos - (int)strlen(operator_call)) + break; + + // this function returns a pointer to a function + // and we matched the arguments of the return type's parameter list + // try again + info.remove(0, info.indexOf('(')); + info.chop(1); + continue; + } else { + break; + } + } + + // find the beginning of the function name + int parencount = 0; + int templatecount = 0; + --pos; + + // make sure special characters in operator names are kept + if (pos > -1) { + switch (info.at(pos)) { + case ')': + if (info.indexOf(operator_call) == pos - (int)strlen(operator_call) + 1) + pos -= 2; + break; + case '<': + if (info.indexOf(operator_lessThan) == pos - (int)strlen(operator_lessThan) + 1) + --pos; + break; + case '>': + if (info.indexOf(operator_greaterThan) == pos - (int)strlen(operator_greaterThan) + 1) + --pos; + break; + case '=': { + int operatorLength = (int)strlen(operator_lessThanEqual); + if (info.indexOf(operator_lessThanEqual) == pos - operatorLength + 1) + pos -= 2; + else if (info.indexOf(operator_greaterThanEqual) == pos - operatorLength + 1) + pos -= 2; + break; + } + default: + break; + } + } + + while (pos > -1) { + if (parencount < 0 || templatecount < 0) + return info; + + char c = info.at(pos); + if (c == ')') + ++parencount; + else if (c == '(') + --parencount; + else if (c == '>') + ++templatecount; + else if (c == '<') + --templatecount; + else if (c == ' ' && templatecount == 0 && parencount == 0) + break; + + --pos; + } + info = info.mid(pos + 1); + + // we have the full function name now. + // clean up the templates + while ((pos = info.lastIndexOf('>')) != -1) { + if (!info.contains('<')) + break; + + // find the matching close + int end = pos; + templatecount = 1; + --pos; + while (pos && templatecount) { + register char c = info.at(pos); + if (c == '>') + ++templatecount; + else if (c == '<') + --templatecount; + --pos; + } + ++pos; + info.remove(pos, end - pos + 1); + } + + return info; +} + +// tokens as recognized in QT_MESSAGE_PATTERN +static const char typeTokenC[] = "%{type}"; +static const char messageTokenC[] = "%{message}"; +static const char fileTokenC[] = "%{file}"; +static const char lineTokenC[] = "%{line}"; +static const char functionTokenC[] = "%{function}"; +static const char emptyTokenC[] = ""; + +struct QMessagePattern { + QMessagePattern(); + ~QMessagePattern(); + + // 0 terminated arrays of literal tokens / literal or placeholder tokens + const char **literals; + const char **tokens; +}; + +QMessagePattern::QMessagePattern() +{ + QString pattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN")); + if (pattern.isEmpty()) { + pattern = QLatin1String("%{message}"); + } + + // scanner + QList lexemes; + QString lexeme; + bool inPlaceholder = false; + for (int i = 0; i < pattern.size(); ++i) { + const QChar c = pattern.at(i); + if ((c == QLatin1Char('%')) + && !inPlaceholder) { + if ((i + 1 < pattern.size()) + && pattern.at(i + 1) == QLatin1Char('{')) { + // beginning of placeholder + if (!lexeme.isEmpty()) { + lexemes.append(lexeme); + lexeme.clear(); + } + inPlaceholder = true; + } + } + + lexeme.append(c); + + if ((c == QLatin1Char('}') && inPlaceholder)) { + // end of placeholder + lexemes.append(lexeme); + lexeme.clear(); + inPlaceholder = false; + } + } + if (!lexeme.isEmpty()) + lexemes.append(lexeme); + + // tokenizer + QVarLengthArray literalsVar; + tokens = new const char*[lexemes.size() + 1]; + tokens[lexemes.size()] = 0; + + for (int i = 0; i < lexemes.size(); ++i) { + const QString lexeme = lexemes.at(i); + if (lexeme.startsWith(QLatin1String("%{")) + && lexeme.endsWith(QLatin1Char('}'))) { + // placeholder + if (lexeme == QLatin1String(typeTokenC)) { + tokens[i] = typeTokenC; + } else if (lexeme == QLatin1String(messageTokenC)) + tokens[i] = messageTokenC; + else if (lexeme == QLatin1String(fileTokenC)) + tokens[i] = fileTokenC; + else if (lexeme == QLatin1String(lineTokenC)) + tokens[i] = lineTokenC; + else if (lexeme == QLatin1String(functionTokenC)) + tokens[i] = functionTokenC; + else { + fprintf(stderr, "%s\n", + QString::fromLatin1("QT_MESSAGE_PATTERN: Unknown placeholder %1\n" + ).arg(lexeme).toLocal8Bit().constData()); + fflush(stderr); + tokens[i] = emptyTokenC; + } + } else { + char *literal = new char[lexeme.size() + 1]; + strncpy(literal, lexeme.toLocal8Bit().constData(), lexeme.size()); + literal[lexeme.size()] = '\0'; + literalsVar.append(literal); + tokens[i] = literal; + } + } + literals = new const char*[literalsVar.size() + 1]; + literals[literalsVar.size()] = 0; + memcpy(literals, literalsVar.constData(), literalsVar.size() * sizeof(const char*)); +} + +QMessagePattern::~QMessagePattern() +{ + for (int i = 0; literals[i] != 0; ++i) + delete [] literals[i]; + delete [] literals; + literals = 0; + delete [] tokens; + tokens = 0; +} + +Q_GLOBAL_STATIC(QMessagePattern, qMessagePattern) + +/*! + \internal +*/ +Q_CORE_EXPORT QByteArray qMessageFormatString(QtMsgType type, const QMessageLogContext &context, + const char *str) +{ + QByteArray message; + + QMessagePattern *pattern = qMessagePattern(); + if (!pattern) { + // after destruction of static QMessagePattern instance + message.append(str); + message.append('\n'); + return message; + } + + // we do not convert file, function, line literals to local encoding due to overhead + for (int i = 0; pattern->tokens[i] != 0; ++i) { + const char *token = pattern->tokens[i]; + if (token == messageTokenC) { + message.append(str); + } else if (token == typeTokenC) { + switch (type) { + case QtDebugMsg: message.append("debug"); break; + case QtWarningMsg: message.append("warning"); break; + case QtCriticalMsg:message.append("critical"); break; + case QtFatalMsg: message.append("fatal"); break; + } + } else if (token == fileTokenC) { + if (context.file) + message.append(context.file); + else + message.append("unknown"); + } else if (token == lineTokenC) { + message.append(QString::number(context.line).toLatin1().constData()); + } else if (token == functionTokenC) { + if (context.function) + message.append(qCleanupFuncinfo(context.function)); + else + message.append("unknown"); + } else { + message.append(token); + } + } + message.append('\n'); + return message; +} + QT_END_NAMESPACE -- cgit v1.2.3