VC日志类log调试信息输出

本文介绍了一个简单的VC日志输出类,支持多线程、控制台输出、日志等级等功能,适用于VC6及以上版本。

功能:输出日志信息logfile.h VC日志类调试信息输出,是编程调试跟踪流程的日志输出好帮手,很有助于程序的排错调试.

1,使用简单方便。只有一个头文件logfile.h include后,直接调用函数即可

2,兼容VC6,VC7(VS系列,VS2008)。 兼容所有VC版本

3,支持源代码文件名及行号的输出。输出日志所在的源文件名和行数。

4,支持多线程应用。CriticalSection控制线程对日志文件的有序访问读写。

5,支持Debug版本输出,Release版本不输出。有效控制调式版本和发布版本的日志输出。

6,支持设置控制台。在MFC程序中,可以增加打开控制台方便查看日志log信息。

7,支持设置文件名。默认文件名为同目录下的log(xxx)YYYY-MM-DD.txt,可以自定义日志文件名。

8,支持设置等级。可以设置需求查看的最小和最大等级。当相等时,查看某一级别的日志。

9,支持按天轮询写日志。默认为一天一个日志文件。

以下为代码:

/******************************************************************************
//功能: 输出日志信息  logfile.h
// 
//版本号: 3.
//
//作者: 王小丁hixi@21cn.com
//时间: 2013/5/16
******************************************************************************/

/******************************************************************************
 1, 使用简单方便.只有一个头文件logfile.h include后,直接调用函数即可
 2, VC6,VC7(VS2008) 兼容VC版本
 3, 可输出文件名及行号
 4, 支持多线程应用

  例如:
  在cpp源代码文件中只要#include "logfile.h"后,就可以直接调用以下函数输出日志信息

 Logout("I am Logout \r\n");
 Logflout(AT"I am LogfloutAT \r\n");
 Loglevelout(3,"I am Loglevelout");

 CString test = " i am  wangxiaoding!";
 int n = 8;
 Logout("CString = %s \r\n",test);
 Logout("Intnumber = %d \r\n",n);


******************************************************************************/
//防止多次include头文件
#if !defined(AFX_LOGFILE_H__EF4BC4B2_3BB6_46E8_B936_0F3A61E20BF0__INCLUDED_)
#define AFX_LOGFILE_H__EF4BC4B2_3BB6_46E8_B936_0F3A61E20BF0__INCLUDED_

#pragma once

//-----------------------------------------------------------------------------

// Debug版本宏1
#if _DEBUG
#ifndef _FLAG_OUTLOG_ENABLE
#define _FLAG_OUTLOG_ENABLE TRUE
#endif // _FLAG_OUTLOG_ENABLE
#endif // _DEBUG

// 设置控制台宏2
#define  _DEBUGCONSOLE

// 设置文件名宏3
//#define  _SETFILENAME
#ifdef _SETFILENAME
#define  FILENAME "log.txt"
#endif //_SETFILENAME

// 设置等级宏4
#define  _LOGLEVEL
#ifdef _LOGLEVEL
#define MIN_LEVEL 1
#define MAX_LEVEL 5
#endif // _LOGLEVEL


//-----------------------------------------------------------------------------

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>

// 日志输出类,静态版
struct CLog
{   
   
 // 取进程执行文件名称
    static void GetProcessFileName(char* lpName)
    {
        if ( ::GetModuleFileNameA(NULL, lpName, MAX_PATH) > 0)
        {
            char* pBegin = lpName;
            char* pTemp  = lpName;
            while ( *pTemp != 0 )
            {
                if ( *pTemp == '\\' )
                {
                    pBegin = pTemp + 1;
                }
                pTemp++;
            }

            memcpy(lpName, pBegin, strlen(pBegin)+1);
        }

    }

