日志:程序运行过程中记录的程序运行状态信息
作用:记录了程序运行状态信息,便于程序员能够随时根据状态信息,对系统程序的运行状态进行分析。能够非常简便的进行详细的日志输出以及控制
本项目主要实现的是一个日志系统,其支持以下功能:
日志系统的技术实现主要包括两种类型:
利用
printf、std::cout
等输出函数将日志信息打印到控制台,但是对于大型商业化项目,为了方便排查问题,我们一般会将日志输出到文件或者说数据库方便查询和分析日志,主要分为同步日志和异步日志
同步写日志指的是当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑,日志输出语句与程序的业务逻辑语句将在同一个线程种运行。每调用一次打印日志API就对应一次系统调用write写日志文件
在高并发场景下,随着日志的数量越来越多,同步日志系统容易产生瓶颈:
异步日志是指在进行日志输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志输出操作,业务线程只需要将日志放在放到一个内存缓冲区,不需要等待即可继续执行后续业务逻辑(作为日志的生产者),而日志的落地操作交给单独的日志输出线程完成(作为日志的消费者)
这样的好处是即使日志没有真正的完成输出也不会影响业务线程,以提高程序的性能
在学C语言的时候,我们就已经接触不定参函数了,例如printf就是一个典型的可以根据格式化字符串解析,对传上来的数据进行格式化的函数
这种不定参函数在实际的使用中非常多见,这里简单的做一下介绍
__FILE__
和 __LINE__
是C语言的宏函数,可以用于获取文件名,和代码当前行数,我们可以使用printf打印一条包含当前文件信息和行数信息的日志
#include
int main() {
printf("[%s : %d] %s - %d\n", __FILE__, __LINE__, "zdp", 666); //输出: [test.cpp : 5] zdp - 666
return 0;
}
但是我们每次打印日志都要写printf,__FILE__,、__LINE__
实在是太麻烦了,我们可以使用不定参的宏函数对其进行替换
#define LOG(fmt, ...) printf("[%s : %d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__);
解释一下:
fmt(format)
:就是我们的格式化字符串,编译器就是以它为依据进行不定参解析的...
: 就是我们的不定参数"[%s:%d]" fmt
因为这两个都是格式化字符串,C语言是支持直接连接的__VA_ARGS__
也是C语言给我们提供的宏函数,用于给我们的fmt传参##__VA_ARGS__
加了##是为了告诉编译器,若我们只想传一个不定参数,可以省略前面的fmt参数的传递##__VA_ARGS__的意思就是
本来我们只想传递一个不定参数需要这么写 LOG(“%s”, “zdp”); 现在可以省略fmt参数的传递 LOG(“zdp”);就可以了
5.1.2 C风格不定参使用
#include
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);
用一段代码来理解这一系列接口的使用
#include
#include
#define LOG(fmt, ...) printf("[%s : %d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__);
void printNum(int count, ...) { // count 不定参数的个数
va_list ap; // va_list实际就是一个char*类型的指针
va_start(ap, count); // 将char*类型指针指向不定参数的起始位置
for (int i = 0; i < count; i++) {
int num = va_arg(ap, int); // 从ap位置取一个整形大小空间数据拷贝给num,并将ap向后移动一个整形大小空间
printf("param[%d], %d\n", i, num);
}
va_end(ap); // 将ap指针置空
}
int main() {
printNum(2, 666, 222);
return 0;
}
这里我们解释传入类型只能是int类型,我们如何使用上述接口将不定参数分离的原理,那么printf这类函数是如何将不定参数分离的呢?这是因为我们在使用printf函数开始传递了format参数,其中包含了%s, %d这类的信息,printf内部通过对format 参数进行解析就知道了后面的参数依次都是什么类型的,然后将类型依次放入va_arg函数,就可以将参数全部提取出来了
void myprintf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char* res;
int ret = vasprintf(&res, fmt, ap);
if (ret != -1) {
printf(res);
free(res);
}
va_end(ap);
}
5.1.3 C++风格不定参数使用
void xprintf() {
std::cout << std::endl;
}
/*C++风格的不定参数*/
template
void xprintf(const T &v, Args&&... args) {
std::cout << v;
if ((sizeof...(args)) > 0) {
xprintf(std::forward(args)...);
} else {
xprintf();
}
}
int main() {
printNum(2, 666, 222);
myprintf("%s - %d\n", "clx", 666);
xprintf("hello");
xprintf("hello", "world");
xprintf("hello", "I", " am" , "clx");
return 0;
}
项目中用到了很多种设计模式,设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它是一套提高代码复用性,可维护性,可读性,稳健性以及安全性的解决方案
从整体上理解六大设计原则,可以简要概括为一句话,用抽象构建框架,用实现扩展细节,具体到每一条设计原则,则对应一条注意事项
/* 饿汉单例模式 以空间换时间 */
class Singleton{
public:
static Singleton& getInstance() { return _eton; }
int getData() { return _data; }
private:
Singleton(int data = 99) : _data(data){}
~Singleton(){};
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
static Singleton _eton;
int _data;
};
Singleton Singleton::_eton;
int main() {
std::cout << Singleton::getInstance().getData() << std::endl;
return 0;
}
/* 懒汉单例模式 懒加载 -- 延时加载思想 -- 一个对象用的时候再实例化 */
// 这里介绍 作者提出的一种更加优雅简便的单例模式 Meyers Singleton int C++
// C++11后是线程安全的
class Singleton{
public:
static Singleton& getInstance() {
static Singleton _eton;
return _eton;
}
int getData() { return _data; }
private:
Singleton(int data = 99) : _data(data){}
~Singleton() {};
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
int _data;
};
int main() {
std::cout << Singleton::getInstance().getData() << std::endl;
return 0;
}
工厂模式是一种创建型的设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,因此实现创建-使用的分离
工厂模式分为:
class Fruit{
public:
virtual void name() = 0;
private:
};
class Apple : public Fruit{
public:
void name() override{
std::cout << "I'm a apple" << std::endl;
}
};
class Banana : public Fruit{
public:
void name() override {
std::cout << "I'm a banana" << std::endl;
}
};
class FruitFactory {
public:
static std::shared_ptr create(const std::string &name) {
if (name == "苹果") {
return std::make_shared();
} else {
return std::make_shared();
}
}
};
int main() {
std::shared_ptr fruit = FruitFactory::create("苹果");
fruit->name();
fruit = FruitFactory::create("香蕉");
fruit->name();
return 0;
}
这个模式的结构和管理产品对象的方式非常简单,但是它的扩展性非常差,当我们需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创造逻辑,违背了开闭原则
/* 工厂方法模式 */
class Fruit{
public:
virtual void name() = 0;
private:
};
class Apple : public Fruit{
public:
void name() override{
std::cout << "I'm a apple" << std::endl;
}
};
class Banana : public Fruit{
public:
void name() override {
std::cout << "I'm a banana" << std::endl;
}
};
class FruitFactory {
public:
virtual std::shared_ptr createFruit() = 0;
};
class AppleFactory : public FruitFactory {
public:
virtual std::shared_ptr createFruit() override {
return std::make_shared();
}
};
class BananaFactory : public FruitFactory {
public:
virtual std::shared_ptr createFruit() override {
return std::make_shared();
}
};
int main() {
std::shared_ptr ff(new AppleFactory());
std::shared_ptr fruit1 = ff->createFruit();
fruit1->name();
ff.reset(new BananaFactory());
std::shared_ptr fruit2 = ff->createFruit();
fruit2->name();
return 0;
}
工厂方法模式每次增减一个产品时,都需要增加一个具体的产品类和工厂类,这使得系统中类的个数成倍的增加,在一定程度上增加了系统的耦合度
#include
/* 简单工厂模式 */
class Fruit{
public:
virtual void name() = 0;
private:
};
class Apple : public Fruit{
public:
void name() override{
std::cout << "I'm a apple" << std::endl;
}
};
class Banana : public Fruit{
public:
void name() override {
std::cout << "I'm a banana" << std::endl;
}
};
class Animal {
public:
virtual void name() = 0;
};
class Lamp : public Animal {
public:
virtual void name() override {
std::cout << "I'm a Lamp" << std::endl;
}
};
class Dog : public Animal {
public:
virtual void name() override {
std::cout << "I'm a dog" << std::endl;
}
};
class Factory {
public:
virtual std::shared_ptr getFruit(const std::string& name) = 0;
virtual std::shared_ptr getAnimal(const std::string& name) = 0;
};
class FruitFactory : public Factory {
public:
virtual std::shared_ptr getFruit(const std::string& name) override{
if (name == "苹果") {
return std::make_shared();
} else {
return std::make_shared();
}
}
virtual std::shared_ptr getAnimal(const std::string& name) override{
return std::shared_ptr();
}
};
class AnimalFactory : public Factory {
public:
virtual std::shared_ptr getFruit(const std::string& name) override {
return std::shared_ptr();
}
virtual std::shared_ptr getAnimal(const std::string& name) override {
if (name == "山羊") {
return std::make_shared();
} else {
return std::make_shared();
}
}
};
class FactoryProducer {
public:
static std::shared_ptr create(const std::string &name) {
if (name == "水果") {
return std::make_shared();
} else {
return std::make_shared();
}
}
};
int main() {
std::shared_ptr ff = FactoryProducer::create("水果");
std::shared_ptr fruit = ff->getFruit("苹果");
fruit->name();
fruit = ff->getFruit("香蕉");
fruit->name();
ff = FactoryProducer::create("动物");
std::shared_ptr animal = ff->getAnimal("山羊");
animal->name();
animal = ff->getAnimal("小狗");
animal->name();
return 0;
}
抽象工厂模式适用于生产多个工厂系列产品衍生的设计模式,增加新的产品等级结构复杂,需要对原有系统进行较大修改,甚至需要修改抽象层代码,违背了开闭原则
建造者模式是一种创建型的设计模式,使用多个简单对象一步一步构建成一个复杂的对象,能够将一个复杂的对象的构建与它的表示分离,提供一种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题
#include
#include
#include
/* 通过MacBook的构造理解建造者模式*/
class Computer{
public:
Computer(){};
void setBoard(const std::string &board) { _board = board; }
void setDisplay(const std::string &display) { _display = display; }
virtual void setOs() = 0;
void showParamaters() {
std::string param = "Computer Paramaters: \n";
param += "\tBoard: " + _board + "\n";
param += "\tDispaly: " + _display + "\n";
param += "\tOs: " + _os + "\n";
std::cout << param << std::endl;
}
protected:
std::string _board;
std::string _display;
std::string _os;
};
class MacBook : public Computer{
public:
virtual void setOs() override {
_os = "Mac OS x12