修改版下载点: LogXMLByUnicode_2013_0407_2347.rar
修改版效果
今天正好要用到Log类, 没有用以前写过的, 觉得写的不好.
去CodeProject逛了逛, 看见一个有新意的日志类, 将日志写成了XML文件.
好处是: 日志可以用市面上现有的优秀XML文件分析工具来浏览, 不用另外写日志查看工具了.
如果日志不大, 用网页浏览器打开即可, 用户体验不错.
原版工程是基于Ansi编码, 我的工程是Unicode编码的, 用的都是wchar_t.
于是修改了一版, 使其符合我的应用, 修改点如下:
* 全部改成Unicode操作, 使用宽字符.
* 建立日志文件时写入 Unicode1 6 Le Bom, 使形成的xml文件可以被网页浏览器正常打开.
* 改变xml文件中的编码标识为 <?xml version=\"1.0\" encoding=\"Unicode\"?>
* 加入日志宏, 使日志类的使用体验更好.
* 修正了原版显示取时间戳毫秒数的BUG.
* 整理代码, 符合我的编码习惯.
* 加入以前写好的CUnicodeFileHelper, 在原版类中作为一个成员使用, 辅助生成UTF16LEBOM的文件头.
* 修正了XML中的显示格式, 增加实际应用中需要的日志字段, 符合我的应用.
日志类的使用还算方便, 测试程序如下:
/** \file LogXML_test.cpp \brief LogCML Test \date 2003-05-12 \author Christian Richardt ([email protected]) @note modify by LostSpeed on 2013-04-07 **/ #include <windows.h> #include <tchar.h> #include <stdio.h> #include "LogXmlUnicode.h" LOG_INIT(L"logAbc.xml", L"AppTest"); int main() { int i = 0; LOG_INFO(L"Starting ...\n"); for(int i = 0; i<100; i++) { LOG_INFO(L"SampleFunction(%i), Writing log entry #%i and waiting for the next ...\n", i, i); } LOG_INFO(L"\nAll sample log entries have been written ...\n"); LOG_INFO(L"\nMission finished ...\n\n\n"); return 0; }
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LogXML 1.0 ------------------ LogXML is a simple and free logging class for all purposes. You can use it freely und unlimited but give me credit where it's due. Source code is ready for DoxyGen (http://www.doxygen.org/). Written by Christian Richardt ([email protected]). Release history: Mai 12, 2003: Version 1.0. First release. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** \file LogXML.h \brief XML log class \date 2003-05-12 \author Christian Richardt ([email protected]) @note modify by LostSpeed on 2013-04-07, support unicode **/ #ifndef LOGXML_H #define LOGXML_H #include <stdio.h> #include "UnicodeFileHelper.h" #ifndef __WFILE__ #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) #define __WFILE__ WIDEN(__FILE__) #define __WFUNCTION__ WIDEN(__FUNCTION__) #endif // #ifndef __WFILE__ #ifndef LOG_INIT /// 日志初始化宏 #define LOG_INIT(LogFileName, LogAppName) \ LogXML XmlLogGolobal(LogFileName, LogAppName); /// 日志记录宏 #define LOG_INFO(...)\ XmlLogGolobal.LogEx(XmlLogGolobal.FileName(__WFILE__), __WFUNCTION__, __LINE__, __VA_ARGS__); #endif // #ifndef LOG_INIT /** \brief LogXML is a simple and free logging class for all purposes. \author Christian Richardt ([email protected]) **/ class LogXML { public: LogXML(); LogXML(wchar_t * pszFileName, wchar_t * pszAppName = L"None"); ~LogXML(); /// useage e.g. calsslog.LogEx(FileName(__WFILE__), __WFUNCTION__, __LINE__, L"hello~"); bool LogEx(wchar_t * pszFilename, wchar_t * pszFunctionName, size_t nLineSn, wchar_t * pszContentFormat, ...); wchar_t * FileName(wchar_t * pszFile); private: bool goDown(wchar_t * pszMessage = NULL, wchar_t * pszFilename = NULL, wchar_t * pszFuncname = NULL); void goUp(void); bool Log(wchar_t * pszFilename, wchar_t * pszFunctionName, size_t nLineSn, wchar_t * pszContentFormat); bool WriteLine(wchar_t * pData); bool WriteLine(wchar_t * pszTagName, wchar_t* pszContents); bool WriteTimestamp(void); bool WriteDateTime(void); bool WriteData(wchar_t * pData); double mtime(unsigned short & millitm); ///< UNIX timestamp: seconds from 1970-01-01 00:00:00 (UTC) void memberInit(); BOOL GetModulePathPrefix(std::wstring & strPathW); ///< @todo 放到工具代码中 private: wchar_t * m_pszFileName; ///< log file name FILE * m_fileLog; ///< file handle of log file int m_iDepth; ///< current depth in document BOOL m_bFileIsUTF16_LE; ///< 文件是UTF16_LE? , 如果文件不是UTF16_LE, 无法再继续操作 CUnicodeFileHelper * m_pFileHelper; }; extern LogXML XmlLogGolobal; #endif // LOGXML_H
#ifndef __UNICODE_FILE_HELPER_H__ #define __UNICODE_FILE_HELPER_H__ #include <windows.h> #include <tchar.h> #include <string> class CUnicodeFileHelper { public: CUnicodeFileHelper(CONST TCHAR * lpcFilePathName); virtual ~CUnicodeFileHelper(void); public: BOOL IsFileExist(); BOOL IsUTF16_LE(); ///< 文件是否是UTF16_LE格式 BOOL CreateFileAsUTF16_LE(); ///< 按照UTF16_LE建立一个新文件 CONST TCHAR * GetIniPathName(); private: BOOL IsValidFileHandle(); BOOL OpenFileForRead(); /// @fn BOOL OpenFileForWrite(BOOL bOpenExist) /// @brief 打开文件去写 /// @param BOOL bOpenExist, 被操作的文件是否是已经存在的文件 /// bOpenExist = TRUE, 指定打开的文件是已经存在的, 如果文件不存在, 打开文件失败 /// bOpenExist = FALSE, 新建文件, 如果文件存在, 会清空原文件内容 BOOL OpenFileForWrite(BOOL bOpenExist); VOID CloseFile(); private: std::wstring m_strFilePathName; ///< 文件全路径 HANDLE m_hFile; ///< 文件句柄 DWORD m_dwLastError; ///< 最近的系统错误码 /// 用UltraEdit观察到 Windows记事本保存成Unicode格式后, BOM 是0xFEFF static CONST WORD m_wUnicode16LeBom = 0xFEFF; ///< UTF16-little endian byte order mark }; #endif
bug list:
* 还不支持转移字符, 参考资料: http://blog.csdn.net/High_Mount/article/details/2953335
使用DTD文件的方法试过了, 不好使.
需要搞一个转义符处理, 写进文件之前进行处理. 这样日志效率很低啊.
也可以等文件关闭后, 做一次处理. 如果文件是海量日志, 这么搞行不通.
最好的方法是不使用XML日志, 还是用传统的行日志~
可以将日志处理发给后台处理, 后台在写入日志前, 进行转义.
这样日志形式就变成接口, 需要开一个日志后台处理程序.
还可以直接用支持unicode的xml类来处理, 直接将xml内容写成文件. 不过效率有问题.
还是要调用接口, 让后台程序慢慢去写日志.
让后台写日志, 就涉及到多线程的问题, 每个日志写入者有自己的PID, TID. 文件名, AppName.
需要后台日志处理程序都记住, 细节也挺多.
先将就用, 写完日志后, 用文本编辑器来浏览日志, 比看传统的行日志, 还是清晰很多.
还有一个临时措施, 写日志时, 不写入转义字符.
如果要记录的是第三方给的数据, 不可避免的会有转义字符, 那就一定要转义了或者不使用xml日志记录.
以后想好了, 再来改.