blob: b1c1d939d7c4c916313ee4b5651dc2cc1c2a8887 [file] [log] [blame]
/*
* 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/propertyconfigurator.h>
#include <log4cxx/helpers/properties.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/logmanager.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/level.h>
#if LOG4CXX_ABI_VERSION <= 15
#include <log4cxx/defaultloggerfactory.h>
#else
#include <log4cxx/spi/loggerfactory.h>
#endif
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/layout.h>
#include <log4cxx/config/propertysetter.h>
#include <log4cxx/helpers/stringtokenizer.h>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/helpers/fileinputstream.h>
#include <log4cxx/helpers/loader.h>
#include <log4cxx/helpers/threadutility.h>
#include <log4cxx/helpers/singletonholder.h>
#include <log4cxx/rolling/rollingfileappender.h>
#define LOG4CXX 1
#include <log4cxx/helpers/aprinitializer.h>
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::spi;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::config;
using namespace LOG4CXX_NS::rolling;
#include <log4cxx/helpers/filewatchdog.h>
namespace LOG4CXX_NS
{
class PropertyWatchdog : public FileWatchdog
{
public:
PropertyWatchdog(const File& filename) : FileWatchdog(filename)
{
}
/**
Call PropertyConfigurator#doConfigure(const String& configFileName,
const spi::LoggerRepositoryPtr& hierarchy) with the
<code>filename</code> to reconfigure log4cxx.
*/
void doOnChange()
{
PropertyConfigurator().doConfigure(file(),
LogManager::getLoggerRepository());
}
static void startWatching(const File& filename, long delay)
{
using WatchdogHolder = SingletonHolder<PropertyWatchdog>;
auto pHolder = APRInitializer::getOrAddUnique<WatchdogHolder>
( [&filename]() -> ObjectPtr
{ return std::make_shared<WatchdogHolder>(filename); }
);
auto& pdog = pHolder->value();
pdog.setFile(filename);
pdog.setDelay(0 < delay ? delay : FileWatchdog::DEFAULT_DELAY);
pdog.start();
}
};
}
IMPLEMENT_LOG4CXX_OBJECT(PropertyConfigurator)
using RegistryType = std::map<LogString, AppenderPtr>;
using RegistryPtr = std::unique_ptr<RegistryType>;
#if 15 < LOG4CXX_ABI_VERSION
struct PropertyConfigurator::PrivateData
{
/**
Used internally to keep track of configured appenders.
*/
RegistryPtr registry{ std::make_unique<RegistryType>() };
/**
Used to create new instances of logger
*/
spi::LoggerFactoryPtr loggerFactory{ std::make_shared<LoggerFactory>() };
/**
True if an appender was added to a logger
*/
bool appenderAdded{ false };
};
PropertyConfigurator::PropertyConfigurator()
: m_priv{ std::make_unique<PrivateData>() }
#else
#define m_priv this
PropertyConfigurator::PropertyConfigurator()
: registry(new std::map<LogString, AppenderPtr>())
, loggerFactory(new DefaultLoggerFactory())
#endif
{
}
PropertyConfigurator::~PropertyConfigurator()
{
#if LOG4CXX_ABI_VERSION <= 15
delete registry;
#endif
}
spi::ConfigurationStatus PropertyConfigurator::doConfigure
( const File& configFileName
#if LOG4CXX_ABI_VERSION <= 15
, spi::LoggerRepositoryPtr repository
#else
, const spi::LoggerRepositoryPtr& repository
#endif
)
{
auto result = spi::ConfigurationStatus::NotConfigured;
if (LogLog::isDebugEnabled())
{
LogLog::debug(LOG4CXX_STR("Loading configuration file [")
+ configFileName.getPath() + LOG4CXX_STR("]"));
}
Properties props = Configurator::properties();
try
{
InputStreamPtr inputStream = InputStreamPtr( new FileInputStream(configFileName) );
props.load(inputStream);
}
catch (const IOException& ex)
{
LogLog::error(LOG4CXX_STR("Could not load properties from [")
+ configFileName.getPath() + LOG4CXX_STR("]"), ex);
return result;
}
try
{
result = doConfigure(props, repository ? repository : LogManager::getLoggerRepository());
#if LOG4CXX_ABI_VERSION <= 15
if (m_priv->registry->empty())
#else
if (!m_priv->appenderAdded)
#endif
{
LogLog::warn(LOG4CXX_STR("[") + configFileName.getPath()
+ LOG4CXX_STR("] did not add an ") + Appender::getStaticClass().getName()
+ LOG4CXX_STR(" to a logger"));
}
}
catch (const std::exception& ex)
{
LogLog::error(LOG4CXX_STR("Exception thrown processing [")
+ configFileName.getPath() + LOG4CXX_STR("]: "), ex);
}
return result;
}
spi::ConfigurationStatus PropertyConfigurator::configure(const File& configFilename)
{
return PropertyConfigurator().doConfigure(configFilename, LogManager::getLoggerRepository());
}
spi::ConfigurationStatus PropertyConfigurator::configure(helpers::Properties& properties)
{
return PropertyConfigurator().doConfigure(properties, LogManager::getLoggerRepository());
}
#if LOG4CXX_ABI_VERSION <= 15
spi::ConfigurationStatus PropertyConfigurator::configureAndWatch(const File& configFilename)
{
return configureAndWatch(configFilename, FileWatchdog::DEFAULT_DELAY);
}
#endif
spi::ConfigurationStatus PropertyConfigurator::configureAndWatch(
const File& configFilename, long delay)
{
spi::ConfigurationStatus stat = PropertyConfigurator().doConfigure(configFilename, LogManager::getLoggerRepository());
PropertyWatchdog::startWatching(configFilename, delay);
return stat;
}
spi::ConfigurationStatus PropertyConfigurator::doConfigure(helpers::Properties& properties,
spi::LoggerRepositoryPtr hierarchy)
{
LogString debugValue(properties.getProperty(LOG4CXX_STR("log4j.debug")));
if (!debugValue.empty())
{
LogLog::setInternalDebugging(OptionConverter::toBoolean(debugValue, true));
}
LogString colorValue(properties.getProperty(LOG4CXX_STR("log4j.color")));
if (!colorValue.empty())
{
LogLog::setColorEnabled(OptionConverter::toBoolean(colorValue, true));
}
LogString thresholdStr =
OptionConverter::findAndSubst(LOG4CXX_STR("log4j.threshold"), properties);
if (!thresholdStr.empty())
{
hierarchy->setThreshold(OptionConverter::toLevel(thresholdStr, Level::getAll()));
if (LogLog::isDebugEnabled())
{
LogLog::debug(LOG4CXX_STR("Repository threshold =[")
+ hierarchy->getThreshold()->toString()
+ LOG4CXX_STR("]"));
}
}
LogString threadConfigurationValue(properties.getProperty(LOG4CXX_STR("log4j.threadConfiguration")));
if ( threadConfigurationValue == LOG4CXX_STR("NoConfiguration") )
{
helpers::ThreadUtility::configure( ThreadConfigurationType::NoConfiguration );
}
else if ( threadConfigurationValue == LOG4CXX_STR("BlockSignalsOnly") )
{
helpers::ThreadUtility::configure( ThreadConfigurationType::BlockSignalsOnly );
}
else if ( threadConfigurationValue == LOG4CXX_STR("NameThreadOnly") )
{
helpers::ThreadUtility::configure( ThreadConfigurationType::NameThreadOnly );
}
else if ( threadConfigurationValue == LOG4CXX_STR("BlockSignalsAndNameThread") )
{
helpers::ThreadUtility::configure( ThreadConfigurationType::BlockSignalsAndNameThread );
}
configureRootLogger(properties, hierarchy);
configureLoggerFactory(properties);
parseCatsAndRenderers(properties, hierarchy);
LogLog::debug(LOG4CXX_STR("Finished configuring."));
#if LOG4CXX_ABI_VERSION <= 15
auto result = m_priv->registry->empty()
#else
auto result = !m_priv->appenderAdded
#endif
? spi::ConfigurationStatus::NotConfigured
: spi::ConfigurationStatus::Configured;
if (spi::ConfigurationStatus::Configured == result)
hierarchy->setConfigured(true);
return result;
}
void PropertyConfigurator::configureLoggerFactory(helpers::Properties& props)
{
LogString factoryClassName =
OptionConverter::findAndSubst(LOG4CXX_STR("log4j.loggerFactory"), props);
if (!factoryClassName.empty())
{
auto instance = OptionConverter::instantiateByClassName
( StringHelper::trim(factoryClassName)
, LoggerFactory::getStaticClass()
#if LOG4CXX_ABI_VERSION <= 15
, std::make_shared<DefaultLoggerFactory>()
#else
, std::make_shared<LoggerFactory>()
#endif
);
m_priv->loggerFactory = LOG4CXX_NS::cast<LoggerFactory>( instance );
Pool p;
PropertySetter::setProperties(m_priv->loggerFactory, props, LOG4CXX_STR("log4j.factory."), p);
}
}
void PropertyConfigurator::configureRootLogger(helpers::Properties& props,
spi::LoggerRepositoryPtr& hierarchy)
{
LogString effectivePrefix(LOG4CXX_STR("log4j.rootLogger"));
LogString value = OptionConverter::findAndSubst(effectivePrefix, props);
if (value.empty())
{
effectivePrefix = LOG4CXX_STR("log4j.rootCategory");
value = OptionConverter::findAndSubst(effectivePrefix, props);
}
if (value.empty())
{
LogLog::debug(LOG4CXX_STR("Neither 'log4j.rootLogger' or 'log4j.rootCategory' found. Is this OK?"));
}
else
{
LoggerPtr root = hierarchy->getRootLogger();
parseLogger(props, root, effectivePrefix, LOG4CXX_STR("root"), value, true);
}
}
void PropertyConfigurator::parseCatsAndRenderers(helpers::Properties& props,
spi::LoggerRepositoryPtr& hierarchy)
{
for (auto key : props.propertyNames())
{
auto categoryFound = (0 == key.find(LOG4CXX_STR("log4j.category.")));
if (categoryFound || 0 == key.find(LOG4CXX_STR("log4j.logger.")))
{
auto prefixLength =
( categoryFound
? LogString(LOG4CXX_STR("log4j.category."))
: LogString(LOG4CXX_STR("log4j.logger."))
).length();
auto loggerName = key.substr(prefixLength);
auto value = OptionConverter::findAndSubst(key, props);
auto logger = hierarchy->getLogger(loggerName, m_priv->loggerFactory);
auto additivity = parseAdditivityForLogger(props, logger, loggerName);
parseLogger(props, logger, key, loggerName, value, additivity);
}
}
}
bool PropertyConfigurator::parseAdditivityForLogger(helpers::Properties& props,
LoggerPtr& cat, const LogString& loggerName)
{
LogString value(OptionConverter::findAndSubst(LOG4CXX_STR("log4j.additivity.") + loggerName, props));
// touch additivity only if necessary
if (!value.empty())
{
bool additivity = OptionConverter::toBoolean(value, true);
if (LogLog::isDebugEnabled())
{
LogLog::debug(LOG4CXX_STR("Setting [") + loggerName + LOG4CXX_STR("] additivity to [")
+ (additivity ? LogString(LOG4CXX_STR("true")) : LogString(LOG4CXX_STR("false")) + LOG4CXX_STR("]")));
}
return additivity;
}
return true;
}
/**
This method must work for the root logger as well.
*/
void PropertyConfigurator::parseLogger(
helpers::Properties& props, LoggerPtr& logger, const LogString& /* optionKey */,
const LogString& loggerName, const LogString& value, bool additivity)
{
if (LogLog::isDebugEnabled())
{
LogLog::debug(((LogString) LOG4CXX_STR("Parsing for ["))
+ loggerName
+ LOG4CXX_STR("] with value=[")
+ value + LOG4CXX_STR("]"));
}
// We must skip over ',' but not white space
StringTokenizer st(value, LOG4CXX_STR(","));
// If value is not in the form ", appender.." or "", then we should set
// the level of the logger.
if (!(value.find(LOG4CXX_STR(",")) == 0 || value.empty()))
{
// just to be on the safe side...
if (!st.hasMoreTokens())
{
return;
}
LogString levelStr = st.nextToken();
// If the level value is inherited, set logger level value to
// null. We also check that the user has not specified inherited for the
// root logger.
if (StringHelper::equalsIgnoreCase(levelStr, LOG4CXX_STR("INHERITED"), LOG4CXX_STR("inherited"))
|| StringHelper::equalsIgnoreCase(levelStr, LOG4CXX_STR("NULL"), LOG4CXX_STR("null")))
{
if (loggerName == LOG4CXX_STR("root"))
{
LogLog::warn(LOG4CXX_STR("Root level cannot be ") + levelStr + LOG4CXX_STR(". Ignoring directive."));
}
else
{
logger->setLevel(0);
}
}
else
{
logger->setLevel(OptionConverter::toLevel(levelStr, Level::getDebug()));
}
if (LogLog::isDebugEnabled())
{
LogLog::debug(loggerName + LOG4CXX_STR(" level set to ") +
logger->getEffectiveLevel()->toString());
}
}
AppenderPtr appender;
LogString appenderName;
std::vector<AppenderPtr> newappenders;
while (st.hasMoreTokens())
{
appenderName = StringHelper::trim(st.nextToken());
if (appenderName.empty() || appenderName == LOG4CXX_STR(","))
{
continue;
}
if (LogLog::isDebugEnabled())
{
LogLog::debug(LOG4CXX_STR("Parsing ") + Appender::getStaticClass().getName()
+ LOG4CXX_STR(" named [") + appenderName + LOG4CXX_STR("]"));
}
appender = parseAppender(props, appenderName);
if (appender != 0)
{
newappenders.push_back(appender);
}
}
#if 15 < LOG4CXX_ABI_VERSION
if (!newappenders.empty())
m_priv->appenderAdded = true;
#endif
logger->reconfigure( newappenders, additivity );
}
AppenderPtr PropertyConfigurator::parseAppender(
helpers::Properties& props, const LogString& appenderName)
{
AppenderPtr appender = registryGet(appenderName);
if (appender != 0)
{
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("Appender [")
+ appenderName + LOG4CXX_STR("] was already parsed."));
}
return appender;
}
// Appender was not previously initialized.
LogString prefix = LOG4CXX_STR("log4j.appender.") + appenderName;
LogString layoutPrefix = prefix + LOG4CXX_STR(".layout");
std::shared_ptr<Object> obj =
OptionConverter::instantiateByKey(
props, prefix, Appender::getStaticClass(), 0);
appender = LOG4CXX_NS::cast<Appender>( obj );
// Map obsolete DailyRollingFileAppender property configuration
if (!appender &&
StringHelper::endsWith(OptionConverter::findAndSubst(prefix, props), LOG4CXX_STR("DailyRollingFileAppender")))
{
appender = std::make_shared<RollingFileAppender>();
auto datePattern = OptionConverter::findAndSubst(prefix + LOG4CXX_STR(".datePattern"), props);
if (!datePattern.empty())
props.put(prefix + LOG4CXX_STR(".fileDatePattern"), datePattern);
}
if (!appender)
{
LogLog::error((LogString) LOG4CXX_STR("Could not instantiate ") + Appender::getStaticClass().getName()
+ LOG4CXX_STR(" named [") + appenderName + LOG4CXX_STR("]"));
return 0;
}
appender->setName(appenderName);
if (appender->instanceof(OptionHandler::getStaticClass()))
{
Pool p;
if (appender->requiresLayout())
{
LayoutPtr layout;
std::shared_ptr<Object> obj =
OptionConverter::instantiateByKey(
props, layoutPrefix, Layout::getStaticClass(), 0);
layout = LOG4CXX_NS::cast<Layout>( obj );
if (layout != 0)
{
appender->setLayout(layout);
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("Parsing ") + Layout::getStaticClass().getName()
+ LOG4CXX_STR(" options for [") + appenderName + LOG4CXX_STR("]"));
}
PropertySetter::setProperties(layout, props, layoutPrefix + LOG4CXX_STR("."), p);
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("End of parsing for [")
+ appenderName + LOG4CXX_STR("]"));
}
}
}
RollingFileAppenderPtr rolling = LOG4CXX_NS::cast<rolling::RollingFileAppender>(appender);
if (rolling)
{
LogString rollingPolicyKey = prefix + LOG4CXX_STR(".rollingPolicy");
if (!OptionConverter::findAndSubst(rollingPolicyKey, props).empty())
{
RollingPolicyPtr rollingPolicy;
std::shared_ptr<Object> rolling_obj =
OptionConverter::instantiateByKey(
props, rollingPolicyKey, RollingPolicy::getStaticClass(), 0);
rollingPolicy = LOG4CXX_NS::cast<RollingPolicy>( rolling_obj );
if(rollingPolicy)
{
rolling->setRollingPolicy(rollingPolicy);
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("Parsing ") + RollingPolicy::getStaticClass().getName()
+ LOG4CXX_STR(" options for [") + appenderName + LOG4CXX_STR("]"));
}
PropertySetter::setProperties(rollingPolicy, props, rollingPolicyKey + LOG4CXX_STR("."), p);
}
}
LogString triggeringPolicyKey = prefix + LOG4CXX_STR(".triggeringPolicy");
if (!OptionConverter::findAndSubst(triggeringPolicyKey, props).empty())
{
TriggeringPolicyPtr triggeringPolicy;
std::shared_ptr<Object> triggering_obj =
OptionConverter::instantiateByKey(
props, triggeringPolicyKey, TriggeringPolicy::getStaticClass(), 0);
triggeringPolicy = LOG4CXX_NS::cast<TriggeringPolicy>( triggering_obj );
if(triggeringPolicy)
{
rolling->setTriggeringPolicy(triggeringPolicy);
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("Parsing ") + TriggeringPolicy::getStaticClass().getName()
+ LOG4CXX_STR(" options for [") + appenderName + LOG4CXX_STR("]"));
}
PropertySetter::setProperties(triggeringPolicy, props, triggeringPolicyKey + LOG4CXX_STR("."), p);
}
}
}
PropertySetter::setProperties(appender, props, prefix + LOG4CXX_STR("."), p);
if (LogLog::isDebugEnabled())
{
LogLog::debug((LogString) LOG4CXX_STR("Parsed [")
+ appenderName + LOG4CXX_STR("] options."));
}
}
registryPut(appender);
return appender;
}
void PropertyConfigurator::registryPut(const AppenderPtr& appender)
{
(*m_priv->registry)[appender->getName()] = appender;
}
AppenderPtr PropertyConfigurator::registryGet(const LogString& name)
{
return (*m_priv->registry)[name];
}