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环境,并成功运行了第一个简单 ...
随机推荐
- 20个面试题让你真正了解jQuery
1. jQuery 库中的 $() 是什么?(答案如下) $() 函数是 jQuery() 函数的别称, $() 函数用于将任何对象包裹成 jQuery 对象,接着你就被允许调用定义在 jQuery ...
- selenium定位多个嵌套iframe
一. driver.switch_to.frame(id):可以通过id切换到iframe 之前学习了selenium切换到iframe的方法,代码如下 from selenium import we ...
- mysql查询语句复习小结
SQL查询语句基本语法: select 字段列表 from 表名|视图列表 [where 条件表达式1] [group by 属性名1 [having 条件表达式2]] [order by 属性名2 ...
- 编译内核时覆盖KBUILD_BUILD_USER和KBUILD_BUILD_HOST
默认情况下make kernel.img编译出来的内核在/proc/version中显示的内容是: Linux version 3.0.36+ (xxx@yyyy) (gcc version 4.6. ...
- 关于设置UITableView的背景图片
在UITableViewController中,要设置UITableView的背景图片,以前常用的方法是使用backgroundcolor属性,这个属性可以通过UIImage来获取,但最近发现这个方法 ...
- ROC曲线是通过样本点分类概率画出的 例如某一个sample预测为1概率为0.6 预测为0概率0.4这样画出来,此外如果曲线不是特别平滑的话,那么很可能存在过拟合的情况
ROC和AUC介绍以及如何计算AUC from:http://alexkong.net/2013/06/introduction-to-auc-and-roc/ ROC(Receiver Operat ...
- 项目管理理论与实践(4)——UML应用(上)
本篇文章介绍UML的相关知识.参考<UML从入门到精通> 一.UML综述 1. UML简介 统一建模语言(UML)是一个通用的可视化建模语言,用于对软件进行描述.可视化处理.构造和建立软件 ...
- 24 Python 对象进阶
isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object): pass obj = Foo() isinstance(obj, Foo) issu ...
- hdoj-1013-Digital Roots(九余数定理)
题目链接 #include <iostream> using namespace std; int main() { string a; int b; ") { b = ; ;i ...
- stencil in unity3d
Pass { Stencil { Ref Comp Always Pass REPLACE } AlphaTest Greater Blend SrcAlpha OneMinusSrcAlpha Co ...