 // 输出到文件
    // lpFile   : 源文件名
    // nLine    : 源文件行号
    // lpFormat : 输出的内容
    static void logout(LPCSTR lpFile, int nLine,LPCSTR lpFormat, ...)
    {
  static CRITICAL_SECTION  m_crit;
  if (m_crit.DebugInfo==NULL)
  ::InitializeCriticalSection(&m_crit);
  /*-----------------------进入临界区(输出信息)------------------------------*/  
  ::EnterCriticalSection(&m_crit);  

        if ( NULL == lpFormat )
            return;

        //当前时间
        SYSTEMTIME st;
        ::GetLocalTime(&st);

        //设置消息头
        const DWORD BufSize = 2048;
        char szMsg[BufSize];

  if (nLine==0)
  { 
   //当nLine==0 时,即Logout("xxx")只打印信息
   sprintf(szMsg, "[%02d:%02d:%02d.%03d]:",
    st.wHour, st.wMinute, st.wSecond,
         st.wMilliseconds);
  }
  else
  {
   //当nLine不等于0 时,即Logflout(AT"xxx")打印文件名行号及信息
   sprintf(szMsg, "[%02d:%02d:%02d.%03d]文件%s第%04d行:",
    st.wHour, st.wMinute, st.wSecond,
           st.wMilliseconds, lpFile, nLine);

  }

  //格式化消息,并完善整条消息
        char* pTemp = szMsg;
        pTemp += strlen(szMsg);
        va_list args;
        va_start(args, lpFormat);   
        wvsprintfA(pTemp,  lpFormat, args);  //vsprintf_s BufSize - strlen(szMsg),
        va_end(args); 

        DWORD dwMsgLen = (DWORD)strlen(szMsg);

        //获取日志文件名
  char szFileName[MAX_PATH];
        char szExeName[MAX_PATH];
        GetProcessFileName(szExeName);
        sprintf(szFileName, "Log(%s)%d-%d-%d.txt", szExeName, //sprintf_s MAX_PATH
                                        st.wYear, st.wMonth, st.wDay);

        // 判断文件名称是否相同,句柄是否有效.
        // 如果不同或无效,则关闭当前文件,创建新文件
        static char   s_szFileName[MAX_PATH] = {0};
        static HANDLE s_hFile = INVALID_HANDLE_VALUE;

  //设置自定义日志文件名
#ifdef _SETFILENAME
  strcpy(szFileName,FILENAME);
#endif //_SETFILENAME

        BOOL bNew = ((strcmp(s_szFileName, szFileName) != 0) || (s_hFile == INVALID_HANDLE_VALUE));
       

#ifdef _DEBUGCONSOLE //控制台输出
  static BOOL bOpenConsole = FALSE;

  if (!bOpenConsole)
  {
   bOpenConsole = ::AllocConsole();
   if (bOpenConsole)
   {
    freopen("CONOUT$","w+t",stdout); 
    freopen("CONIN$","r+t",stdin);
    freopen("CONERR", "w", stderr);
    
    HANDLE handle= GetStdHandle(STD_OUTPUT_HANDLE); 
    SetConsoleTitle("DebugCosole");
    SetConsoleTextAttribute((HANDLE)handle, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
    
    HWND hwnd=NULL;
    while(NULL==hwnd)
     hwnd=::FindWindow(NULL,(LPCTSTR)"DebugCosole");
    
    HMENU hmenu = ::GetSystemMenu ( hwnd, FALSE );
    DeleteMenu ( hmenu, SC_CLOSE, MF_BYCOMMAND );
   }
  }
#endif //_DEBUGCONSOLE

        printf("%s", szMsg);

        if ( bNew ) // 关闭旧文件,创建新文件
        {
            if ( s_hFile != INVALID_HANDLE_VALUE)
            {
                ::CloseHandle(s_hFile);
                s_hFile = INVALID_HANDLE_VALUE;
            }
   
   //创建日志文件. 有文件时追加方式打开,没有时创建.
            s_hFile = ::CreateFileA( szFileName,
                                     GENERIC_WRITE,
                                     FILE_SHARE_WRITE | FILE_SHARE_READ,
                                     0,
                                     OPEN_ALWAYS,
                                     FILE_ATTRIBUTE_NORMAL,
                                     0);

            if ( s_hFile == INVALID_HANDLE_VALUE)
            {
                printf("::CreateFile Error: %d", ::GetLastError());
                return;
            }
        }

        //把消息写到文件
        if ( s_hFile != INVALID_HANDLE_VALUE)
        {
            DWORD dwWrite = 0;
            ::SetFilePointer(s_hFile, 0, NULL, FILE_END);
            ::WriteFile(s_hFile, szMsg, dwMsgLen, &dwWrite, NULL);
           
   //备份创建成功的新文件名
   strcpy(s_szFileName,szFileName);
        }

  ::LeaveCriticalSection(&m_crit);   
  /*----------------------------退出临界区---------------------------------*/
    }

}; // CLog


//宏定义文件名和行号
#define AT __FILE__, __LINE__,

#if (_FLAG_OUTLOG_ENABLE)

//日志输出接口函数1
static void Logout(LPCSTR lpFormat, ...)
{
 const DWORD BufSize = 2048;
    char szMsg[BufSize];
 
 va_list args;  //格式化消息

 va_start(args, lpFormat);   
 wvsprintfA(szMsg,  lpFormat, args);  //vsprintf_s BufSize - strlen(szMsg),
 va_end(args); 
  
 //输出信息
 CLog::logout("0",0,szMsg);
}

//日志输出接口函数2  使用于logflout(AT"xxxx")形式
//(LPCSTR lpFile, int nLine)有时适配这个函数名,所以修改函数名 fl = file and line
static void Logflout(LPCSTR lpFile, int nLine,LPCSTR lpFormat, ...)
{
 const DWORD BufSize = 2048;
    char szMsg[BufSize];
 
 char* pTemp = szMsg;
 
 va_list args; //格式化消息
 
 va_start(args, lpFormat);   
 wvsprintfA(szMsg,  lpFormat, args);  //vsprintf_s BufSize - strlen(szMsg),
 va_end(args); 
 
 //输出有文件名及行号的消息
 CLog::logout(lpFile, nLine,szMsg);
}

#ifdef _LOGLEVEL
//日志输出接口函数3
static void Loglevelout(int nshowlevel,LPCSTR lpFormat, ...)
{

#ifdef _SETFILENAME
 if (MIN_LEVEL<=nshowlevel && nshowlevel<= MAX_LEVEL)
#endif
 {
  const DWORD BufSize = 2048;
  char szMsg[BufSize];
  
  va_list args;  //格式化消息
  
  va_start(args, lpFormat);   
  wvsprintfA(szMsg,  lpFormat, args);  //vsprintf_s BufSize - strlen(szMsg),
  va_end(args); 
  
  char buffer[20];
     _itoa(nshowlevel, buffer, 10 );
  strcat(szMsg,"......Level=");
        strcat(szMsg,buffer);
  strcat(szMsg,"\r\n");

  //输出信息
  CLog::logout("0",0,szMsg);
 }

}
#endif //_LOGLEVEL

#ifdef _DEBUGCONSOLE
//关闭控制台接口函数4
static void Logconsole_close()
{
 FreeConsole();
}

//隐藏或显示控制台接口函数5
static void Logcconsole_win(BOOL pSHWinConsole = FALSE)
{
 static BOOL bGetWinConsole = FALSE;
 HWND wincmd = NULL;

 if (!bGetWinConsole)
  {
   typedef HWND (WINAPI *PROCGETCONSOLEWINDOW)();
   PROCGETCONSOLEWINDOW GetConsoleWindow;
   
   HMODULE hKernel32 = GetModuleHandle("kernel32");
   GetConsoleWindow = (PROCGETCONSOLEWINDOW)GetProcAddress(hKernel32,"GetConsoleWindow");

   wincmd=GetConsoleWindow();
  }
 if (pSHWinConsole)
 {
  ShowWindowAsync(wincmd, SW_SHOWNORMAL);
 }
 else
 {
  ShowWindowAsync(wincmd, SW_HIDE );
 }
}
#endif //_DEBUGCONSOLE

#else  //_FLAG_OUTLOG_ENABLE

//日志输出接口函数1 空 用于Release版本
static void Logout(LPCSTR lpFormat, ...)
{
}
//日志输出接口函数2 空 用于Release版本
static void Logflout(LPCSTR lpFile, int nLine,LPCSTR lpFormat, ...)
{
}

#ifdef _LOGLEVEL
//日志输出接口函数3 空 用于Release版本
static void Loglevelout(int nshowlevel,LPCSTR lpFormat, ...)
{
}
#endif //_LOGLEVEL

#ifdef _DEBUGCONSOLE
//关闭控制台接口函数4 空 用于Release版本
static void Logconsole_close()
{
}
//隐藏或显示控制台接口函数5 空 用于Release版本
static void Logcconsole_win(BOOL pSHWinConsole = FALSE)
{
}
#endif //_DEBUGCONSOLE

#endif //_FLAG_OUTLOG_ENABLE

#endif //!defined(AFX_LOGFILE_H__EF4BC4B2_3BB6_46E8_B936_0F3A61E20BF0__INCLUDED_)

/******************************************************************************
版本号: 3.
时间: 2013/5/16
为更方便书写,将函数名的首字母C去掉。如CLogout 更改为 Logout等
-----------------------------------------------------------------------------
版本号: 2.
时间: 2013/5/15
由于参数匹配有时混乱问题,所以修改函数名CLogout(LPCSTR lpFile, int nLine,LPCSTR lpFormat, ...)
为CLogflout(LPCSTR lpFile, int nLine,LPCSTR lpFormat, ...)
-----------------------------------------------------------------------------
版本号: 1.
时间: 2013/5/15
正常摘用
******************************************************************************/

代码及例程下载链接 http://download.youkuaiyun.com/detail/hixi2007/5383127

202512.05 更新最新写日志源代码如下:

/******************************************************************************
//功能:	输出日志信息  logexthwd.h
//  
//版本号:	2.
//
//作者:	王小丁hixi@21cn.com 23465028@qq.com
//时间:	2021/09/10
//修改日期2025.12.05
******************************************************************************/

/******************************************************************************
使用方法:

	1,	只有一个头文件logexthwd.h, 单线程include后,直接调用函数即可
	2,	VC6,VS2015兼容VC版本
	3,	可输出文件名及行号
	4,	支持多线程应用

  例如:
  在StdAfx.h或 xx.h头文件 或xx.cpp源代码文件中#include "logexthwd.h",就可以直接调用以下函数输出日志信息

	LOGEXT("CLOGEXT::OnRButtonUp()");
	
	int Action = 1;
	char item[] = "Test";
	char userID[] = "88889999";
	float fPer = 0.123;	
	LOGEXT("CLOGEXT::Frame()  Action=%d, item=%s, userID=%s, fPer=%f ", Action, item, userID, fPer);
	......

******************************************************************************/

//防止多次include头文件
#ifndef __LOGEXTHWD_H_   // 防止重复定义
#define __LOGEXTHWD_H_
#pragma once
#include <stdarg.h> 	//包含va_list
#include <stdio.h> 	//包含vsprintf()
#include <string.h>

// Debug版本宏开关控制##############################edit0
#if 1 // _DEBUG
#ifndef _FLAG_OUTLOG_ENABLE	
#define _FLAG_OUTLOG_ENABLE 1		
#endif // _FLAG_OUTLOG_ENABLE
#endif // _DEBUG

#if (_FLAG_OUTLOG_ENABLE) 
// 宏定义日志加强版输出加上文件名和行号的类函数
#define LOGEXT CLOGEXT(__FILE__,__LINE__).logfun
#else  //_FLAG_OUTLOG_ENABLE
#define LOGEXT  
#endif//_FLAG_OUTLOG_ENABLE

class CLogLock	// 多线程加锁类
{
public:
	CLogLock()		{ InitializeCriticalSection(&m_cs); }
	~CLogLock()	{ 	DeleteCriticalSection(&m_cs); }
	void Lock()  { EnterCriticalSection(&m_cs); }
	void UnLock() { LeaveCriticalSection( &m_cs ); }
private:
	CRITICAL_SECTION  m_cs ;	
};

// 日志加强版类
class CLOGEXT 
{ 
public: 
	
