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环境,并成功运行了第一个简单 ...
随机推荐
- 5.2 Selenium2环境搭建
1.Java开发环境的搭建 本课程中将使用Java语言编写Selenium自动化测试脚本,在Eclipse集成开发环境中运行. (1)jdk的安装 a.下载 官网下载,http://www. ...
- uva 103 经典DAG变形
https://vjudge.net/problem/UVA-103 也是一个经典的DAG模型,因为书上的翻译和原文不照导致WA两发= = 对于同一维度的两个箱子A,B,A可以嵌套在B中的一个充分条件 ...
- mysql 注意事项
1. mysql所有的存储引擎均不支持check约束,但可以使用check约束,而没有任何效果
- vue项目打包注意的地方
打包有两种方式: 第一种方式:1.更改config文件夹下prod.env.js下的地址: 2.将config文件夹下index.js中build下改为 assetsPublicPath: '', 第 ...
- mysql插入一万条数据
定义一个存储过程 mysql> delimiter $$ mysql> create procedure ptest() -> begin -> declare p ...
- zoj1967 poj2570 Fiber Network (floyd算法)
虽然不是最短路,但是询问时任意两点之间的信息都要知道才能回答,由此联想到floyd算法,只要都floyd算法的原理理解清楚了就会发现:这道题的思想和求任意两点之间的最短路的一样的,只不过是更新的信息不 ...
- CodeForces - 13D :Triangles(向量法:问多少个蓝点三角形内部无红点)
Little Petya likes to draw. He drew N red and M blue points on the plane in such a way that no three ...
- grep---Linux下文本处理五大神器之五
转自:http://www.cnblogs.com/dong008259/archive/2011/12/12/2285264.html grep是linux中很常用的一个命令,主要功能就是进行字符串 ...
- kong插件官方文档翻译
kong插件官方文档翻译 目录 介绍 文件结构 编写自定义逻辑 存储配置 访问数据存储 自定义实体 缓存自定义实体 扩展Admin API 编写测试 (卸载)安装你的插件 插件开发 - 介绍 什么是插 ...
- Inception-Resnet-V2
零.Inception-Resnet-V2的网络模型 整体结构如下,整体设计简洁直观: 其中的stem部分网络结构如下,inception设计,并且conv也使用了7*1+1*7这种优化形式: inc ...