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

使用场景:

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

技术方案:

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

日志记录

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

.h文件

  1 #pragma once
  2 //********************************************************************
  3 // 创建时间:  2016/05/12  14:42 
  4 // 文件名称:  GB_Logger.h 
  5 // 作    者:  GP_Fore
  6 //********************************************************************
  7 // 功能说明:  提供写日志功能,支持多线程,支持可变形参数操作,支持写日志级别的设置
  8 // 接    口:  SetLogLevel:设置写日志级别
  9 //               TraceKeyInfo:忽略日志级别,写关键信息
 10 //             TraceError:写错误信息
 11 //             TraceWarning:写警告信息
 12 //             TraceInfo:写一般信息
 13 // 备    注:  在有中文输出的环境下,记者使用setlocale(LC_ALL, "chs");先进行本地化设置。
 14 //********************************************************************
 15 #ifndef GB_Logger_H_
 16 #define GB_Logger_H_
 17 #include <Windows.h>
 18 #include <typeinfo.h>
 19 #include <tchar.h>
 20 #define _CRT_SECURE_NO_WARNINGS
 21 
 22 
 23 
 24 //日志模块公共变量定义
 25 #ifndef GB_Logger_DEFINE  
 26 #define GB_Logger_DEFINE
 27 
 28 #define WM_USERDEFINEWINDOWSINFO WM_USER+2000 //支持windows消息机制。
 29 
 30 //日志级别的提示信息
 31 static const TCHAR * KEYINFOPREFIX = _T(" Key:");     //关键日志前缀
 32 static const TCHAR * ERRORPREFIX = _T(" Error:");    //错误日志前缀
 33 static const TCHAR * WARNINGPREFIX = _T(" Warning:"); //警告日志前缀
 34 static const TCHAR * INFOPREFIX = _T(" Info: ");      //一般日志前缀
 35 static const int MAX_STR_LEN = 1024;               //字符串最大缓存
 36 static const long MAX_LOGFILESIZE = 1000000;     //日志文件最大。<4G
 37 
 38 extern    TCHAR g_LOGDIRECTORYPATH[];       //存放日志目录
 39 
 40 #endif
 41 
 42 
 43 //日志类型
 44 typedef enum EnumLogType
 45 {
 46     ErrInfo=0,      //错误日志信息
 47     WarnInfo,       //警告日志信息
 48     trackingInfo,        //一般日志信息
 49     DataInfo        //数据跟踪信息
 50 };
 51 
 52 typedef enum EnumLogLevel
 53 {
 54     LogLevelAll = 0,    //所有信息都写日志
 55     LogLevelMid,        //写错误、警告信息
 56     LogLevelNormal,     //只写错误信息
 57     LogLevelStop        //不写日志
 58 };
 59 
 60 //static EnumLogType ErrLog = ErrInfo;
 61 
 62 class GB_Logger
 63 {
 64 private:
 65     //默认构造函数
 66     GB_Logger();
 67     //析构函数
 68     virtual ~GB_Logger();
 69     //写文件操作
 70 
 71     //********************************************************************
 72     // 创建时间:  2016-11-30 16:24:13 
 73     // 作    者:  GP_Fore
 74     //********************************************************************
 75     // 函数说明:  获取当前时间字符串。
 76     // 参数列表:  OUT TCHAR * p_ResultBuffer
 77     // 参数列表:  int p_SizeOfResultBuffer,为p_ResultBuffer缓冲区对应的字节个数。注意:多字节和双字节的区分,当环境为Unicode时,为缓冲区大小除2。
 78     // 返回值:     int
 79     // 备    注:    
 80     //********************************************************************
 81     int GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer);
 82 
 83 
 84 public:
 85 
 86     //控制日记记录的级别
 87     EnumLogLevel m_nLogLevel;
 88 
 89 
 90     int WriteStrToLoggerFile(const TCHAR * strInfo);
 91 
 92     //********************************************************************
 93     // 创建时间:  2016-11-29 11:24:37 
 94     // 作    者:  GP_Fore
 95     //********************************************************************
 96     // 函数说明:  创建日志文件名称。 
 97     // 返回值:     void
 98     // 备    注:    
 99     //********************************************************************
