Repository: cayenne Updated Branches: refs/heads/master 5855ffc71 -> 7694a64d3
CAY-2485 Added CompactSl4jJdbcEventLogger. Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/a07bba55 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/a07bba55 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/a07bba55 Branch: refs/heads/master Commit: a07bba5542a79f534b80bab28de2b1354a52d593 Parents: 20b166a Author: kkomyak <const1...@gmail.com> Authored: Wed Oct 10 11:18:17 2018 +0300 Committer: kkomyak <const1...@gmail.com> Committed: Thu Oct 11 16:40:30 2018 +0300 ---------------------------------------------------------------------- cayenne-server/pom.xml | 6 + .../cayenne/log/CompactSl4jJdbcEventLogger.java | 201 +++++++++++++++++++ .../log/CompactSl4jJdbcEventLoggerTest.java | 109 ++++++++++ .../cayenne/log/Slf4jJdbcEventLoggerTest.java | 4 - .../org/apache/cayenne/log/TestAppender.java | 16 ++ .../src/test/resources/logback-test.xml | 42 ++++ 6 files changed, 374 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/pom.xml ---------------------------------------------------------------------- diff --git a/cayenne-server/pom.xml b/cayenne-server/pom.xml index 0ede0dd..e965ffa 100644 --- a/cayenne-server/pom.xml +++ b/cayenne-server/pom.xml @@ -46,6 +46,12 @@ <!-- Test dependencies --> <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.0.13</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/src/main/java/org/apache/cayenne/log/CompactSl4jJdbcEventLogger.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/log/CompactSl4jJdbcEventLogger.java b/cayenne-server/src/main/java/org/apache/cayenne/log/CompactSl4jJdbcEventLogger.java new file mode 100644 index 0000000..38e4370 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/log/CompactSl4jJdbcEventLogger.java @@ -0,0 +1,201 @@ +/***************************************************************** + * 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.cayenne.log; + +import org.apache.cayenne.access.translator.DbAttributeBinding; +import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.configuration.RuntimeProperties; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.map.DbAttribute; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * @since 4.1 + */ +public class CompactSl4jJdbcEventLogger extends Slf4jJdbcEventLogger { + + private static final String UNION = "UNION"; + private static final String SELECT = "SELECT"; + private static final String FROM = "FROM"; + private static final String SPACE = " "; + + public CompactSl4jJdbcEventLogger(@Inject RuntimeProperties runtimeProperties) { + super(runtimeProperties); + } + + @Override + public void logQuery(String sql, ParameterBinding[] bindings) { + if (!isLoggable()) { + return; + } + + String str; + if (sql.toUpperCase().contains(UNION)) { + str = processUnionSql(sql); + } else { + str = formatSqlSelectColumns(sql); + } + + StringBuilder stringBuilder = new StringBuilder(str); + appendParameters(stringBuilder, "bind", bindings); + if (stringBuilder.length() < 0) { + return; + } + + super.logQuery(stringBuilder.toString(), new ParameterBinding[0]); + } + + private String processUnionSql(String sql) { + + String modified = Pattern.compile(UNION.toLowerCase(), Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(UNION); + String[] queries = modified.split( + UNION); + List<String> formattedQueries = Arrays.stream(queries).map(this::formatSqlSelectColumns).collect(Collectors.toList()); + StringBuilder buffer = new StringBuilder(); + boolean used = false; + for (String q: formattedQueries) { + if(!used){ + used = true; + } else { + buffer.append(SPACE).append(UNION); + } + buffer.append(q); + } + return buffer.toString(); + } + + private String formatSqlSelectColumns(String sql) { + int selectIndex = sql.toUpperCase().indexOf(SELECT); + if (selectIndex == -1) { + return sql; + } + selectIndex += SELECT.length(); + int fromIndex = sql.toUpperCase().indexOf(FROM); + String columns = sql.substring(selectIndex, fromIndex); + String[] columnsArray = columns.split(","); + if (columnsArray.length <= 3) { + return sql; + } + + columns = "(" + columnsArray.length + " columns)"; + return new StringBuilder(sql.substring(0, selectIndex)) + .append(SPACE) + .append(columns) + .append(SPACE) + .append(sql, fromIndex, sql.length()) + .toString(); + } + + private void appendParameters(StringBuilder buffer, String label, ParameterBinding[] bindings) { + int bindingLength = bindings.length; + if (bindingLength == 0) { + return; + } + + buildBinding(buffer, label, collectBindings(bindings)); + } + + @SuppressWarnings("unchecked") + private Map<String, List<String>> collectBindings(ParameterBinding[] bindings) { + Map<String, List<String>> bindingsMap = new HashMap<>(); + + String key = null; + String value; + for (int i = 0; i < bindings.length; i++) { + ParameterBinding b = bindings[i]; + + if (b.isExcluded()) { + continue; + } + + if (b instanceof DbAttributeBinding) { + DbAttribute attribute = ((DbAttributeBinding) b).getAttribute(); + if (attribute != null) { + key = attribute.getName(); + } + } + + if (b.getExtendedType() != null) { + value = b.getExtendedType().toString(b.getValue()); + } else if(b.getValue() == null) { + value = "NULL"; + } else { + value = new StringBuilder(b.getValue().getClass().getName()) + .append("@") + .append(System.identityHashCode(b.getValue())).toString(); + } + + List<String> objects = bindingsMap.computeIfAbsent(key, k -> new ArrayList<>()); + objects.add(value); + } + + return bindingsMap; + } + + private void buildBinding(StringBuilder buffer, String label, Map<String, List<String>> bindingsMap) { + int j = 1; + boolean hasIncluded = false; + for (String k : bindingsMap.keySet()) { + if (!hasIncluded) { + hasIncluded = true; + buffer.append(" [").append(label).append(": "); + } else { + buffer.append(", "); + } + buffer.append(j).append("->").append(k).append(": "); + + List<String> bindingsList = bindingsMap.get(k); + if (bindingsList.size() == 1 ) { + buffer.append(bindingsList.get(0)); + } else { + buffer.append("{"); + boolean wasAdded = false; + for (Object val : bindingsList) { + if (wasAdded) { + buffer.append(", "); + } else { + wasAdded = true; + } + buffer.append(val); + } + buffer.append("}"); + } + j++; + } + + if (hasIncluded) { + buffer.append("]"); + } + } + + @Override + public void logBeginTransaction(String transactionLabel) { + } + + @Override + public void logCommitTransaction(String transactionLabel) { + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/src/test/java/org/apache/cayenne/log/CompactSl4jJdbcEventLoggerTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/log/CompactSl4jJdbcEventLoggerTest.java b/cayenne-server/src/test/java/org/apache/cayenne/log/CompactSl4jJdbcEventLoggerTest.java new file mode 100644 index 0000000..f7dbe76 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/log/CompactSl4jJdbcEventLoggerTest.java @@ -0,0 +1,109 @@ +/***************************************************************** + * 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.cayenne.log; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.LoggingEvent; +import org.apache.cayenne.access.translator.DbAttributeBinding; +import org.apache.cayenne.access.types.BooleanType; +import org.apache.cayenne.access.types.CharType; +import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.access.types.IntegerType; +import org.apache.cayenne.configuration.DefaultRuntimeProperties; +import org.apache.cayenne.map.DbAttribute; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class CompactSl4jJdbcEventLoggerTest { + + @Before + public void before() { + TestAppender.events.clear(); + } + + @Test + public void logWithCompact_Union() { + + CompactSl4jJdbcEventLogger compactSl4jJdbcEventLogger = new CompactSl4jJdbcEventLogger(new DefaultRuntimeProperties(Collections.emptyMap())); + DbAttributeBinding[] bindings = createBindings(); + final List<LoggingEvent> log = TestAppender.events; + assertEquals(log.size(), 0); + + compactSl4jJdbcEventLogger.logQuery( + "SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1, t0.F_KEY2 AS ec0_2," + + " t0.PKEY AS ec0_3 FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST " + + "t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) WHERE t1.NAME LIKE ?", createBindings()); + assertEquals(log.size(), 1); + LoggingEvent firstLogEntry = log.get(0); + assertThat(firstLogEntry.getLevel(), is(Level.INFO)); + assertThat(firstLogEntry.getMessage(), is("SELECT (4 columns) FROM COMPOUND_FK_TEST t0 " + + "INNER JOIN COMPOUND_PK_TEST t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) " + + "WHERE t1.NAME LIKE ? [bind: 1->t0.NAME: {'', 52, 'true'}, 2->t0.F_KEY1: 'true'] ")); + assertThat(firstLogEntry.getLoggerName(), is("org.apache.cayenne.log.JdbcEventLogger")); + + compactSl4jJdbcEventLogger.logQuery( + "SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1, " + + "t0.PKEY AS ec0_3 FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST " + + "t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) WHERE t1.NAME LIKE ?" + + "UNION ALL " + + "SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1," + + " t0.PKEY AS ec0_3 FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST " + + "t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) WHERE t1.NAME LIKE ?" + + "union all " + + "SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1, t0.F_KEY2 AS ec0_2," + + " t0.PKEY AS ec0_3 FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST " + + "t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) WHERE t1.NAME LIKE ?", bindings); + assertEquals(log.size(), 2); + firstLogEntry = log.get(1); + assertThat(firstLogEntry.getLevel(), is(Level.INFO)); + assertThat(firstLogEntry.getMessage(), is("SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1, t0.PKEY AS ec0_3 FROM COMPOUND_FK_TEST t0 " + + "INNER JOIN COMPOUND_PK_TEST t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) " + + "WHERE t1.NAME LIKE ? UNION ALL SELECT t0.NAME AS ec0_0, t0.F_KEY1 AS ec0_1, t0.PKEY AS ec0_3 " + + "FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) " + + "WHERE t1.NAME LIKE ? UNION all SELECT (4 columns) FROM COMPOUND_FK_TEST t0 INNER JOIN COMPOUND_PK_TEST t1 ON (t0.F_KEY1 = t1.KEY1 AND t0.F_KEY2 = t1.KEY2) " + + "WHERE t1.NAME LIKE ? [bind: 1->t0.NAME: {'', 52, 'true'}, 2->t0.F_KEY1: 'true'] ")); + assertThat(firstLogEntry.getLoggerName(), is("org.apache.cayenne.log.JdbcEventLogger")); + + } + + private DbAttributeBinding [] createBindings() { + return new DbAttributeBinding[] { createBinding("t0.NAME", 1, "", new CharType(false, false)), + createBinding("t0.NAME", 2, 52, new IntegerType()), + createBinding("t0.NAME", 3, true, new BooleanType()), + createBinding("t0.F_KEY1", 4, true, new BooleanType())}; + } + + private DbAttributeBinding createBinding(String name, int position, Object object, ExtendedType type){ + + DbAttributeBinding dbAttributeBinding = new DbAttributeBinding(new DbAttribute(name)); + dbAttributeBinding.setValue(object); + dbAttributeBinding.setStatementPosition(position); + if (type != null) { + dbAttributeBinding.setExtendedType(type); + } + + return dbAttributeBinding; + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/src/test/java/org/apache/cayenne/log/Slf4jJdbcEventLoggerTest.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/log/Slf4jJdbcEventLoggerTest.java b/cayenne-server/src/test/java/org/apache/cayenne/log/Slf4jJdbcEventLoggerTest.java index a41861c..322eb7f 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/log/Slf4jJdbcEventLoggerTest.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/log/Slf4jJdbcEventLoggerTest.java @@ -18,14 +18,10 @@ ****************************************************************/ package org.apache.cayenne.log; -import org.apache.cayenne.configuration.DefaultRuntimeProperties; import org.apache.cayenne.util.IDUtil; import org.junit.Test; -import java.util.Collections; - import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; public class Slf4jJdbcEventLoggerTest { http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/src/test/java/org/apache/cayenne/log/TestAppender.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/log/TestAppender.java b/cayenne-server/src/test/java/org/apache/cayenne/log/TestAppender.java new file mode 100644 index 0000000..5560c07 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/log/TestAppender.java @@ -0,0 +1,16 @@ +package org.apache.cayenne.log; + +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.AppenderBase; + +import java.util.ArrayList; +import java.util.List; + +public class TestAppender extends AppenderBase<LoggingEvent> { + static List<LoggingEvent> events = new ArrayList<>(); + + @Override + protected void append(LoggingEvent e) { + events.add(e); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cayenne/blob/a07bba55/cayenne-server/src/test/resources/logback-test.xml ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/resources/logback-test.xml b/cayenne-server/src/test/resources/logback-test.xml new file mode 100644 index 0000000..616beac --- /dev/null +++ b/cayenne-server/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ +<configuration> + + <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern> + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + </Pattern> + </layout> + </appender> + + + <appender name="TEST-INFO" + class="org.apache.cayenne.log.TestAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <Pattern> + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + </Pattern> + </encoder> + + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- rollover daily --> + <fileNamePattern>archived/error.%d{yyyy-MM-dd}.%i.log + </fileNamePattern> + <timeBasedFileNamingAndTriggeringPolicy + class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>10MB</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + + </appender> + + <root level="info"> + <appender-ref ref="TEST-INFO" /> + </root> + + <logger name="org.apache.cayenne" level="info"> + <appender-ref ref="STDOUT"/> + </logger> + +</configuration> \ No newline at end of file