当前位置:  编程技术>c/c++/嵌入式
本页文章导读:
    ▪C++ 异步&类型安全&printf风格的日志库      摘要      C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并不会过度讨论gdb,读者可以寻找.........
    ▪关于C++中static_cast和reinterpret_cast的区别 以及实例应用      〈一〉开门见山我们先通过两个例子对比了解一下reinterpret_cast(32位操作系统为例)1〉#include <iostream>using namespace std;int main(){int myArray[5]={0,1,2,3,4};char myChar[5]={'H','a','o','Y','u'};int *aPoint = myA.........
    ▪FFLIB C++ 异步&类型安全&printf风格的日志库      摘要      C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并不会过度讨论gdb,读者可以寻找.........

[1]C++ 异步&类型安全&printf风格的日志库
    来源:    发布时间: 2013-10-17

摘要

      C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并不会过度讨论gdb,读者可以寻找相关的资料阅读。Gdb是C++程序调试中非常重要的调试手段,其有如下特点:

  • l 通过增加断点,可以观察重点代码的执行
  • l 若程序出现segmentation fault,gdb可以输出调用堆栈,方便找到bug之所在
  • l 有些逻辑代码段非常不容易触发,可以在gdb环境下通过加断点、修改内存来强制进入特定的代码段
  • l 但是gdb不能用于生产环境,在几百上千在线的服务器程序上执行gdb的attach操作,是不可能接受的

      Gdb绝对是调试期的利器,另外一个调试期使用的既简单又实用的方法是printf,就是使用c库的函数printf输出变量到控制台。其优点是直观,可以完整的、清晰的观察程序的运行过程,而不需像gdb一样暂停程序。另外printf也只能用于开发调试环境,上线时服务器程序都是在后台运行的,printf将会失去作用。更重要的是因为gdb和printf都不会将数据存储,历史数据或历史操作都会在重启程序后消失。日志文件可以弥补gdb和printf的不足,我们需要一个具有如下功能的日志组件:

  • l 用于调试可以显示、记录变量、数据,即能支持像printf一样可以实时的在控制台输出显示,又能将记录存储文件,方便搜索查看历史记录
  • l 日志应该拥有良好的格式,即方便开发和运维人员的阅读,又要包含足够多的信息,例如事件记录时间、线程id、事件类型,事件的严重级别
  • l 日志文件应该被良好的组织,一方面日志应该按照每天单独文件夹分类,另一方面日志日志文件并应该过大,否则使用编辑器打开、搜索将会非常困难。日志内容也应该组织分类,比如数据库的操作日志和用户做任务的日志应该有明确的标志,这样可以对日志文件进行grep等进行过滤分类查看。
  • l 日志文件必须非常容易配置,当调试时期望看到尽可能多的内容,但是不关心的内容需要被过滤掉,比如调试用户任务模块时,可以不显示数据库相关日志。在上线后,运维只关心报错信息,比警告级别低的日志需要被屏蔽。在调试时,开发人员经常会盯着控制台的输出,相比于普通级别日志内容,错误级别的日志更应该引起开发注意力,所以重要级别的日志在控制台输出时应该有彩色高亮显示。
  • l 日志组件必须有高效的性能,一方面调用者期望日志组件调用后立即返回不影响逻辑层的效率,另一方面写文件属于io操作,比起内存操作慢的多得多。所以要求日志的接口调用是异步的,日志组件单独开启线程执行写文件操作,只有如此才能尽最大程度满足程序的实时性。

下面来探讨一下日志 的实现。

实现类

定义log_t类来封装对于日志配置、格式化、输出的操作。log_t主要的功能有:

  • l 支持对日志级别的配置
  • l 支持对日志类别的配置
  • l 支持配置日志内容是否输出到文件和控制台
  • l 格式化日志
class log_t
{
public:
log_t(int level_, const string& all_class_, const string& path_, const string& file_,
bool print_file_, bool print_screen_);
virtual ~log_t();

void mod_level(int level_, bool flag_);
void mod_class(const string& class_, bool flag_);
void mod_print_file(bool flag_);
void mod_print_screen(bool flag_);
bool is_level_enabled(int level_);
const char* find_class_name(const char* class_);

void log_content(int level_, const char* str_class_, const string& content_);
};

接口log_content 负责格式化和输出日志内容,其主要实现代码如下:

