今天发现 log4qt 在非主线程中记录日志的时候中文会出现乱码,具体是 RollingFileAppenderConsoleAppender 出现乱码,而 DatabaseAppender 不会出现乱码。使用的配置文件如下:

log4j.rootLogger=All,console,database,rollingFile
log4j.additivity.org.apache=true

log4j.appender.rollingFile=Log4Qt::RollingFileAppender
log4j.appender.rollingFile.Threshold=INFO
log4j.appender.rollingFile.ImmediateFlush=true
log4j.appender.rollingFile.AppendFile=true
log4j.appender.rollingFile.File=logs/app.log
log4j.appender.rollingFile.MaxFileSize=4096KB
log4j.appender.rollingFile.MaxBackupIndex=50
log4j.appender.rollingFile.layout=Log4Qt::PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=[%5p] %d -->[%c] %m %n


log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.immediateFlush=true
log4j.appender.console.target=STDOUT_TARGET
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%5p] %d -->[%c] %m %n


log4j.appender.database=Log4Qt::DatabaseAppender
log4j.appender.database.Threshold=INFO
log4j.appender.database.connection=log4qt_connection
log4j.appender.database.table=t_log
log4j.appender.database.layout=org.apache.log4j.DatabaseLayout
log4j.appender.database.layout.timeStampColumn=timeStamp
log4j.appender.database.layout.loggenameColumn=loggename
log4j.appender.database.layout.threadNameColumn=threadName
log4j.appender.database.layout.levelColumn=level
log4j.appender.database.layout.messageColumn=message

初步怀疑应该是编码问题引起的,如果能设置 log4qt 使用的编码,问题应该能够得到解决。于是,在配置文件中增加如下编码配置:

log4j.appender.console.encoding=UTF-8

重新运行后,发现问题依旧。

跟踪 log4qt 源码后发现,编码是在 WriterAppender 中设置的:

Q_PROPERTY(QTextCodec *encoding READ encoding WRITE setEncoding)

而将配置文件中的属性设置到对象上是在 factory.cpp 中的 doSetObjectProperty 方法中完成的,如下所示:

void Factory::doSetObjectProperty(QObject *object,
                                  const QString &property,
                                  const QString &value)
{
    // - Validate property
    // - Get correct property name from meta object
    // - Find specific property setter
    // - If no specfifc propery setter can be found,
    //   find general property setter
    // - Call property setter

    QMetaProperty meta_property;
    if (!validateObjectProperty(meta_property, property, object))
        return;

    QString propertyString = QLatin1String(meta_property.name());
    QString type = QLatin1String(meta_property.typeName());
    logger()->debug(QStringLiteral("Setting property '%1' on object of class '%2' to value '%3'"),
                    propertyString,
                    QLatin1String(object->metaObject()->className()),
                    value);

    QVariant variant;
    bool ok = true;
    if (type == QStringLiteral("bool"))
        variant = OptionConverter::toBoolean(value, &ok);
    else if (type == QStringLiteral("int"))
        variant = OptionConverter::toInt(value, &ok);
    else if (type == QStringLiteral("Log4Qt::Level"))
        variant = QVariant::fromValue(OptionConverter::toLevel(value, &ok));
    else if (type == QStringLiteral("QString"))
        variant = value;
    else
    {
        LogError e = LOG4QT_ERROR(QT_TR_NOOP("Cannot convert to type '%1' for property '%2' on object of class '%3'"),
                                  CONFIGURATOR_UNKNOWN_TYPE_ERROR,
                                  "Log4Qt::Factory");
        e << type
          << property
          << QString::fromLatin1(object->metaObject()->className());
        logger()->error(e);
        return;
    }
    if (!ok)
        return;

    // Everything is checked and the type is the one of the property.
    // Write should never return false
    if (!meta_property.write(object, variant))
        logger()->warn(QStringLiteral("Unxpected error result from QMetaProperty.write()"));
}

仔细看这段代码,居然没有设置 encoding 对应类型 QTextCodec* 的相关代码,那就自己加上吧。

改完后的代码如下所示:

void Factory::doSetObjectProperty(QObject *object,
                                  const QString &property,
                                  const QString &value)
{
    // - Validate property
    // - Get correct property name from meta object
    // - Find specific property setter
    // - If no specfifc propery setter can be found,
    //   find general property setter
    // - Call property setter

    QMetaProperty meta_property;
    if (!validateObjectProperty(meta_property, property, object))
        return;

    QString propertyString = QLatin1String(meta_property.name());
    QString type = QLatin1String(meta_property.typeName());
    logger()->debug(QStringLiteral("Setting property '%1' on object of class '%2' to value '%3'"),
                    propertyString,
                    QLatin1String(object->metaObject()->className()),
                    value);

    QVariant variant;
    bool ok = true;
    if (type == QStringLiteral("bool"))
        variant = OptionConverter::toBoolean(value, &ok);
    else if (type == QStringLiteral("int"))
        variant = OptionConverter::toInt(value, &ok);
    else if (type == QStringLiteral("Log4Qt::Level"))
        variant = QVariant::fromValue(OptionConverter::toLevel(value, &ok));
    else if (type == QStringLiteral("QString"))
        variant = value;
    else if (type == QStringLiteral("QTextCodec*"))
        variant = QVariant::fromValue(QTextCodec::codecForName(value.toUtf8()));
    else
    {
        LogError e = LOG4QT_ERROR(QT_TR_NOOP("Cannot convert to type '%1' for property '%2' on object of class '%3'"),
                                  CONFIGURATOR_UNKNOWN_TYPE_ERROR,
                                  "Log4Qt::Factory");
        e << type
          << property
          << QString::fromLatin1(object->metaObject()->className());
        logger()->error(e);
        return;
    }
    if (!ok)
        return;

    // Everything is checked and the type is the one of the property.
    // Write should never return false
    if (!meta_property.write(object, variant))
        logger()->warn(QStringLiteral("Unxpected error result from QMetaProperty.write()"));
}

还要记着在全局范围声明元类型:Q_DECLARE_METATYPE(QTextCodec*)

再次重新运行,编码是设置成功了,可输出还是乱码!!!

不要慌,换个编码试试,然后换成 GB18030,再次重新运行,终于没有乱码了!

下一步准备去 github 提个 PR,不知道能否被 merge。