[logback-user] Logging to Oracle with DBAppender and connection pooling doesn't work
ros246
ros246 at gmail.com
Wed Oct 29 09:58:01 CET 2008
Hi,
I have the same problem. I debug it and found 2 problems.
1) getGeneratedKeys doesn't work on Oracle 10
2) Oracle driver rolback transaction if you don't set autocommit to true
after commit.
I think DBAppender needs rewrite. I solved it by writing my own appender
subclassed from DBAppender redefining methods that I don't like. Source
is included.
Regards
Marian Habsuda
package logtest;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import ch.qos.logback.classic.db.DBAppender;
import ch.qos.logback.classic.spi.CallerData;
import ch.qos.logback.classic.spi.LoggingEvent;
public class DBAppenderOracle10g extends DBAppender {
static int ii = 0;
public DBAppenderOracle10g() {
super();
ii++;
System.out.println("DBAppenderOracle10g, instances = " + ii);
}
Connection connection = null;
public void append(Object eventObject) {
try {
if (connection == null) {
connection = connectionSource.getConnection();
connection.setAutoCommit(false);
}
PreparedStatement insertStatement =
connection.prepareStatement(getInsertSQL());
subAppend(eventObject, connection, insertStatement);
// we no longer need the insertStatement
if (insertStatement != null) {
insertStatement.close();
insertStatement = null;
}
connection.commit();
} catch (Throwable sqle) {
addError("problem appending event", sqle);
} finally {
closeConnection(connection);
}
}
static public void closeConnection(Connection connection) {
if (connection != null) {
try {
connection.setAutoCommit(true);
connection.close();
} catch (SQLException sqle) {
// static utility classes should not log without an explicit repository
// reference
}
}
}
public static void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException sqle) {
}
}
}
protected void subAppend(Object eventObject, Connection connection,
PreparedStatement insertStatement)
throws Throwable {
LoggingEvent event = (LoggingEvent) eventObject;
addLoggingEvent(insertStatement, event);
// This is very expensive... should we do it every time?
addCallerData(insertStatement, event.getCallerData());
int updateCount = insertStatement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert loggingEvent");
}
Map mergedMap = mergePropertyMaps(event);
int eventId = -1;
Set propertiesKeys = mergedMap.keySet();
if (propertiesKeys.size() > 0
|| (event.getThrowableInformation() != null &&
event.getThrowableInformation().getThrowableStrRep().length > 0)) {
eventId = getEventId(insertStatement, connection);
}
insertProperties(mergedMap, connection, eventId);
if (event.getThrowableInformation() != null) {
insertThrowable(event.getThrowableInformation().getThrowableStrRep(),
connection, eventId);
}
}
protected int getEventId(PreparedStatement insertStatement, Connection
connection) throws SQLException,
InvocationTargetException {
ResultSet rs = null;
Statement idStatement = null;
insertStatement.close();
insertStatement = null;
idStatement = connection.createStatement();
idStatement.setMaxRows(1);
rs = idStatement.executeQuery(sqlDialect.getSelectInsertId());
// A ResultSet cursor is initially positioned before the first row;
// the
// first call to the method next makes the first row the current row
rs.next();
int eventId = rs.getInt(1);
rs.close();
if (idStatement != null) {
idStatement.close();
idStatement = null;
}
return eventId;
}
protected void insertProperties(Map mergedMap, Connection connection,
int eventId) throws SQLException {
Set propertiesKeys = mergedMap.keySet();
if (propertiesKeys.size() > 0) {
PreparedStatement insertPropertiesStatement =
connection.prepareStatement(insertPropertiesSQL);
for (Iterator i = propertiesKeys.iterator(); i.hasNext();) {
String key = (String) i.next();
String value = (String) mergedMap.get(key);
insertPropertiesStatement.setInt(1, eventId);
insertPropertiesStatement.setString(2, key);
insertPropertiesStatement.setString(3, value);
if (cnxSupportsBatchUpdates) {
insertPropertiesStatement.addBatch();
} else {
insertPropertiesStatement.execute();
}
}
if (cnxSupportsBatchUpdates) {
insertPropertiesStatement.executeBatch();
}
insertPropertiesStatement.close();
insertPropertiesStatement = null;
}
}
Map mergePropertyMaps(LoggingEvent event) {
Map mergedMap = new HashMap();
// we add the context properties first, then the event properties, since
// we consider that event-specific properties should have priority over
// context-wide
// properties.
Map loggerContextMap =
event.getLoggerRemoteView().getLoggerContextView().getPropertyMap();
Map mdcMap = event.getMDCPropertyMap();
if (loggerContextMap != null) {
mergedMap.putAll(loggerContextMap);
}
if (mdcMap != null) {
mergedMap.putAll(mdcMap);
}
return mergedMap;
}
void addLoggingEvent(PreparedStatement stmt, LoggingEvent event) throws
SQLException {
stmt.setLong(1, event.getTimeStamp());
stmt.setString(2, event.getFormattedMessage());
stmt.setString(3, event.getLoggerRemoteView().getName());
stmt.setString(4, event.getLevel().toString());
stmt.setString(5, event.getThreadName());
stmt.setShort(6, computeReferenceMask(event));
}
void addCallerData(PreparedStatement stmt, CallerData[]
callerDataArray) throws SQLException {
CallerData callerData = callerDataArray[0];
if (callerData != null) {
stmt.setString(7, callerData.getFileName());
stmt.setString(8, callerData.getClassName());
stmt.setString(9, callerData.getMethodName());
stmt.setString(10, Integer.toString(callerData.getLineNumber()));
}
}
public static short PROPERTIES_EXIST = 0x01;
public static short EXCEPTION_EXISTS = 0x02;
public static short computeReferenceMask(LoggingEvent event) {
short mask = 0;
int mdcPropSize = 0;
if (event.getMDCPropertyMap() != null) {
mdcPropSize = event.getMDCPropertyMap().keySet().size();
}
int contextPropSize = 0;
if
(event.getLoggerRemoteView().getLoggerContextView().getPropertyMap() !=
null) {
contextPropSize =
event.getLoggerRemoteView().getLoggerContextView().getPropertyMap().size();
}
if (mdcPropSize > 0 || contextPropSize > 0) {
mask = PROPERTIES_EXIST;
}
if (event.getThrowableInformation() != null) {
String[] strRep = event.getThrowableInformation().getThrowableStrRep();
if (strRep != null) {
mask |= EXCEPTION_EXISTS;
}
}
return mask;
}
}
Morten Egelund Rasmussen wrote:
> Hi all,
>
> Does anybody have a working example on how to log to Oracle with a
> connection pool on a Tomcat server?
>
> I have the following:
>
> In Tomcat's server.xml:
>
> <Resource name="jdbc/logger" auth="Container"
> type="javax.sql.DataSource"
> driverClassName="oracle.jdbc.OracleDriver"
> url="foo"
> username="foo" password="foobar"
> maxActive="50" maxIdle="5" initialSize="10"
> maxWait="-1" removeAbandoned="true"
> validationQuery="select 1 from dual"/>
>
> In Tomcat's context.xml:
>
> <ResourceLink name="jdbc/logger"
> global="jdbc/logger"
> type="javax.sql.DataSource"/>
>
> I have defined the following Logback appenders:
>
> <appender name="db" class="myAppenderClass">
> <connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
> <param name="jndiLocation" value="java:comp/env/jdbc/logger" />
> </connectionSource>
> </appender>
>
> <appender name="db2" class="myAppenderClass">
> <connectionSource
> class="ch.qos.logback.core.db.DriverManagerConnectionSource">
> <driverClass>oracle.jdbc.driver.OracleDriver</driverClass>
> <url>foo</url>
> <user>foo</user>
> <password>foobar</password>
> </connectionSource>
> </appender>
>
> .....where "myAppenderClass" is an appender class as described here:
> http://jira.qos.ch/browse/LBCORE-15
>
> Using the "db" appender gives nothing, but using the "db2" appender
> gives lots of output. What am I missing?
>
> Any ideas?
>
> ~Morten :-/
More information about the Logback-user
mailing list