Redirect C++ std::clog to syslog on Unix

孤者浪人 提交于 2019-12-02 15:37:10
eater

I needed something simple like this too, so I just put this together:

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;
}

In main() I initialize clog:

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

Then whenever I want to log, it's easy:

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

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

You could define an streambuf that calls syslog. For example:

// 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(...); 
    } 
}

then you would simply write the following to redirect clog:

clog.rdbuf( new syslog_streambuf ); 

There are a few more functions you would probably have to override, here's a good reference to the streambuf api.

Another version in part inspired by eater. It doesn't redirect std::clog per se, but uses familiar stream syntax.

#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

To use it, you can do something like:

syslog_stream clog;

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

I designed a OStreamedLog class very similar to what was shown above, except my OStreamedLog objects are set up to use an arbitrary ostringstream object, like @Basilevs suggested.

First, there is the class definition of Log, very similar to what @eater and @Chris Kaminski mentioned above. Then, my OStreamedLog class definition, which contains a Log object:

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;
};

Now, when you need to log, just call:

OStreamedLog someLog ("MyOwnProgramThatNeedsLogging", LOG_LOCAL1);
someLog << "Log testing" << endl;
someLog << LOG_ERR << "some other error log" << endl;

Of course, you could collapse the whole Log definition into your OStreamedLog class, but you might want to do other things in your base Log object and use wrappers like above to differentiate the different types of logs. For example you could have human-readable diagnostic logs (sent as ASCII text), binary logs (for processing later) or a TLS-streaming log (to a northbound server, for example).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!