之前看到有个方法是在项目属性设置里实现的
以VS2010为例:
右键Project选择Properties->Configuration Properties->Build Events->Post-Build Event,在Command Line后面添加
editbin /SUBSYSTEM:CONSOLE $(OUTDIR)$(TargetName).exe
该文同时指出“使用AllocConsole()的方法,对printf和cout有效,而对log4cxx无效”。
此法虽然可行,但是无论是否有信息输出到Console,程序启动就会开启一个Console窗口。不灵活。
我要补充的是:使用AllocConsole()方法也是可以的,只是调用的位置不在主程序中,而是对log4cxx的源码稍做修改即可。
因为是控制台log相关的,所以我决定修改consoleappender.h和consoleappender.cpp,要做的就是为程序分配一个Console窗口,红色部分为添加的代码
consoleappender.h
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _LOG4CXX_CONSOLE_APPENDER_H #define _LOG4CXX_CONSOLE_APPENDER_H #include <windows.h> #include <tchar.h> #include <log4cxx/writerappender.h> namespace log4cxx { /** * ConsoleAppender appends log events to <code>stdout</code> or * <code>stderr</code> using a layout specified by the user. The * default target is <code>stdout</code>. */ class LOG4CXX_EXPORT ConsoleAppender : public WriterAppender { private: void AllocConsole(); LogString target; HWND m_wnd_console; public: DECLARE_LOG4CXX_OBJECT(ConsoleAppender) BEGIN_LOG4CXX_CAST_MAP() LOG4CXX_CAST_ENTRY(ConsoleAppender) LOG4CXX_CAST_ENTRY_CHAIN(AppenderSkeleton) END_LOG4CXX_CAST_MAP() ConsoleAppender(); ConsoleAppender(const LayoutPtr& layout); ConsoleAppender(const LayoutPtr& layout, const LogString& target); ~ConsoleAppender(); /** * Sets the value of the <b>target</b> property. Recognized values * are "System.out" and "System.err". Any other value will be * ignored. * */ void setTarget(const LogString& value); /** * Returns the current value of the <b>target</b> property. The * default value of the option is "System.out". * * See also #setTarget. * */ LogString getTarget() const; void activateOptions(log4cxx::helpers::Pool& p); void setOption(const LogString& option, const LogString& value); static const LogString& getSystemOut(); static const LogString& getSystemErr(); private: void targetWarn(const LogString& val); static log4cxx::helpers::WriterPtr createWriter(const LogString& target); }; LOG4CXX_PTR_DEF(ConsoleAppender); } //namespace log4cxx #endif //_LOG4CXX_CONSOLE_APPENDER_H
consoleappender.cpp
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <log4cxx/logstring.h> #include <log4cxx/consoleappender.h> #include <log4cxx/helpers/loglog.h> #include <log4cxx/helpers/systemoutwriter.h> #include <log4cxx/helpers/systemerrwriter.h> #include <log4cxx/helpers/stringhelper.h> #include <log4cxx/layout.h> using namespace log4cxx; using namespace log4cxx::helpers; IMPLEMENT_LOG4CXX_OBJECT(ConsoleAppender) ConsoleAppender::ConsoleAppender() : target(getSystemOut()), m_wnd_console(GetConsoleWindow()) { AllocConsole(); } ConsoleAppender::ConsoleAppender(const LayoutPtr& layout1) :target(getSystemOut()), m_wnd_console(GetConsoleWindow()) { AllocConsole(); setLayout(layout1); WriterPtr wr(createWriter(getSystemOut())); setWriter(wr); Pool p; WriterAppender::activateOptions(p); } ConsoleAppender::ConsoleAppender(const LayoutPtr& layout1, const LogString& target1) :m_wnd_console(GetConsoleWindow()), target(target1) { AllocConsole(); setLayout(layout1); WriterPtr wr(createWriter(target1)); setWriter(wr); Pool p; WriterAppender::activateOptions(p); } ConsoleAppender::~ConsoleAppender() { finalize(); } const LogString& ConsoleAppender::getSystemOut() { static const LogString name(LOG4CXX_STR("System.out")); return name; } const LogString& ConsoleAppender::getSystemErr() { static const LogString name(LOG4CXX_STR("System.err")); return name; } WriterPtr ConsoleAppender::createWriter(const LogString& value) { LogString v = StringHelper::trim(value); if (StringHelper::equalsIgnoreCase(v, LOG4CXX_STR("SYSTEM.ERR"), LOG4CXX_STR("system.err"))) { return new SystemErrWriter(); } return new SystemOutWriter(); } void ConsoleAppender::setTarget(const LogString& value) { LogString v = StringHelper::trim(value); if (StringHelper::equalsIgnoreCase(v, LOG4CXX_STR("SYSTEM.OUT"), LOG4CXX_STR("system.out"))) { target = getSystemOut(); } else if (StringHelper::equalsIgnoreCase(v, LOG4CXX_STR("SYSTEM.ERR"), LOG4CXX_STR("system.err"))) { target = getSystemErr(); } else { targetWarn(value); } } LogString ConsoleAppender::getTarget() const { return target; } void ConsoleAppender::targetWarn(const LogString& val) { LogLog::warn(((LogString) LOG4CXX_STR("[")) + val + LOG4CXX_STR("] should be system.out or system.err.")); LogLog::warn(LOG4CXX_STR("Using previously set target, System.out by default.")); } void ConsoleAppender::activateOptions(Pool& p) { if(StringHelper::equalsIgnoreCase(target, LOG4CXX_STR("SYSTEM.OUT"), LOG4CXX_STR("system.out"))) { WriterPtr writer1(new SystemOutWriter()); setWriter(writer1); } else if (StringHelper::equalsIgnoreCase(target, LOG4CXX_STR("SYSTEM.ERR"), LOG4CXX_STR("system.err"))) { WriterPtr writer1(new SystemErrWriter()); setWriter(writer1); } WriterAppender::activateOptions(p); } void ConsoleAppender::setOption(const LogString& option, const LogString& value) { if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("TARGET"), LOG4CXX_STR("target"))) { setTarget(value); } else { WriterAppender::setOption(option, value); } } void ConsoleAppender::AllocConsole() { if(NULL == m_wnd_console) { ::AllocConsole(); m_wnd_console = GetConsoleWindow(); FILE* stream = NULL; _tfreopen_s(&stream, _T("CONOUT$"), _T("w"), stdout); } }
propertyconfigurator.cpp中修改如下
void PropertyConfigurator::doConfigure(const File& configFileName, spi::LoggerRepositoryPtr& hierarchy) { std::locale::global(std::locale("")); hierarchy->setConfigured(true); Properties props; try { InputStreamPtr inputStream = new FileInputStream(configFileName); props.load(inputStream); } catch(const IOException& ie) { LogLog::error(((LogString) LOG4CXX_STR("Could not read configuration file [")) + configFileName.getPath() + LOG4CXX_STR("].")); return; } try { doConfigure(props, hierarchy); } catch(const std::exception& ex) { LogLog::error(((LogString) LOG4CXX_STR("Could not parse configuration file [")) + configFileName.getPath() + LOG4CXX_STR("]."), ex); } }
在程序中写了一个对log4cxx的封装类CLoggerFramework,主要是为了修改控制台文本颜色和分离log(TRACE信息只输出到控制台,不输出到文件)
LoggerFramework.h
#pragma once // log4cxx #include "log4cxx/logger.h" #include "log4cxx/propertyconfigurator.h" using namespace log4cxx; #define _TRACE CLoggerFramework::LoggerInstance()->LoggerTrace #define _INFO CLoggerFramework::LoggerInstance()->LoggerInfo #define _WARN CLoggerFramework::LoggerInstance()->LoggerWarn #define _ERROR CLoggerFramework::LoggerInstance()->LoggerError class CLoggerFramework { public: // TODO: add your methods here. void LoggerTrace(const TCHAR* msg, ...); void LoggerInfo (const TCHAR* msg, ...); void LoggerWarn (const TCHAR* msg, ...); void LoggerError(const TCHAR* msg, ...); static CLoggerFramework* LoggerInstance(); private: CLoggerFramework(void); ~CLoggerFramework(void); void AttachConsoleAndSetTextColor(unsigned int color); unsigned int m_buf_len; static CLoggerFramework* m_pLoggerFramework; bool m_console_attached; // 之前字体颜色,如果相同则不用再设置 unsigned int m_prev_color; // 控制台句柄,用来设置字体颜色 HANDLE m_console_handle; CRITICAL_SECTION m_criticalSection; static LoggerPtr m_pFileLogger; static LoggerPtr m_pConsoleLogger; };
LoggerFramework.cpp
#include "stdafx.h" #include "LoggerFramework.h" CLoggerFramework* CLoggerFramework::m_pLoggerFramework = NULL; LoggerPtr CLoggerFramework::m_pFileLogger = NULL; LoggerPtr CLoggerFramework::m_pConsoleLogger = NULL; // This is the constructor of a class that has been exported. // see LoggerFramework.h for the class definition CLoggerFramework::CLoggerFramework() : m_buf_len(0) { m_console_attached = false; m_console_handle = NULL; m_prev_color = 0; InitializeCriticalSectionAndSpinCount(&m_criticalSection, 4000); // get logger PropertyConfigurator::configure(g_moduleDirectory + _T("log4cxx.properties")); m_pFileLogger = Logger::getLogger(_T("PMLog")); m_pConsoleLogger = Logger::getRootLogger(); } CLoggerFramework::~CLoggerFramework() { DeleteCriticalSection(&m_criticalSection); SetConsoleTextAttribute(m_console_handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); } CLoggerFramework* CLoggerFramework::LoggerInstance() { if(NULL == m_pLoggerFramework) { m_pLoggerFramework = new CLoggerFramework(); } return m_pLoggerFramework; } void CLoggerFramework::LoggerTrace(const TCHAR* msg, ...) { EnterCriticalSection(&m_criticalSection); AttachConsoleAndSetTextColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); va_list argList; va_start(argList, msg); m_buf_len = _vsctprintf(msg, argList) + 1; TCHAR* buffer = new TCHAR[m_buf_len](); _vstprintf_s(buffer, m_buf_len, msg, argList); va_end(argList); std::tstring message(buffer); delete[] buffer; LOG4CXX_TRACE(m_pConsoleLogger, message.c_str()); LeaveCriticalSection(&m_criticalSection); } void CLoggerFramework::LoggerInfo(const TCHAR* msg, ...) { EnterCriticalSection(&m_criticalSection); AttachConsoleAndSetTextColor(FOREGROUND_GREEN); va_list argList; va_start(argList, msg); m_buf_len = _vsctprintf(msg, argList) + 1; TCHAR* buffer = new TCHAR[m_buf_len](); _vstprintf_s(buffer, m_buf_len, msg, argList); va_end(argList); std::tstring message(buffer); delete[] buffer; LOG4CXX_INFO(m_pFileLogger, message.c_str()); LeaveCriticalSection(&m_criticalSection); } void CLoggerFramework::LoggerWarn(const TCHAR* msg, ...) { EnterCriticalSection(&m_criticalSection); AttachConsoleAndSetTextColor(FOREGROUND_RED | FOREGROUND_GREEN); va_list argList; va_start(argList, msg); m_buf_len = _vsctprintf(msg, argList) + 1; TCHAR* buffer = new TCHAR[m_buf_len](); _vstprintf_s(buffer, m_buf_len, msg, argList); va_end(argList); std::tstring message(buffer); delete[] buffer; LOG4CXX_WARN(m_pFileLogger, message.c_str()); LeaveCriticalSection(&m_criticalSection); } void CLoggerFramework::LoggerError(const TCHAR* msg, ...) { EnterCriticalSection(&m_criticalSection); AttachConsoleAndSetTextColor(FOREGROUND_RED); va_list argList; va_start(argList, msg); m_buf_len = _vsctprintf(msg, argList) + 1; TCHAR* buffer = new TCHAR[m_buf_len](); _vstprintf_s(buffer, m_buf_len, msg, argList); va_end(argList); std::tstring message(buffer); delete[] buffer; LOG4CXX_ERROR(m_pFileLogger, message.c_str()); LeaveCriticalSection(&m_criticalSection); } void CLoggerFramework::AttachConsoleAndSetTextColor(unsigned int color) { if(!m_console_attached) { // 获取控制台句柄用来设置字体颜色 m_console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTitle(_T("日志监控")); m_console_attached = true; } if(color != m_prev_color) { SetConsoleTextAttribute(m_console_handle, color); m_prev_color = color; } }
配置文件如下
#设置root logger为TRACE级别,使用了ca和fa两个Appender log4j.rootLogger = TRACE, ca log4j.logger.PMLog = INFO, fa, ha #对Appender ca进行设置 log4j.appender.ca = org.apache.log4j.ConsoleAppender log4j.appender.ca.ImmediateFlush = true log4j.appender.ca.Append = false log4j.appender.ca.layout = org.apache.log4j.PatternLayout log4j.appender.ca.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS}[%-5p]: %m%n #对Appender fa进行设置 log4j.appender.fa = org.apache.log4j.FileAppender log4j.appender.fa.ImmediateFlush = true log4j.appender.fa.File = output.log log4j.appender.fa.Append = false log4j.appender.fa.MaxFileSize = 2MB log4j.appender.fa.MaxBackupIndex = 10 log4j.appender.fa.layout = org.apache.log4j.PatternLayout log4j.appender.fa.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS}[%t][%c][%-5p]: %m%n #对Appender ha进行配置 log4j.appender.ha = org.apache.log4j.FileAppender log4j.appender.ha.ImmediateFlush = true log4j.appender.ha.File = output.htm log4j.appender.ha.Append = false log4j.appender.ha.MaxFileSize = 10MB log4j.appender.ha.MaxBackupIndex = 10 log4j.appender.ha.layout = org.apache.log4j.HTMLLayout log4j.appender.ha.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS}[%t][%c][%-5p]: %m%n
得到如下log效果
控制台:
文本log
htm网页log
另外关于每天产生一个日志文件的配置要注意,不要在File属性中指定文件名,否则不会按日期产生文件,要在DatePattern中指定,字符串最好用单引号分隔开,避免出现奇怪的文件后缀.还有,使用html格式输出时将layout指定为org.apache.log4j.HTMLLayout时layout.ConversionPattern指定的格式将失效.修改后的配置文件如下所示
#设置root logger为TRACE级别,使用了ca和fa两个Appender log4j.rootLogger = TRACE, ca log4j.logger.PMLog = INFO, fa, ha #对Appender ca进行设置 log4j.appender.ca = org.apache.log4j.ConsoleAppender log4j.appender.ca.ImmediateFlush = true log4j.appender.ca.Append = false log4j.appender.ca.layout = org.apache.log4j.PatternLayout log4j.appender.ca.layout.ConversionPattern = [%-5p][%d{yyyy-MM-dd HH:mm:ss,SSS}]: %m%n #对Appender fa进行设置 log4j.appender.fa = org.apache.log4j.DailyRollingFileAppender log4j.appender.fa.ImmediateFlush = true log4j.appender.fa.Append = true #log4j.appender.fa.File = ./Logs/Text/ParkingLog.log log4j.appender.fa.DatePattern = './Logs/Text/ParkingLog_'yyyy-MM-dd'.log' log4j.appender.fa.layout = org.apache.log4j.PatternLayout log4j.appender.fa.layout.ConversionPattern = [%-5p][%d{HH:mm:ss,SSS}]: %m%n #对Appender ha进行配置 log4j.appender.ha = org.apache.log4j.DailyRollingFileAppender log4j.appender.ha.ImmediateFlush = true log4j.appender.ha.Append = true #log4j.appender.ha.File = ./Logs/Htm/ParkingLog.htm log4j.appender.ha.DatePattern = './Logs/Html/ParkingLog_'yyyy-MM-dd'.html' log4j.appender.ha.layout = org.apache.log4j.HTMLLayout