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");
1 Answer 1
- 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.
- Given all the other stuff the function has to do, I'd go with a dynamic buffer here as well. (Calling vsnprintf with
- the
log
function seems horribly inefficient: Creating the facet each time, multipleappend
without preallocation - Just use a single
stringstream
for the wholelogMessage
Explore related questions
See similar questions with these tags.