void log_t::log_content(int level_, const char* str_class_, const string& content_)
{
struct timeval curtm;
gettimeofday(&curtm, NULL);
struct tm tm_val = *localtime(&(curtm.tv_sec));

char log_buff[512];
::snprintf(log_buff, sizeof(log_buff), "%02d:%02d:%02d.%03ld %s [%ld] [%s] ",
tm_val.tm_hour, tm_val.tm_min, tm_val.tm_sec, curtm.tv_usec/1000,
g_log_level_desp[level_], gettid(), str_class_);

if (m_enable_file && check_and_create_dir(&tm_val))
{
m_file << log_buff << content_ << endl;
m_file.flush();
}

if (m_enable_screen)
{
printf("%s%s%s%s\n", g_log_color_head[level_], log_buff, content_.c_str(), g_log_color_tail[level_]);
}
}

其执行的主要过程如下:

  • l 格式化时间,包括调用时的时分秒以及毫秒,为什么没有年月日呢?日志目录已经安装每天一个文件夹分类了,故这里省略了年月日信息。
  • l 增加日志级别信息,日志级别对应一个字符串描述,如debug级别日志每一行会包含DEBUG字符串。
  • l 记录线程id,这里并没有直接使用::pthread_self() 获取线程id,而是获取线程在系统中分配的“TID”,要知道线程和进程在内核中都有唯一的id,可以通过top进行查看,top -H –p [pid] 可以查看进程内的所有线程的运行负载情况,如果某个线程运行负载很高,我们需要知道到底是那一部分逻辑是热点,通过搜索日志,可以知道该线程负责了哪块逻辑,从而能够发现问题。
  • l 记录日志类别
  • l 若配置允许输出屏幕,那么利用printf输出,不同的日志级别会有不同的显示颜色,如printf("\033[1;33mDEBUG\033[0m"), DEBUG 会以黄色输出。
  • l 若配置允许输出文件,那么flush到文件中,若日期发生变化,重新创建日期目录,保证每一天一个文件夹,若单个文件内容超过5000行,会创建新的文件,避免文件内容过大,最终目录机构如下


    
[2]关于C++中static_cast和reinterpret_cast的区别 以及实例应用
    来源:    发布时间: 2013-10-17

〈一〉开门见山我们先通过两个例子对比了解一下reinterpret_cast(32位操作系统为例)

1〉

#include <iostream>
using namespace std;

int main()
{
int myArray[5]={0,1,2,3,4};
char myChar[5]={'H','a','o','Y','u'};


int *aPoint = myArray;
char *bPoint = myChar;

for(int i=0;i<5;i++)
{
cout<<*(aPoint++) <<"----------------" <<reinterpret_cast<unsigned long>(aPoint)<<endl;;

}

cout <<"-------------------------"<<endl;
for(i=0;i<5;i++)
{
cout<<*(bPoint++) <<"-------"<<reinterpret_cast<unsigned long>(bPoint)<<endl;
}

return 0;
}

 运行结果如下:

2〉

#include <iostream>
using namespace std;

int main()
{
int myArray[5]={0,1,2,3,4};
char myChar[5]={'H','a','o','Y','u'};


int *aPoint = myArray;
char *bPoint = myChar;

for(int i=0;i<5;i++)
{
//cout<<*(aPoint++) <<"--------" <<reinterpret_cast<unsigned long>(aPoint)<<endl;;
cout<<*(aPoint++) <<"--------" <<(aPoint)<<endl;;
}

cout <<"-------------------------"<<endl;
for(i=0;i<5;i++)
{
cout.clear();
//cout<<*(bPoint++) <<"-------"<<reinterpret_cast<unsigned long>(bPoint)<<endl;
cout<<*(bPoint++) <<"-------"<<(bPoint)<<endl;
}

return 0;
}

 运行结果为:\

以上结果有什么不同??原因是什么?不着急请继续往下看!!

我们由浅入深进行研究reinterpret_cast:

reinterpret_cast是C++里的强制类型转换符。
  操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。
  例如:int *n= new int ;
  double *d=reinterpret_cast<double*> (n);
  在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。
  因此, 需要谨慎使用 reinterpret_cast.
  并且:reinterpret_cast 只能在指针之间转换。
  == ===========================================
  == static_cast .vs. reinterpret_cast
  == ================================================
  reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)

 

