1、一个日志库大体可分为前端(frontend)与后端(backend)。前端是供应用程序使用的接口(API),并生成日志信息;后端则是负责将日志信息写到目的地。每个线程都有自己的前端,而整个程序共用一个后端。对于生产者(前端)而言,要尽量做到低延迟、低CPU开销、无阻塞;对消费者(后端)而言,要尽量做到足够大的吞吐量,并占用较少的资源。
对C++程序而言,最好整个程序(包括主程序和程序库)都使用相同的日志库,程序有一个整体的日志输出,而不要每个组件都有各自的日志输出。从这个意义上讲,日志库是个singleton。
异步日志:每个【进程】最好只写一个日志文件,这样分析日志文件更容易,不必再多个文件中跳来跳去。实现方法是,用一个背景线程负责收集日志消息,并写入日志文件,其他业务线程只管往这个“日志线程”发送日志消息,这称为“异步日志”。
muduo日志库采用的是双缓冲技术,基本思路是准备两块缓冲:A与B,前端负责往buffer A中填数据(日志消息),后端负责将buffer B中的数据写入文件。当buffer A写满之后,交换A与B,让后端将buffer A中的数据写入文件,而前端负责往buffer B中填入新的日志文件。如此往复。用两个buffer的好处是在新建日志消息的时候不必等待磁盘文件操作,也避免每条消息都触发(唤醒)了后端日志线程。换言之,前端不是将一条条消息分别传送给后端,而是将多个日志消息拼成一个大的buffer传送给后端,相当于批处理,减少了线程唤醒的频率,降低了开销。另外,为了及时将消息写入文件,即使前端的buffer A未写满,日志库也会每三秒执行一次上述交换写入操作。
【.h】
#ifndef MUDUO_BASE_ASYNCLOGGING_H
#define MUDUO_BASE_ASYNCLOGGING_H
#include
#include
#include
#include
#include
#include
#include
#include
namespace muduo
{
class AsyncLogging : noncopyable
{
public:
AsyncLogging(const string& basename,
off_t rollSize,
int flushInterval = 3);
~AsyncLogging()
{
if (running_)
{
stop();
}
}
void append(const char* logline, int len);
void start()
{
running_ = true;
thread_.start();
latch_.wait();
}
void stop()
{
running_ = false;
cond_.notify();
thread_.join();
}
private:
void threadFunc();
typedef muduo::detail::FixedBuffer Buffer;
typedef std::vector> BufferVector;
typedef BufferVector::value_type BufferPtr;
const int flushInterval_;
std::atomic running_;
string basename_;
off_t rollSize_;
muduo::Thread thread_;
muduo::CountDownLatch latch_;
muduo::MutexLock mutex_;
muduo::Condition cond_;
BufferPtr currentBuffer_;
BufferPtr nextBuffer_;
BufferVector buffers_;
};
}
#endif // MUDUO_BASE_ASYNCLOGGING_H
【.cpp】
#include
#include
#include
#include
using namespace muduo;
AsyncLogging::AsyncLogging(const string& basename,
off_t rollSize,
int flushInterval)
: flushInterval_(flushInterval),
running_(false),
basename_(basename),
rollSize_(rollSize),
thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"),
latch_(1),
mutex_(),
cond_(mutex_),
currentBuffer_(new Buffer),
nextBuffer_(new Buffer),
buffers_()
{
currentBuffer_->bzero();
nextBuffer_->bzero();
buffers_.reserve(16);
}
void AsyncLogging::append(const char* logline, int len)
{
muduo::MutexLockGuard lock(mutex_);
if (currentBuffer_->avail() > len)
{
currentBuffer_->append(logline, len);
}
else
{
buffers_.push_back(std::move(currentBuffer_));
if (nextBuffer_)
{
currentBuffer_ = std::move(nextBuffer_);
}
else
{
currentBuffer_.reset(new Buffer); // Rarely happens
}
currentBuffer_->append(logline, len);
cond_.notify();
}
}
void AsyncLogging::threadFunc()
{
assert(running_ == true);
latch_.countDown();
LogFile output(basename_, rollSize_, false);
BufferPtr newBuffer1(new Buffer);
BufferPtr newBuffer2(new Buffer);
newBuffer1->bzero();
newBuffer2->bzero();
BufferVector buffersToWrite;
buffersToWrite.reserve(16);
while (running_)
{
assert(newBuffer1 && newBuffer1->length() == 0);
assert(newBuffer2 && newBuffer2->length() == 0);
assert(buffersToWrite.empty());
{
muduo::MutexLockGuard lock(mutex_);
if (buffers_.empty()) // unusual usage!
{
cond_.waitForSeconds(flushInterval_);
}
buffers_.push_back(std::move(currentBuffer_));
currentBuffer_ = std::move(newBuffer1);
buffersToWrite.swap(buffers_);
if (!nextBuffer_)
{
nextBuffer_ = std::move(newBuffer2);
}
}
assert(!buffersToWrite.empty());
if (buffersToWrite.size() > 25)
{
char buf[256];
snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
Timestamp::now().toFormattedString().c_str(),
buffersToWrite.size()-2);
fputs(buf, stderr);
output.append(buf, static_cast(strlen(buf)));
buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
}
for (size_t i = 0; i < buffersToWrite.size(); ++i)
{
// FIXME: use unbuffered stdio FILE ? or use ::writev ?
output.append(buffersToWrite[i]->data(), buffersToWrite[i]->length());
}
if (buffersToWrite.size() > 2)
{
// drop non-bzero-ed buffers, avoid trashing
buffersToWrite.resize(2);
}
if (!newBuffer1)
{
assert(!buffersToWrite.empty());
newBuffer1 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer1->reset();
}
if (!newBuffer2)
{
assert(!buffersToWrite.empty());
newBuffer2 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer2->reset();
}
buffersToWrite.clear();
output.flush();
}
output.flush();
}
在 陈硕所著《Linux多线程服务端编程中》,第117~119页,阐述了异步日志前端与后端交互的四种情形。在此不再详述。