3. 优雅地实现简易日志

发布时间:2023-07-11 01:03:32

前言

日志能够记录程序运行情况,从而帮助发现和解决系统问题。

有许多完善的日志库可以使用,然而多少是需要学习成本的,还会引入依赖库。

在程序的起步阶段,我们可能喜欢更clean的方式。

自己实现一个,能够满足基础功能需求即可。

实现

这里只给出了主要逻辑,也即模拟IO库。
对于程序信息、日志格式、日志级别、异常细化等,可以在此基础上添加/修改。

log.hpp

#pragma once

#include 
#include 

namespace YQ {
using std::string;
using std::ofstream;
using std::ostream;

class Logger {
	template<typename T>
	friend Logger &operator<< (Logger &logger, const T &var);
	friend Logger &operator<< (Logger &logger, ostream & (*op) (ostream &));
public:
	Logger (const string &out_file);
	~Logger();
	void reset();
private:
	const string m_out_file;
	ofstream m_ofstream;
};


template<typename T>
Logger &operator<< (Logger &logger, const T &var)
{
	logger.m_ofstream << var;
	if (logger.m_ofstream.fail())
		throw std::runtime_error ("failed to output to file");
	return logger;
}

Logger &operator<< (Logger &logger, ostream & (*op) (ostream &));

}

我们提供来reset()接口来让用户手动恢复日志流的状态。这是因为日志IO过程中可能会抛出异常,这里只象征性地抛出rumtime_error,应该细化。

这里提供了两个operator<<实现。
第一个模板实现是为了匹配所有内置类型、自定义类型。
第二个实现,是为了能够使用std::endl等操纵符。

操纵符为模板实现,实际也就是函数,它接受流作为参数,并对其进行操纵。

logger.cpp

#include "log.h"
#include "err.h"

namespace YQ {

Logger::Logger (const string &out_file) :
	m_out_file (out_file)
{
	m_ofstream.open (out_file, std::ios::out | std::ios::app);
	if (!m_ofstream)
		throw std::runtime_error ("open()");
}

Logger::~Logger()
{
	m_ofstream.close();
	if (m_ofstream.fail())
		err (-1, "file:%s, line:%d", __FILE__, __LINE__);
}

void Logger::reset()
{
	m_ofstream.clear();
}

Logger &operator<< (Logger &logger, ostream & (*op) (ostream &))
{
	(*op) (logger.m_ofstream);
	return logger;
}

}

析构的过程中,理论上来说m_ofstream.close()是有可能失败的。我们检查流的状态来判断,如果失败,就调用err打印错误并终止程序。

如果失败,那么有三种策略:

  1. 抛出异常
    • 这个虽然看起来很美好,但原则上是不可取的,析构函数不应该抛出异常(见《Effective C++》条款08)。
  2. 终止程序
    • 虽然看起来很痛苦,但很合理。它保证程序绝对正确、符合预期地运行。
  3. 打印警告,不终止
    • 这容忍了一些错误,使得程序继续运行。当你希望程序(或者更适合)即使发生错误也继续运行下去时,可以采取这种策略。

例子

#include 
using namespace std;

#include "log.h"
using YQ::Logger;

int main (int argc, char **argv)

{
	Logger logger ("1.txt");
	for (int i = 1 ; i < argc ; ++i) {
		logger << argv[i] << std::endl;
	}
}

我们实际项目中,不直接使用Logger类,而是定义静态的Logger对象,使用单例/多例模式。构造时传入配置文件名。

定义一些获取对象的方法,例如:

Logger&
log()
{
	static Logger logger("/var/log/mylog/log.log");
	return logger;
}

使用时: log() << "hello world" << std::endl;

你可能感兴趣的:(项目小技巧,c++,c++)