632 lines
27 KiB
Diff
632 lines
27 KiB
Diff
From: Markus Koschany <apo@debian.org>
|
|
Date: Mon, 31 Jan 2022 10:55:04 +0100
|
|
Subject: CVE-2022-23305
|
|
|
|
Origin: https://github.com/qos-ch/reload4j/pull/26/commits/33d1697bb13b8cf869c450a64f8550b1593f8035
|
|
---
|
|
pom.xml | 6 +
|
|
.../java/org/apache/log4j/jdbc/JDBCAppender.java | 97 ++++++++--
|
|
.../org/apache/log4j/jdbc/JdbcPatternParser.java | 101 ++++++++++
|
|
src/test/input/jdbc_h2_bufferSize1.properties | 25 +++
|
|
src/test/input/jdbc_h2_bufferSize2.properties | 25 +++
|
|
.../org/apache/log4j/jdbc/JdbcAppenderTest.java | 208 +++++++++++++++++++++
|
|
.../apache/log4j/jdbc/JdbcPatternParserTest.java | 50 +++++
|
|
7 files changed, 495 insertions(+), 17 deletions(-)
|
|
create mode 100644 src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
|
|
create mode 100644 src/test/input/jdbc_h2_bufferSize1.properties
|
|
create mode 100644 src/test/input/jdbc_h2_bufferSize2.properties
|
|
create mode 100644 src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
|
|
create mode 100644 src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
|
|
|
|
diff --git a/pom.xml b/pom.xml
|
|
index 93881cd..3ec2ccc 100644
|
|
--- a/pom.xml
|
|
+++ b/pom.xml
|
|
@@ -577,6 +577,12 @@ target platform and specify -Dntdll_target=msbuild on the mvn command line.
|
|
<version>1.0</version>
|
|
<optional>true</optional>
|
|
</dependency>
|
|
+ <dependency>
|
|
+ <groupId>com.h2database</groupId>
|
|
+ <artifactId>h2</artifactId>
|
|
+ <version>2.1.210</version>
|
|
+ <scope>test</scope>
|
|
+ </dependency>
|
|
</dependencies>
|
|
<distributionManagement>
|
|
<repository>
|
|
diff --git a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
|
|
index ad35f65..2f979ae 100644
|
|
--- a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
|
|
+++ b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
|
|
@@ -18,6 +18,7 @@ package org.apache.log4j.jdbc;
|
|
|
|
import java.sql.Connection;
|
|
import java.sql.DriverManager;
|
|
+import java.sql.PreparedStatement;
|
|
import java.sql.SQLException;
|
|
import java.sql.Statement;
|
|
import java.util.ArrayList;
|
|
@@ -78,6 +79,12 @@ import org.apache.log4j.spi.LoggingEvent;
|
|
public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
|
|
implements org.apache.log4j.Appender {
|
|
|
|
+ private final Boolean secureSqlReplacement =
|
|
+ Boolean.parseBoolean(System.getProperty("org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement", "true"));
|
|
+
|
|
+ private static final IllegalArgumentException ILLEGAL_PATTERN_FOR_SECURE_EXEC =
|
|
+ new IllegalArgumentException("Only org.apache.log4j.PatternLayout is supported for now due to CVE-2022-23305");
|
|
+
|
|
/**
|
|
* URL of the DB for default connection handling
|
|
*/
|
|
@@ -113,6 +120,8 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
|
|
*/
|
|
protected String sqlStatement = "";
|
|
|
|
+ private JdbcPatternParser preparedStatementParser = new JdbcPatternParser();
|
|
+
|
|
/**
|
|
* size of LoggingEvent buffer before writting to the database.
|
|
* Default is 1.
|
|
@@ -245,11 +254,11 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
|
|
*/
|
|
protected Connection getConnection() throws SQLException {
|
|
if (!DriverManager.getDrivers().hasMoreElements())
|
|
- setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
|
|
+ setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
|
|
|
|
if (connection == null) {
|
|
connection = DriverManager.getConnection(databaseURL, databaseUser,
|
|
- databasePassword);
|
|
+ databasePassword);
|
|
}
|
|
|
|
return connection;
|
|
@@ -280,29 +289,83 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
|
|
* If a statement fails the LoggingEvent stays in the buffer!
|
|
*/
|
|
public void flushBuffer() {
|
|
+ if (buffer.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
//Do the actual logging
|
|
removes.ensureCapacity(buffer.size());
|
|
- for (Iterator i = buffer.iterator(); i.hasNext();) {
|
|
- LoggingEvent logEvent = (LoggingEvent)i.next();
|
|
- try {
|
|
- String sql = getLogStatement(logEvent);
|
|
- execute(sql);
|
|
- }
|
|
- catch (SQLException e) {
|
|
- errorHandler.error("Failed to excute sql", e,
|
|
- ErrorCode.FLUSH_FAILURE);
|
|
- } finally {
|
|
- removes.add(logEvent);
|
|
- }
|
|
+ if (secureSqlReplacement) {
|
|
+ flushBufferSecure();
|
|
+ } else {
|
|
+ flushBufferInsecure();
|
|
}
|
|
-
|
|
// remove from the buffer any events that were reported
|
|
buffer.removeAll(removes);
|
|
-
|
|
+
|
|
// clear the buffer of reported events
|
|
removes.clear();
|
|
}
|
|
|
|
+ private void flushBufferInsecure() {
|
|
+ for (Iterator i = buffer.iterator(); i.hasNext(); ) {
|
|
+ LoggingEvent logEvent = (LoggingEvent) i.next();
|
|
+ try {
|
|
+ String sql = getLogStatement(logEvent);
|
|
+ execute(sql);
|
|
+ } catch (SQLException e) {
|
|
+ errorHandler.error("Failed to excute sql", e, ErrorCode.FLUSH_FAILURE);
|
|
+ } finally {
|
|
+ removes.add(logEvent);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void flushBufferSecure() {
|
|
+ // Prepare events that we will store to the DB
|
|
+ removes.addAll(buffer);
|
|
+
|
|
+ if (layout.getClass() != PatternLayout.class) {
|
|
+ errorHandler.error("Failed to convert pattern " + layout + " to SQL", ILLEGAL_PATTERN_FOR_SECURE_EXEC, ErrorCode.MISSING_LAYOUT);
|
|
+ return;
|
|
+ }
|
|
+ PatternLayout patternLayout = (PatternLayout) layout;
|
|
+ preparedStatementParser.setPattern(patternLayout.getConversionPattern());
|
|
+ Connection con = null;
|
|
+ boolean useBatch = removes.size() > 1;
|
|
+ try {
|
|
+ con = getConnection();
|
|
+ PreparedStatement ps = con.prepareStatement(preparedStatementParser.getParameterizedSql());
|
|
+ try {
|
|
+ for (Iterator i = removes.iterator(); i.hasNext(); ) {
|
|
+ LoggingEvent logEvent = (LoggingEvent) i.next();
|
|
+ try {
|
|
+ preparedStatementParser.setParameters(ps, logEvent);
|
|
+ if (useBatch) {
|
|
+ ps.addBatch();
|
|
+ }
|
|
+ } catch (SQLException e) {
|
|
+ errorHandler.error("Failed to append parameters", e, ErrorCode.FLUSH_FAILURE);
|
|
+ }
|
|
+ }
|
|
+ if (useBatch) {
|
|
+ ps.executeBatch();
|
|
+ } else {
|
|
+ ps.execute();
|
|
+ }
|
|
+ } finally {
|
|
+ try {
|
|
+ ps.close();
|
|
+ } catch (SQLException ignored) {
|
|
+ }
|
|
+ }
|
|
+ } catch (SQLException e) {
|
|
+ errorHandler.error("Failed to append messages sql", e, ErrorCode.FLUSH_FAILURE);
|
|
+ } finally {
|
|
+ if (con != null) {
|
|
+ closeConnection(con);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
/** closes the appender before disposal */
|
|
public void finalize() {
|
|
@@ -391,7 +454,7 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
|
|
Class.forName(driverClass);
|
|
} catch (Exception e) {
|
|
errorHandler.error("Failed to load driver", e,
|
|
- ErrorCode.GENERIC_FAILURE);
|
|
+ ErrorCode.GENERIC_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
diff --git a/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
|
|
new file mode 100644
|
|
index 0000000..691ed56
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
|
|
@@ -0,0 +1,101 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+package org.apache.log4j.jdbc;
|
|
+
|
|
+import org.apache.log4j.helpers.PatternConverter;
|
|
+import org.apache.log4j.helpers.PatternParser;
|
|
+import org.apache.log4j.spi.LoggingEvent;
|
|
+
|
|
+import java.sql.PreparedStatement;
|
|
+import java.sql.SQLException;
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+import java.util.regex.Matcher;
|
|
+import java.util.regex.Pattern;
|
|
+
|
|
+class JdbcPatternParser {
|
|
+ private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'((?>[^']|'')+)'");
|
|
+
|
|
+ private String lastPattern;
|
|
+ private String parameterizedSql;
|
|
+ private final List<String> argPatterns = new ArrayList<String>();
|
|
+ private final List<PatternConverter> args = new ArrayList<PatternConverter>();
|
|
+ private StringBuffer buffer = new StringBuffer();
|
|
+
|
|
+ public String getParameterizedSql() {
|
|
+ return parameterizedSql;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "JdbcPatternParser{sql=" + parameterizedSql + ",args=" + argPatterns + "}";
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Converts '....' literals into bind variables in JDBC.
|
|
+ */
|
|
+ void setPattern(String pattern) {
|
|
+ if (pattern == null) {
|
|
+ throw new IllegalArgumentException("Null pattern");
|
|
+ }
|
|
+ if (pattern.equals(lastPattern)) {
|
|
+ return;
|
|
+ }
|
|
+ Matcher m = STRING_LITERAL_PATTERN.matcher(pattern);
|
|
+ StringBuffer sb = new StringBuffer();
|
|
+ args.clear();
|
|
+ argPatterns.clear();
|
|
+ while (m.find()) {
|
|
+ String literal = m.group(1);
|
|
+ if (literal.indexOf('%') == -1) {
|
|
+ // Just literal, append it as is
|
|
+ // It can't contain user-provided parts like %m, etc.
|
|
+ m.appendReplacement(sb, "'$1'");
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // Replace with bind
|
|
+ m.appendReplacement(sb, "?");
|
|
+ // We will use prepared statements, so we don't need to escape quotes.
|
|
+ // And we assume the users had 'That''s a string with quotes' in their configs.
|
|
+ literal = literal.replaceAll("''", "'");
|
|
+ argPatterns.add(literal);
|
|
+ args.add(new PatternParser(literal).parse());
|
|
+ }
|
|
+ m.appendTail(sb);
|
|
+ parameterizedSql = sb.toString();
|
|
+ lastPattern = pattern;
|
|
+ }
|
|
+
|
|
+ public void setParameters(PreparedStatement ps, LoggingEvent logEvent) throws SQLException {
|
|
+ for (int i = 0; i < args.size(); i++) {
|
|
+ buffer.setLength(0);
|
|
+ PatternConverter c = args.get(i);
|
|
+ while (c != null) {
|
|
+ c.format(buffer, logEvent);
|
|
+ c = c.next;
|
|
+ }
|
|
+ ps.setString(i + 1, buffer.toString());
|
|
+ }
|
|
+ // This clears "toString cache"
|
|
+ buffer.setLength(0);
|
|
+ if (buffer.capacity() > 100000) {
|
|
+ // Avoid leaking too much memory if we discover long parameter
|
|
+ buffer = new StringBuffer();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/test/input/jdbc_h2_bufferSize1.properties b/src/test/input/jdbc_h2_bufferSize1.properties
|
|
new file mode 100644
|
|
index 0000000..77e6083
|
|
--- /dev/null
|
|
+++ b/src/test/input/jdbc_h2_bufferSize1.properties
|
|
@@ -0,0 +1,25 @@
|
|
+# 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.
|
|
+
|
|
+log4j.threshold=ALL
|
|
+log4j.rootLogger=ALL,A
|
|
+log4j.appender.A=org.apache.log4j.jdbc.JDBCAppender
|
|
+log4j.appender.A.URL=jdbc:h2:mem:test_db
|
|
+log4j.appender.A.driver=org.h2.Driver
|
|
+log4j.appender.A.bufferSize=1
|
|
+log4j.appender.A.user=
|
|
+log4j.appender.A.password=
|
|
+log4j.appender.A.layout=org.apache.log4j.PatternLayout
|
|
+log4j.appender.A.sql=insert into logs(level,location,message,message2) values('%p','%c','%m', ' %c %p %m')
|
|
diff --git a/src/test/input/jdbc_h2_bufferSize2.properties b/src/test/input/jdbc_h2_bufferSize2.properties
|
|
new file mode 100644
|
|
index 0000000..fba02a3
|
|
--- /dev/null
|
|
+++ b/src/test/input/jdbc_h2_bufferSize2.properties
|
|
@@ -0,0 +1,25 @@
|
|
+# 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.
|
|
+
|
|
+log4j.threshold=ALL
|
|
+log4j.rootLogger=ALL,A
|
|
+log4j.appender.A=org.apache.log4j.jdbc.JDBCAppender
|
|
+log4j.appender.A.URL=jdbc:h2:mem:test_db
|
|
+log4j.appender.A.driver=org.h2.Driver
|
|
+log4j.appender.A.bufferSize=2
|
|
+log4j.appender.A.user=
|
|
+log4j.appender.A.password=
|
|
+log4j.appender.A.layout=org.apache.log4j.PatternLayout
|
|
+log4j.appender.A.sql=insert into logs(level,location,message,message2) values('%p','%c','%m', ' %c %p %m')
|
|
diff --git a/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java b/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
|
|
new file mode 100644
|
|
index 0000000..f851063
|
|
--- /dev/null
|
|
+++ b/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
|
|
@@ -0,0 +1,208 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+package org.apache.log4j.jdbc;
|
|
+
|
|
+import org.apache.log4j.Appender;
|
|
+import org.apache.log4j.LogManager;
|
|
+import org.apache.log4j.Logger;
|
|
+import org.apache.log4j.PropertyConfigurator;
|
|
+import org.apache.log4j.TestContants;
|
|
+import org.apache.log4j.VectorErrorHandler;
|
|
+import org.apache.log4j.xml.XLevel;
|
|
+import org.junit.After;
|
|
+import org.junit.Assert;
|
|
+import org.junit.Before;
|
|
+import org.junit.Test;
|
|
+
|
|
+import java.sql.Connection;
|
|
+import java.sql.DriverManager;
|
|
+import java.sql.PreparedStatement;
|
|
+import java.sql.ResultSet;
|
|
+import java.sql.SQLException;
|
|
+import java.sql.Statement;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Collections;
|
|
+import java.util.List;
|
|
+
|
|
+public class JdbcAppenderTest {
|
|
+ // Closing the last connection to "in-memory h2" removes the database
|
|
+ // So we keep the connection during the test
|
|
+ // The logger opens its own connection
|
|
+ Connection con;
|
|
+
|
|
+ @Before
|
|
+ public void setup() throws SQLException {
|
|
+ con = DriverManager.getConnection("jdbc:h2:mem:test_db");
|
|
+ Statement st = con.createStatement();
|
|
+ try {
|
|
+ st.execute("create table logs(level varchar(100),location varchar(100),message varchar(100),message2 varchar(100))");
|
|
+ } finally {
|
|
+ st.close();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @After
|
|
+ public void cleanup() throws SQLException {
|
|
+ LogManager.shutdown();
|
|
+ con.close();
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void verifyJdbcInsecure() throws SQLException {
|
|
+ String secureJdbcReplacement = "org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement";
|
|
+ System.setProperty(secureJdbcReplacement, "false");
|
|
+ try {
|
|
+ PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize1.properties");
|
|
+ Appender jdbcAppender = LogManager.getRootLogger().getAppender("A");
|
|
+ // Keep errors
|
|
+ VectorErrorHandler errorHandler = new VectorErrorHandler();
|
|
+ jdbcAppender.setErrorHandler(errorHandler);
|
|
+
|
|
+ Logger logger = Logger.getLogger(JdbcAppenderTest.class);
|
|
+
|
|
+ String oldThreadName = Thread.currentThread().getName();
|
|
+ try {
|
|
+ Thread.currentThread().setName("main");
|
|
+ logger.debug("message with '' quote");
|
|
+ Assert.assertEquals(
|
|
+ "batchSize=1, so messages should be added immediately",
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
|
|
+ joinSorted(getMessages()));
|
|
+
|
|
+ // It should fail
|
|
+ logger.fatal("message with ' quote");
|
|
+
|
|
+ Assert.assertEquals(
|
|
+ "Inserting a message with ' should cause failure in insecure mode, so only one message should be in the DB",
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
|
|
+ joinSorted(getMessages()));
|
|
+
|
|
+ StringBuilder exceptions = new StringBuilder();
|
|
+ StringBuilder errorCodes = new StringBuilder();
|
|
+ for (int i = 0; i < errorHandler.size(); i++) {
|
|
+ Exception ex = errorHandler.getException(i);
|
|
+ exceptions.append(ex.toString());
|
|
+ errorCodes.append(
|
|
+ ex instanceof SQLException
|
|
+ ? ("SQLException.getErrorCode() = " + ((SQLException) ex).getErrorCode())
|
|
+ : ("SQL Exception expected, got " + ex.getClass()));
|
|
+ exceptions.append('\n');
|
|
+ errorCodes.append('\n');
|
|
+ }
|
|
+ Assert.assertEquals(
|
|
+ "Logging a message with ' should trigger SQLException 'Syntax error in SQL statement...' when using insecure JDBCAppender mode, actual exceptions are\n" + exceptions,
|
|
+ "SQLException.getErrorCode() = 42001\n",
|
|
+ errorCodes.toString()
|
|
+ );
|
|
+ } finally {
|
|
+ Thread.currentThread().setName(oldThreadName);
|
|
+ }
|
|
+ } finally {
|
|
+ System.getProperties().remove(secureJdbcReplacement);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void verifyJdbcBufferSize1() throws SQLException {
|
|
+ PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize1.properties");
|
|
+
|
|
+ Logger logger = Logger.getLogger(JdbcAppenderTest.class);
|
|
+
|
|
+ String oldThreadName = Thread.currentThread().getName();
|
|
+ try {
|
|
+ Thread.currentThread().setName("main");
|
|
+ logger.debug("message with ' quote");
|
|
+ Assert.assertEquals(
|
|
+ "batchSize=1, so messages should be added immediately",
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
|
|
+ joinSorted(getMessages()));
|
|
+
|
|
+ logger.fatal("message with \" quote");
|
|
+
|
|
+ Assert.assertEquals(
|
|
+ "batchSize=1, so two messages should be in DB after two logging calls",
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
|
|
+ "FATAL; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest FATAL message with \" quote\n",
|
|
+ joinSorted(getMessages()));
|
|
+ } finally {
|
|
+ Thread.currentThread().setName(oldThreadName);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void verifyJdbcBufferSize2() throws SQLException {
|
|
+ PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize2.properties");
|
|
+
|
|
+ Logger logger = Logger.getLogger(JdbcAppenderTest.class);
|
|
+
|
|
+ String oldThreadName = Thread.currentThread().getName();
|
|
+ try {
|
|
+ Thread.currentThread().setName("main");
|
|
+ logger.log(XLevel.TRACE, "xtrace message");
|
|
+ logger.debug("message with ' quote");
|
|
+ logger.info("message with \" quote");
|
|
+ logger.warn("?");
|
|
+ // bufferSize=2, so this message won't yet be stored to the db
|
|
+ logger.error("m4");
|
|
+
|
|
+ Assert.assertEquals(
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
|
|
+ "INFO; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest INFO message with \" quote\n" +
|
|
+ "TRACE; org.apache.log4j.jdbc.JdbcAppenderTest; xtrace message; org.apache.log4j.jdbc.JdbcAppenderTest TRACE xtrace message\n" +
|
|
+ "WARN; org.apache.log4j.jdbc.JdbcAppenderTest; ?; org.apache.log4j.jdbc.JdbcAppenderTest WARN ?\n",
|
|
+ joinSorted(getMessages()));
|
|
+
|
|
+ logger.fatal("m5");
|
|
+
|
|
+ Assert.assertEquals(
|
|
+ "Logging m5 message should trigger buffer flush for both m4 and m5",
|
|
+ "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
|
|
+ "ERROR; org.apache.log4j.jdbc.JdbcAppenderTest; m4; org.apache.log4j.jdbc.JdbcAppenderTest ERROR m4\n" +
|
|
+ "FATAL; org.apache.log4j.jdbc.JdbcAppenderTest; m5; org.apache.log4j.jdbc.JdbcAppenderTest FATAL m5\n" +
|
|
+ "INFO; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest INFO message with \" quote\n" +
|
|
+ "TRACE; org.apache.log4j.jdbc.JdbcAppenderTest; xtrace message; org.apache.log4j.jdbc.JdbcAppenderTest TRACE xtrace message\n" +
|
|
+ "WARN; org.apache.log4j.jdbc.JdbcAppenderTest; ?; org.apache.log4j.jdbc.JdbcAppenderTest WARN ?\n",
|
|
+ joinSorted(getMessages()));
|
|
+ } finally {
|
|
+ Thread.currentThread().setName(oldThreadName);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static String joinSorted(List<String> list) {
|
|
+ Collections.sort(list);
|
|
+ StringBuilder sb = new StringBuilder();
|
|
+ for (String s : list) {
|
|
+ sb.append(s).append('\n');
|
|
+ }
|
|
+ return sb.toString();
|
|
+ }
|
|
+
|
|
+ private List<String> getMessages() throws SQLException {
|
|
+ List<String> res = new ArrayList<String>();
|
|
+ PreparedStatement ps = con.prepareStatement("select level,location,message,message2 from logs");
|
|
+ ResultSet rs = ps.executeQuery();
|
|
+ try {
|
|
+ while (rs.next()) {
|
|
+ res.add(rs.getString(1) + "; " + rs.getString(2)
|
|
+ + "; " + rs.getString(3) + "; " + rs.getString(4));
|
|
+ }
|
|
+ } finally {
|
|
+ rs.close();
|
|
+ }
|
|
+ return res;
|
|
+ }
|
|
+}
|
|
diff --git a/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java b/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
|
|
new file mode 100644
|
|
index 0000000..aafbd80
|
|
--- /dev/null
|
|
+++ b/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
|
|
@@ -0,0 +1,50 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+package org.apache.log4j.jdbc;
|
|
+
|
|
+import org.junit.Assert;
|
|
+import org.junit.Test;
|
|
+
|
|
+public class JdbcPatternParserTest {
|
|
+ JdbcPatternParser parser = new JdbcPatternParser();
|
|
+
|
|
+ @Test
|
|
+ public void testParameterizedSql() {
|
|
+ assertParameterizedSql(
|
|
+ "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ? ),args=[ %d - %c %-5p %c %x - %m%n ]}",
|
|
+ "INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n ' )"
|
|
+ );
|
|
+ assertParameterizedSql(
|
|
+ "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ?, ?, ?, ?, ?, ? ),args=[%d, %c, %-5p, '%c, %x, - %m%n ]}",
|
|
+ "INSERT INTO A1 (TITLE3) VALUES ( '%d', '%c', '%-5p', ' ''%c', '%x', ' - %m%n ' )"
|
|
+ );
|
|
+
|
|
+ assertParameterizedSql(
|
|
+ "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ' just string literal', 'another literal with quotes '' asdf', ?),args=[message: %m]}",
|
|
+ "INSERT INTO A1 (TITLE3) VALUES ( ' just string literal', 'another literal with quotes '' asdf', 'message: %m')"
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private void assertParameterizedSql(String expected, String input) {
|
|
+ parser.setPattern(input);
|
|
+ Assert.assertEquals(
|
|
+ "parser.setPattern(...).toString() for " + input,
|
|
+ expected,
|
|
+ parser.toString()
|
|
+ );
|
|
+ }
|
|
+}
|