基于spdlog实现日志控制台输出、文件输出或控制台+文件同时输出

基于spdlog封装了一套接口,可实现控制台log输出、文件log输出,或控制台+文件同时输出,根据自己需求自由切换。亲测OK,分享一下。

头文件定义基类HrgLogger和三个子类ConsoleLogger、FileLogger和MultiLogger:

#ifndef __HRG_LOG__
#define __HRG_LOG__

#include <iostream>
#include <string>
#include <sstream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>


#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/logger.h"

#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/common.h"

enum LoggerMode
{
	LAB_MODE = 0,  //实验室模式,打印比较细致的信息
	TRIAL_MODE = 1,   //试用模式,打印调试信息
	USER_MODE = 2,    //用户模式,打印较重要的信息
};

#define LOG_MODE USER_MODE

using namespace spdlog;

#define LOG_LEVEL_TRACE spdlog::level::trace
#define LOG_LEVEL_DEBUG spdlog::level::debug
#define LOG_LEVEL_INFO spdlog::level::info
#define LOG_LEVEL_WARN spdlog::level::warn
#define LOG_LEVEL_ERROR spdlog::level::err
#define LOG_LEVEL_CRITICAL spdlog::level::critical
#define LOG_LEVEL_OFF spdlog::level::off

using print_level = spdlog::level::level_enum;    //变量重命名
using format_string = string_view_t;      // 变量重命名 //wstring_view_t;   //fmt::basic_string_view<char>;


class HrgLogger
{
public:
	HrgLogger()
	{
		logger_created = false;
		logger_droped = false;
	}
	
	~HrgLogger();
	
	std::shared_ptr<spdlog::logger> hrg_logger;
	
	bool logger_created;         //标识日志创建状态
	bool logger_droped;          //标识日志关闭状态
	time_t logger_create_time;   //日志创建时间

	/* Generate log file name automatically according to real time, usually used when many real-time log files should be output */
	virtual void generate_file_name_automaticaly();

	/* Set a specified log file name, usually used when only one log file is needed.
		input param @filename is the specified filename.
	  */
	void set_specified_file_name(std::string filename);

	/* The abstract api for create logger, would be realized by the derived classes */
	virtual void create_logger() = 0;   //创建logger的纯虚函数,只能在子函数中实现
		
	/* 设置日志的打印级别 */
	void set_print_level(print_level lvl)
	{
		hrg_logger->set_level(lvl);
	}

	/* 重新封装logger的trace, debug, warn, error和critical打印接口 */
	template<typename... Args>
	inline void print_trace(format_string fmt, const Args &... args)
	{
		hrg_logger->trace(fmt, args...);
	}
	
	template<typename... Args>
	inline void print_debug(format_string fmt, const Args &... args)
	{
		hrg_logger->debug(fmt, args...);
	}
	
	template<typename... Args>
	inline void print_info(format_string fmt, const Args &... args)
	{
		hrg_logger->info(fmt, args...);
	}
	
	template<typename... Args>
	inline void print_warn(format_string fmt, const Args &... args)
	{
		hrg_logger->warn(fmt, args...);
	}
	
	template<typename... Args>
	inline void print_error(format_string fmt, const Args &... args)
	{
		hrg_logger->error(fmt, args...);
	}

	template<typename... Args>
	inline void print_critical(format_string fmt, const Args &... args)
	{
		hrg_logger->critical(fmt, args...);
	}
	
	/* 销毁logger */
	void destroy_logger()
	{
		spdlog::drop_all();
		logger_droped = true;
	}
	
protected:
	std::string log_full_name;  //The full name contains log_path and log_file_name
	std::string log_path;  // The log path which is specified in class constructor
	
};

/* 控制台logger子类 */
class ConsoleLogger : public HrgLogger
{
public:
	ConsoleLogger()
	{
		std::cout << "ConsoleLogger constructor." << std::endl;
	}
	
	void generate_file_name_automaticaly(){}  //对于控制台子类来说,无需生成file文件,因此该方法的函数体置空
	
	virtual void create_logger();
	
};

/* 文件logger子类 */
class FileLogger : public HrgLogger
{
public:
	FileLogger(std::string str)
	{
		log_path = str;
		std::cout << "FileLogger Constructor." << std::endl;
	}
	
	virtual void create_logger();
};

/* 控制台+文件复合logger子类 */
class MultiLogger : public HrgLogger
{
public:
	MultiLogger(std::string str)       
	{
		log_path = str;
		std::cout << "MultiLogger with parameters constructor." << std::endl;
	}
	
	virtual void create_logger();
};