100     void GenerateLogName();
101 
102     //********************************************************************
103     // 创建时间:  2016-11-29 11:21:18 
104     // 作    者:  GP_Fore
105     //********************************************************************
106     // 函数说明:  输入日志字符串到日志文件
107     // 参数列表:  EnumLogType pLogType
108     // 参数列表:  const TCHAR * strInfo  详细日志信息,注:日志长度不超过MAX_STR_LEN(1024)。
109     // 参数列表:  ...
110     // 返回值:     void
111     // 备    注:    
112     //********************************************************************
113     int TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...);
114     //设置日志文件保存目录
115     //********************************************************************
116     // 创建时间:  2016-11-29 11:24:01 
117     // 作    者:  GP_Fore
118     //********************************************************************
119     // 函数说明:  设置日志存储目录。文件夹地址。 
120     // 参数列表:  const TCHAR * strDirectoryPath
121     // 返回值:     int
122     // 备    注:    
123     //********************************************************************
124     int SetLogDirectory(const TCHAR * strDirectoryPath);
125     //将信息发送到指定的windows窗体
126     int SendInfoToWindows(HWND hWnd, const TCHAR * strInfo, ...);
127     //获取唯一日志实例对象。
128     static GB_Logger* GetInstance();
129 
130     
131 
132 private:
133 
134     //日志文件句柄
135     HANDLE m_hFile;
136     //写日志文件流
137     //日志的名称
138     TCHAR m_strCurLogName[MAX_STR_LEN];
139     //线程同步的临界区变量
140     CRITICAL_SECTION m_cs;
141     //当前实例静态指针
142     static GB_Logger* logger;
143     //防止拷贝构造和赋值操作
144     GB_Logger(const GB_Logger&);
145     GB_Logger& operator=(const GB_Logger&);
146 
147 };
148 
149 #endif
View Code

