增强共享库中的日志记录通道过滤在 linux 上无法按预期工作

Posted

技术标签:

【中文标题】增强共享库中的日志记录通道过滤在 linux 上无法按预期工作【英文标题】:boost logging channel filtering in shared libraries not working as expected on linux 【发布时间】:2015-07-27 15:56:59 【问题描述】:

我有一个包含三个 dll/共享库的 Linux 和 Windows 可执行文件,并且我正在使用 boost.log 进行日志记录。我希望每个模块都有一个单独的文件日志。我的方法是为每个模块创建一个 severity_channel_logger_mt,为每个具有通道名称的模块构建一个单例。

class LogSingleton final

  src::severity_channel_logger_mt<logging::trivial::severity_level, std::string> slg_;
...
protected:
  LogSingleton() : slg_(boost::log::keywords::channel = "ModuleOne") 
public:
  src::severity_channel_logger_mt<logging::trivial::severity_level, std::string>& logger()  return slg_; 
...
;    

然后每个模块在初始化时创建自己的文件接收器,并设置一个通道过滤器,以便只有该模块的日志消息应该到达这个接收器。

logging::add_file_log(logging::keywords::file_name = "module_one.log",
  logging::keywords::open_mode = std::ios::app,
  ->set_filter(logging::trivial::severity >= level 
    && expr::attr< std::string >("Channel") == "ModuleOne");

然后我创建了我自己的模块特定宏用于记录,它传递给正确的记录器。

#define BLOG(lvl)  BOOST_LOG_STREAM_WITH_PARAMS((LogSingleton::instance()->logger()), (::boost::log::keywords::severity = ::boost::log::trivial::lvl))

这样使用:

BLOG(info) << "Hello world";

虽然在 Windows 上日志记录按预期工作,但在 Linux 上,ModuleOne(第一个初始化)和 ModuleThree(第三个初始化)的日志文件不会收到任何日志消息。 ModuleTwo 正确记录。除了日志接收器文件名、通道名称和单例的类名之外,所有三个模块的日志记录代码都是相同的。

我想知道问题是否出在我的宏上,但欢迎提出任何想法,以及方法上的 cmets。

更新

我已将这个问题简化为一个最小的示例,全部在一个可执行文件中。共享库问题是一个误导。问题似乎是,如果我在单例中创建 severity_channel_logger,则记录失败。如果我使用本地记录器,则记录工作。即使我在单例中创建了一个记录器并且不使用它,它也会阻止本地记录器运行。尽管跟踪代码,但我不明白为什么会发生这种情况。 (平台 = Fedora 21,gcc 4.9.2,boost 1.58)

#include <boost/config.hpp> 
#include <boost/filesystem.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/severity_feature.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/attributes/current_thread_id.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

namespace logging = boost::log;
namespace expr = boost::log::expressions;
namespace src = boost::log::sources;
namespace fs = boost::filesystem;

class LogSingleton

    src::severity_channel_logger<logging::trivial::severity_level, std::string> slg_;
    static LogSingleton* instance_;

    LogSingleton(const LogSingleton&);
    LogSingleton& operator=(const LogSingleton&);

protected:
    LogSingleton() : slg_(boost::log::keywords::channel = "Core") 
public:
    src::severity_channel_logger<logging::trivial::severity_level, std::string>&  logger()
    
        return slg_;
    

    static LogSingleton* instance()
    
        if (!instance_)
        
            instance_ = new LogSingleton;
        
        return instance_;
    
;

LogSingleton* LogSingleton::instance_ = nullptr;

// 1. doesn't work
//#define BLOG(lvl)  BOOST_LOG_STREAM_WITH_PARAMS((LogSingleton::instance()->logger()), (::boost::log::keywords::severity = ::boost::log::trivial::lvl))

// access to logger via reference. Works if it is passed a ref to logger declared in main. Doesn't work if it is ref to logger in singleton
#define BLOG(lvl)  BOOST_LOG_STREAM_WITH_PARAMS((rlogger), (::boost::log::keywords::severity = ::boost::log::trivial::lvl))

int main(int argc, char **argv)

    logging::add_common_attributes();
    logging::trivial::severity_level level = logging::trivial::trace;
        auto formatter = expr::stream
        << "[" << expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%dT%H:%M:%S.%f")
        << "] (" << logging::trivial::severity
        << "): " << expr::message;

    fs::path plog = fs::system_complete(fs::path(".."));
    if (!fs::exists(plog) || !fs::is_directory(plog))
        throw std::invalid_argument("Log directory doesn't exist, or path isn't a directory");
    plog /= "core.log";

    logging::add_file_log(logging::keywords::file_name = plog.string(),
        logging::keywords::open_mode = std::ios::app,
        logging::keywords::format = formatter)
        ->set_filter(logging::trivial::severity >= level &&    expr::attr< std::string >("Channel") == "Core");

    // this works with rlogger macro variant
    src::severity_channel_logger<logging::trivial::severity_level, std::string> logger(boost::log::keywords::channel = "Core");
    auto& rlogger = logger;

    // 2. this doesn't work, with same macro
    //auto& rlogger = LogSingleton::instance()->logger();

    // 3. just creating the singleton, before or after the creation of the local logger, stops logging from working
    //LogSingleton::instance();

    BLOG(info) << "Hello world";

    return 0;

正如目前所写,本示例适用于本地记录器。标记为 1 的注释 - 这是直接使用单例记录器的宏的变体(失败)。还注释掉本地记录器并启用对单例的引用将证明该问题。 (评论 2)。标记为 3 的注释表明,仅创建单例记录器会导致通过本地记录器进行记录失败。

【问题讨论】:

更多细节。我不能在 Windows 中使用单个全局记录器,因为它们在其中一个中定义时不会在不同的 dll 之间共享(我唯一可以控制的地方)。将通道添加到宏也不会改变行为,如#define BLOG(lvl) BOOST_LOG_STREAM_WITH_PARAMS((LogSingleton::instance()-&gt;logger()), (::boost::log::keywords::channel="ModuleOne") (::boost::log::keywords::severity = ::boost::log::trivial::lvl)) 【参考方案1】:

问题是由您的代码中的LogSingleton 泄漏引起的。单例包含一个记录器,它可以防止记录核心和接收器被破坏。您制作的日志记录已正确处理并写入文件流,但未刷新(即它最终进入文件流缓冲区)。通常,当流被销毁时(在 Boost.Log 的情况下,这会在接收器被销毁时,在程序终止时发生)或在每个日志记录之后,如果启用自动刷新(将 keywords::auto_flush = true 参数传递给add_file_log 函数调用)。

如果您将LogSingleton::instance_ 更改为std::unique_ptr,则可以修复它。

【讨论】:

当然,这很有意义。谢谢安德烈。【参考方案2】:

首先确保您将所有模块与 Boost.Log 的共享库链接,而不是静态的。这是 Boost.Log 的requirement,用于所有平台上的多模块应用程序。此外,请确保使用影响 Boost.Log 的同一组配置宏构建所有模块。

接下来,如果您管理符号 visibility,请确保您从库中导出类型信息符号。这对于异常类型和属性值类型尤其重要。如果您不熟悉符号可见性,您可能希望从导出所有符号开始,这可以通过构建不带任何 -fvisibility* 标志的模块来实现。

如果以上都没有帮助,您将不得不调试您的应用程序。特别是,请确保您设置的过滤器实际上正在传递日志记录。您可能希望暂时删除过滤器以查看是否有帮助。如果这仍然没有帮助,请在调试器中逐步检查代码以查看删除日志记录的位置。

【讨论】:

安德烈,非常感谢这些建议,我仔细检查了这些建议。我现在已将问题简化为单个可执行示例,包含在上述原始问题的更新中。

以上是关于增强共享库中的日志记录通道过滤在 linux 上无法按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

C++ 静态库中的共享全局变量:Linux

关于 Linux 中的共享库,有没有办法在库中选择导出函数?

记一次数据库奇怪问题 “在从服务器接收结果时发生传输级错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)"...

27-linux日志管理

是否可以防止 C 共享库中的函数覆盖?

linux查看日志的命令是?具体怎么用