    // 类定义文件名变量.行号变量.	
    char * m_file; 
    int m_line; 
    // 静态自动锁.// 多线程加静态锁防遗漏日志. 	
    /*static*/ CLogLock  m_lock;		 // 单线程可去掉static后,则不用在此文件外定义CLogLock CLOGEXT::m_lock; ##############################edit1
    //static CLogLock  m_lock;			 // 应用到多线程, 不定义静态锁则日志信息会有丢失.
    
	//从路径名中获取源代码文件名.
	const char * getFileName(const char * filePath) 
	{
	#ifdef _WIN32
		char delimeter = '\\';
	#else
		char delimeter = '/';
	#endif
		char *tmp = (char*)filePath;
		for (const char * c = tmp + strlen(filePath) - 1; c != filePath; --c) {
			if (*c == delimeter)
				return ++c;
		}
		return filePath;
	}	
	
    // 类构造函数获取文件名行号并开始加锁.	
    CLOGEXT(char * file,int line) 
    { 
    	 m_lock.Lock();	// 开始加锁// 多线程加静态锁防遗漏日志.
        m_file=file; 
        m_line=line; 
    }
	
    // 主要的写日志函数	
    void logfun(LPCSTR lpFormat,...) 
    {   
	 if ( NULL == lpFormat )
            return;		
	 
	// 获取当前时间精确到毫秒
	char m_now[64];
	ZeroMemory(m_now, sizeof(m_now));
	SYSTEMTIME st;
	GetLocalTime(&st);
	
	//获取日志文件名
	char buff[MAX_PATH] = { 0 };
	GetModuleFileName(NULL, buff, MAX_PATH);
	CString strpathfilename = buff;  //CString strpathfilename;
	int pos = strpathfilename.ReverseFind('\\');
	strpathfilename = strpathfilename.Left(pos + 1);
	CString TimeIDFileName; 
	//TimeIDFileName.Format("sm_clock_%d-%d-%d.txt",st.wYear, st.wMonth, st.wDay);	// 日志文件名可以修改##############################edit2
	TimeIDFileName.Format("Logs\\LogInfo_%d-%d.txt",st.wYear, st.wMonth);	// 日志文件名可以修改##############################edit2
	strpathfilename = strpathfilename + TimeIDFileName;

	pos = strpathfilename.ReverseFind('\\');
	CString strpathfile = strpathfilename.Left(pos + 1);
	if( PathFileExists(strpathfile)==FALSE )
	{
		// TimeIDFileName 只能添加一层不存在的目录,不然CreateDirectory创建多层目录时会失败.
		// 作为bool类型,他们的返回值在操作成功返回0,否则返回非零。
		BOOL Bret = CreateDirectory( strpathfile, NULL ); 
	}
	
	// win10 C:\\mylog.txt -> C:\Users\akin-pc\AppData\Local\VirtualStore
	//strpathfilename.Format("D:\\Log\\mylog.txt");	//可以自定义修改要保存的日志文件路径名##############################edit3	
	
	// 填写日志消息头的时间,文件名,行号,线程ID号.
	CString strtime = "";
	sprintf(m_now, "%04d-%02d-%02d %02d:%02d:%02d.%03d",st.wYear,st.wMonth,st.wDay, st.wHour,st.wMinute,st.wSecond, st.wMilliseconds);
	strtime.Format( _T("[%s %s Line=%d ThreadId=%d]   "),m_now, getFileName(m_file), m_line, GetCurrentThreadId());
	
	// 获取不定参数日志信息到字组变量szMsg中.
	 const DWORD BufSize = 214048;  // 2048
        char szMsg[BufSize] = {0};
	ZeroMemory(szMsg, sizeof(szMsg));
        va_list args; 
        va_start(args, lpFormat);    
        _vstprintf_s(szMsg, BufSize, lpFormat, args); // _vstprintf // 安全替代方案:使用 _vstprintf_s(带缓冲区大小检查的版本)  //wvsprintfA vsprintf_s 
        va_end(args);  
		
	// 打开文件并追加写入日志信息到文件后面.	
	CFile file;
	BOOL bOpen = file.Open(strpathfilename, CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate);
	if (bOpen)
	{
		file.SeekToEnd();	//  移动文件指针到最后	
		file.Write(strtime, strtime.GetLength());	// 写入消息头	
		file.Write(szMsg, strlen(szMsg));			// 写入消息内容
		file.Write(_T("\r\n"), strlen(_T("\r\n")));	// 写入换行符
		file.Close();	// 关闭文件
	}	
	m_lock.UnLock();	// 释放解锁// 多线程加静态锁防遗漏日志.
    } 
	
};
#endif // __LOGEXTHWD_H_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值