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环境,并成功运行了第一个简单 ...
随机推荐
- Jquery Ajax模版
$.ajax({ type: "GET", url: "test.json", data: {username:'tt', content:'tt'}, dat ...
- PHP的目录路径问题
在windows下,可以用“/”或者“\”来表示目录层次,而linux下只能用“/”:同时在linux下没有盘符的概念,只有用“/”符号表示唯一的根目录.所以,用一个变量表示目录位置的话,用“/”最安 ...
- Word 2010 怎么在每一章中使用不同的页眉
1.要做到每一章的页眉不同首先要进行 分节 word2010中 页面布局 -> 分隔符 ->下一页 上述操作即可实现分节 2.实现分节后,在每一节开头的那一页,编辑页眉 ...
- UML类图(三)-------实例
实例分析1——登录模块 某基于C/S的即时聊天系统登录模块功能描述如下: 用户通过登录界面(LoginForm)输入账号和密码,系统将输入的账号和密码与存储在数据库(User)表中的用户信息进行比较, ...
- 分享知识-快乐自己:Java中的经典算法之冒泡排序(Bubble Sort)
原理:比较两个相邻的元素,将值大的元素交换至右端. 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面.即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后.然后比较第2个数和第3个数,将 ...
- Educational Codeforces Round 33 (Rated for Div. 2)A-F
总的来说这套题还是很不错的,让我对主席树有了更深的了解 A:水题,模拟即可 #include<bits/stdc++.h> #define fi first #define se seco ...
- 获得Version和Build版本号
[[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBu ...
- 如何关闭Windows10系统更新
单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”搜索 在结果中选择查看本地服务 在服务列表中找到windows update服务双击它 点击停止按钮先停止该服务,稍等片 ...
- LNMP安装及配置
LNMP官方网站:http://lnmp.org http://oneinstack.com/install/ 安装详细介绍:http://lnmp.org/install.html 1,安装LNMP ...
- 关于/usr/bin/ld: cannot find -lcrypto 的错误
Linux下 build code 时,要做 -lssl, -lcrypto 的链接,出现类似下面的错误: /usr/bin/ld: cannot find -lcrypto /usr/bin/ld: ...