将C++的std::clog重定向到Unix系统的syslog

28

我在Unix上开发一个发送消息到syslog的C++程序。

当前代码使用类似printf的syslog系统调用。

现在我想使用流,通常是内置的std::clog。但是clog仅将输出重定向到stderr而不是syslog,对我来说毫无用处,因为我还会将stderr和stdout用于其他目的。

我在另一个答案中看到,使用rdbuf()方法将其重定向到文件相当容易,但我看不到任何方法将该方法应用于调用syslog,因为openlog不返回文件处理程序,我无法将流绑定到它上面。

是否有其他方法可以实现?(看起来很基本的unix编程)?

编辑:我正在寻找不使用外部库的解决方案。@Chris提出的方法可能是一个不错的开始,但还没有足够明确成为被接受的答案。

编辑:使用Boost.IOStreams也可以,因为我的项目已经在使用Boost。

链接外部库是可能的,但这也是一个问题,因为它是GPL代码。依赖关系也会导致麻烦,因为它们可能与其他组件冲突,不可用于我的Linux发行版,引入第三方错误等等。如果这是唯一的解决方案,我可能会完全避免使用流...(太遗憾了)。


2
看看可用的日志记录库。许多库都允许您编写自己的后端,将消息写入任何想要写入的位置。许多库还带有内置的过滤和其他不错的功能。只有很少量的轻量级库,但如果您想找到它们,可以找到它们。我正在使用这个:http://www.templog.org/ 它只有几个源文件,几乎全部在头文件中,并且在编译时(对于时间关键代码)以及运行时进行良好的过滤。但是您可能会喜欢其他一些库。只是不要重复造轮子。 - sbi
如果无法使用内置的 clog 完成,另一个用户定义的专用流也可以达到同样的效果。如果级别是使用操作符或成员函数设置的,我并不太在意。 - kriss
@kriss:你几乎从不想专门化_stream_类,但几乎总是要编写自己的流_buffer_。 - sbi
1
@sbi:我正在寻找一些真正轻量级的东西。看起来很奇怪,如此简单的事情居然不能用二十行C++代码实现。感谢您指出templog.org,它看起来不错,但似乎已经执行syslog已经执行的操作(例如日志过滤)。我不想将第二个轮子放入我的操作系统中,也不想用其他替代现有的syslog轮子。实际上,内置的C++轮子(std :: clog)感觉就像是方形的... - kriss
@kriss:你觉得我交付的东西缺少什么?不幸的是,clog 是一个钝器。如果你想要不同的错误报告级别,比如 ERROR、FATAL、TRACE、DEBUG,你必须有单独的 ostreams 来真正支持它。 - Chris K
显示剩余5条评论
4个回答

39

我也需要类似这样简单的东西,所以我就把这个东西放在了一起:

log.h:

#include <streambuf>
#include <syslog.h>
enum LogPriority {
    kLogEmerg   = LOG_EMERG,   // system is unusable
    kLogAlert   = LOG_ALERT,   // action must be taken immediately
    kLogCrit    = LOG_CRIT,    // critical conditions
    kLogErr     = LOG_ERR,     // error conditions
    kLogWarning = LOG_WARNING, // warning conditions
    kLogNotice  = LOG_NOTICE,  // normal, but significant, condition
    kLogInfo    = LOG_INFO,    // informational message
    kLogDebug   = LOG_DEBUG    // debug-level message
};

std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority);

class Log : public std::basic_streambuf<char, std::char_traits<char> > {
public:
    explicit Log(std::string ident, int facility);

protected:
    int sync();
    int overflow(int c);

private:
    friend std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority);
    std::string buffer_;
    int facility_;
    int priority_;
    char ident_[50];
};

log.cc:

#include <cstring>
#include <ostream>
#include "log.h"

Log::Log(std::string ident, int facility) {
    facility_ = facility;
    priority_ = LOG_DEBUG;
    strncpy(ident_, ident.c_str(), sizeof(ident_));
    ident_[sizeof(ident_)-1] = '\0';

    openlog(ident_, LOG_PID, facility_);
}

int Log::sync() {
    if (buffer_.length()) {
        syslog(priority_, "%s", buffer_.c_str());
        buffer_.erase();
        priority_ = LOG_DEBUG; // default to debug for each message
    }
    return 0;
}

int Log::overflow(int c) {
    if (c != EOF) {
        buffer_ += static_cast<char>(c);
    } else {
        sync();
    }
    return c;
}

std::ostream& operator<< (std::ostream& os, const LogPriority& log_priority) {
    static_cast<Log *>(os.rdbuf())->priority_ = (int)log_priority;
    return os;
}

main()函数中我初始化了:

std::clog.rdbuf(new Log("foo", LOG_LOCAL0));

那么每当我想要记录日志时,这很容易:

std::clog << kLogNotice << "test log message" << std::endl;

std::clog << "the default is debug level" << std::endl;

14

您可以定义一个调用syslog的streambuf。例如:

