
【C++模块实现】| 【01】日志系统实现
【日志类型】:
【日志功能】:
日志通常用来故障诊断和追踪、性能分析等;
【日志库】:可分为前、后端;
【如何将数据高效地传输到后端】
【前端API风格】:
日志文件压缩与归档
如何解决崩溃,日志丢失
日志格式

建议
运行时的日志过滤器,控制不同的部件的输出日志级别,但可用放到编译器做,让整个程序有一个整体的输出级别即可;
【优化】
【直接写日志】
muduo双缓冲技术


【接受方】:

re-fill newBufer1和newBuffer2会被buffer重新填充,便于替换前端的当前缓冲和预备缓冲,四个缓冲会在启动时填为0,避免出现page fault;
前后端各有两个缓冲区以及一个缓冲区数组;
【前端写日志频度不高,后端3s后超时写入文件】

【3s超时前写满了缓冲区,唤醒后端线程开始写入文件】

【前端需要分配新buffer的情况】

【文件写入速度慢,导致前端耗尽了两个缓冲区,并分配新的缓冲区】


即前端陷入死循环,发送的日志,超过后端处理;
// 往队列增加数据
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); // 打开logFile
BufferPtr newBuffer1(new Buffer); // 缓冲区1
BufferPtr newBuffer2(new Buffer); // 缓冲区2
newBuffer1->bzero();
newBuffer2->bzero(); // 初始化为0
BufferVector buffersToWrite; // 写入队列
buffersToWrite.reserve(16); // 设置大小为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_));
// 将buf1的缓冲区交给当当前缓冲区
currentBuffer_ = std::move(newBuffer1);
// 将队列转移到写入队列
buffersToWrite.swap(buffers_);
// 若该缓冲区为空,则将buf2给它
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<int>(strlen(buf))); // 追加
buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
}
// 遍历队列
for (const auto& buffer : buffersToWrite)
{
// FIXME: use unbuffered stdio FILE ? or use ::writev ?
output.append(buffer->data(), buffer->length());
}
// 删除其他无关的数据,只保留2个
if (buffersToWrite.size() > 2)
{
// drop non-bzero-ed buffers, avoid trashing
buffersToWrite.resize(2);
}
// 若buf1为空
if (!newBuffer1)
{
assert(!buffersToWrite.empty());
// 将最后一个队列给它
newBuffer1 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer1->reset();
}
// 若buf2为空
if (!newBuffer2)
{
assert(!buffersToWrite.empty());
// 将最后一个队列给它
newBuffer2 = std::move(buffersToWrite.back());
buffersToWrite.pop_back();
newBuffer2->reset();
}
buffersToWrite.clear(); // 清空
output.flush(); //
}
output.flush();
}
【使用队列】
高效的前后端消息传递可使用BlockingQueue/BoundedBlockingQueue,但需要每条日志都分配内存,后端线程需要将其释放;
【更改core dump文件名】
通过sysctl设置kernel.core_pattern参数或修改/proc/sys/kernel/core_pattern,让core dump都产生不同的文件;