Windows系统上release版本程序bug跟踪解决方案(1)-日志记录

使用场景:

Win32程序在release模式下编译完成,发送给最终用户使用时,我们的程序有时候也会出现崩溃的情况,这个时候如果能快速定位崩溃原因或提供一些程序崩溃时的状态信息,对我们解决问题将会带来很大的帮助。一般程序崩溃时我们需要搜集的信息包括:系统信息、CPU寄存器信息、堆栈信息、调用堆栈信息、CPU和内存状态、内存当前地址等。调用堆栈是我们最常用到的。

技术方案:

目前我搜集的方法有以下三种,日志记录、dbghelp 、SHE(Structured Exception Handling)。

日志记录

这是最常用的,就是在项目中添加一个日志记录函数,在程序关键位置将需要跟踪的信息输出到日志中。提供一个简单日志输出类(支持跨线程):

.h文件

 #pragma once
//********************************************************************
// 创建时间: 2016/05/12 14:42
// 文件名称: GB_Logger.h
// 作 者: GP_Fore
//********************************************************************
// 功能说明: 提供写日志功能,支持多线程,支持可变形参数操作,支持写日志级别的设置
// 接 口: SetLogLevel:设置写日志级别
// TraceKeyInfo:忽略日志级别,写关键信息
// TraceError:写错误信息
// TraceWarning:写警告信息
// TraceInfo:写一般信息
// 备 注: 在有中文输出的环境下,记者使用setlocale(LC_ALL, "chs");先进行本地化设置。
//********************************************************************
#ifndef GB_Logger_H_
#define GB_Logger_H_
#include <Windows.h>
#include <typeinfo.h>
#include <tchar.h>
#define _CRT_SECURE_NO_WARNINGS //日志模块公共变量定义
#ifndef GB_Logger_DEFINE
#define GB_Logger_DEFINE #define WM_USERDEFINEWINDOWSINFO WM_USER+2000 //支持windows消息机制。 //日志级别的提示信息
static const TCHAR * KEYINFOPREFIX = _T(" Key:"); //关键日志前缀
static const TCHAR * ERRORPREFIX = _T(" Error:"); //错误日志前缀
static const TCHAR * WARNINGPREFIX = _T(" Warning:"); //警告日志前缀
static const TCHAR * INFOPREFIX = _T(" Info: "); //一般日志前缀
static const int MAX_STR_LEN = ; //字符串最大缓存
static const long MAX_LOGFILESIZE = ; //日志文件最大。<4G extern TCHAR g_LOGDIRECTORYPATH[]; //存放日志目录 #endif //日志类型
typedef enum EnumLogType
{
ErrInfo=, //错误日志信息
WarnInfo, //警告日志信息
trackingInfo, //一般日志信息
DataInfo //数据跟踪信息
}; typedef enum EnumLogLevel
{
LogLevelAll = , //所有信息都写日志
LogLevelMid, //写错误、警告信息
LogLevelNormal, //只写错误信息
LogLevelStop //不写日志
}; //static EnumLogType ErrLog = ErrInfo; class GB_Logger
{
private:
//默认构造函数
GB_Logger();
//析构函数
virtual ~GB_Logger();
//写文件操作 //********************************************************************
// 创建时间: 2016-11-30 16:24:13
// 作 者: GP_Fore
//********************************************************************
// 函数说明: 获取当前时间字符串。
// 参数列表: OUT TCHAR * p_ResultBuffer
// 参数列表: int p_SizeOfResultBuffer,为p_ResultBuffer缓冲区对应的字节个数。注意:多字节和双字节的区分,当环境为Unicode时,为缓冲区大小除2。
// 返回值: int
// 备 注:
//********************************************************************
int GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer); public: //控制日记记录的级别
EnumLogLevel m_nLogLevel; int WriteStrToLoggerFile(const TCHAR * strInfo); //********************************************************************
// 创建时间: 2016-11-29 11:24:37
// 作 者: GP_Fore
//********************************************************************
// 函数说明: 创建日志文件名称。
// 返回值: void
// 备 注:
//********************************************************************
void GenerateLogName(); //********************************************************************
// 创建时间: 2016-11-29 11:21:18
// 作 者: GP_Fore
//********************************************************************
// 函数说明: 输入日志字符串到日志文件
// 参数列表: EnumLogType pLogType
// 参数列表: const TCHAR * strInfo 详细日志信息,注:日志长度不超过MAX_STR_LEN(1024)。
// 参数列表: ...
// 返回值: void
// 备 注:
//********************************************************************
int TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...);
//设置日志文件保存目录
//********************************************************************
// 创建时间: 2016-11-29 11:24:01
// 作 者: GP_Fore
//********************************************************************
// 函数说明: 设置日志存储目录。文件夹地址。
// 参数列表: const TCHAR * strDirectoryPath
// 返回值: int
// 备 注:
//********************************************************************
int SetLogDirectory(const TCHAR * strDirectoryPath);
//将信息发送到指定的windows窗体
int SendInfoToWindows(HWND hWnd, const TCHAR * strInfo, ...);
//获取唯一日志实例对象。
static GB_Logger* GetInstance(); private: //日志文件句柄
HANDLE m_hFile;
//写日志文件流
//日志的名称
TCHAR m_strCurLogName[MAX_STR_LEN];
//线程同步的临界区变量
CRITICAL_SECTION m_cs;
//当前实例静态指针
static GB_Logger* logger;
//防止拷贝构造和赋值操作
GB_Logger(const GB_Logger&);
GB_Logger& operator=(const GB_Logger&); }; #endif

