| /* | |
| * 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/spi/loggerfactory.h> | |
| #include <log4cxx/spi/loggerrepository.h> | |
| #include <log4cxx/appenderskeleton.h> | |
| #include <log4cxx/helpers/optionconverter.h> | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <ctype.h> | |
| #include <log4cxx/helpers/stringhelper.h> | |
| #include <log4cxx/helpers/exception.h> | |
| #include <stdlib.h> | |
| #include <cerrno> | |
| #include <limits> | |
| #include <log4cxx/helpers/properties.h> | |
| #include <log4cxx/helpers/loglog.h> | |
| #include <log4cxx/level.h> | |
| #include <log4cxx/helpers/object.h> | |
| #include <log4cxx/helpers/class.h> | |
| #include <log4cxx/helpers/loader.h> | |
| #include <log4cxx/helpers/system.h> | |
| #include <log4cxx/propertyconfigurator.h> | |
| #include <log4cxx/helpers/transcoder.h> | |
| #include <log4cxx/file.h> | |
| #include <log4cxx/xml/domconfigurator.h> | |
| #include <log4cxx/logmanager.h> | |
| #if !defined(LOG4CXX) | |
| #define LOG4CXX 1 | |
| #endif | |
| #include <log4cxx/helpers/aprinitializer.h> | |
| #include <log4cxx/helpers/filewatchdog.h> | |
| #include <log4cxx/helpers/singletonholder.h> | |
| namespace | |
| { | |
| using namespace LOG4CXX_NS; | |
| void skipWhitespace(char*& p) | |
| { | |
| while (std::isspace(static_cast<unsigned char>(*p))) | |
| { | |
| ++p; | |
| } | |
| } | |
| /// For recursion checking | |
| struct LogStringChain | |
| { | |
| const LogString& item; | |
| const LogStringChain* parent; | |
| }; | |
| /// Is \c item referenced in \c path | |
| bool isRecursiveReference(const LogString& newkey, const LogStringChain* path) | |
| { | |
| bool result = false; | |
| if (path->item == newkey) | |
| result = true; | |
| else if (path->parent) | |
| result = isRecursiveReference(newkey, path->parent); | |
| return result; | |
| } | |
| LogString substVarsSafely(const LogString& val, helpers::Properties& props, const LogStringChain* path = 0) | |
| { | |
| LogString sbuf; | |
| const logchar delimStartArray[] = { 0x24, 0x7B, 0 }; // '$', '{' | |
| const LogString delimStart(delimStartArray); | |
| const logchar delimStop = 0x7D; // '}'; | |
| const size_t DELIM_START_LEN = 2; | |
| const size_t DELIM_STOP_LEN = 1; | |
| size_t i = 0; | |
| while (true) | |
| { | |
| size_t j = val.find(delimStart, i); | |
| if (j == val.npos) | |
| { | |
| // no more variables | |
| if (i == 0) | |
| { | |
| // this is a simple string | |
| return val; | |
| } | |
| else | |
| { | |
| // add the tail string which contails no variables and return the result. | |
| sbuf.append(val.substr(i, val.length() - i)); | |
| return sbuf; | |
| } | |
| } | |
| else | |
| { | |
| sbuf.append(val.substr(i, j - i)); | |
| size_t k = val.find(delimStop, j); | |
| if (k == val.npos) | |
| { | |
| LogString msg(1, (logchar) 0x22 /* '\"' */); | |
| msg.append(val); | |
| msg.append(LOG4CXX_STR("\" has no closing brace. Opening brace at position ")); | |
| helpers::Pool p; | |
| helpers::StringHelper::toString(j, msg); | |
| msg.append(1, (logchar) 0x2E /* '.' */); | |
| throw helpers::IllegalArgumentException(msg); | |
| } | |
| else | |
| { | |
| j += DELIM_START_LEN; | |
| LogString key = val.substr(j, k - j); | |
| if (path && isRecursiveReference(key, path)) | |
| { | |
| LogString msg(LOG4CXX_STR("The variable ${")); | |
| msg.append(key); | |
| msg.append(LOG4CXX_STR("} is used recursively")); | |
| throw helpers::IllegalArgumentException(msg); | |
| } | |
| // first try in System properties | |
| LogString replacement(helpers::OptionConverter::getSystemProperty(key, LogString())); | |
| // then try props parameter | |
| if (replacement.empty()) | |
| { | |
| replacement = props.getProperty(key); | |
| } | |
| if (!replacement.empty()) | |
| { | |
| // Do variable substitution on the replacement string | |
| // such that we can solve "Hello ${x2}" as "Hello p1" | |
| // the where the properties are | |
| // x1=p1 | |
| // x2=${x1} | |
| LogStringChain current{ key, path }; | |
| LogString recursiveReplacement = substVarsSafely(replacement, props, ¤t); | |
| sbuf.append(recursiveReplacement); | |
| } | |
| i = k + DELIM_STOP_LEN; | |
| } | |
| } | |
| } | |
| } | |
| } // namespace | |
| using namespace LOG4CXX_NS; | |
| using namespace LOG4CXX_NS::helpers; | |
| using namespace LOG4CXX_NS::spi; | |
| LogString OptionConverter::convertSpecialChars(const LogString& s) | |
| { | |
| logchar c; | |
| LogString sbuf; | |
| LogString::const_iterator i = s.begin(); | |
| while (i != s.end()) | |
| { | |
| c = *i++; | |
| if (i != s.end() && c == 0x5C /* '\\' */) | |
| { | |
| c = *i++; | |
| switch (c) | |
| { | |
| case 0x6E: //'n' | |
| c = 0x0A; | |
| break; | |
| case 0x72: //'r' | |
| c = 0x0D; | |
| break; | |
| case 0x74: //'t' | |
| c = 0x09; | |
| break; | |
| case 0x66: //'f' | |
| c = 0x0C; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| sbuf.append(1, c); | |
| } | |
| return sbuf; | |
| } | |
| bool OptionConverter::toBoolean(const LogString& value, bool dEfault) | |
| { | |
| if (value.length() >= 4) | |
| { | |
| if (StringHelper::equalsIgnoreCase(value.substr(0, 4), | |
| LOG4CXX_STR("TRUE"), LOG4CXX_STR("true"))) | |
| { | |
| return true; | |
| } | |
| } | |
| if (dEfault && value.length() >= 5) | |
| { | |
| if (StringHelper::equalsIgnoreCase(value.substr(0, 5), | |
| LOG4CXX_STR("FALSE"), LOG4CXX_STR("false"))) | |
| { | |
| return false; | |
| } | |
| } | |
| return dEfault; | |
| } | |
| int OptionConverter::toInt(const LogString& value, int dEfault) | |
| { | |
| LogString trimmed(StringHelper::trim(value)); | |
| if (trimmed.empty()) | |
| { | |
| return dEfault; | |
| } | |
| LOG4CXX_ENCODE_CHAR(cvalue, trimmed); | |
| char* endptr = nullptr; | |
| errno = 0; | |
| long long parsed = strtoll(cvalue.c_str(), &endptr, 10); | |
| if (endptr == cvalue.c_str() || *endptr != '\0' || errno == ERANGE || | |
| parsed < (std::numeric_limits<int>::min)() || | |
| parsed > (std::numeric_limits<int>::max)()) | |
| { | |
| return dEfault; | |
| } | |
| return static_cast<int>(parsed); | |
| } | |
| long OptionConverter::toFileSize(const LogString& s, long dEfault) | |
| { | |
| LogString trimmed(StringHelper::trim(s)); | |
| if (trimmed.empty()) | |
| { | |
| return dEfault; | |
| } | |
| long long multiplier = 1; | |
| LOG4CXX_ENCODE_CHAR(cvalue, trimmed); | |
| char* endptr; | |
| errno = 0; | |
| long long parsed = strtoll(cvalue.c_str(), &endptr, 10); | |
| if (endptr == cvalue.c_str() || errno == ERANGE || parsed < 0) | |
| { | |
| return dEfault; | |
| } | |
| skipWhitespace(endptr); | |
| if (*endptr != '\0') | |
| { | |
| const char unit = static_cast<char>(std::toupper(static_cast<unsigned char>(*endptr))); | |
| if (unit == 'K') | |
| { | |
| multiplier = 1024; | |
| ++endptr; | |
| } | |
| else if (unit == 'M') | |
| { | |
| multiplier = 1024 * 1024; | |
| ++endptr; | |
| } | |
| else if (unit == 'G') | |
| { | |
| multiplier = 1024 * 1024 * 1024; | |
| ++endptr; | |
| } | |
| if (static_cast<char>(std::toupper(static_cast<unsigned char>(*endptr))) != 'B') | |
| { | |
| return dEfault; | |
| } | |
| ++endptr; | |
| skipWhitespace(endptr); | |
| if (*endptr != '\0') | |
| { | |
| return dEfault; | |
| } | |
| } | |
| if (multiplier != 0 && parsed > std::numeric_limits<long long>::max() / multiplier) | |
| { | |
| return dEfault; | |
| } | |
| long long scaled = parsed * multiplier; | |
| if (scaled > std::numeric_limits<long>::max()) | |
| { | |
| return dEfault; | |
| } | |
| return static_cast<long>(scaled); | |
| } | |
| LogString OptionConverter::findAndSubst(const LogString& key, Properties& props) | |
| { | |
| LogString value(props.getProperty(key)); | |
| if (value.empty()) | |
| { | |
| return value; | |
| } | |
| try | |
| { | |
| return substVars(value, props); | |
| } | |
| catch (IllegalArgumentException& e) | |
| { | |
| LogLog::error(((LogString) LOG4CXX_STR("Bad option value [")) | |
| + value + LOG4CXX_STR("]."), e); | |
| return value; | |
| } | |
| } | |
| LogString OptionConverter::substVars(const LogString& val, Properties& props) | |
| { | |
| return substVarsSafely(val, props); | |
| } | |
| LogString OptionConverter::getSystemProperty(const LogString& key, const LogString& def) | |
| { | |
| if (!key.empty()) | |
| { | |
| LogString value(System::getProperty(key)); | |
| if (!value.empty()) | |
| { | |
| return value; | |
| } | |
| } | |
| return def; | |
| } | |
| LevelPtr OptionConverter::toLevel(const LogString& value, | |
| const LevelPtr& defaultValue) | |
| { | |
| size_t hashIndex = value.find(LOG4CXX_STR("#")); | |
| if (hashIndex == LogString::npos) | |
| { | |
| // no class name specified : use standard Level class | |
| if (value.empty()) | |
| { | |
| return defaultValue; | |
| } | |
| else | |
| { | |
| return Level::toLevelLS(value, defaultValue); | |
| } | |
| } | |
| LogString clazz = value.substr(hashIndex + 1); | |
| LogString levelName = value.substr(0, hashIndex); | |
| // This is degenerate case but you never know. | |
| if (levelName.empty() || clazz.empty()) | |
| { | |
| return Level::toLevelLS(value, defaultValue); | |
| } | |
| if (LogLog::isDebugEnabled()) | |
| { | |
| LogLog::debug(LOG4CXX_STR("Desired ") + Level::getStaticClass().getName() | |
| + LOG4CXX_STR(" sub-class: [") + clazz + LOG4CXX_STR("]")); | |
| } | |
| try | |
| { | |
| // Note: the dynamic_cast could fail across DLL boundaries. | |
| // However, without the dynamic_cast a poorly formed XML file | |
| // could attempt to load an invalid class as a filter, causing | |
| // a crash. If it can't be converted, a std::bad_cast should be | |
| // thrown(and caught by the exception handler below) | |
| const Level::LevelClass& levelClass = | |
| dynamic_cast<const Level::LevelClass&>(Loader::loadClass(clazz)); | |
| return levelClass.toLevel(levelName); | |
| } | |
| catch (Exception& oops) | |
| { | |
| LogLog::error(LOG4CXX_STR("Could not create ") + Level::getStaticClass().getName() + LOG4CXX_STR(" sub-class"), oops); | |
| } | |
| catch (const std::bad_cast&) | |
| { | |
| LogLog::warn( | |
| LOG4CXX_STR("class [") + clazz + LOG4CXX_STR("] unable to be converted to " | |
| "Level::LevelClass")); | |
| } | |
| catch (...) | |
| { | |
| LogLog::warn( | |
| LOG4CXX_STR("class [") + clazz + LOG4CXX_STR("], level [") + levelName + | |
| LOG4CXX_STR("] conversion) failed.")); | |
| } | |
| return defaultValue; | |
| } | |
| ObjectPtr OptionConverter::instantiateByKey(Properties& props, const LogString& key, | |
| const Class& superClass, const ObjectPtr& defaultValue) | |
| { | |
| // Get the value of the property in string form | |
| LogString className(findAndSubst(key, props)); | |
| if (className.empty()) | |
| { | |
| LogLog::error( | |
| ((LogString) LOG4CXX_STR("Could not find value for key ")) + key); | |
| return defaultValue; | |
| } | |
| // Trim className to avoid trailing spaces that cause problems. | |
| return OptionConverter::instantiateByClassName( | |
| StringHelper::trim(className), superClass, defaultValue); | |
| } | |
| ObjectPtr OptionConverter::instantiateByClassName(const LogString& className, | |
| const Class& superClass, const ObjectPtr& defaultValue) | |
| { | |
| if (LogLog::isDebugEnabled()) | |
| { | |
| LogLog::debug(LOG4CXX_STR("Desired ") + superClass.getName() | |
| + LOG4CXX_STR(" sub-class: [") + className + LOG4CXX_STR("]")); | |
| } | |
| try | |
| { | |
| const Class& classObj = Loader::loadClass(className); | |
| ObjectPtr newObject = ObjectPtr(classObj.newInstance()); | |
| if (!newObject->instanceof(superClass)) | |
| { | |
| LogLog::error(LOG4CXX_STR("Not a ") + superClass.getName() + LOG4CXX_STR(" sub-class")); | |
| return defaultValue; | |
| } | |
| return newObject; | |
| } | |
| catch (Exception& e) | |
| { | |
| LogLog::error(LOG4CXX_STR("Could not create ") + superClass.getName() + LOG4CXX_STR(" sub-class"), e); | |
| } | |
| return defaultValue; | |
| } | |
| spi::ConfigurationStatus OptionConverter::selectAndConfigure | |
| ( const File& configFileName | |
| , const LogString& className | |
| , const spi::LoggerRepositoryPtr& target | |
| , int millisecondDelay | |
| ) | |
| { | |
| LogString clazz = className; | |
| LogString filename(configFileName.getPath()); | |
| #if LOG4CXX_HAS_DOMCONFIGURATOR | |
| if (clazz.empty() | |
| && filename.length() > 4 | |
| && StringHelper::equalsIgnoreCase( | |
| filename.substr(filename.length() - 4), | |
| LOG4CXX_STR(".XML"), LOG4CXX_STR(".xml"))) | |
| { | |
| clazz = LOG4CXX_NS::xml::DOMConfigurator::getStaticClass().getName(); | |
| } | |
| #endif | |
| auto result = spi::ConfigurationStatus::NotConfigured; | |
| ConfiguratorPtr configurator; | |
| if (!clazz.empty()) | |
| { | |
| if (LogLog::isDebugEnabled()) | |
| LogLog::debug(LOG4CXX_STR("Preferred configurator class: ") + clazz); | |
| const Class& clazzObj = Loader::loadClass(clazz); | |
| ObjectPtr obj = ObjectPtr(clazzObj.newInstance()); | |
| configurator = LOG4CXX_NS::cast<Configurator>(obj); | |
| if (configurator == 0) | |
| { | |
| LogLog::error(LOG4CXX_STR("Could not instantiate configurator [") | |
| + clazz + LOG4CXX_STR("].")); | |
| return result; | |
| } | |
| } | |
| else | |
| { | |
| configurator = std::make_shared<PropertyConfigurator>(); | |
| } | |
| if (0 < millisecondDelay) | |
| result = FileWatchdog::startWatching(configFileName, configurator, target, millisecondDelay); | |
| else | |
| result = configurator->doConfigure(configFileName, target); | |
| return result; | |
| } |