/* * 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 #include #include #include #include #include #include #if !defined(LOG4CXX) #define LOG4CXX 1 #endif #include #if LOG4CXX_HAVE_ODBC #if defined(WIN32) || defined(_WIN32) #include #endif #include #endif using namespace log4cxx; using namespace log4cxx::helpers; using namespace log4cxx::db; using namespace log4cxx::spi; SQLException::SQLException(short fHandleType, void* hInput, const char* prolog, log4cxx::helpers::Pool& p) : Exception(formatMessage(fHandleType, hInput, prolog, p)) { } SQLException::SQLException(const char* msg) : Exception(msg) { } SQLException::SQLException(const SQLException& src) : Exception(src) { } const char* SQLException::formatMessage(short fHandleType, void* hInput, const char* prolog, log4cxx::helpers::Pool& p) { std::string strReturn(prolog); strReturn.append(" - "); #if LOG4CXX_HAVE_ODBC SQLCHAR SqlState[6]; SQLCHAR Msg[SQL_MAX_MESSAGE_LENGTH]; SQLINTEGER NativeError; SQLSMALLINT i; SQLSMALLINT MsgLen; SQLRETURN rc2; // Get the status records. i = 1; while ((rc2 = SQLGetDiagRecA(fHandleType, hInput, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) { strReturn.append((char*) Msg); i++; } #else strReturn.append("log4cxx built without ODBC support"); #endif return apr_pstrdup((apr_pool_t*) p.getAPRPool(), strReturn.c_str()); } IMPLEMENT_LOG4CXX_OBJECT(ODBCAppender) ODBCAppender::ODBCAppender() : connection(0), env(0), bufferSize(1) { } ODBCAppender::~ODBCAppender() { finalize(); } void ODBCAppender::setOption(const LogString& option, const LogString& value) { if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize"))) { setBufferSize((size_t)OptionConverter::toInt(value, 1)); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PASSWORD"), LOG4CXX_STR("password"))) { setPassword(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SQL"), LOG4CXX_STR("sql"))) { setSql(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("URL"), LOG4CXX_STR("url")) || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DSN"), LOG4CXX_STR("dsn")) || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CONNECTIONSTRING"), LOG4CXX_STR("connectionstring")) ) { setURL(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("USER"), LOG4CXX_STR("user"))) { setUser(value); } else { AppenderSkeleton::setOption(option, value); } } void ODBCAppender::activateOptions(log4cxx::helpers::Pool&) { #if !LOG4CXX_HAVE_ODBC LogLog::error(LOG4CXX_STR("Can not activate ODBCAppender unless compiled with ODBC support.")); #endif } void ODBCAppender::append(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) { #if LOG4CXX_HAVE_ODBC buffer.push_back(event); if (buffer.size() >= bufferSize) flushBuffer(p); #endif } LogString ODBCAppender::getLogStatement(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) const { LogString sbuf; getLayout()->format(sbuf, event, p); return sbuf; } void ODBCAppender::execute(const LogString& sql, log4cxx::helpers::Pool& p) { #if LOG4CXX_HAVE_ODBC SQLRETURN ret; SQLHDBC con = SQL_NULL_HDBC; SQLHSTMT stmt = SQL_NULL_HSTMT; try { con = getConnection(p); ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &stmt); if (ret < 0) { throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate sql handle.", p); } SQLWCHAR* wsql; encode(&wsql, sql, p); ret = SQLExecDirectW(stmt, wsql, SQL_NTS); if (ret < 0) { throw SQLException(SQL_HANDLE_STMT, stmt, "Failed to execute sql statement.", p); } } catch (SQLException& e) { if (stmt != SQL_NULL_HSTMT) { SQLFreeHandle(SQL_HANDLE_STMT, stmt); } throw; } SQLFreeHandle(SQL_HANDLE_STMT, stmt); closeConnection(con); #else throw SQLException("log4cxx build without ODBC support"); #endif } /* The default behavior holds a single connection open until the appender is closed (typically when garbage collected).*/ void ODBCAppender::closeConnection(ODBCAppender::SQLHDBC /* con */) { } ODBCAppender::SQLHDBC ODBCAppender::getConnection(log4cxx::helpers::Pool& p) { #if LOG4CXX_HAVE_ODBC SQLRETURN ret; if (env == SQL_NULL_HENV) { ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); if (ret < 0) { SQLException ex(SQL_HANDLE_ENV, env, "Failed to allocate SQL handle.", p); env = SQL_NULL_HENV; throw ex; } ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER); if (ret < 0) { SQLException ex(SQL_HANDLE_ENV, env, "Failed to set odbc version.", p); SQLFreeHandle(SQL_HANDLE_ENV, env); env = SQL_NULL_HENV; throw ex; } } if (connection == SQL_NULL_HDBC) { ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &connection); if (ret < 0) { SQLException ex(SQL_HANDLE_DBC, connection, "Failed to allocate sql handle.", p); connection = SQL_NULL_HDBC; throw ex; } SQLWCHAR *wURL, *wUser, *wPwd; encode(&wURL, databaseURL, p); encode(&wUser, databaseUser, p); encode(&wPwd, databasePassword, p); ret = SQLConnectW( connection, wURL, SQL_NTS, wUser, SQL_NTS, wPwd, SQL_NTS); if (ret < 0) { SQLException ex(SQL_HANDLE_DBC, connection, "Failed to connect to database.", p); SQLFreeHandle(SQL_HANDLE_DBC, connection); connection = SQL_NULL_HDBC; throw ex; } } return connection; #else return 0; #endif } void ODBCAppender::close() { if (closed) { return; } Pool p; try { flushBuffer(p); } catch (SQLException& e) { errorHandler->error(LOG4CXX_STR("Error closing connection"), e, ErrorCode::GENERIC_FAILURE); } #if LOG4CXX_HAVE_ODBC if (connection != SQL_NULL_HDBC) { SQLDisconnect(connection); SQLFreeHandle(SQL_HANDLE_DBC, connection); } if (env != SQL_NULL_HENV) { SQLFreeHandle(SQL_HANDLE_ENV, env); } #endif this->closed = true; } void ODBCAppender::flushBuffer(Pool& p) { std::list::iterator i; for (i = buffer.begin(); i != buffer.end(); i++) { try { const LoggingEventPtr& logEvent = *i; LogString sql = getLogStatement(logEvent, p); execute(sql, p); } catch (SQLException& e) { errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e, ErrorCode::FLUSH_FAILURE); } } // clear the buffer of reported events buffer.clear(); } void ODBCAppender::setSql(const LogString& s) { sqlStatement = s; if (getLayout() == 0) { this->setLayout(new PatternLayout(s)); } else { PatternLayoutPtr patternLayout = this->getLayout(); if (patternLayout != 0) { patternLayout->setConversionPattern(s); } } } void ODBCAppender::encode(wchar_t** dest, const LogString& src, Pool& p) { *dest = Transcoder::wencode(src, p); } void ODBCAppender::encode(unsigned short** dest, const LogString& src, Pool& p) { // worst case double number of characters from UTF-8 or wchar_t *dest = (unsigned short*) p.palloc((src.size() + 1) * 2 * sizeof(unsigned short)); unsigned short* current = *dest; for(LogString::const_iterator i = src.begin(); i != src.end();) { unsigned int sv = Transcoder::decode(src, i); if (sv < 0x10000) { *current++ = (unsigned short) sv; } else { unsigned char u = (unsigned char) (sv >> 16); unsigned char w = (unsigned char) (u - 1); unsigned short hs = (0xD800 + ((w & 0xF) << 6) + ((sv & 0xFFFF) >> 10)); unsigned short ls = (0xDC00 + (sv & 0x3FF)); *current++ = (unsigned short) hs; *current++ = (unsigned short) ls; } } *current = 0; }