reinterpret_cast <new_type> (expression)

reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。

什么是无关类型?我没有弄清楚,没有找到好的文档来说明类型之间到底都有些什么关系(除了类的继承以外)。后半句倒是看出了reinterpret_cast的字面意思:重新解释(类型的比特位)。我们真的可以随意将一个类型值的比特位交给另一个类型作为它的值吗?其实不然。

IBM的C++指南里倒是明确告诉了我们reinterpret_cast可以,或者说应该在什么地方用来作为转换运算符:

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。

(所谓"足够大的整数类型",取决于操作系统的参数,如果是32位的操作系统,就需要整形(int)以上的;如果是64位的操作系统,则至少需要长整形(long)。具体大小可以通过sizeof运算符来查看)。

 

MSDN的Visual C++ Developer Center 给出了它的使用价值:用来辅助哈希函数。下边是MSNDN上的例子:

#include <iostream>
using namespace std;

unsigned short Hash(void *p)
{
unsigned int val = reinterpret_cast<unsigned long>(p);
return (unsigned short)(val ^ (val>>16));
}

int main()
{
int a[20];
for(int i=0;i<20;i++)
{
cout<<Hash(a+i)<<endl;
}
return 0;
}

 运行结果如下:

 

 这段代码适合体现哈希的思想,暂时不做深究,但至少看Hash函数里面的操作,也能体会到,对整数的操作显然要对地址操作更方便。在集合中存放整形数值,也要比存放地址更具有扩展性(当然如果存void *扩展性也是一样很高的),唯一损失的可能就是存取的时候整形和地址的转换(这完全可以忽略不计)。

 〈二〉我们仍然先通过几个例子进行说明:

#include <iostr
    
[3]FFLIB C++ 异步&类型安全&printf风格的日志库
    来源:    发布时间: 2013-10-17

摘要

      C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并不会过度讨论gdb,读者可以寻找相关的资料阅读。Gdb是C++程序调试中非常重要的调试手段,其有如下特点:

  • l 通过增加断点,可以观察重点代码的执行
  • l 若程序出现segmentation fault,gdb可以输出调用堆栈,方便找到bug之所在
  • l 有些逻辑代码段非常不容易触发,可以在gdb环境下通过加断点、修改内存来强制进入特定的代码段
  • l 但是gdb不能用于生产环境,在几百上千在线的服务器程序上执行gdb的attach操作,是不可能接受的

      Gdb绝对是调试期的利器,另外一个调试期使用的既简单又实用的方法是printf,就是使用c库的函数printf输出变量到控制台。其优点是直观,可以完整的、清晰的观察程序的运行过程,而不需像gdb一样暂停程序。另外printf也只能用于开发调试环境,上线时服务器程序都是在后台运行的,printf将会失去作用。更重要的是因为gdb和printf都不会将数据存储,历史数据或历史操作都会在重启程序后消失。日志文件可以弥补gdb和printf的不足,我们需要一个具有如下功能的日志组件:

  • l 用于调试可以显示、记录变量、数据,即能支持像printf一样可以实时的在控制台输出显示,又能将记录存储文件,方便搜索查看历史记录
  • l 日志应该拥有良好的格式,即方便开发和运维人员的阅读,又要包含足够多的信息,例如事件记录时间、线程id、事件类型,事件的严重级别
  • l 日志文件应该被良好的组织,一方面日志应该按照每天单独文件夹分类,另一方面日志日志文件并应该过大,否则使用编辑器打开、搜索将会非常困难。日志内容也应该组织分类,比如数据库的操作日志和用户做任务的日志应该有明确的标志,这样可以对日志文件进行grep等进行过滤分类查看。
  • l 日志文件必须非常容易配置,当调试时期望看到尽可能多的内容,但是不关心的内容需要被过滤掉,比如调试用户任务模块时,可以不显示数据库相关日志。在上线后,运维只关心报错信息,比警告级别低的日志需要被屏蔽。在调试时,开发人员经常会盯着控制台的输出,相比于普通级别日志内容,错误级别的日志更应该引起开发注意力,所以重要级别的日志在控制台输出时应该有彩色高亮显示。
  • l 日志组件必须有高效的性能,一方面调用者期望日志组件调用后立即返回不影响逻辑层的效率,另一方面写文件属于io操作,比起内存操作慢的多得多。所以要求日志的接口调用是异步的,日志组件单独开启线程执行写文件操作,只有如此才能尽最大程度满足程序的实时性。