.cpp文件

 #include "stdafx.h"
#include "GB_Logger.h"
#include <imagehlp.h>
#include <time.h>
#include <stdarg.h>
#include <tchar.h> GB_Logger* GB_Logger::logger = NULL; TCHAR g_LOGDIRECTORYPATH[MAX_STR_LEN] = {}; //存放日志目录 //默认构造函数
GB_Logger::GB_Logger()
{
//初始化
//memset(g_LOGDIRECTORYPATH, 0, MAX_STR_LEN);
memset(m_strCurLogName, , MAX_STR_LEN); //设置默认的写日志级别
m_nLogLevel = EnumLogLevel::LogLevelNormal;
GenerateLogName();
//初始化临界区变量
InitializeCriticalSection(&m_cs); //创建日志文件名 } //析构函数
GB_Logger::~GB_Logger()
{
//释放临界区
DeleteCriticalSection(&m_cs);
//关闭文件流
if (m_hFile)
{
CloseHandle(m_hFile);
}
} int GB_Logger::TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...)
{
if (!strInfo)
return ;
//TCHAR* str_buffer = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN);
TCHAR str_Buffer[MAX_STR_LEN] = { };
GetCurrentTimeToTChar(str_Buffer, MAX_STR_LEN);
switch (pLogType)
{
case ErrInfo:
if (m_nLogLevel >= EnumLogLevel::LogLevelStop) //不写日志。
return ;
lstrcat(str_Buffer, _T(" Error: "));
break;
case WarnInfo:
if (m_nLogLevel >= EnumLogLevel::LogLevelNormal) //只写错误日志。
return ;
lstrcat(str_Buffer, _T(" Warning:"));
break;
case trackingInfo:
if (m_nLogLevel >= EnumLogLevel::LogLevelMid) //当前只记录错误和警告信息。
return ;
lstrcat(str_Buffer, _T(" trackingInfo:"));
break;
default:
lstrcat(str_Buffer, _T(":"));
return ;
}
va_list arg_ptr = NULL;
va_start(arg_ptr, strInfo);
TCHAR p_Content[MAX_STR_LEN] = { };
_vsntprintf_s(p_Content,sizeof(TCHAR)*MAX_STR_LEN, strInfo, arg_ptr);
lstrcat(str_Buffer, p_Content);
va_end(arg_ptr);
arg_ptr = NULL;
WriteStrToLoggerFile(str_Buffer);
return ;
} //将信息发送到指定的windows窗体
int GB_Logger::SendInfoToWindows(HWND hWnd,const TCHAR * strFormat, ...)
{
//判断当前的写日志级别,若设置只写错误和警告信息则函数返回
if (!strFormat)
return ;
TCHAR prefix[MAX_STR_LEN] = { };
TCHAR str_Buffer[MAX_STR_LEN] = { };
GetCurrentTimeToTChar(str_Buffer,MAX_STR_LEN);
lstrcat(str_Buffer, _T(": "));
va_list arg_ptr = NULL;
va_start(arg_ptr, strFormat); //让arg_ptr指向参数列表中的第一参数地址。注意:函数参数是以数据结构,栈的形式存取,从右至左入栈。
TCHAR p_Content[MAX_STR_LEN] = { };
_vsntprintf_s(p_Content, MAX_STR_LEN, strFormat, arg_ptr);
lstrcat(str_Buffer, p_Content);
va_end(arg_ptr);
arg_ptr = NULL;
::SendMessage(hWnd, WM_USERDEFINEWINDOWSINFO, , (LPARAM)&str_Buffer); //同步
return ;
} int GB_Logger::SetLogDirectory(const TCHAR * strDirectoryPath)
{
__try{
//进入临界区
EnterCriticalSection(&m_cs);
if (m_hFile)
{
if (CloseHandle(m_hFile) != )
perror("close file fail!");
else
m_hFile = nullptr;
}
_tcscpy_s(g_LOGDIRECTORYPATH, MAX_STR_LEN, strDirectoryPath);
if ( != _tcslen(g_LOGDIRECTORYPATH))
{
lstrcat(g_LOGDIRECTORYPATH, _T("//"));
}
int hr = CreateDirectory(g_LOGDIRECTORYPATH, NULL);
if (hr<)
{
return hr;
}
GenerateLogName();
return ;
}
__finally{
LeaveCriticalSection(&m_cs);
}
} //获取系统当前时间
int GB_Logger::GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer)
{
time_t curTime;
tm pTimeInfo;
time(&curTime);
localtime_s(&pTimeInfo, &curTime);
_stprintf_s(p_ResultBuffer, p_SizeOfResultBuffer, _T("%02d:%02d:%02d"), pTimeInfo.tm_hour, pTimeInfo.tm_min, pTimeInfo.tm_sec);
return ;
} //写文件操作
int GB_Logger::WriteStrToLoggerFile(const TCHAR * strInfo)
{
if (!strInfo)
return ;
try
{
//进入临界区
EnterCriticalSection(&m_cs);
//若文件流没有打开,则重新打开
if (!m_hFile)
{
HANDLE hFile;
TCHAR stBuffer[] = { };
lstrcat(stBuffer, g_LOGDIRECTORYPATH);
lstrcat(stBuffer, m_strCurLogName);
hFile = CreateFile(stBuffer, //指向文件名的指针
GENERIC_WRITE | GENERIC_READ, //访问模式(读/写) 写,读
FILE_SHARE_READ, // 共享模式 不共享
NULL, //指向安全属性的指针
OPEN_ALWAYS, //如何让创建
FILE_ATTRIBUTE_NORMAL, //文件属性
NULL); //用于复制文件句柄
if (hFile == INVALID_HANDLE_VALUE)
{
AfxMessageBox(_T("创建日志文件失败"));
return GetLastError();
}
this->m_hFile = hFile;
}
if (m_hFile)
{
//写日志信息到文件流
TCHAR pLogbuff[MAX_PATH] = { };
_stprintf_s(pLogbuff, _T("%s\n"), strInfo); if (SetFilePointer(m_hFile, , NULL, FILE_END) == -)
{
printf("SetFilePointer error\n");
return ;
} DWORD ReturnCharNumber;
WriteFile(m_hFile, pLogbuff, _tcslen(pLogbuff)*sizeof(TCHAR), &ReturnCharNumber, NULL);
//判断当前日志文件大小,单位是字节。
//LONGLONG file_size = 0;
//file_size = GetFileSize(m_hFile, NULL); LARGE_INTEGER FileSize;
GetFileSizeEx(m_hFile, &FileSize);
if (FileSize.QuadPart >= MAX_LOGFILESIZE)
{
CloseHandle(m_hFile);
TCHAR str_Buffer[] = { };
TCHAR* str_TimeBuffer = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN);
GetCurrentTimeToTChar(str_TimeBuffer, MAX_STR_LEN);
lstrcat(str_Buffer, m_strCurLogName);
lstrcat(str_Buffer, str_TimeBuffer);
memset(m_strCurLogName, , MAX_STR_LEN);
lstrcat(m_strCurLogName, str_Buffer);
}
} //离开临界区
LeaveCriticalSection(&m_cs);
return ;
}
//若发生异常,则先离开临界区,防止死锁
catch (...)
{
LeaveCriticalSection(&m_cs);
return ;
}
return ;
} //创建日志文件的名称
void GB_Logger::GenerateLogName()
{
time_t curTime;
tm pTimeInfo;
time(&curTime);
localtime_s(&pTimeInfo, &curTime);
TCHAR temp[] = { };
//日志的名称如:2013-01-01.log
_stprintf_s(temp, _T("%04d-%02d-%02d-%02d.log"), pTimeInfo.tm_year + , pTimeInfo.tm_mon + , pTimeInfo.tm_mday,pTimeInfo.tm_hour);
if ( != _tcscmp(m_strCurLogName, temp)) //如果文件名称不存在,创建该文件。
{
_tcscpy_s(m_strCurLogName, temp);
if (m_hFile)
CloseHandle(m_hFile);
TCHAR temp[] = { };
lstrcat(temp, g_LOGDIRECTORYPATH);
lstrcat(temp, m_strCurLogName);
//以追加的方式打开文件流
//_tfopen_s(&m_pFileStream, temp, _T("a+"));
} } GB_Logger* GB_Logger::GetInstance()
{
if (NULL == logger)
{
GB_Logger* pLog = new GB_Logger();
logger = pLog;
}
return logger;
}

调用方法:

GB_Logger::GetInstance()->SetLogDirectory(loggerFolderPath+"\\Logger\\");  //设置一个日志保存目录。
GB_Logger::GetInstance()->m_nLogLevel = LogLevelAll; //设置日志级别
GB_Logger::GetInstance()->TraceLogger(trackingInfo, _T("系统开始初始化……!\r\n")); 日志输出。
上一篇:【渝粤教育】21秋期末考试建设监理10219k2


下一篇:Windows系统上release版本程序bug跟踪解决方案-.dmp文件。