/* Logger选择类,通过传入的logger模式生成相应的logger */
class LoggerSelector
{
public:
	LoggerSelector(){}
	LoggerSelector(std::string str)
	{
		path = str;
	}
	~LoggerSelector(){}
	
	HrgLogger* select_logger(std::string out_type);
	HrgLogger* select_logger(int mode);

private:
	std::string path;
};


#endif

基类方法的实现:

HrgLogger::~HrgLogger()
{
	destroy_logger();
}


void HrgLogger::generate_file_name_automaticaly()
{
	struct tm *cur_time;
	time_t local_time;
	time(&local_time);
	cur_time = localtime(&local_time);

	/* 通过时间命名日志文件 */
	char filename[200];
	strftime(filename, 100, "%Y-%m-%d_%H-%M-%S.log", cur_time);

	/* log file name, does not contain file path */
	std::string log_file_name = std::string(filename);

	/* 判断日志路径是否存在,若不存在,则创建 */
	/* Check if the input log path exists, if not, create the path */
	if(access(log_path.c_str(), F_OK) != 0)
	{
		mkdir(log_path.c_str(), S_IRWXU); 
	}

	/* 日志文件全路径 */
	log_full_name = log_path + log_file_name;

	/* 记录日志创建时间,在通过时间长度控制日志文件大小时,该成员变量会被使用 */
	logger_create_time = local_time;

}

/* 除了使用时间命名日志文件,用户还可以自定义日志文件名称 */
void HrgLogger::set_specified_file_name(std::string filename)
{
	/* Check if the input log path exists, if not, create the path */
	if(access(log_path.c_str(), F_OK) != 0)
	{
		mkdir(log_path.c_str(), S_IRWXU); 
	}
	
	/* Make full log path */
	log_full_name = log_path + filename;
}

ConsoleLogger类方法实现:

/* 创建控制台logger */
void ConsoleLogger::create_logger()
{
	try
	{	
		hrg_logger = spdlog::stdout_color_mt("console");
		//hrg_logger->set_pattern("%+");	
		hrg_logger->set_pattern("[%Y-%m-%d %T][thread %t][%l]%v");
	}
	catch(const spdlog::spdlog_ex &ex)
	{
		std::cout << "Create console logger failed: " << ex.what() << std::endl;
		exit(EXIT_FAILURE);
	}
	
	if(!hrg_logger)
	{
		std::cout << "Create console logger failed." << std::endl;
	}
	else
	{
		std::cout << "Create console logger seccessfully." << std::endl;
		logger_created = true;
	}

}

FileLogger类方法实现:

/* 创建文件logger */
void FileLogger::create_logger()
{
	try
	{
		hrg_logger = spdlog::basic_logger_mt("basic_logger", log_full_name.c_str());	
		
		spdlog::set_pattern("[%Y-%m-%d %T][%l]%v"); 
		hrg_logger->set_level(spdlog::level::trace);
	}
	catch(const spdlog::spdlog_ex &ex)
	{
		std::cout << "Create file logger failed: " << ex.what() << std::endl;
		exit(EXIT_FAILURE);
	}
	
	if(!hrg_logger)
	{
		std::cout << " Create file logger failed." << std::endl;
	}
	else
	{
		std::cout << "Create file logger successfully." << std::endl;
		logger_created = true;
	}
}

MultiLogger类方法实现:

/* 创建复合logger */
void MultiLogger::create_logger()
{
	try
	{
		/* 通过multi-sink的方式创建复合logger,实现方式为:先分别创建文件sink和控制台sink,并将两者放入sink 向量中,组成一个复合logger */
		/* file sink */
		std::cout << "MultiLogger: log_full_name = " << log_full_name << std::endl;
		auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_full_name.c_str(), true);
		file_sink->set_level(spdlog::level::trace);
		file_sink->set_pattern("[%Y-%m-%d %T][%l]%v");
		std::cout << "MultiLogger: create file sink OK." << std::endl;
		
		/* 控制台sink */
		auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
		console_sink->set_level(spdlog::level::trace);
		console_sink->set_pattern("%+");
		std::cout << "MultiLogger: create console sink OK." << std::endl;
		
		/* Sink组合 */
		std::vector<spdlog::sink_ptr> sinks;
		sinks.push_back(console_sink);
		sinks.push_back(file_sink);
		hrg_logger = std::make_shared<spdlog::logger>("multi-sink", begin(sinks), end(sinks));
		
		std::cout << "MultiLogger: create multi sink OK." << std::endl;
		
	}
	catch(const spdlog::spdlog_ex &ex)
	{
		std::cout << "Create multi-logger failed: " << ex.what() << std::endl;
		exit(EXIT_FAILURE);
	}
	
	if(!hrg_logger)
	{
		std::cout << " Create multi-logger failed." << std::endl;
	}
	else
	{
		std::cout << "Create multi-logger successfully." << std::endl;
		logger_created = true;
	}
	
}