下面来探讨一下日志 的实现。

实现类

定义log_t类来封装对于日志配置、格式化、输出的操作。log_t主要的功能有:

  • l 支持对日志级别的配置
  • l 支持对日志类别的配置
  • l 支持配置日志内容是否输出到文件和控制台
  • l 格式化日志
class log_t
{
public:
log_t(int level_, const string& all_class_, const string& path_, const string& file_,
bool print_file_, bool print_screen_);
virtual ~log_t();

void mod_level(int level_, bool flag_);
void mod_class(const string& class_, bool flag_);
void mod_print_file(bool flag_);
void mod_print_screen(bool flag_);
bool is_level_enabled(int level_);
const char* find_class_name(const char* class_);

void log_content(int level_, const char* str_class_, const string& content_);
};

接口log_content 负责格式化和输出日志内容,其主要实现代码如下:

void log_t::log_content(int level_, const char* str_class_, const string& content_)
{
struct timeval curtm;
gettimeofday(&curtm, NULL);
struct tm tm_val = *localtime(&(curtm.tv_sec));

char log_buff[512];
::snprintf(log_buff, sizeof(log_buff), "%02d:%02d:%02d.%03ld %s [%ld] [%s] ",
tm_val.tm_hour, tm_val.tm_min, tm_val.tm_sec, curtm.tv_usec/1000,
g_log_level_desp[level_], gettid(), str_class_);

if (m_enable_file && check_and_create_dir(&tm_val))
{
m_file << log_buff << content_ << endl;
m_file.flush();
}

if (m_enable_screen)
{
printf("%s%s%s%s\n", g_log_color_head[level_], log_buff, content_.c_str(), g_log_color_tail[level_]);
}
}

其执行的主要过程如下:

  • l 格式化时间,包括调用时的时分秒以及毫秒,为什么没有年月日呢?日志目录已经安装每天一个文件夹分类了,故这里省略了年月日信息。
  • l 增加日志级别信息,日志级别对应一个字符串描述,如debug级别日志每一行会包含DEBUG字符串。
  • l 记录线程id,这里并没有直接使用::pthread_self() 获取线程id,而是获取线程在系统中分配的“TID”,要知道线程和进程在内核中都有唯一的id,可以通过top进行查看,top -H –p [pid] 可以查看进程内的所有线程的运行负载情况,如果某个线程运行负载很高,我们需要知道到底是那一部分逻辑是热点,通过搜索日志,可以知道该线程负责了哪块逻辑,从而能够发现问题。
  • l 记录日志类别
  • l 若配置允许输出屏幕,那么利用printf输出,不同的日志级别会有不同的显示颜色,如printf("\033[1;33mDEBUG\033[0m"), DEBUG 会以黄色输出。
  • l 若配置允许输出文件,那么flush到文件中,若日期发生变化,重新创建日期目录,保证每一天一个文件夹,若单个文件内容超过5000行,会创建新的文件,避免文件内容过大,最终目录机构如下


    
最新技术文章:
▪C++单例模式应用实例
▪C++设计模式之迭代器模式
▪C++实现动态分配const对象实例
▪C++设计模式之中介者模式
▪C++设计模式之备忘录模式
▪C++插入排序算法实例
▪C++冒泡排序算法实例
▪C++选择排序算法实例
▪C++归并排序算法实例
▪C++设计模式之观察者模式
▪C++中复制构造函数和重载赋值操作符总结
▪C++设计模式之状态模式
▪C++设计模式之策略模式
▪C++设计模式之访问者模式
▪C++设计模式之模板方法模式
▪C++实现下载的代码
▪C++模板之特化与偏特化详解
▪C++实现查壳程序代码实例
▪C语言、C++内存对齐问题详解
▪C语言、C++中的union用法总结
▪C++基于CreateToolhelp32Snapshot获取系统进程实例
▪C++中memcpy和memmove的区别总结
编程语言 iis7站长之家
▪C++内存查找实例
▪C++实现CreatThread函数主线程与工作线程交互的...
▪C++设计模式之桥接模式
▪C++中关键字Struct和Class的区别
▪C++设计模式之组合模式
▪C++ COM编程之什么是组件?
▪C++ COM编程之什么是接口?
 


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3