blob: 6bfa519ee5f06dec6daafd331799ffc93b9f2a38 [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/db/odbcappender.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/patternlayout.h>
#include <apr_strings.h>
#if !defined(LOG4CXX)
#define LOG4CXX 1
#endif
#include <log4cxx/private/log4cxx_private.h>
#if LOG4CXX_HAVE_ODBC
#if defined(WIN32) || defined(_WIN32)
#include <windows.h>
#endif
#include <sqlext.h>
#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&)
{
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<spi::LoggingEventPtr>::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;
}