Redirect C++ std::clog to syslog on Unix
Asked Answered
E

4

29

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

Expunction answered 14/4, 2010 at 15:22 Comment(10)
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) ?Cibis
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: 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.Goggles
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.Expunction
@kriss: You almost never want to specialize stream class, but almost always write your own stream buffer.Goggles
@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...Expunction
@sbi: does'nt adding a new formatting method like "set_log_level()" or new manipulators need a new class ?Expunction
@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.Goggles
@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.Goggles
@sbi: the log I want to redirect are actual errors, warnings, etc and I definitely want to keep them in production code.Expunction
@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.Amplitude
B
40

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;
Burnet answered 16/12, 2010 at 3:16 Comment(0)
A
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.

Amplitude answered 14/4, 2010 at 16:5 Comment(2)
I think it's a good practice to create a separate ostream object for this specific task. Beware cplusplus.com/reference/iostream/ios_base/sync_with_stdio .Cavorilievo
@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 :-(Expunction
S
8

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;
Swainson answered 5/3, 2015 at 19:54 Comment(0)
P
3

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

Paregmenon answered 10/4, 2012 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.