From 6370372f5a04cdec6598fa8199b762ce33fa4d40 Mon Sep 17 00:00:00 2001
From: Mikolaj Izdebski <mizdebsk@redhat.com>
Date: Wed, 2 Feb 2022 19:37:17 +0100
Subject: [PATCH] Fix CVE-2022-23305 JDBCAppender
---
.../apache/log4j/helpers/PatternParser.java | 944 +++++++++---------
.../org/apache/log4j/jdbc/JDBCAppender.java | 715 ++++++-------
.../apache/log4j/jdbc/JdbcPatternParser.java | 115 +++
3 files changed, 942 insertions(+), 832 deletions(-)
create mode 100644 src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
diff --git a/src/main/java/org/apache/log4j/helpers/PatternParser.java b/src/main/java/org/apache/log4j/helpers/PatternParser.java
index 0d3ead67..4b169a2d 100644
--- a/src/main/java/org/apache/log4j/helpers/PatternParser.java
+++ b/src/main/java/org/apache/log4j/helpers/PatternParser.java
@@ -30,541 +30,503 @@ import java.util.Arrays;
// Reinhard Deschler <reinhard.deschler@web.de>
/**
- Most of the work of the {@link org.apache.log4j.PatternLayout} class
- is delegated to the PatternParser class.
-
- <p>It is this class that parses conversion patterns and creates
- a chained list of {@link OptionConverter OptionConverters}.
-
- @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
- @author Ceki Gülcü
- @author Anders Kristensen
-
- @since 0.8.2
-*/
+ * Most of the work of the {@link org.apache.log4j.PatternLayout} class is
+ * delegated to the PatternParser class.
+ *
+ * <p>
+ * It is this class that parses conversion patterns and creates a chained list
+ * of {@link OptionConverter OptionConverters}.
+ *
+ * @author James P. Cakalic
+ * @author Ceki Gülcü
+ * @author Anders Kristensen
+ *
+ * @since 0.8.2
+ */
public class PatternParser {
- private static final char ESCAPE_CHAR = '%';
-
- private static final int LITERAL_STATE = 0;
- private static final int CONVERTER_STATE = 1;
- private static final int DOT_STATE = 3;
- private static final int MIN_STATE = 4;
- private static final int MAX_STATE = 5;
-
- static final int FULL_LOCATION_CONVERTER = 1000;
- static final int METHOD_LOCATION_CONVERTER = 1001;
- static final int CLASS_LOCATION_CONVERTER = 1002;
- static final int LINE_LOCATION_CONVERTER = 1003;
- static final int FILE_LOCATION_CONVERTER = 1004;
-
- static final int RELATIVE_TIME_CONVERTER = 2000;
- static final int THREAD_CONVERTER = 2001;
- static final int LEVEL_CONVERTER = 2002;
- static final int NDC_CONVERTER = 2003;
- static final int MESSAGE_CONVERTER = 2004;
-
- int state;
- protected StringBuffer currentLiteral = new StringBuffer(32);
- protected int patternLength;
- protected int i;
- PatternConverter head;
- PatternConverter tail;
- protected FormattingInfo formattingInfo = new FormattingInfo();
- protected String pattern;
-
- public
- PatternParser(String pattern) {
- this.pattern = pattern;
- patternLength = pattern.length();
- state = LITERAL_STATE;
- }
-
- private
- void addToList(PatternConverter pc) {
- if(head == null) {
- head = tail = pc;
- } else {
- tail.next = pc;
- tail = pc;
+ private static final char LEFT_BRACKET = '{';
+ private static final char RIGHT_BRACKET = '}';
+ private static final char N_CHAR = 'n';
+ private static final char DOT_CHAR = '.';
+ private static final char DASH_CHAR = '-';
+ private static final char ESCAPE_CHAR = '%';
+
+ private static final int LITERAL_STATE = 0;
+ private static final int CONVERTER_STATE = 1;
+ private static final int DOT_STATE = 3;
+ private static final int MIN_STATE = 4;
+ private static final int MAX_STATE = 5;
+
+ static final int FULL_LOCATION_CONVERTER = 1000;
+ static final int METHOD_LOCATION_CONVERTER = 1001;
+ static final int CLASS_LOCATION_CONVERTER = 1002;
+ static final int LINE_LOCATION_CONVERTER = 1003;
+ static final int FILE_LOCATION_CONVERTER = 1004;
+
+ static final int RELATIVE_TIME_CONVERTER = 2000;
+ static final int THREAD_CONVERTER = 2001;
+ static final int LEVEL_CONVERTER = 2002;
+ static final int NDC_CONVERTER = 2003;
+ static final int MESSAGE_CONVERTER = 2004;
+
+ int state;
+ protected StringBuffer currentLiteral = new StringBuffer(32);
+ protected int patternLength;
+ protected int i;
+ PatternConverter head;
+ PatternConverter tail;
+ protected FormattingInfo formattingInfo = new FormattingInfo();
+ protected String pattern;
+
+ public PatternParser(String pattern) {
+ this.pattern = pattern;
+ patternLength = pattern.length();
+ state = LITERAL_STATE;
}
- }
-
- protected
- String extractOption() {
- if((i < patternLength) && (pattern.charAt(i) == '{')) {
- int end = pattern.indexOf('}', i);
- if (end > i) {
- String r = pattern.substring(i + 1, end);
- i = end+1;
- return r;
- }
- }
- return null;
- }
-
-
- /**
- The option is expected to be in decimal and positive. In case of
- error, zero is returned. */
- protected
- int extractPrecisionOption() {
- String opt = extractOption();
- int r = 0;
- if(opt != null) {
- try {
- r = Integer.parseInt(opt);
- if(r <= 0) {
- LogLog.error(
- "Precision option (" + opt + ") isn't a positive integer.");
- r = 0;
+
+ private void addToList(PatternConverter pc) {
+ if (head == null) {
+ head = tail = pc;
+ } else {
+ tail.next = pc;
+ tail = pc;
}
- }
- catch (NumberFormatException e) {
- LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
- }
}
- return r;
- }
-
- public
- PatternConverter parse() {
- char c;
- i = 0;
- while(i < patternLength) {
- c = pattern.charAt(i++);
- switch(state) {
- case LITERAL_STATE:
- // In literal state, the last char is always a literal.
- if(i == patternLength) {
- currentLiteral.append(c);
- continue;
- }
- if(c == ESCAPE_CHAR) {
- // peek at the next char.
- switch(pattern.charAt(i)) {
- case ESCAPE_CHAR:
- currentLiteral.append(c);
- i++; // move pointer
- break;
- case 'n':
- currentLiteral.append(Layout.LINE_SEP);
- i++; // move pointer
- break;
- default:
- if(currentLiteral.length() != 0) {
- addToList(new LiteralPatternConverter(
- currentLiteral.toString()));
- //LogLog.debug("Parsed LITERAL converter: \""
- // +currentLiteral+"\".");
- }
- currentLiteral.setLength(0);
- currentLiteral.append(c); // append %
- state = CONVERTER_STATE;
- formattingInfo.reset();
- }
- }
- else {
- currentLiteral.append(c);
- }
- break;
- case CONVERTER_STATE:
- currentLiteral.append(c);
- switch(c) {
- case '-':
- formattingInfo.leftAlign = true;
- break;
- case '.':
- state = DOT_STATE;
- break;
- default:
- if(c >= '0' && c <= '9') {
- formattingInfo.min = c - '0';
- state = MIN_STATE;
- }
- else
- finalizeConverter(c);
- } // switch
- break;
- case MIN_STATE:
- currentLiteral.append(c);
- if(c >= '0' && c <= '9')
- formattingInfo.min = formattingInfo.min*10 + (c - '0');
- else if(c == '.')
- state = DOT_STATE;
- else {
- finalizeConverter(c);
- }
- break;
- case DOT_STATE:
- currentLiteral.append(c);
- if(c >= '0' && c <= '9') {
- formattingInfo.max = c - '0';
- state = MAX_STATE;
- }
- else {
- LogLog.error("Error occured in position "+i
- +".\n Was expecting digit, instead got char \""+c+"\".");
- state = LITERAL_STATE;
- }
- break;
- case MAX_STATE:
- currentLiteral.append(c);
- if(c >= '0' && c <= '9')
- formattingInfo.max = formattingInfo.max*10 + (c - '0');
- else {
- finalizeConverter(c);
- state = LITERAL_STATE;
+
+ protected String extractOption() {
+ if ((i < patternLength) && (pattern.charAt(i) == LEFT_BRACKET)) {
+ int end = pattern.indexOf(RIGHT_BRACKET, i);
+ if (end > i) {
+ String r = pattern.substring(i + 1, end);
+ i = end + 1;
+ return r;
+ }
}
- break;
- } // switch
- } // while
- if(currentLiteral.length() != 0) {
- addToList(new LiteralPatternConverter(currentLiteral.toString()));
- //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
+ return null;
}
- return head;
- }
-
- protected
- void finalizeConverter(char c) {
- PatternConverter pc = null;
- switch(c) {
- case 'c':
- pc = new CategoryPatternConverter(formattingInfo,
- extractPrecisionOption());
- //LogLog.debug("CATEGORY converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'C':
- pc = new ClassNamePatternConverter(formattingInfo,
- extractPrecisionOption());
- //LogLog.debug("CLASS_NAME converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'd':
- String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
- DateFormat df;
- String dOpt = extractOption();
- if(dOpt != null)
- dateFormatStr = dOpt;
-
- if(dateFormatStr.equalsIgnoreCase(
- AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
- df = new ISO8601DateFormat();
- else if(dateFormatStr.equalsIgnoreCase(
- AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
- df = new AbsoluteTimeDateFormat();
- else if(dateFormatStr.equalsIgnoreCase(
- AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
- df = new DateTimeDateFormat();
- else {
- try {
- df = new SimpleDateFormat(dateFormatStr);
- }
- catch (IllegalArgumentException e) {
- LogLog.error("Could not instantiate SimpleDateFormat with " +
- dateFormatStr, e);
- df = (DateFormat) OptionConverter.instantiateByClassName(
- "org.apache.log4j.helpers.ISO8601DateFormat",
- DateFormat.class, null);
+
+ /**
+ * The option is expected to be in decimal and positive. In case of error, zero
+ * is returned.
+ */
+ protected int extractPrecisionOption() {
+ String opt = extractOption();
+ int r = 0;
+ if (opt != null) {
+ try {
+ r = Integer.parseInt(opt);
+ if (r <= 0) {
+ LogLog.error("Precision option (" + opt + ") isn't a positive integer.");
+ r = 0;
+ }
+ } catch (NumberFormatException e) {
+ LogLog.error("Category option \"" + opt + "\" not a decimal integer.", e);
+ }
}
- }
- pc = new DatePatternConverter(formattingInfo, df);
- //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'F':
- pc = new LocationPatternConverter(formattingInfo,
- FILE_LOCATION_CONVERTER);
- //LogLog.debug("File name converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'l':
- pc = new LocationPatternConverter(formattingInfo,
- FULL_LOCATION_CONVERTER);
- //LogLog.debug("Location converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'L':
- pc = new LocationPatternConverter(formattingInfo,
- LINE_LOCATION_CONVERTER);
- //LogLog.debug("LINE NUMBER converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'm':
- pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
- //LogLog.debug("MESSAGE converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'M':
- pc = new LocationPatternConverter(formattingInfo,
- METHOD_LOCATION_CONVERTER);
- //LogLog.debug("METHOD converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'p':
- pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
- //LogLog.debug("LEVEL converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 'r':
- pc = new BasicPatternConverter(formattingInfo,
- RELATIVE_TIME_CONVERTER);
- //LogLog.debug("RELATIVE time converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- case 't':
- pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
- //LogLog.debug("THREAD converter.");
- //formattingInfo.dump();
- currentLiteral.setLength(0);
- break;
- /*case 'u':
- if(i < patternLength) {
- char cNext = pattern.charAt(i);
- if(cNext >= '0' && cNext <= '9') {
- pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
- LogLog.debug("USER converter ["+cNext+"].");
- formattingInfo.dump();
- currentLiteral.setLength(0);
- i++;
+ return r;
+ }
+
+ public PatternConverter parse() {
+ char c;
+ i = 0;
+ while (i < patternLength) {
+ c = pattern.charAt(i++);
+ switch (state) {
+ case LITERAL_STATE:
+ // In literal state, the last char is always a literal.
+ if (i == patternLength) {
+ currentLiteral.append(c);
+ continue;
+ }
+ if (c == ESCAPE_CHAR) {
+ // peek at the next char.
+ switch (pattern.charAt(i)) {
+ case ESCAPE_CHAR:
+ currentLiteral.append(c);
+ i++; // move pointer
+ break;
+ case N_CHAR:
+ currentLiteral.append(Layout.LINE_SEP);
+ i++; // move pointer
+ break;
+ default:
+ if (currentLiteral.length() != 0) {
+ addToList(new LiteralPatternConverter(currentLiteral.toString()));
+ // LogLog.debug("Parsed LITERAL converter: \""
+ // +currentLiteral+"\".");
+ }
+ currentLiteral.setLength(0);
+ currentLiteral.append(c); // append %
+ state = CONVERTER_STATE;
+ formattingInfo.reset();
+ }
+ } else {
+ currentLiteral.append(c);
+ }
+ break;
+ case CONVERTER_STATE:
+ currentLiteral.append(c);
+ switch (c) {
+ case DASH_CHAR:
+ formattingInfo.leftAlign = true;
+ break;
+ case DOT_CHAR:
+ state = DOT_STATE;
+ break;
+ default:
+ if (c >= '0' && c <= '9') {
+ formattingInfo.min = c - '0';
+ state = MIN_STATE;
+ } else
+ finalizeConverter(c);
+ } // switch
+ break;
+ case MIN_STATE:
+ currentLiteral.append(c);
+ if (c >= '0' && c <= '9')
+ formattingInfo.min = formattingInfo.min * 10 + (c - '0');
+ else if (c == '.')
+ state = DOT_STATE;
+ else {
+ finalizeConverter(c);
+ }
+ break;
+ case DOT_STATE:
+ currentLiteral.append(c);
+ if (c >= '0' && c <= '9') {
+ formattingInfo.max = c - '0';
+ state = MAX_STATE;
+ } else {
+ LogLog.error("Error occured in position " + i + ".\n Was expecting digit, instead got char \"" + c
+ + "\".");
+ state = LITERAL_STATE;
+ }
+ break;
+ case MAX_STATE:
+ currentLiteral.append(c);
+ if (c >= '0' && c <= '9')
+ formattingInfo.max = formattingInfo.max * 10 + (c - '0');
+ else {
+ finalizeConverter(c);
+ state = LITERAL_STATE;
+ }
+ break;
+ } // switch
+ } // while
+ if (currentLiteral.length() != 0) {
+ addToList(new LiteralPatternConverter(currentLiteral.toString()));
+ // LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
}
- else
- LogLog.error("Unexpected char" +cNext+" at position "+i);
- }
- break;*/
- case 'x':
- pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
- //LogLog.debug("NDC converter.");
- currentLiteral.setLength(0);
- break;
- case 'X':
- String xOpt = extractOption();
- pc = new MDCPatternConverter(formattingInfo, xOpt);
- currentLiteral.setLength(0);
- break;
- default:
- LogLog.error("Unexpected char [" +c+"] at position "+i
- +" in conversion patterrn.");
- pc = new LiteralPatternConverter(currentLiteral.toString());
- currentLiteral.setLength(0);
+ return head;
}
- addConverter(pc);
- }
-
- protected
- void addConverter(PatternConverter pc) {
- currentLiteral.setLength(0);
- // Add the pattern converter to the list.
- addToList(pc);
- // Next pattern is assumed to be a literal.
- state = LITERAL_STATE;
- // Reset formatting info
- formattingInfo.reset();
- }
-
- // ---------------------------------------------------------------------
- // PatternConverters
- // ---------------------------------------------------------------------
-
- private static class BasicPatternConverter extends PatternConverter {
- int type;
-
- BasicPatternConverter(FormattingInfo formattingInfo, int type) {
- super(formattingInfo);
- this.type = type;
+ protected void finalizeConverter(char c) {
+ PatternConverter pc = null;
+ switch (c) {
+ case 'c':
+ pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption());
+ // LogLog.debug("CATEGORY converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'C':
+ pc = new ClassNamePatternConverter(formattingInfo, extractPrecisionOption());
+ // LogLog.debug("CLASS_NAME converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'd':
+ String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
+ DateFormat df;
+ String dOpt = extractOption();
+ if (dOpt != null)
+ dateFormatStr = dOpt;
+
+ if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
+ df = new ISO8601DateFormat();
+ else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
+ df = new AbsoluteTimeDateFormat();
+ else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
+ df = new DateTimeDateFormat();
+ else {
+ try {
+ df = new SimpleDateFormat(dateFormatStr);
+ } catch (IllegalArgumentException e) {
+ LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, e);
+ df = (DateFormat) OptionConverter.instantiateByClassName(
+ "org.apache.log4j.helpers.ISO8601DateFormat", DateFormat.class, null);
+ }
+ }
+ pc = new DatePatternConverter(formattingInfo, df);
+ // LogLog.debug("DATE converter {"+dateFormatStr+"}.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'F':
+ pc = new LocationPatternConverter(formattingInfo, FILE_LOCATION_CONVERTER);
+ // LogLog.debug("File name converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'l':
+ pc = new LocationPatternConverter(formattingInfo, FULL_LOCATION_CONVERTER);
+ // LogLog.debug("Location converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'L':
+ pc = new LocationPatternConverter(formattingInfo, LINE_LOCATION_CONVERTER);
+ // LogLog.debug("LINE NUMBER converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'm':
+ pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
+ // LogLog.debug("MESSAGE converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'M':
+ pc = new LocationPatternConverter(formattingInfo, METHOD_LOCATION_CONVERTER);
+ // LogLog.debug("METHOD converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'p':
+ pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
+ // LogLog.debug("LEVEL converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 'r':
+ pc = new BasicPatternConverter(formattingInfo, RELATIVE_TIME_CONVERTER);
+ // LogLog.debug("RELATIVE time converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ case 't':
+ pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
+ // LogLog.debug("THREAD converter.");
+ // formattingInfo.dump();
+ currentLiteral.setLength(0);
+ break;
+ /*
+ * case 'u': if(i < patternLength) { char cNext = pattern.charAt(i); if(cNext >=
+ * '0' && cNext <= '9') { pc = new UserFieldPatternConverter(formattingInfo,
+ * cNext - '0'); LogLog.debug("USER converter ["+cNext+"].");
+ * formattingInfo.dump(); currentLiteral.setLength(0); i++; } else
+ * LogLog.error("Unexpected char" +cNext+" at position "+i); } break;
+ */
+ case 'x':
+ pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
+ // LogLog.debug("NDC converter.");
+ currentLiteral.setLength(0);
+ break;
+ case 'X':
+ String xOpt = extractOption();
+ pc = new MDCPatternConverter(formattingInfo, xOpt);
+ currentLiteral.setLength(0);
+ break;
+ default:
+ LogLog.error("Unexpected char [" + c + "] at position " + i + " in conversion patterrn.");
+ pc = new LiteralPatternConverter(currentLiteral.toString());
+ currentLiteral.setLength(0);
+ }
+
+ addConverter(pc);
}
- public
- String convert(LoggingEvent event) {
- switch(type) {
- case RELATIVE_TIME_CONVERTER:
- return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
- case THREAD_CONVERTER:
- return event.getThreadName();
- case LEVEL_CONVERTER:
- return event.getLevel().toString();
- case NDC_CONVERTER:
- return event.getNDC();
- case MESSAGE_CONVERTER: {
- return event.getRenderedMessage();
- }
- default: return null;
- }
+ protected void addConverter(PatternConverter pc) {
+ currentLiteral.setLength(0);
+ // Add the pattern converter to the list.
+ addToList(pc);
+ // Next pattern is assumed to be a literal.
+ state = LITERAL_STATE;
+ // Reset formatting info
+ formattingInfo.reset();
}
- }
- private static class LiteralPatternConverter extends PatternConverter {
- private String literal;
+ // ---------------------------------------------------------------------
+ // PatternConverters
+ // ---------------------------------------------------------------------
- LiteralPatternConverter(String value) {
- literal = value;
- }
+ private static class BasicPatternConverter extends PatternConverter {
+ int type;
- public
- final
- void format(StringBuffer sbuf, LoggingEvent event) {
- sbuf.append(literal);
- }
+ BasicPatternConverter(FormattingInfo formattingInfo, int type) {
+ super(formattingInfo);
+ this.type = type;
+ }
- public
- String convert(LoggingEvent event) {
- return literal;
+ public String convert(LoggingEvent event) {
+ switch (type) {
+ case RELATIVE_TIME_CONVERTER:
+ return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
+ case THREAD_CONVERTER:
+ return event.getThreadName();
+ case LEVEL_CONVERTER:
+ return event.getLevel().toString();
+ case NDC_CONVERTER:
+ return event.getNDC();
+ case MESSAGE_CONVERTER: {
+ return event.getRenderedMessage();
+ }
+ default:
+ return null;
+ }
+ }
}
- }
- private static class DatePatternConverter extends PatternConverter {
- private DateFormat df;
- private Date date;
+ private static class LiteralPatternConverter extends PatternConverter {
+ private String literal;
- DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
- super(formattingInfo);
- date = new Date();
- this.df = df;
- }
+ LiteralPatternConverter(String value) {
+ literal = value;
+ }
- public
- String convert(LoggingEvent event) {
- date.setTime(event.timeStamp);
- String converted = null;
- try {
- converted = df.format(date);
- }
- catch (Exception ex) {
- LogLog.error("Error occured while converting date.", ex);
- }
- return converted;
+ public final void format(StringBuffer sbuf, LoggingEvent event) {
+ sbuf.append(literal);
+ }
+
+ public String convert(LoggingEvent event) {
+ return literal;
+ }
}
- }
- private static class MDCPatternConverter extends PatternConverter {
- private String key;
+ private static class DatePatternConverter extends PatternConverter {
+ private DateFormat df;
+ private Date date;
- MDCPatternConverter(FormattingInfo formattingInfo, String key) {
- super(formattingInfo);
- this.key = key;
- }
+ DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
+ super(formattingInfo);
+ date = new Date();
+ this.df = df;
+ }
- public
- String convert(LoggingEvent event) {
- if (key == null) {
- StringBuffer buf = new StringBuffer("{");
- Map properties = event.getProperties();
- if (properties.size() > 0) {
- Object[] keys = properties.keySet().toArray();
- Arrays.sort(keys);
- for (int i = 0; i < keys.length; i++) {
- buf.append('{');
- buf.append(keys[i]);
- buf.append(',');
- buf.append(properties.get(keys[i]));
- buf.append('}');
- }
- }
- buf.append('}');
- return buf.toString();
- } else {
- Object val = event.getMDC(key);
- if(val == null) {
- return null;
- } else {
- return val.toString();
- }
- }
+ public String convert(LoggingEvent event) {
+ date.setTime(event.timeStamp);
+ String converted = null;
+ try {
+ converted = df.format(date);
+ } catch (Exception ex) {
+ LogLog.error("Error occured while converting date.", ex);
+ }
+ return converted;
+ }
}
- }
+ private static class MDCPatternConverter extends PatternConverter {
+ private String key;
- private class LocationPatternConverter extends PatternConverter {
- int type;
+ MDCPatternConverter(FormattingInfo formattingInfo, String key) {
+ super(formattingInfo);
+ this.key = key;
+ }
- LocationPatternConverter(FormattingInfo formattingInfo, int type) {
- super(formattingInfo);
- this.type = type;
+ public String convert(LoggingEvent event) {
+ if (key == null) {
+ StringBuffer buf = new StringBuffer("{");
+ Map properties = event.getProperties();
+ if (properties.size() > 0) {
+ Object[] keys = properties.keySet().toArray();
+ Arrays.sort(keys);
+ for (int i = 0; i < keys.length; i++) {
+ buf.append('{');
+ buf.append(keys[i]);
+ buf.append(',');
+ buf.append(properties.get(keys[i]));
+ buf.append('}');
+ }
+ }
+ buf.append('}');
+ return buf.toString();
+ } else {
+ Object val = event.getMDC(key);
+ if (val == null) {
+ return null;
+ } else {
+ return val.toString();
+ }
+ }
+ }
}
- public
- String convert(LoggingEvent event) {
- LocationInfo locationInfo = event.getLocationInformation();
- switch(type) {
- case FULL_LOCATION_CONVERTER:
- return locationInfo.fullInfo;
- case METHOD_LOCATION_CONVERTER:
- return locationInfo.getMethodName();
- case LINE_LOCATION_CONVERTER:
- return locationInfo.getLineNumber();
- case FILE_LOCATION_CONVERTER:
- return locationInfo.getFileName();
- default: return null;
- }
- }
- }
+ private class LocationPatternConverter extends PatternConverter {
+ int type;
- private static abstract class NamedPatternConverter extends PatternConverter {
- int precision;
+ LocationPatternConverter(FormattingInfo formattingInfo, int type) {
+ super(formattingInfo);
+ this.type = type;
+ }
- NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
- super(formattingInfo);
- this.precision = precision;
+ public String convert(LoggingEvent event) {
+ LocationInfo locationInfo = event.getLocationInformation();
+ switch (type) {
+ case FULL_LOCATION_CONVERTER:
+ return locationInfo.fullInfo;
+ case METHOD_LOCATION_CONVERTER:
+ return locationInfo.getMethodName();
+ case LINE_LOCATION_CONVERTER:
+ return locationInfo.getLineNumber();
+ case FILE_LOCATION_CONVERTER:
+ return locationInfo.getFileName();
+ default:
+ return null;
+ }
+ }
}
- abstract
- String getFullyQualifiedName(LoggingEvent event);
-
- public
- String convert(LoggingEvent event) {
- String n = getFullyQualifiedName(event);
- if(precision <= 0)
- return n;
- else {
- int len = n.length();
-
- // We substract 1 from 'len' when assigning to 'end' to avoid out of
- // bounds exception in return r.substring(end+1, len). This can happen if
- // precision is 1 and the category name ends with a dot.
- int end = len -1 ;
- for(int i = precision; i > 0; i--) {
- end = n.lastIndexOf('.', end-1);
- if(end == -1)
- return n;
+ private static abstract class NamedPatternConverter extends PatternConverter {
+ int precision;
+
+ NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
+ super(formattingInfo);
+ this.precision = precision;
+ }
+
+ abstract String getFullyQualifiedName(LoggingEvent event);
+
+ public String convert(LoggingEvent event) {
+ String n = getFullyQualifiedName(event);
+ if (precision <= 0)
+ return n;
+ else {
+ int len = n.length();
+
+ // We substract 1 from 'len' when assigning to 'end' to avoid out of
+ // bounds exception in return r.substring(end+1, len). This can happen if
+ // precision is 1 and the category name ends with a dot.
+ int end = len - 1;
+ for (int i = precision; i > 0; i--) {
+ end = n.lastIndexOf('.', end - 1);
+ if (end == -1)
+ return n;
+ }
+ return n.substring(end + 1, len);
+ }
}
- return n.substring(end+1, len);
- }
}
- }
- private class ClassNamePatternConverter extends NamedPatternConverter {
+ private class ClassNamePatternConverter extends NamedPatternConverter {
- ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
- super(formattingInfo, precision);
- }
+ ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
+ super(formattingInfo, precision);
+ }
- String getFullyQualifiedName(LoggingEvent event) {
- return event.getLocationInformation().getClassName();
+ String getFullyQualifiedName(LoggingEvent event) {
+ return event.getLocationInformation().getClassName();
+ }
}
- }
- private class CategoryPatternConverter extends NamedPatternConverter {
+ private class CategoryPatternConverter extends NamedPatternConverter {
- CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
- super(formattingInfo, precision);
- }
+ CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
+ super(formattingInfo, precision);
+ }
- String getFullyQualifiedName(LoggingEvent event) {
- return event.getLoggerName();
+ String getFullyQualifiedName(LoggingEvent event) {
+ return event.getLoggerName();
+ }
}
- }
}
-
diff --git a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
index ad35f657..6ba9b66c 100644
--- a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
+++ b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
@@ -18,381 +18,414 @@ 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;
-import java.util.Iterator;
import org.apache.log4j.PatternLayout;
+import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LoggingEvent;
-
/**
- The JDBCAppender provides for sending log events to a database.
-
- <p><b><font color="#FF2222">WARNING: This version of JDBCAppender
- is very likely to be completely replaced in the future. Moreoever,
- it does not log exceptions</font></b>.
-
- <p>Each append call adds to an <code>ArrayList</code> buffer. When
- the buffer is filled each log event is placed in a sql statement
- (configurable) and executed.
-
- <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
- configurable options in the standard log4j ways.
-
- <p>The <code>setSql(String sql)</code> sets the SQL statement to be
- used for logging -- this statement is sent to a
- <code>PatternLayout</code> (either created automaticly by the
- appender or added by the user). Therefore by default all the
- conversion patterns in <code>PatternLayout</code> can be used
- inside of the statement. (see the test cases for examples)
-
- <p>Overriding the {@link #getLogStatement} method allows more
- explicit control of the statement used for logging.
-
- <p>For use as a base class:
-
- <ul>
-
- <li>Override <code>getConnection()</code> to pass any connection
- you want. Typically this is used to enable application wide
- connection pooling.
-
- <li>Override <code>closeConnection(Connection con)</code> -- if
- you override getConnection make sure to implement
- <code>closeConnection</code> to handle the connection you
- generated. Typically this would return the connection to the
- pool it came from.
-
- <li>Override <code>getLogStatement(LoggingEvent event)</code> to
- produce specialized or dynamic statements. The default uses the
- sql option value.
-
- </ul>
-
- @author Kevin Steppe (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
-
-*/
-public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
- implements org.apache.log4j.Appender {
-
- /**
- * URL of the DB for default connection handling
- */
- protected String databaseURL = "jdbc:odbc:myDB";
-
- /**
- * User to connect as for default connection handling
- */
- protected String databaseUser = "me";
-
- /**
- * User to use for default connection handling
- */
- protected String databasePassword = "mypassword";
-
- /**
- * Connection used by default. The connection is opened the first time it
- * is needed and then held open until the appender is closed (usually at
- * garbage collection). This behavior is best modified by creating a
- * sub-class and overriding the <code>getConnection</code> and
- * <code>closeConnection</code> methods.
- */
- protected Connection connection = null;
-
- /**
- * Stores the string given to the pattern layout for conversion into a SQL
- * statement, eg: insert into LogTable (Thread, Class, Message) values
- * ("%t", "%c", "%m").
- *
- * Be careful of quotes in your messages!
- *
- * Also see PatternLayout.
- */
- protected String sqlStatement = "";
-
- /**
- * size of LoggingEvent buffer before writting to the database.
- * Default is 1.
- */
- protected int bufferSize = 1;
-
- /**
- * ArrayList holding the buffer of Logging Events.
- */
- protected ArrayList buffer;
-
- /**
- * Helper object for clearing out the buffer
- */
- protected ArrayList removes;
-
- private boolean locationInfo = false;
-
- public JDBCAppender() {
- super();
- buffer = new ArrayList(bufferSize);
- removes = new ArrayList(bufferSize);
- }
-
- /**
- * Gets whether the location of the logging request call
- * should be captured.
- *
- * @since 1.2.16
- * @return the current value of the <b>LocationInfo</b> option.
- */
- public boolean getLocationInfo() {
- return locationInfo;
- }
-
- /**
- * The <b>LocationInfo</b> option takes a boolean value. By default, it is
- * set to false which means there will be no effort to extract the location
- * information related to the event. As a result, the event that will be
- * ultimately logged will likely to contain the wrong location information
- * (if present in the log format).
- * <p/>
- * <p/>
- * Location information extraction is comparatively very slow and should be
- * avoided unless performance is not a concern.
- * </p>
- * @since 1.2.16
- * @param flag true if location information should be extracted.
- */
- public void setLocationInfo(final boolean flag) {
- locationInfo = flag;
- }
-
-
- /**
- * Adds the event to the buffer. When full the buffer is flushed.
- */
- public void append(LoggingEvent event) {
- event.getNDC();
- event.getThreadName();
- // Get a copy of this thread's MDC.
- event.getMDCCopy();
- if (locationInfo) {
- event.getLocationInformation();
- }
- event.getRenderedMessage();
- event.getThrowableStrRep();
- buffer.add(event);
-
- if (buffer.size() >= bufferSize)
- flushBuffer();
- }
-
- /**
- * By default getLogStatement sends the event to the required Layout object.
- * The layout will format the given pattern into a workable SQL string.
- *
- * Overriding this provides direct access to the LoggingEvent
- * when constructing the logging statement.
- *
- */
- protected String getLogStatement(LoggingEvent event) {
- return getLayout().format(event);
- }
-
- /**
- *
- * Override this to provide an alertnate method of getting
- * connections (such as caching). One method to fix this is to open
- * connections at the start of flushBuffer() and close them at the
- * end. I use a connection pool outside of JDBCAppender which is
- * accessed in an override of this method.
- * */
- protected void execute(String sql) throws SQLException {
-
- Connection con = null;
- Statement stmt = null;
-
- try {
- con = getConnection();
-
- stmt = con.createStatement();
- stmt.executeUpdate(sql);
- } finally {
- if(stmt != null) {
- stmt.close();
- }
- closeConnection(con);
+ * The JDBCAppender provides for sending log events to a database.
+ *
+ * <p>
+ * <b><font color="#FF2222">WARNING: This version of JDBCAppender is very likely
+ * to be completely replaced in the future. Moreoever, it does not log
+ * exceptions</font></b>.
+ *
+ * <p>
+ * Each append call adds to an <code>ArrayList</code> buffer. When the buffer is
+ * filled each log event is placed in a sql statement (configurable) and
+ * executed.
+ *
+ * <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
+ * configurable options in the standard log4j ways.
+ *
+ * <p>
+ * The <code>setSql(String sql)</code> sets the SQL statement to be used for
+ * logging -- this statement is sent to a <code>PatternLayout</code> (either
+ * created automatically by the appender or added by the user). Therefore by
+ * default all the conversion patterns in <code>PatternLayout</code> can be used
+ * inside of the statement. (see the test cases for examples)
+ *
+ * <p>
+ * Overriding the {@link #getLogStatement} method allows more explicit control
+ * of the statement used for logging.
+ *
+ * <p>
+ * For use as a base class:
+ *
+ * <ul>
+ *
+ * <li>Override <code>getConnection()</code> to pass any connection you want.
+ * Typically this is used to enable application wide connection pooling.
+ *
+ * <li>Override <code>closeConnection(Connection con)</code> -- if you override
+ * getConnection make sure to implement <code>closeConnection</code> to handle
+ * the connection you generated. Typically this would return the connection to
+ * the pool it came from.
+ *
+ * <li>Override <code>getLogStatement(LoggingEvent event)</code> to produce
+ * specialized or dynamic statements. The default uses the sql option value.
+ *
+ * </ul>
+ *
+ * @author Kevin Steppe
+ * (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
+ *
+ */
+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
+ */
+ protected String databaseURL = "jdbc:odbc:myDB";
+
+ /**
+ * User to connect as for default connection handling
+ */
+ protected String databaseUser = "me";
+
+ /**
+ * User to use for default connection handling
+ */
+ protected String databasePassword = "mypassword";
+
+ /**
+ * Connection used by default. The connection is opened the first time it is
+ * needed and then held open until the appender is closed (usually at garbage
+ * collection). This behavior is best modified by creating a sub-class and
+ * overriding the <code>getConnection</code> and <code>closeConnection</code>
+ * methods.
+ */
+ protected Connection connection = null;
+
+ /**
+ * Stores the string given to the pattern layout for conversion into a SQL
+ * statement, eg: insert into LogTable (Thread, Class, Message) values ("%t",
+ * "%c", "%m").
+ *
+ * Be careful of quotes in your messages!
+ *
+ * Also see PatternLayout.
+ */
+ protected String sqlStatement = "";
+
+ private JdbcPatternParser preparedStatementParser;
+ /**
+ * size of LoggingEvent buffer before writting to the database. Default is 1.
+ */
+ protected int bufferSize = 1;
+
+ /**
+ * ArrayList holding the buffer of Logging Events.
+ */
+ protected ArrayList<LoggingEvent> buffer;
+
+ private boolean locationInfo = false;
+
+ private boolean isActive = false;
+
+ public JDBCAppender() {
+ super();
+ buffer = new ArrayList<LoggingEvent>(bufferSize);
}
- //System.out.println("Execute: " + sql);
- }
-
-
- /**
- * Override this to return the connection to a pool, or to clean up the
- * resource.
- *
- * The default behavior holds a single connection open until the appender
- * is closed (typically when garbage collected).
- */
- protected void closeConnection(Connection con) {
- }
-
- /**
- * Override this to link with your connection pooling system.
- *
- * By default this creates a single connection which is held open
- * until the object is garbage collected.
- */
- protected Connection getConnection() throws SQLException {
- if (!DriverManager.getDrivers().hasMoreElements())
- setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
-
- if (connection == null) {
- connection = DriverManager.getConnection(databaseURL, databaseUser,
- databasePassword);
- }
-
- return connection;
- }
-
- /**
- * Closes the appender, flushing the buffer first then closing the default
- * connection if it is open.
- */
- public void close()
- {
- flushBuffer();
-
- try {
- if (connection != null && !connection.isClosed())
- connection.close();
- } catch (SQLException e) {
- errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
+ @Override
+ public void activateOptions() {
+
+ if (getSql() == null || getSql().trim().length() == 0) {
+ LogLog.error("JDBCAppender.sql parameter is mandatory. Skipping all futher processing");
+ isActive = false;
+ return;
+ }
+
+ LogLog.debug("JDBCAppender constructing internal pattern parser");
+ preparedStatementParser = new JdbcPatternParser(getSql());
+ isActive = true;
}
- this.closed = true;
- }
-
- /**
- * loops through the buffer of LoggingEvents, gets a
- * sql string from getLogStatement() and sends it to execute().
- * Errors are sent to the errorHandler.
- *
- * If a statement fails the LoggingEvent stays in the buffer!
- */
- public void flushBuffer() {
- //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);
- }
+
+ /**
+ * Gets whether the location of the logging request call should be captured.
+ *
+ * @since 1.2.16
+ * @return the current value of the <b>LocationInfo</b> option.
+ */
+ public boolean getLocationInfo() {
+ return locationInfo;
}
-
- // remove from the buffer any events that were reported
- buffer.removeAll(removes);
-
- // clear the buffer of reported events
- removes.clear();
- }
-
-
- /** closes the appender before disposal */
- public void finalize() {
- close();
- }
-
-
- /**
- * JDBCAppender requires a layout.
- * */
- public boolean requiresLayout() {
- return true;
- }
-
-
- /**
- *
- */
- public void setSql(String s) {
- sqlStatement = s;
- if (getLayout() == null) {
- this.setLayout(new PatternLayout(s));
+
+ /**
+ * The <b>LocationInfo</b> option takes a boolean value. By default, it is set
+ * to false which means there will be no effort to extract the location
+ * information related to the event. As a result, the event that will be
+ * ultimately logged will likely to contain the wrong location information (if
+ * present in the log format).
+ * <p/>
+ * <p/>
+ * Location information extraction is comparatively very slow and should be
+ * avoided unless performance is not a concern.
+ * </p>
+ *
+ * @since 1.2.16
+ * @param flag true if location information should be extracted.
+ */
+ public void setLocationInfo(final boolean flag) {
+ locationInfo = flag;
}
- else {
- ((PatternLayout)getLayout()).setConversionPattern(s);
+
+ /**
+ * Adds the event to the buffer. When full the buffer is flushed.
+ */
+ public void append(LoggingEvent event) {
+ if (!isActive) {
+ return;
+ }
+ event.getNDC();
+ event.getThreadName();
+ // Get a copy of this thread's MDC.
+ event.getMDCCopy();
+ if (locationInfo) {
+ event.getLocationInformation();
+ }
+ event.getRenderedMessage();
+ event.getThrowableStrRep();
+ buffer.add(event);
+
+ if (buffer.size() >= bufferSize)
+ flushBuffer();
}
- }
+ /**
+ * By default getLogStatement sends the event to the required Layout object. The
+ * layout will format the given pattern into a workable SQL string.
+ *
+ * Overriding this provides direct access to the LoggingEvent when constructing
+ * the logging statement.
+ *
+ */
+ protected String getLogStatement(LoggingEvent event) {
+ return getLayout().format(event);
+ }
- /**
- * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
- */
- public String getSql() {
- return sqlStatement;
- }
+ /**
+ *
+ * Override this to provide an alertnate method of getting connections (such as
+ * caching). One method to fix this is to open connections at the start of
+ * flushBuffer() and close them at the end. I use a connection pool outside of
+ * JDBCAppender which is accessed in an override of this method.
+ */
+ protected void execute(String sql) throws SQLException {
+
+ Connection con = null;
+ Statement stmt = null;
+
+ try {
+ con = getConnection();
+
+ stmt = con.createStatement();
+ stmt.executeUpdate(sql);
+ } finally {
+ if (stmt != null) {
+ stmt.close();
+ }
+ closeConnection(con);
+ }
+
+ // System.out.println("Execute: " + sql);
+ }
+ /**
+ * Override this to return the connection to a pool, or to clean up the
+ * resource.
+ *
+ * The default behavior holds a single connection open until the appender is
+ * closed (typically when garbage collected).
+ */
+ protected void closeConnection(Connection con) {
+ }
- public void setUser(String user) {
- databaseUser = user;
- }
+ /**
+ * Override this to link with your connection pooling system.
+ *
+ * By default this creates a single connection which is held open until the
+ * object is garbage collected.
+ */
+ protected Connection getConnection() throws SQLException {
+ if (!DriverManager.getDrivers().hasMoreElements())
+ setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
+
+ if (connection == null) {
+ connection = DriverManager.getConnection(databaseURL, databaseUser, databasePassword);
+ }
+
+ return connection;
+ }
+ /**
+ * Closes the appender, flushing the buffer first then closing the default
+ * connection if it is open.
+ */
+ public void close() {
+ flushBuffer();
+
+ try {
+ if (connection != null && !connection.isClosed())
+ connection.close();
+ } catch (SQLException e) {
+ errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
+ }
+ this.closed = true;
+ }
- public void setURL(String url) {
- databaseURL = url;
- }
+ /**
+ * loops through the buffer of LoggingEvents, gets a sql string from
+ * getLogStatement() and sends it to execute(). Errors are sent to the
+ * errorHandler.
+ *
+ * If a statement fails the LoggingEvent stays in the buffer!
+ */
+ public void flushBuffer() {
+ if (buffer.isEmpty()) {
+ return;
+ }
+ flushBufferSecure();
+ }
+ private void flushBufferSecure() {
+ // Prepare events that we will store to the DB
+ ArrayList<LoggingEvent> removes = new ArrayList<LoggingEvent>(buffer);
+ buffer.removeAll(removes);
+
+ if (layout.getClass() != PatternLayout.class) {
+ errorHandler.error("Failed to convert pattern " + layout + " to SQL", ILLEGAL_PATTERN_FOR_SECURE_EXEC,
+ ErrorCode.MISSING_LAYOUT);
+ return;
+ }
+ Connection con = null;
+ boolean useBatch = removes.size() > 1;
+ try {
+ con = getConnection();
+ PreparedStatement ps = con.prepareStatement(preparedStatementParser.getParameterizedSql());
+ safelyInsertIntoDB(removes, useBatch, ps);
+ } catch (SQLException e) {
+ errorHandler.error("Failed to append messages sql", e, ErrorCode.FLUSH_FAILURE);
+ } finally {
+ if (con != null) {
+ closeConnection(con);
+ }
+ }
+
+ }
+
+ private void safelyInsertIntoDB(ArrayList<LoggingEvent> removes, boolean useBatch, PreparedStatement ps)
+ throws SQLException {
+ try {
+ for (LoggingEvent logEvent : removes) {
+ 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) {
+ }
+ }
+ }
- public void setPassword(String password) {
- databasePassword = password;
- }
+ /** closes the appender before disposal */
+ public void finalize() {
+ close();
+ }
+ /**
+ * JDBCAppender requires a layout.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
- public void setBufferSize(int newBufferSize) {
- bufferSize = newBufferSize;
- buffer.ensureCapacity(bufferSize);
- removes.ensureCapacity(bufferSize);
- }
+ /**
+ *
+ */
+ public void setSql(String s) {
+ sqlStatement = s;
+ if (getLayout() == null) {
+ this.setLayout(new PatternLayout(s));
+ } else {
+ ((PatternLayout) getLayout()).setConversionPattern(s);
+ }
+ }
+ /**
+ * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
+ */
+ public String getSql() {
+ return sqlStatement;
+ }
- public String getUser() {
- return databaseUser;
- }
+ public void setUser(String user) {
+ databaseUser = user;
+ }
+ public void setURL(String url) {
+ databaseURL = url;
+ }
- public String getURL() {
- return databaseURL;
- }
+ public void setPassword(String password) {
+ databasePassword = password;
+ }
+ public void setBufferSize(int newBufferSize) {
+ bufferSize = newBufferSize;
+ buffer.ensureCapacity(bufferSize);
+ }
- public String getPassword() {
- return databasePassword;
- }
+ public String getUser() {
+ return databaseUser;
+ }
+ public String getURL() {
+ return databaseURL;
+ }
- public int getBufferSize() {
- return bufferSize;
- }
+ public String getPassword() {
+ return databasePassword;
+ }
+ public int getBufferSize() {
+ return bufferSize;
+ }
- /**
- * Ensures that the given driver class has been loaded for sql connection
- * creation.
- */
- public void setDriver(String driverClass) {
- try {
- Class.forName(driverClass);
- } catch (Exception e) {
- errorHandler.error("Failed to load driver", e,
- ErrorCode.GENERIC_FAILURE);
+ /**
+ * Ensures that the given driver class has been loaded for sql connection
+ * creation.
+ */
+ public void setDriver(String driverClass) {
+ try {
+ Class.forName(driverClass);
+ } catch (Exception e) {
+ errorHandler.error("Failed to load driver", e, 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 00000000..d6807dee
--- /dev/null
+++ b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
@@ -0,0 +1,115 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class JdbcPatternParser {
+ private static final String QUESTION_MARK = "?";
+
+ private static final char PERCENT_CHAR = '%';
+
+ private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'((?>[^']|'')+)'");
+ // NOTE: capturing group work seem to work just as well.
+ //private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'(([^']|'')+)'");
+
+ private String parameterizedSql;
+ final private List<String> patternStringRepresentationList = new ArrayList<String>();
+ final private List<PatternConverter> args = new ArrayList<PatternConverter>();
+
+ JdbcPatternParser(String insertString) {
+ init(insertString);
+ }
+
+ public String getParameterizedSql() {
+ return parameterizedSql;
+ }
+
+ public List<String> getUnmodifiablePatternStringRepresentationList() {
+ return Collections.unmodifiableList(patternStringRepresentationList);
+ }
+
+ @Override
+ public String toString() {
+ return "JdbcPatternParser{sql=" + parameterizedSql + ",args=" + patternStringRepresentationList + "}";
+ }
+
+ /**
+ * Converts '....' literals into bind variables in JDBC.
+ */
+ private void init(String insertString) {
+ if (insertString == null) {
+ throw new IllegalArgumentException("Null pattern");
+ }
+
+ Matcher m = STRING_LITERAL_PATTERN.matcher(insertString);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String matchedStr = m.group(1);
+ if (matchedStr.indexOf(PERCENT_CHAR) == -1) {
+ replaceWithMatchedStr(m, sb);
+ } else {
+ // Replace with bind
+ replaceWithBind(m, sb, matchedStr);
+ }
+ }
+ m.appendTail(sb);
+ this.parameterizedSql = sb.toString();
+ }
+
+ private void replaceWithMatchedStr(Matcher m, StringBuffer sb) {
+ // Just literal, append it as is
+ m.appendReplacement(sb, "'$1'");
+ }
+
+ private void replaceWithBind(Matcher m, StringBuffer sb, String matchedStr) {
+ m.appendReplacement(sb, QUESTION_MARK);
+ // 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.
+ matchedStr = matchedStr.replaceAll("''", "'");
+ patternStringRepresentationList.add(matchedStr);
+ args.add(new PatternParser(matchedStr).parse());
+ }
+
+ public void setParameters(PreparedStatement ps, LoggingEvent logEvent) throws SQLException {
+ for (int i = 0; i < args.size(); i++) {
+ final PatternConverter head = args.get(i);
+ String value = buildValueStr(logEvent, head);
+ ps.setString(i + 1, value);
+ }
+ }
+
+ private String buildValueStr(LoggingEvent logEvent, final PatternConverter head) {
+ StringBuffer buffer = new StringBuffer();
+ PatternConverter c = head;
+ while (c != null) {
+ c.format(buffer, logEvent);
+ c = c.next;
+ }
+ return buffer.toString();
+ }
+}
--
2.33.1