.cpp文件

  1 #include "stdafx.h"
  2 #include "GB_Logger.h"
  3 #include <imagehlp.h>
  4 #include <time.h>
  5 #include <stdarg.h>
  6 #include <tchar.h>
  7  
  8 
  9 
 10 GB_Logger* GB_Logger::logger = NULL; 
 11 
 12 
 13 TCHAR g_LOGDIRECTORYPATH[MAX_STR_LEN] = {0};       //存放日志目录
 14 
 15 //默认构造函数
 16 GB_Logger::GB_Logger()
 17 {
 18     //初始化
 19     //memset(g_LOGDIRECTORYPATH, 0, MAX_STR_LEN);
 20     memset(m_strCurLogName, 0, MAX_STR_LEN);
 21  
 22     //设置默认的写日志级别
 23     m_nLogLevel = EnumLogLevel::LogLevelNormal;
 24     GenerateLogName();
 25     //初始化临界区变量
 26     InitializeCriticalSection(&m_cs);
 27      
 28     //创建日志文件名
 29     
 30 }
 31 
 32 //析构函数
 33 GB_Logger::~GB_Logger()
 34 {
 35     //释放临界区
 36     DeleteCriticalSection(&m_cs);
 37     //关闭文件流
 38     if (m_hFile)
 39     {
 40         CloseHandle(m_hFile);
 41     }
 42 }
 43 
 44 int GB_Logger::TraceLogger(EnumLogType pLogType, const TCHAR * strInfo, ...)
 45 {
 46     if (!strInfo)
 47         return 0;
 48     //TCHAR* str_buffer  = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN);
 49     TCHAR str_Buffer[MAX_STR_LEN] = { 0 }; 
 50     GetCurrentTimeToTChar(str_Buffer, MAX_STR_LEN);
 51     switch (pLogType)
 52     {
 53     case ErrInfo:
 54         if (m_nLogLevel >= EnumLogLevel::LogLevelStop)   //不写日志。
 55             return 2;
 56         lstrcat(str_Buffer, _T(" Error: "));
 57         break;
 58     case WarnInfo:
 59         if (m_nLogLevel >= EnumLogLevel::LogLevelNormal) //只写错误日志。
 60             return 2;
 61         lstrcat(str_Buffer, _T(" Warning:"));
 62         break;
 63     case trackingInfo:
 64         if (m_nLogLevel >= EnumLogLevel::LogLevelMid)   //当前只记录错误和警告信息。
 65             return 2;
 66         lstrcat(str_Buffer, _T(" trackingInfo:"));
 67         break;
 68     default:
 69         lstrcat(str_Buffer, _T(":"));
 70         return 2;
 71     }
 72     va_list arg_ptr = NULL;
 73     va_start(arg_ptr, strInfo);
 74     TCHAR p_Content[MAX_STR_LEN] = { 0 };
 75     _vsntprintf_s(p_Content,sizeof(TCHAR)*MAX_STR_LEN, strInfo, arg_ptr);
 76     lstrcat(str_Buffer, p_Content);
 77     va_end(arg_ptr);
 78     arg_ptr = NULL;
 79     WriteStrToLoggerFile(str_Buffer);
 80     return 1;
 81 }
 82 
 83 //将信息发送到指定的windows窗体
 84 int GB_Logger::SendInfoToWindows(HWND hWnd,const TCHAR * strFormat, ...)
 85 {
 86     //判断当前的写日志级别,若设置只写错误和警告信息则函数返回
 87     if (!strFormat)
 88         return 0;
 89     TCHAR prefix[MAX_STR_LEN] = { 0 };
 90     TCHAR str_Buffer[MAX_STR_LEN] = { 0 };
 91     GetCurrentTimeToTChar(str_Buffer,MAX_STR_LEN);
 92     lstrcat(str_Buffer, _T(": "));
 93     va_list arg_ptr = NULL;
 94     va_start(arg_ptr, strFormat);  //让arg_ptr指向参数列表中的第一参数地址。注意:函数参数是以数据结构,栈的形式存取,从右至左入栈。
 95     TCHAR p_Content[MAX_STR_LEN] = { 0 };
 96     _vsntprintf_s(p_Content, MAX_STR_LEN, strFormat, arg_ptr);
 97     lstrcat(str_Buffer, p_Content);
 98     va_end(arg_ptr);
 99     arg_ptr = NULL;
100     ::SendMessage(hWnd, WM_USERDEFINEWINDOWSINFO, 0, (LPARAM)&str_Buffer); //同步
101     return 1;
102 }
103 
104 int GB_Logger::SetLogDirectory(const TCHAR * strDirectoryPath)
105 {
106     __try{
107         //进入临界区
108         EnterCriticalSection(&m_cs);
109         if (m_hFile)
110         {
111             if (CloseHandle(m_hFile) != 0)
112                 perror("close file fail!");
113             else
114                 m_hFile = nullptr;
115         }
116         _tcscpy_s(g_LOGDIRECTORYPATH, MAX_STR_LEN, strDirectoryPath);
117         if (0 != _tcslen(g_LOGDIRECTORYPATH))
118         {
119             lstrcat(g_LOGDIRECTORYPATH, _T("//"));
120         }
121         int hr = CreateDirectory(g_LOGDIRECTORYPATH, NULL);
122         if (hr<0)
123         {
124             return hr;
125         }
126         GenerateLogName();
127         return 1;
128     }
129     __finally{
130         LeaveCriticalSection(&m_cs);
131     }
132 }
133 
134 //获取系统当前时间
135 int GB_Logger::GetCurrentTimeToTChar(OUT TCHAR* p_ResultBuffer, int p_SizeOfResultBuffer)
136 {
137     time_t curTime;
138     tm pTimeInfo;
139     time(&curTime);
140     localtime_s(&pTimeInfo, &curTime);
141     _stprintf_s(p_ResultBuffer, p_SizeOfResultBuffer, _T("%02d:%02d:%02d"), pTimeInfo.tm_hour, pTimeInfo.tm_min, pTimeInfo.tm_sec);
142     return 1;
143 }
144 
145 //写文件操作
146 int GB_Logger::WriteStrToLoggerFile(const TCHAR * strInfo)
147 {
148     if (!strInfo)
149         return 0;
150     try
151     {
152         //进入临界区
153         EnterCriticalSection(&m_cs);
154         //若文件流没有打开,则重新打开
155         if (!m_hFile)
156         {
157             HANDLE hFile;
158             TCHAR stBuffer[1024] = { 0 };
159             lstrcat(stBuffer, g_LOGDIRECTORYPATH);
160             lstrcat(stBuffer, m_strCurLogName);
161             hFile = CreateFile(stBuffer,               //指向文件名的指针 
162                 GENERIC_WRITE | GENERIC_READ,          //访问模式(读/写) 写,读
163                 FILE_SHARE_READ,                       // 共享模式  不共享
164                 NULL,                                  //指向安全属性的指针 
165                 OPEN_ALWAYS,                           //如何让创建
166                 FILE_ATTRIBUTE_NORMAL,                 //文件属性
167                 NULL);                                 //用于复制文件句柄
168             if (hFile == INVALID_HANDLE_VALUE)
169             {
170                 AfxMessageBox(_T("创建日志文件失败"));
171                 return GetLastError();
172             }
173             this->m_hFile = hFile;
174         }
175         if (m_hFile)
176         {
177             //写日志信息到文件流
178             TCHAR pLogbuff[MAX_PATH] = { 0 };
179             _stprintf_s(pLogbuff, _T("%s
"), strInfo);
180 
181             if (SetFilePointer(m_hFile, 0, NULL, FILE_END) == -1)
182             {
183                 printf("SetFilePointer error
");
184                 return 0;
185             }
186 
187             DWORD ReturnCharNumber;
188             WriteFile(m_hFile, pLogbuff, _tcslen(pLogbuff)*sizeof(TCHAR), &ReturnCharNumber, NULL);
189             //判断当前日志文件大小,单位是字节。
190             //LONGLONG file_size = 0;
191             //file_size = GetFileSize(m_hFile, NULL);
192              
193             LARGE_INTEGER FileSize;
194             GetFileSizeEx(m_hFile, &FileSize);
195             if (FileSize.QuadPart >= MAX_LOGFILESIZE)
196             {
197                 CloseHandle(m_hFile);
198                 TCHAR str_Buffer[1024] = { 0 };
199                 TCHAR* str_TimeBuffer = (TCHAR *)malloc(sizeof(TCHAR)* MAX_STR_LEN);
200                 GetCurrentTimeToTChar(str_TimeBuffer, MAX_STR_LEN);
201                 lstrcat(str_Buffer, m_strCurLogName);
202                 lstrcat(str_Buffer, str_TimeBuffer);
203                 memset(m_strCurLogName, 0, MAX_STR_LEN);
204                 lstrcat(m_strCurLogName, str_Buffer);
205             }
206         }
207 
208         //离开临界区
209         LeaveCriticalSection(&m_cs);
210         return 1;
211     }
212     //若发生异常,则先离开临界区,防止死锁
213     catch (...)
214     {
215         LeaveCriticalSection(&m_cs);
216         return 0;
217     }
218     return 1;
219 }
220 
221 //创建日志文件的名称
222 void GB_Logger::GenerateLogName()
223 {
224     time_t curTime;
225     tm pTimeInfo;
226     time(&curTime);
227     localtime_s(&pTimeInfo, &curTime);
228     TCHAR temp[1024] = { 0 };
229     //日志的名称如:2013-01-01.log
230     _stprintf_s(temp, _T("%04d-%02d-%02d-%02d.log"), pTimeInfo.tm_year + 1900, pTimeInfo.tm_mon + 1, pTimeInfo.tm_mday,pTimeInfo.tm_hour);
231     if (0 != _tcscmp(m_strCurLogName, temp))  //如果文件名称不存在,创建该文件。
232     {
233         _tcscpy_s(m_strCurLogName, temp);
234         if (m_hFile)
235             CloseHandle(m_hFile);
236         TCHAR temp[1024] = { 0 };
237         lstrcat(temp, g_LOGDIRECTORYPATH);
238         lstrcat(temp, m_strCurLogName);
239         //以追加的方式打开文件流
240         //_tfopen_s(&m_pFileStream, temp, _T("a+"));
241     }
242 
243 }
244 
245 GB_Logger* GB_Logger::GetInstance()
246 {
247     if (NULL == logger)
248     { 
249         GB_Logger* pLog = new GB_Logger();
250         logger = pLog;
251     }
252     return logger;
253 }
View Code

调用方法:

GB_Logger::GetInstance()->SetLogDirectory(loggerFolderPath+"\Logger\");  //设置一个日志保存目录。
GB_Logger::GetInstance()->m_nLogLevel = LogLevelAll;  //设置日志级别
GB_Logger::GetInstance()->TraceLogger(trackingInfo, _T("系统开始初始化……!
")); 日志输出。
View Code
原文地址:https://www.cnblogs.com/mymhj/p/7170713.html