功能:输出日志信息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_
本文介绍了一个简单的VC日志输出类,支持多线程、控制台输出、日志等级等功能,适用于VC6及以上版本。
7577

被折叠的 条评论
为什么被折叠?