通过简单工厂模式实现logger的选择,提供两种方式,一种是通过字符串指定logger类,一种是通过int型mode指定目前使用的模式,可适用于实验室阶段、试用阶段和正式的客户使用阶段,每个阶段分别设置不同的日志level。其中,实验室阶段使用控制台输出,打印的信息较细致,设置输出level为trace;试用阶段使用控制台+文件输出,打印debug以上级别的信息,便于在客户现场定位问题;正式客户使用阶段只需通过文件输出日志,可保留比较重要的打印信息,设置打印级别为info以上即可。

/* 通过指定输出名称选择logger */
HrgLogger* LoggerSelector::select_logger(std::string out_type)
{
	HrgLogger *p_logger = NULL;
	
	if(out_type == "console")
	{
		p_logger = new ConsoleLogger();
		p_logger->create_logger();
		
	}
	else if(out_type == "file")
	{
		p_logger = new FileLogger(path);
		std::cout << "For file logger, path = " << path << std::endl;
		p_logger->generate_file_name_automaticaly();
		p_logger->create_logger();
	}
	else if(out_type == "both")
	{
		p_logger = new MultiLogger(path);
		p_logger->generate_file_name_automaticaly();
		p_logger->create_logger();
	}
	else 
	{
		std::cout << "Unsupported logger type!" << std::endl;
		return NULL;
	}
	
	return p_logger;
}


/* 通过日志模式选择logger */
HrgLogger* LoggerSelector::select_logger(int mode)
{
	HrgLogger *p_logger = NULL;
	
	switch(mode)
	{
		case LAB_MODE:
		{
			p_logger = new ConsoleLogger();
			p_logger->create_logger();
			p_logger->set_print_level(LOG_LEVEL_TRACE);
			
			break;			
		}
		case TRIAL_MODE:
		{
			p_logger = new MultiLogger(path);
			p_logger->generate_file_name_automaticaly();
			p_logger->create_logger();
			p_logger->set_print_level(LOG_LEVEL_DEBUG);
			
			break;
		}
		case USER_MODE:
		{
			p_logger = new FileLogger(path);
			p_logger->generate_file_name_automaticaly();
			p_logger->create_logger();
			p_logger->set_print_level(LOG_LEVEL_INFO);
			
			break;
		}
		default:
		{
			break;
		}
	}

	return p_logger;
}

测试程序,只测试了通过字符串形式创建logger,但在实际工程使用中,较多使用mode形式,用户可根据自己喜好及习惯选择实现方式。

#include <iostream>
#include <string>
#include <unistd.h>
#include "hrg_log.h"


void multi_sink_example()
{
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_sink->set_level(spdlog::level::warn);
    console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");

    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log/multisink.txt", true);
    file_sink->set_level(spdlog::level::trace);

#if 0
    spdlog::logger logger("multi_sink", {console_sink, file_sink});
    logger.set_level(spdlog::level::debug);
    logger.warn("this should appear in both console and file");
    logger.info("this message should not appear in the console, only in the file");
#endif

	std::vector<spdlog::sink_ptr> sinks;
	sinks.push_back(console_sink);
	sinks.push_back(file_sink);
	std::shared_ptr<spdlog::logger> p_logger;
	p_logger = std::make_shared<spdlog::logger>("multi_sink", begin(sinks), end(sinks));
	
	p_logger->warn("this should appear in both console and file");
	p_logger->info("this message should not appear in the console, only in the file");
}

