日志的解读
日志的格式 :fmt formatter
日志格式是指日志消息的结构化表示方式,通常包括时间戳、日志等级、日志来源(如文件名和行号)、日志消息本身等。
日志模式 pattern
是指日志记录的实现方式,例如是同步记录还是异步记录,以及如何处理日志文件(如分割、滚动等)。常见的日志模式包括:
日志等级 level debug info warn error off
日志等级用于指示日志消息的严重性
spdlog 里的核心设计:logger以及sink。 在spdlog中,我们将整个日志系统的运作分为两个部分:
日志器(logger) : 接受用户的日志输入,将日志内容分发到关联的sink中进行处理
输出处理器(sink):是日志输出的实际处理者,每个sink可以有自己的格式和日志等级
class SPDLOG_API base_sink : public sink {
设计模式
synchronous_factory 工厂来创建 logger 对象:
struct synchronous_factory
// 创建 Sink
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); //完美转发SinkArgs
// 创建 Logger
auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink));
// 注册 Logger
details::registry::instance().initialize_logger(new_logger);
include/spdlog/details/registry.h
通过 std::move,我们显式地将资源所有权从当前作用域转移到 logger 的构造函数中,而不是进行资源的深拷贝。
这种模式不仅高效,还避免了不必要的拷贝操作
构造函数
pass by value and move的哲学,即通过值传递 (pass by value) 来接收参数,随后通过移动操作转移其内容。
当资源所有权变换的时候使用std::move
基本内容
分层和分类关注点
###粗粒度关注点分离
库 源文件 头文件 编译文件
模块化编程:通过函数-类以及名字空间的组合和源码的组织来表达模块化
模块使用另外一个模块--接口(模块的实现和使用--实现和接口分开)
###细粒度的关注点分离-抽象
名字空间 函数 类
namespace class function template
macro #include #ifdef #endif 宏 macro
inline friend 作用域 生命周期
###语法层面落实
单个类型-数据类型 数据结构 算法等精致表达
namespace
01.背景: name clash 命名冲突--
02.名字空间--相同作用域--表达逻辑分组的一种机制
03.定义-声明--使用
namespace using ::
声明和使用-组合和选择机制
using std::string; using声明(using \<namespace>::\<identifier>)
using namespace std; using指令(using namespace \<namespace>)
04.名字空间的组合--名字冲突和二义性
名字空间的别名--内联名字空间--名字空间嵌套--版本控制
namespace DTT= Data_test_transformer;
DTT:String s1= "mytest";
05.using是一个非常有用的关键字,
主要用于两个重要的场景:命名空间(Namespace)的使用和类型别名(Type Alias)的创建。
它能够在一定程度上简化代码书写,同时也提供了更灵活的类型定义方式
using声明(using \<namespace>::\<identifier>)
using指令(using namespace \<namespace>)
using用于类型别名(Type Alias)方面的定义
using用于类型别名是一种创建新类型名来指代现有类型的方式。
例如using MyInt = int;,这里using关键字后面跟着新的类型别名(MyInt),再通过=指定它所代表的实际类型(int)
pragma(预处理指令)
宏 macro-- #define #ifdef #endif
宏: 在预处理阶段就会将函数与程序中对应的语句进行替换,进而优化了多次调用函数所开辟的函数栈帧。
条件编译
using std::cout,它告诉编译器,在当前作用域内,可以直接使用std命名空间中的cout这个标识符,而不需要每次都使用完整的std::cout限定符
using namespace std,这是一种比较宽泛的方式。
它的定义是让指定命名空间中的所有标识符在当前作用域内都可以直接使用,就好像这些标识符是在当前作用域中定义的一样。
这种方式在方便的同时也带来了潜在的命名冲突风险
预定义宏
pragma
once参数在头文件(*.h)的开头使用,目的是防止该文件被包含(#include)多次。
#pragma once
class
1.所有成员的声明都必须在类的内部,
但是成员函数体的定义则既可以在类的内部也可以在类的外部。
当定义在类的外部时函数名之前需要加上类名和作用域运算符(::)以显式的指出该函数是对应类的成员函数,并且,定义需与声明匹配
在类的外部定义成员函数时,必须同时提供类名和函数名
2.定义在类内部的函数是隐式的inline函数
类内声明
定义
类的成员函数可以分为内联函数和外联函数-外联是相对于内联说的,只有类才有内联外联函数。
类内定义:将函数的函数体定义在类内
类内定义与内联函数的关系:类内定义就是默认为inline内联函数
类外定义:
类外定义就是函数体在类外面,分为同文件类外定义和分文件类外定义
inline 是对编译器的建议
外联函数是声明在类体内,定义在类体外的成员函数。外联函数的函数体在类的实现部分
inline virtual friend
inline 语句行替换
virtual -- 动态绑定
虚函数,虚析构函数,纯虚函数,多态,虚基类
动态绑定是指在运行时根据对象的实际类型决定调用哪个方法。实现动态绑定的关键是虚函数。通过在基类中声明虚函数,派生类可以重写这些函数;
function
编写方法--对象--编译方式
内联函数:
inline关键字修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,
因此没有函数调用建立栈帧的开销,进而提升程序运行的效率
内联外联函数
和类有关的内联函数--又对应外联函数的说法
函数模版中的函数参数列表的类型尽量使用引用&,如参数不修要修改则加上const修饰。
virtual void flsuh_() = 0 ;
①纯虚函数没有函数体;
②最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
③这是一个声明语句,最后应有分号
模板
类型-泛型
泛型编程是用来编写与类型无关的通用代码,是代码复用的一种手段
模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。
模版可以分为两类,一个是函数模版,另外一个是类模版
模板(Template)是实现泛型编程的核心技术
函数模板、类模板、非类型模板参数、模板特化
模版函数不允许自动类型转换,但普通函数可以进行自动类型转换
实例化
不同类型的参数使用函数模版的过程称为函数模版的实例化。函数模版的实例化分为:隐式实例化和显式实例化。
1.隐式实例化:让编译器根据实参推演模版参数的实际类型
2.显式实例化:在函数名后的<>中指定模板参数的实际类型
模板的特化:在原模版类的基础上,针对特殊类型所进行特殊化的实现方式。
模版特化又分为函数模版特化和类模版特化。
模版的声明与定义分离开,在头文件中进行声明,源文件中完成定义
C++提供了多种方式来定义类型别名,包括typedef、using和#define
类型别名(Type Alias)
别名模板(Alias Template 使用 using 结合模板参数,为泛型类型创建别名。保留模板的泛型特性,允许延迟类型确定。
// 为 std::vector 创建泛型别名
template<typename T>
using Vec = std::vector<T>;
变参数模板(Variadic Templates)的概念
变参数模板的语法是使用typename...来表示可变数量的模板参数。在函数参数中,Args... args表示可变数量的函数参
编译
跨平台
SPDLOG_API宏通常用于控制类的导出(在DLL构建中)或确保类的正确导入(在DLL使用者中)。这对于跨平台开发尤其重要,
因为在Windows平台上,DLLs需要特定的导出声明,
而在Unix-like系统(如Linux和macOS)上,这通常不是必需的,或者使用不同的机制(如__attribute__)
typename是用来定义模板参数关键字,也可以使用class。
模版参数列表<typename 类型1, typename 类型2,......> 类比 函数参数列表(类型 变量1 , 类型 变量2, .......)
语法
1.类语法的细则:
= default是C++11引入的一种特性,它允许显式要求编译器生成默认的特殊成员函数。
特殊成员函数包括默认构造函数、复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符以及析构函数
=default 明确表明你的意图,即要求编译器生成默认特殊成员函数的定义
=delete 是一个新的特性,它允许我们显式地禁止编译器自动生成某些函数
explicit 是一个关键字,用于修饰类的构造函数,以防止编译器进行隐式类型转换。它的主要作用是避免意外的隐式转换,从而提高代码的安全性和可读性。
隐式类型转换的问题
2.enum 枚举类别
3.指针--智能指针
std::unique_ptr<spdlog::formatter>
std::make_shared 是创建 std::shared_ptr 的推荐方式之一
std::mutex (互斥锁)提供了基本的互斥访问保护
std::atomic<int>
std::atomic是 C++ 中的一个模板类,它提供了原子操作,使得在多线程环境中对数据的访问是线程安全的 避免了数据竞争(data race)和其他并发问题
load():从原子对象中加载值。
store():将值存储到原子对象中。
std::memory_order_relaxed
memory_order_relaxed 进行的读写操作只保证了这个操作本身的原子性,但不提供同步或顺序一致性的保证
static_cast<>
强制类型转换是:static_cast,dynamic_cast,const_cast,reinterpret_cast1、static_caststatic_cast(静态转换)
static_cast<目标类型>(表达式)
int c = static_cast<int>(3.14); // 将 double 转换为 int
4.一,final
1,final用于修饰类,表示此类不想被其他的类继承。
2, final用于修饰虚函数,表示该虚函数不想被重写
override关键字用于派生类中,表示该成员函数重写了基类中的虚函数。如果基类中没有此虚函数,则会编译报错。
5.完美转发(Perfect Forwading)
std::forward
std::move和std::forward
每个sink都是继承自base_sink,在base_sink中使用互斥锁的结构来保证每个线程的日志被完整输出。
日志:
### 读取日志 写入日志 配置日志 分析日志
log
记录系统活动信息--when where who what
IP 地址在什么时间用什么服务做了什么操作-- IP 进程 事件
###系统日志
systemd-journald.service 内存的日子还记录文件
deamon-- rsyslog.service
工具: logrotate 日志轮询--容量和更新
安全--
###配置文件
/etc/systemd/journald.conf
/etc/rsyslog.conf
/etc/cron.daily/
/etc/logrotate.conf
###文件地址-目录
/var/log/
本次开机启动 /var/log/boot.log
###命令和参数
journalctl
###第三方日志
自定义数据存储到 logger
分析日志 logwatch
cat 命令用于查看文件的内容,适用于查看较短的日志文件
less /var/log/syslog
head
tail 命令用于查看文件的末尾部分,默认显示最后10行。常用选项包括-n指定行数和-f用于实时跟踪日志文件的更新
tail -f /var/log/syslog
grep命令用于在文件中搜索匹配特定模式的行,适用于从日志文件中提取特定信息
awk sed grep
journalctl 提供了-f(--follow)参数来追踪日志,可实时输出所产生的日志。
参考
https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/sink.h
https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/base_sink.h
Spdlog源码解析(一):从一个基本的文件日志入手学习Modern C++ https://zhuanlan.zhihu.com/p/13885082148