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")); 日志输出。
Windows系统上release版本程序bug跟踪解决方案(1)-日志记录的更多相关文章
- Windows系统上release版本程序bug跟踪解决方案-.dmp文件。
使用场景: Win32程序在release模式下编译完成,发送给最终用户使用时,我们的程序有时候也会出现崩溃的情况,这个时候如果能快速定位崩溃原因或提供一些程序崩溃时的状态信息,对我们解决问题将会带来 ...
- 在Windows系统上一批可以下载但是需要经过编译再安装的第三方的直接编译后的版本(UCI页面)
在Windows系统上一批可以下载但是需要经过编译再安装的第三方的直接编译后的版本(UCI页面) (https://www.lfd.uci.edu/~gohlke/pythonlibs/) win10 ...
- 使用VS2017 编写Linux系统上的Opencv程序
背景 之前写图像算法的程序都是在window10下使用VS编写,VS这个IDE结合“ImageWatch.vsix“插件,用于调试opencv相关的图像算法程序十分方便.后因项目需要,需将相关程序移植 ...
- windows系统上安装与使用Android NDK r5 (转)
windows系统上安装与使用Android NDK r5 很早就听说了android的NDK应用,只是一直没有时间去研究,今天花了点时间在windows平台搭建了NDK环境,并成功运行了第一个简单 ...
- Redis进阶实践之三如何在Windows系统上安装安装Redis
一.Redis的简介 Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合 ...
- Redis进阶实践之三如何在Windows系统上安装安装Redis(转载)
Redis进阶实践之三如何在Windows系统上安装安装Redis 一.Redis的简介 Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括 ...
- 解决Tomcat6解压版在64位windows系统上无法启动服务的问题
解决Tomcat6解压版在64位windows系统上无法启动服务的问题 由于客户环境为64位windows系统,开发环境一直用32位.tomcat使用6.0.20非安装版.部署时发现在 ...
- 如何在Windows系统上利用Telnet协议连接Linux服务器
Telnet协议是Internet远程登录服务的标准协议,它为用户提供了在本地计算机上完成远程主机工作的能力.很多终端使用者都习惯在计算机上利用Telnet会话来远程控制服务器.这里小编就分两步为大家 ...
- windows系统上安装与使用Android NDK r5
windows系统上安装与使用Android NDK r5 很早就听说了android的NDK应用,只是一直没有时间去研究,今天花了点时间在windows平台搭建了NDK环境,并成功运行了第一个简单 ...
随机推荐
- OpenGL的镶嵌
镶嵌(tessellation)是将凹边形分割或者是凸边形相交边组成的多边形.因为OpenGL只接受凸多边形的渲染,这些非凸多边形必须在绘制前进行镶嵌. 上图分别为凹四边形.中间有洞及自交的多边形. ...
- GIT和SVN比较
SVN与Git比较 摘要Svn是目前得到大多数人认可,使用得最多的版本控制管理工具,而Git的优势在于易于本地增加分支和分布式的特性,可离线提交,解决了异地团队协同开发等svn不能解决的问题.本文就这 ...
- Linux下tar命令的各种参数选项和他们的作用整理
1.建立TAR包(打包)命令格式:tar cvf TAR包文件名.tar 所备份的文件或目录功能描述:tar cvf命令用于把指定的目录或文件打包到指定的文件中.“c”指定建立(或压缩)TAR包,“v ...
- 项目管理理论与实践(4)——UML应用(上)
本篇文章介绍UML的相关知识.参考<UML从入门到精通> 一.UML综述 1. UML简介 统一建模语言(UML)是一个通用的可视化建模语言,用于对软件进行描述.可视化处理.构造和建立软件 ...
- jstat 简介(2)
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量.命令的格式如下: jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数] 注意:使用的jdk版本是jdk8. 类加载统 ...
- @angular/cli项目构建--路由1
app.module.ts import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angu ...
- UVA - 1606 Amphiphilic Carbon Molecules (计算几何,扫描法)
平面上给你一些具有黑或白颜色的点,让你设置一个隔板,使得隔板一侧的黑点加上另一侧的白点数最多.隔板上的点可视作任意一侧. 易知一定存在一个隔板穿过两个点且最优,因此可以先固定以一个点为原点,将其他点中 ...
- HihoCoder1407 后缀数组二·重复旋律2
重复旋律2 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi在练习过很多 ...
- 《Javascript高级程序设计》阅读记录(三):第五章 上
这个系列以往文字地址: <Javascript高级程序设计>阅读记录(一):第二.三章 <Javascript高级程序设计>阅读记录(二):第四章 这个系列,我会把阅读< ...
- [独孤九剑]Oracle知识点梳理(七)数据库常用对象之Cursor
本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...