// Pseudo-code
class syslog_streambuf : public streambuf { 
private: 
    void internal_log(string& log) { 
        syslog(..., log, ...); 
    }
public: 
    int sputc ( char c ) { 
        internal_log(...); 
    }
    streamsize sputn ( const char * s, streamsize n ) { 
        internal_log(...); 
    } 
}

那么你只需写以下内容即可重定向clog:

clog.rdbuf( new syslog_streambuf ); 

你可能需要覆盖一些其他函数,这里有一个好的streambuf api参考


1
我认为为这个特定的任务创建一个单独的ostream对象是一个好的实践。注意http://www.cplusplus.com/reference/iostream/ios_base/sync_with_stdio/。 - Basilevs
@Basilevs:感谢提供示例。它仍然很简短,而且绝对是我正在寻找的类型。无论如何,我将不得不解决套接字特定的问题,以适应syslog的代码...看起来这至少需要我几天 :-( - kriss

7

受eater启发,这个版本部分采用了熟悉的流语法,但并不会直接重定向std::clog。

#ifndef SYSLOG_HPP
#define SYSLOG_HPP

#include <ostream>
#include <streambuf>
#include <string>

#include <syslog.h>

namespace log
{

enum level
{
    emergency = LOG_EMERG,
    alert     = LOG_ALERT,
    critical  = LOG_CRIT,
    error     = LOG_ERR,
    warning   = LOG_WARNING,
    notice    = LOG_NOTICE,
    info      = LOG_INFO,
    debug     = LOG_DEBUG,
};

enum type
{
    auth   = LOG_AUTH,
    cron   = LOG_CRON,
    daemon = LOG_DAEMON,
    local0 = LOG_LOCAL0,
    local1 = LOG_LOCAL1,
    local2 = LOG_LOCAL2,
    local3 = LOG_LOCAL3,
    local4 = LOG_LOCAL4,
    local5 = LOG_LOCAL5,
    local6 = LOG_LOCAL6,
    local7 = LOG_LOCAL7,
    print  = LOG_LPR,
    mail   = LOG_MAIL,
    news   = LOG_NEWS,
    user   = LOG_USER,
    uucp   = LOG_UUCP,
};

}

class syslog_stream;

class syslog_streambuf: public std::basic_streambuf<char>
{
public:
    explicit syslog_streambuf(const std::string& name, log::type type):
        std::basic_streambuf<char>()
    {
        openlog(name.size() ? name.data() : nullptr, LOG_PID, type);
    }
    ~syslog_streambuf() override { closelog(); }

protected:
    int_type overflow(int_type c = traits_type::eof()) override
    {
        if(traits_type::eq_int_type(c, traits_type::eof()))
            sync();
        else buffer += traits_type::to_char_type(c);

        return c;
    }

    int sync() override
    {
        if(buffer.size())
        {
            syslog(level, "%s", buffer.data());

            buffer.clear();
            level = ini_level;
        }
        return 0;
    }

    friend class syslog_stream;
    void set_level(log::level new_level) noexcept { level = new_level; }

private:
    static constexpr log::level ini_level = log::info;
    log::level level = ini_level;

    std::string buffer;
};

class syslog_stream: public std::basic_ostream<char>
{
public:
    explicit syslog_stream(const std::string& name = std::string(), log::type type = log::user):
        std::basic_ostream<char>(&streambuf),
        streambuf(name, type)
    { }

    syslog_stream& operator<<(log::level level) noexcept
    {
        streambuf.set_level(level);
        return (*this);
    }

private:
    syslog_streambuf streambuf;
};

#endif // SYSLOG_HPP

要使用它,您可以这样做:
syslog_stream clog;

clog << "Hello, world!" << std::endl;
clog << log::emergency << "foo" << "bar" << "baz" << 42 << std::endl;

2

我设计了一个类似于上面展示的OStreamedLog类,但我的OStreamedLog对象是使用任意ostringstream对象设置的,就像@Basilevs建议的那样。

首先,有一个类定义Log,与@eater和@Chris Kaminski上面提到的非常相似。然后,是我的OStreamedLog类定义,其中包含一个Log对象:

class OStreamedLog : public ostringstream
{
   public:
     OStreamedLog (const char* ident, int facility)
     {
          log = new Log (ident, facility);
          (static_cast<ostream*>(this))->rdbuf (log);
     }
   private:
     Log* log;
};

现在,当您需要登录时,只需调用以下代码:
OStreamedLog someLog ("MyOwnProgramThatNeedsLogging", LOG_LOCAL1);
someLog << "Log testing" << endl;
someLog << LOG_ERR << "some other error log" << endl;

当然,你可以将整个日志定义合并到你的OStreamedLog类中,但是你可能希望在基本的Log对象中进行其他操作,并使用上面的包装器来区分不同类型的日志。例如,你可以拥有易于阅读的诊断日志(以ASCII文本形式发送),二进制日志(以供以后处理)或TLS流日志(向北向服务器流),例如。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接