7
\$\begingroup\$

Motivation: for whatever reason, some of the available 3rd party logging libraries don't really deal with programs that get fork'ed/exectuted well. For instance, boost::log creates some static state that can cause deadlocks if the program using it gets forked.

This class attempts to be a simple logger for programs that get forked/exec'ed.

For locking, I chose to use a boost interprocess mutex rather than a file lock, because if a child process gets stuck with the lock taken out, restarting the parent process will destroy the current mutex and create a new one.

I'm looking for style, performance and viability feedback. I've tested it and it seems to work well enough.

#pragma once

#include <iostream>
#include <fstream>
#include <locale>
#include <string>

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/permissions.hpp>
#include <boost/interprocess/sync/interprocess_sharable_mutex.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/format.hpp>

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>


static const char *kLoggerSegmentName = "MPLLoggerSeg";
static const char *kLoggerMutexName= "MPLLoggerIPCMutex";

enum LogLevel { MPLTRACE, MPLDEBUG, MPLINFO, MPLWARN, MPLERROR, MPLFATAL };


class MultiProcessLogger  {
private:
    LogLevel      fLoggingLevel;
    bool          fEnabled;
    bool          fParent;
    std::ofstream fLogFile;


    inline void writeToFile(std::string &msg) {
        using namespace boost::interprocess;
        managed_shared_memory segment(open_only, kLoggerSegmentName);
        interprocess_sharable_mutex *mutex = segment.find<interprocess_sharable_mutex>(kLoggerMutexName).first;
        scoped_lock<interprocess_sharable_mutex> lock(*mutex);
        fLogFile  << msg;
        //lock automatically unlocks when scope is left.
    }

    inline const char *getLevelString() {
        switch(fLoggingLevel) {
            case MPLTRACE:
                return "<trace>";
            case MPLDEBUG:
                return "<debug>";
            case MPLINFO:
                return "<info.>";
            case MPLWARN:
                return "<warn.>";
            case MPLERROR:
                return "<error>";
            case MPLFATAL:
                return "<fatal>";
            default:
                return "<     >";
        }
    }

    void destroySharedMemory() {
        using namespace boost::interprocess;
        try
        {
            shared_memory_object::remove(kLoggerSegmentName);
        } catch (...) {
            std::cerr << "Error:  unable to remove segment: " << kLoggerSegmentName << std::endl;
        }
    }

    //disable copy constructor.
    MultiProcessLogger(MultiProcessLogger &that);

public:

    MultiProcessLogger(bool enabled, const char* logDir, const char* fileName, LogLevel level) :
        fLoggingLevel(level), fEnabled(enabled), fParent(false) {
        if (!fEnabled) {
            return;
        }
        std::string logFilePath(logDir);
        logFilePath.append("/");
        logFilePath.append(fileName);
        fLogFile.open(logFilePath.c_str(), std::ios::app);
    }


    ~MultiProcessLogger() {
        if (!fEnabled) {
            return;
        }
        fLogFile.close();
        if (fParent) {
            destroySharedMemory();
        }
    }

    void initParentProcess() {
        using namespace boost::interprocess;
        fParent = true;
        destroySharedMemory();
        permissions perms;
        perms.set_unrestricted();
        managed_shared_memory segment(create_only, kLoggerSegmentName, 1024, 0, perms);
        interprocess_sharable_mutex *mutex= segment.construct<interprocess_sharable_mutex>(kLoggerMutexName)();
        if (!mutex) {
            std::cerr << "Error:  unable to create interprocess mutex for logger" << std::endl;
            abort();
        }
    }

    void initChildProcess() {
        fParent = false;
    }


    void log(LogLevel level, const char* msg, ...) {
        if (!fEnabled) {
            return;
        }
        if (fLoggingLevel > level) {
            return;
        }

        va_list args;
        va_start(args, msg);
        char msgBuf[512];
        vsnprintf(msgBuf, sizeof msgBuf, msg, args);
        va_end(args);
        std::string logMessage;

        //format:
        //time <level> proc:pid message


        boost::posix_time::ptime now  = boost::posix_time::second_clock::local_time();
        boost::posix_time::time_facet* facet = new boost::posix_time::time_facet();
        facet->format("%m-%d-%Y %I:%M:%S %p %Q");
        std::stringstream stream;
        stream.imbue(std::locale(std::locale::classic(), facet));
        stream << now;

        logMessage.append(stream.str());
        logMessage.append(" ");
        logMessage.append(getLevelString());
        logMessage.append(" ");
        logMessage.append("process");
        logMessage.append(":");
        char pidStr[6];
        snprintf(pidStr, 6, "%05d", getpid());
        logMessage.append(pidStr);
        logMessage.append(" ");
        logMessage.append(msgBuf);
        logMessage.append("\n");
        writeToFile(logMessage);
    }
};

Usage:

//in parent process
MultiProcessLogger logger(true, "/tmp", "myLog.log", MLPINFO);
logger.initParentProcess();
...
logger.log(MPLERROR, "hello %s", "world");
\$\endgroup\$
2
\$\begingroup\$
  • 512 is really a small fixed-size buffer for the formatted message (seriously)
    • Given all the other stuff the function has to do, I'd go with a dynamic buffer here as well. (Calling vsnprintf with 0 as buffer size should tell you how much you need.
  • the log function seems horribly inefficient: Creating the facet each time, multiple append without preallocation
  • Just use a single stringstream for the whole logMessage
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.