| /* | |
| * 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/db/dbappender.h> | |
| #include <log4cxx/appenderskeleton.h> | |
| #include <log4cxx/helpers/stringhelper.h> | |
| #include <log4cxx/helpers/pool.h> | |
| #include <log4cxx/helpers/loglog.h> | |
| #include <log4cxx/patternlayout.h> | |
| #include <log4cxx/helpers/transcoder.h> | |
| #include <log4cxx/pattern/loggerpatternconverter.h> | |
| #include <log4cxx/pattern/classnamepatternconverter.h> | |
| #include <log4cxx/pattern/datepatternconverter.h> | |
| #include <log4cxx/pattern/filelocationpatternconverter.h> | |
| #include <log4cxx/pattern/fulllocationpatternconverter.h> | |
| #include <log4cxx/pattern/shortfilelocationpatternconverter.h> | |
| #include <log4cxx/pattern/linelocationpatternconverter.h> | |
| #include <log4cxx/pattern/messagepatternconverter.h> | |
| #include <log4cxx/pattern/methodlocationpatternconverter.h> | |
| #include <log4cxx/pattern/levelpatternconverter.h> | |
| #include <log4cxx/pattern/threadpatternconverter.h> | |
| #include <log4cxx/pattern/threadusernamepatternconverter.h> | |
| #include <log4cxx/pattern/ndcpatternconverter.h> | |
| #include <log4cxx/private/appenderskeleton_priv.h> | |
| #include <apr_dbd.h> | |
| #include <assert.h> | |
| using namespace LOG4CXX_NS; | |
| using namespace LOG4CXX_NS::helpers; | |
| using namespace LOG4CXX_NS::db; | |
| using namespace LOG4CXX_NS::spi; | |
| using namespace LOG4CXX_NS::pattern; | |
| IMPLEMENT_LOG4CXX_OBJECT(DBAppender) | |
| #define _priv static_cast<DBAppenderPriv*>(m_priv.get()) | |
| struct DBAppender::DBAppenderPriv : public AppenderSkeleton::AppenderSkeletonPrivate | |
| { | |
| DBAppenderPriv() : | |
| AppenderSkeletonPrivate() | |
| { | |
| static bool initialized = false; | |
| if (!initialized) | |
| { | |
| initialized = true; | |
| apr_status_t stat = apr_dbd_init(m_pool.getAPRPool()); | |
| assert(stat == APR_SUCCESS); | |
| } | |
| } | |
| const apr_dbd_driver_t* m_driver = nullptr; | |
| apr_dbd_t* m_databaseHandle = nullptr; | |
| apr_dbd_prepared_t* preparedStmt = nullptr; | |
| std::vector<LogString> mappedName; | |
| std::string driverName; | |
| std::string driverParams; | |
| std::string databaseName; | |
| std::string sqlStatement; | |
| Pool m_pool; | |
| std::vector<pattern::LoggingEventPatternConverterPtr> converters; | |
| void close(); | |
| }; | |
| #define RULES_PUT(spec, cls) \ | |
| specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), cls ::newInstance)) | |
| static PatternMap getFormatSpecifiers() | |
| { | |
| PatternMap specs; | |
| if (specs.empty()) | |
| { | |
| RULES_PUT("logger", LoggerPatternConverter); | |
| RULES_PUT("class", ClassNamePatternConverter); | |
| RULES_PUT("time", DatePatternConverter); | |
| RULES_PUT("shortfilename", ShortFileLocationPatternConverter); | |
| RULES_PUT("fullfilename", FileLocationPatternConverter); | |
| RULES_PUT("location", FullLocationPatternConverter); | |
| RULES_PUT("line", LineLocationPatternConverter); | |
| RULES_PUT("message", MessagePatternConverter); | |
| RULES_PUT("method", MethodLocationPatternConverter); | |
| RULES_PUT("level", LevelPatternConverter); | |
| RULES_PUT("thread", ThreadPatternConverter); | |
| RULES_PUT("threadname", ThreadUsernamePatternConverter); | |
| RULES_PUT("ndc", NDCPatternConverter); | |
| } | |
| return specs; | |
| } | |
| DBAppender::DBAppender() | |
| : AppenderSkeleton (std::make_unique<DBAppenderPriv>()) | |
| { | |
| } | |
| DBAppender::~DBAppender() | |
| { | |
| if (_priv->setClosed()) | |
| _priv->close(); | |
| } | |
| void DBAppender::close() | |
| { | |
| if (_priv->setClosed()) | |
| _priv->close(); | |
| } | |
| void DBAppender::DBAppenderPriv::close() | |
| { | |
| if(this->m_driver && this->m_databaseHandle){ | |
| apr_dbd_close(this->m_driver, this->m_databaseHandle); | |
| } | |
| this->m_driver = nullptr; | |
| this->m_databaseHandle = nullptr; | |
| } | |
| void DBAppender::setOption(const LogString& option, const LogString& value){ | |
| if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("COLUMNMAPPING"), LOG4CXX_STR("columnmapping"))) | |
| { | |
| _priv->mappedName.push_back(value); | |
| } | |
| else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DRIVERNAME"), LOG4CXX_STR("drivername"))) | |
| { | |
| Transcoder::encodeUTF8(value, _priv->driverName); | |
| } | |
| else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DRIVERPARAMS"), LOG4CXX_STR("driverparams"))) | |
| { | |
| Transcoder::encodeUTF8(value, _priv->driverParams); | |
| } | |
| else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DATABASENAME"), LOG4CXX_STR("databasename"))) | |
| { | |
| Transcoder::encodeUTF8(value, _priv->databaseName); | |
| } | |
| else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SQL"), LOG4CXX_STR("sql"))) | |
| { | |
| Transcoder::encodeUTF8(value, _priv->sqlStatement); | |
| } | |
| else | |
| { | |
| AppenderSkeleton::setOption(option, value); | |
| } | |
| } | |
| void DBAppender::activateOptions( ) | |
| { | |
| apr_status_t stat = apr_dbd_get_driver(_priv->m_pool.getAPRPool(), | |
| _priv->driverName.c_str(), | |
| &_priv->m_driver); | |
| if(stat != APR_SUCCESS){ | |
| LogString errMsg = LOG4CXX_STR("Unable to get driver named "); | |
| LOG4CXX_DECODE_CHAR(driverName, _priv->driverName); | |
| errMsg.append(driverName); | |
| LogLog::error(errMsg); | |
| _priv->errorHandler->error(errMsg); | |
| return; | |
| } | |
| stat = apr_dbd_open(_priv->m_driver, | |
| _priv->m_pool.getAPRPool(), | |
| _priv->driverParams.c_str(), | |
| &_priv->m_databaseHandle); | |
| if(stat != APR_SUCCESS){ | |
| LogLog::error(LOG4CXX_STR("Unable to open database")); | |
| _priv->errorHandler->error(LOG4CXX_STR("Unable to open database")); | |
| return; | |
| } | |
| if(!_priv->databaseName.empty()){ | |
| apr_dbd_set_dbname(_priv->m_driver, | |
| _priv->m_pool.getAPRPool(), | |
| _priv->m_databaseHandle, | |
| _priv->databaseName.c_str()); | |
| } | |
| stat = apr_dbd_prepare(_priv->m_driver, | |
| _priv->m_pool.getAPRPool(), | |
| _priv->m_databaseHandle, | |
| _priv->sqlStatement.c_str(), | |
| "log_insert", | |
| &_priv->preparedStmt); | |
| if(stat != APR_SUCCESS){ | |
| LogString error = LOG4CXX_STR("Unable to prepare statement: "); | |
| std::string dbdErr(apr_dbd_error(_priv->m_driver, _priv->m_databaseHandle, stat)); | |
| LOG4CXX_DECODE_CHAR(dbdErrLS, dbdErr); | |
| error.append(dbdErrLS); | |
| LogLog::error(error); | |
| _priv->errorHandler->error(error); | |
| return; | |
| } | |
| auto specs = getFormatSpecifiers(); | |
| for (auto& name : _priv->mappedName) | |
| { | |
| auto pItem = specs.find(StringHelper::toLowerCase(name)); | |
| if (specs.end() == pItem) | |
| LogLog::error(name + LOG4CXX_STR(" is not a supported ColumnMapping value")); | |
| else | |
| { | |
| std::vector<LogString> options; | |
| if (LOG4CXX_STR("time") == pItem->first) | |
| options.push_back(LOG4CXX_STR("yyyy-MM-ddTHH:mm:ss.SSS")); | |
| pattern::LoggingEventPatternConverterPtr converter = LOG4CXX_NS::cast<LoggingEventPatternConverter>((pItem->second)(options)); | |
| _priv->converters.push_back(converter); | |
| } | |
| } | |
| } | |
| void DBAppender::append( const spi::LoggingEventPtr& event ){ | |
| std::vector<std::string> ls_args; | |
| std::vector<const char*> args; | |
| int stat; | |
| int num_rows; | |
| if(_priv->m_driver == nullptr || | |
| _priv->m_databaseHandle == nullptr || | |
| _priv->preparedStmt == nullptr){ | |
| _priv->errorHandler->error(LOG4CXX_STR("DBAppender not initialized properly: logging not available")); | |
| return; | |
| } | |
| for(auto& converter : _priv->converters){ | |
| LogString str_data; | |
| converter->format(event, str_data); | |
| LOG4CXX_ENCODE_CHAR(new_str_data, str_data); | |
| ls_args.push_back(new_str_data); | |
| } | |
| for(std::string& str : ls_args){ | |
| args.push_back(str.data()); | |
| } | |
| args.push_back(nullptr); | |
| stat = apr_dbd_pquery(_priv->m_driver, | |
| _priv->m_pool.getAPRPool(), | |
| _priv->m_databaseHandle, | |
| &num_rows, | |
| _priv->preparedStmt, | |
| int(args.size()), | |
| args.data()); | |
| if(stat != APR_SUCCESS){ | |
| LogString error = LOG4CXX_STR("Unable to insert: "); | |
| LOG4CXX_DECODE_CHAR(local_error, apr_dbd_error(_priv->m_driver, _priv->m_databaseHandle, stat)); | |
| error.append(local_error); | |
| LogLog::error(error); | |
| _priv->errorHandler->error(error); | |
| } | |
| } |