28

I work on Unix on a C++ program that send messages to syslog.

The current code uses the syslog system call that works like printf.

Now I would prefer to use a stream for that purpose instead, typically the built-in std::clog. But clog merely redirect output to stderr, not to syslog and that is useless for me as I also use stderr and stdout for other purposes.

I've seen in another answer that it's quite easy to redirect it to a file using rdbuf() but I see no way to apply that method to call syslog as openlog does not return a file handler I could use to tie a stream on it.

Is there another method to do that ? (looks pretty basic for unix programming) ?

Edit: I'm looking for a solution that does not use external library. What @Chris is proposing could be a good start but is still a bit vague to become the accepted answer.

Edit: using Boost.IOStreams is OK as my project already use Boost anyway.

Linking with external library is possible but is also a concern as it's GPL code. Dependencies are also a burden as they may conflict with other components, not be available on my Linux distribution, introduce third-party bugs, etc. If this is the only solution I may consider completely avoiding streams... (a pity).

Community
  • 1
  • 1
kriss
  • 23,497
  • 17
  • 97
  • 116
  • syslog requires more than just a message string; it also require an `error level` and so on. I'm not sure if this is possible using streams. Maybe with manipulators (just like `std::hex`) ? – ereOn Apr 14 '10 at 15:26
  • 2
    Have a look at available logging libraries. Many will allow you to write your own backends to write your message to wherever you want to write them to. many also come with built-in filtering and other nice features. There's only few light-weight ones that come with only little baggage, but you can find them if you want to. I'm using this one: http://www.templog.org/ It's just a few source file, almost all in headers, and good at filtering at compile-time (for time-critical code) as well as at run-time. But you might like some other. Just don't re-invent the wheel. – sbi Apr 14 '10 at 15:35
  • If it can't be done using built-in clog, another user defined specialized stream would be nearly as good, I don't care that much if level is set using manipulators or member function. – kriss Apr 14 '10 at 15:36
  • @kriss: You almost never want to specialize _stream_ class, but almost always write your own stream _buffer_. – sbi Apr 14 '10 at 15:42
  • 1
    @sbi: I'm looking for something really light-weight. Looks strange something so simple couldn't be done in twenty lines of C++. Thanks for pointing out **templog.org** it look nice but seems to be doing the things that syslog allready does (like log filtering). I do not feel like putting a second wheel to my operating system, or replace the existing syslog wheel by some other. Actually it feels like the built-in C++ wheel (std::clog) was square... – kriss Apr 14 '10 at 15:55
  • @sbi: does'nt adding a new formatting method like "set_log_level()" or new manipulators need a new class ? – kriss Apr 14 '10 at 16:07
  • @kriss: The main point in using templog is that it does filtering _at compile time_, thereby _completely eliminating_ whole log statements if they are "turned off". If you don't need that, then I agree that there isn't that much point in using it. – sbi Apr 14 '10 at 16:09
  • @kriss: No, manipulators don't need a new stream class. What the streams do is the formatting. Usually what you want to do is write to (or read from) some specific place. That's what stream buffers do. Which is why you usually create your own buffer. Chris's answer shows the basics of how to do that. – sbi Apr 14 '10 at 16:12
  • @sbi: the log I want to redirect are actual errors, warnings, etc and I definitely want to keep them in production code. – kriss Apr 14 '10 at 16:12
  • @kriss: What do you feel is lacking in what I delivered? Unfortunately, clog is a bludgeon. If you want different error reporting levels, like ERROR, FATAL, TRACE, DEBUG, you'd have to have separate ostreams to really support it. – Chris K Apr 27 '10 at 22:37

4 Answers4

39

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;
gerardw
  • 5,822
  • 46
  • 39
eater
  • 2,697
  • 1
  • 21
  • 24
14

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.

Chris K
  • 11,996
  • 7
  • 37
  • 65
  • 1
    I think it's a good practice to create a separate ostream object for this specific task. Beware http://www.cplusplus.com/reference/iostream/ios_base/sync_with_stdio/ . – Basilevs Apr 27 '10 at 10:15
  • @Basilevs: thank for the sample. It's still short and it's definitely the kind of thing I was looking for. Anyway I will have to sort out the socket specific stuff to adapt the code for syslog... looks like it will take me at least a couple of days :-( – kriss Apr 28 '10 at 05:23
7

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

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).

Sonny
  • 2,103
  • 1
  • 26
  • 34