#if 0
int main()
{
	std::string file_path = "./log/";
	
	/* 测试控制台日志 */
	HrgLogger *my_console_logger = new ConsoleLogger();
	if(!my_console_logger)
	{
		std::cout << "Create console logger failed!" << std::endl;
		exit(1);
	}
	my_console_logger->create_logger();
	my_console_logger->set_print_level(LOG_LEVEL_TRACE);
	
	my_console_logger->print_trace("This is a trace message for console logger, number {}.", 1);
	my_console_logger->print_debug("This is a debug message for console logger, number {}.", 2);
	my_console_logger->print_info("This is a info message for console logger, number {}.", 3);
	my_console_logger->print_warn("This is a warn message for console logger, number {}.", 4);
	my_console_logger->print_error("This is a error message for console logger, number {}.", 5);
	
	delete my_console_logger;
	
	
	/* 测试文件日志 */
	HrgLogger *my_file_logger = new FileLogger(file_path);
	if(!my_file_logger)
	{
		std::cout << "Create file logger failed!" << std::endl;
		exit(1);
	}
	my_file_logger->set_specified_file_name("myfilelog.txt");  //测试指定日志名称
	my_file_logger->create_logger();
	my_file_logger->set_print_level(LOG_LEVEL_INFO);
	
	my_file_logger->print_trace("This is a(n) {} message for file logger.", "trace");
	my_file_logger->print_debug("This is a(n) {} message for file logger.", "debug");
	my_file_logger->print_info("This is a(n) {} message for file logger.", "info");
	my_file_logger->print_warn("This is a(n) {} message for file logger.", "warn");
	my_file_logger->print_error("This is a(n) {} message for file logger.", "error");
	
	delete my_file_logger;
	
	sleep(3);
	
	/* 测试复合日志 */
	HrgLogger *my_multi_logger = new MultiLogger(file_path);
	if(!my_multi_logger)
	{
		std::cout << "Create multi logger failed!" << std::endl;
		exit(1);
	}
	
	my_multi_logger->generate_file_name_automaticaly();  //测试自动生成日志名称
	my_multi_logger->create_logger();
	my_multi_logger->set_print_level(LOG_LEVEL_DEBUG);
	
	my_multi_logger->print_trace("This is a trace message for multi logger.");
	my_multi_logger->print_debug("This is a debug message for multi logger.");
	my_multi_logger->print_info("This is a info message for multi logger.");
	my_multi_logger->print_warn("This is a warn message for multi logger.");
	my_multi_logger->print_error("This is a error message for multi logger.");
	
	delete my_multi_logger;

}
#endif

int main()
{
	std::string file_path = "./log/";
	
	LoggerSelector *p_selector = new LoggerSelector(file_path);
	
	/* 测试控制台日志 */
	HrgLogger *my_console_logger = p_selector->select_logger("console");
	if(!my_console_logger)
	{
		std::cout << "Create console logger failed!" << std::endl;
		exit(1);
	}
	//my_console_logger->create_logger();
	my_console_logger->set_print_level(LOG_LEVEL_TRACE);
	
	my_console_logger->print_trace("This is a trace message for console logger, number {}.", 1);
	my_console_logger->print_debug("This is a debug message for console logger, number {}.", 2);
	my_console_logger->print_info("This is an info message for console logger, number {}.", 3);
	my_console_logger->print_warn("This is a warn message for console logger, number {}.", 4);
	my_console_logger->print_error("This is an error message for console logger, number {}.", 5);
	
	delete my_console_logger;
	
	
	/* 测试文件日志 */
	HrgLogger *my_file_logger = p_selector->select_logger("file");
	if(!my_file_logger)
	{
		std::cout << "Create file logger failed!" << std::endl;
		exit(1);
	}
	my_file_logger->set_print_level(LOG_LEVEL_INFO);
	
	my_file_logger->print_trace("This is a(n) {} message for file logger.", "trace");
	my_file_logger->print_debug("This is a(n) {} message for file logger.", "debug");
	my_file_logger->print_info("This is a(n) {} message for file logger.", "info");
	my_file_logger->print_warn("This is a(n) {} message for file logger.", "warn");
	my_file_logger->print_error("This is a(n) {} message for file logger.", "error");
	
	delete my_file_logger;
	
	/* 延迟3秒,以免复合日志中的文件名称与文件日志的文件名称冲突 */
	sleep(3);
	
	/* 测试复合日志 */
	HrgLogger *my_multi_logger = p_selector->select_logger("both");
	if(!my_multi_logger)
	{
		std::cout << "Create multi logger failed!" << std::endl;
		exit(1);
	}
	
	my_multi_logger->generate_file_name_automaticaly();  //测试自动生成日志名称
	my_multi_logger->set_print_level(LOG_LEVEL_DEBUG);
	
	my_multi_logger->print_trace("This is a trace message for multi logger.");
	my_multi_logger->print_debug("This is a debug message for multi logger.");
	my_multi_logger->print_info("This is an info message for multi logger.");
	my_multi_logger->print_warn("This is a warn message for multi logger.");
	my_multi_logger->print_error("This is an error message for multi logger.");
	
	delete my_multi_logger;
	
	delete p_selector;
	
	return 0;

}

编译执行,输出分为控制台输出和日志文件输出。

控制台输出:

 

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值