Repository: housepower/ClickHouse-Native-JDBC
Pull request #231 (2020-12-02 07:13:30)

Introduce log classes to avoid hard dependence on slf4j
`slf4j` is de facto standard log facade in Java ecosystem, and we don't want to shade and relocate it in `shaded` version(`hadoop-client-api` also does), but any way, it's a 3rd party dependency, not part of JDK, as our component is a JDBC driver, we'd better provide a single jar which should work properly without `slf4j` to end user, it's more convenient for users who want to use it in GUI tools like `DataGrip` and `Dbeaver`

codecov[bot]: # [Codecov](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=h1) Report
> Merging [#231](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=desc) (b05c106) into [master](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/commit/6ccfcc99534f407abc1391eec4e13ad0afe459e6?el=desc) (6ccfcc9) will **decrease** coverage by `1.08%`.
> The diff coverage is `46.77%`.

[![Impacted file tree graph](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/graphs/tree.svg?width=650&height=150&src=pr&token=Izz9Qy1I72)](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##             master     #231      +/-   ##
============================================
- Coverage     60.07%   58.98%   -1.09%     
- Complexity      812      874      +62     
============================================
  Files            95      104       +9     
  Lines          3514     3816     +302     
  Branches        330      372      +42     
============================================
+ Hits           2111     2251     +140     
- Misses         1216     1371     +155     
- Partials        187      194       +7     
```


| [Impacted Files](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [.../housepower/jdbc/BalancedClickhouseDataSource.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL0JhbGFuY2VkQ2xpY2tob3VzZURhdGFTb3VyY2UuamF2YQ==) | `67.81% <ø> (ø)` | `18.00 <0.00> (ø)` | |
| [...m/github/housepower/jdbc/ClickHouseConnection.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL0NsaWNrSG91c2VDb25uZWN0aW9uLmphdmE=) | `60.91% <ø> (ø)` | `23.00 <0.00> (ø)` | |
| [...a/com/github/housepower/jdbc/ClickHouseDriver.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL0NsaWNrSG91c2VEcml2ZXIuamF2YQ==) | `40.62% <ø> (ø)` | `6.00 <0.00> (ø)` | |
| [...ithub/housepower/jdbc/ClickhouseJdbcUrlParser.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL0NsaWNraG91c2VKZGJjVXJsUGFyc2VyLmphdmE=) | `75.75% <ø> (ø)` | `19.00 <0.00> (ø)` | |
| [...java/com/github/housepower/jdbc/log/JdkLogger.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9KZGtMb2dnZXIuamF2YQ==) | `0.00% <0.00%> (ø)` | `0.00 <0.00> (?)` | |
| [...b/housepower/jdbc/log/JdkLoggerFactoryAdaptor.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9KZGtMb2dnZXJGYWN0b3J5QWRhcHRvci5qYXZh) | `0.00% <0.00%> (ø)` | `0.00 <0.00> (?)` | |
| [...main/java/com/github/housepower/jdbc/log/Util.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9VdGlsLmphdmE=) | `0.00% <0.00%> (ø)` | `0.00 <0.00> (?)` | |
| [...va/com/github/housepower/jdbc/log/Slf4jLogger.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9TbGY0akxvZ2dlci5qYXZh) | `37.93% <37.93%> (ø)` | `5.00 <5.00> (?)` | |
| [.../com/github/housepower/jdbc/log/LoggerFactory.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9Mb2dnZXJGYWN0b3J5LmphdmE=) | `40.00% <40.00%> (ø)` | `2.00 <2.00> (?)` | |
| [...m/github/housepower/jdbc/log/MessageFormatter.java](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree#diff-Y2xpY2tob3VzZS1uYXRpdmUtamRiYy9zcmMvbWFpbi9qYXZhL2NvbS9naXRodWIvaG91c2Vwb3dlci9qZGJjL2xvZy9NZXNzYWdlRm9ybWF0dGVyLmphdmE=) | `70.44% <70.44%> (ø)` | `46.00 <46.00> (?)` | |
| ... and [16 more](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=footer). Last update [6ccfcc9...7bb2fa6](https://codecov.io/gh/housepower/ClickHouse-Native-JDBC/pull/231?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/clickhouse-native-jdbc-shaded/pom.xml b/clickhouse-native-jdbc-shaded/pom.xml
index 8b328c4..2f2bfc4 100644
--- a/clickhouse-native-jdbc-shaded/pom.xml
+++ b/clickhouse-native-jdbc-shaded/pom.xml
@@ -32,6 +32,7 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>com.github.housepower</groupId>
diff --git a/clickhouse-native-jdbc/pom.xml b/clickhouse-native-jdbc/pom.xml
index 044b429..028ab63 100644
--- a/clickhouse-native-jdbc/pom.xml
+++ b/clickhouse-native-jdbc/pom.xml
@@ -32,14 +32,15 @@
     </properties>
 
     <dependencies>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.lz4</groupId>
             <artifactId>lz4-java</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/BalancedClickhouseDataSource.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/BalancedClickhouseDataSource.java
index 2b6b276..3289107 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/BalancedClickhouseDataSource.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/BalancedClickhouseDataSource.java
@@ -19,8 +19,8 @@ import com.github.housepower.jdbc.misc.StrUtil;
 import com.github.housepower.jdbc.misc.Validate;
 import com.github.housepower.jdbc.settings.ClickHouseConfig;
 import com.github.housepower.jdbc.settings.SettingKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.github.housepower.jdbc.log.Logger;
+import com.github.housepower.jdbc.log.LoggerFactory;
 
 import javax.sql.DataSource;
 import java.io.PrintWriter;
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
index 561de3e..a79822b 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
@@ -27,8 +27,8 @@ import com.github.housepower.jdbc.statement.ClickHousePreparedInsertStatement;
 import com.github.housepower.jdbc.statement.ClickHousePreparedQueryStatement;
 import com.github.housepower.jdbc.statement.ClickHouseStatement;
 import com.github.housepower.jdbc.wrapper.SQLConnection;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.github.housepower.jdbc.log.Logger;
+import com.github.housepower.jdbc.log.LoggerFactory;
 
 import javax.annotation.Nullable;
 import java.net.InetSocketAddress;
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseDriver.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseDriver.java
index eff5c75..f68455a 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseDriver.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickHouseDriver.java
@@ -21,7 +21,6 @@ import com.github.housepower.jdbc.settings.SettingKey;
 import java.sql.*;
 import java.util.Map;
 import java.util.Properties;
-import java.util.logging.Logger;
 
 public class ClickHouseDriver implements Driver {
 
@@ -91,7 +90,7 @@ public class ClickHouseDriver implements Driver {
     }
 
     @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
         throw new SQLFeatureNotSupportedException();
     }
 }
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickhouseJdbcUrlParser.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickhouseJdbcUrlParser.java
index 93a50b4..7b8e27f 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickhouseJdbcUrlParser.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/ClickhouseJdbcUrlParser.java
@@ -17,8 +17,8 @@ package com.github.housepower.jdbc;
 import com.github.housepower.jdbc.exception.InvalidValueException;
 import com.github.housepower.jdbc.misc.Validate;
 import com.github.housepower.jdbc.settings.SettingKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.github.housepower.jdbc.log.Logger;
+import com.github.housepower.jdbc.log.LoggerFactory;
 
 import java.net.URI;
 import java.net.URISyntaxException;
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/connect/NativeClient.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/connect/NativeClient.java
index 1b04435..4e4aff2 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/connect/NativeClient.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/connect/NativeClient.java
@@ -14,6 +14,7 @@
 
 package com.github.housepower.jdbc.connect;
 
+import com.github.housepower.jdbc.buffer.SocketBuffedReader;
 import com.github.housepower.jdbc.buffer.SocketBuffedWriter;
 import com.github.housepower.jdbc.data.Block;
 import com.github.housepower.jdbc.misc.Validate;
@@ -23,8 +24,8 @@ import com.github.housepower.jdbc.serde.BinarySerializer;
 import com.github.housepower.jdbc.settings.ClickHouseConfig;
 import com.github.housepower.jdbc.settings.ClickHouseDefines;
 import com.github.housepower.jdbc.settings.SettingKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.github.housepower.jdbc.log.Logger;
+import com.github.housepower.jdbc.log.LoggerFactory;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -51,8 +52,9 @@ public class NativeClient {
             socket.setKeepAlive(configure.tcpKeepAlive());
             socket.connect(endpoint, (int) configure.connectTimeout().toMillis());
 
-            return new NativeClient(socket, new BinarySerializer(
-                    new SocketBuffedWriter(socket), true), new BinaryDeserializer(socket));
+            return new NativeClient(socket,
+                    new BinarySerializer(new SocketBuffedWriter(socket), true),
+                    new BinaryDeserializer(new SocketBuffedReader(socket)));
         } catch (IOException ex) {
             throw new SQLException(ex.getMessage(), ex);
         }
@@ -65,9 +67,9 @@ public class NativeClient {
 
     public NativeClient(Socket socket, BinarySerializer serializer, BinaryDeserializer deserializer) {
         this.socket = socket;
+        this.address = socket.getLocalSocketAddress();
         this.serializer = serializer;
         this.deserializer = deserializer;
-        this.address = socket.getLocalSocketAddress();
     }
 
     public SocketAddress address() {
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java
index 6242e5b..74aa7d1 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java
@@ -43,4 +43,4 @@ public class ColumnWriterBuffer {
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/FormattingTuple.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/FormattingTuple.java
new file mode 100644
index 0000000..41b67ea
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/FormattingTuple.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed 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.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+package com.github.housepower.jdbc.log;
+
+/**
+ * Holds the results of formatting done by {@link MessageFormatter}.
+ * 
+ * @author Joern Huxhorn
+ */
+public class FormattingTuple {
+
+    static public FormattingTuple NULL = new FormattingTuple(null);
+
+    private final String message;
+    private final Throwable throwable;
+    private final Object[] argArray;
+
+    public FormattingTuple(String message) {
+        this(message, null, null);
+    }
+
+    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {
+        this.message = message;
+        this.throwable = throwable;
+        this.argArray = argArray;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public Object[] getArgArray() {
+        return argArray;
+    }
+
+    public Throwable getThrowable() {
+        return throwable;
+    }
+
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLogger.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLogger.java
new file mode 100644
index 0000000..7f7928f
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLogger.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+import java.util.ResourceBundle;
+
+public class JdkLogger implements Logger {
+
+    private static final Object[] EMPTY_ARRAY = new Object[]{};
+
+    private final java.util.logging.Logger logger;
+
+    public JdkLogger(java.util.logging.Logger logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    public String getName() {
+        return this.logger.getName();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return this.logger.isLoggable(java.util.logging.Level.FINEST);
+    }
+
+    @Override
+    public void trace(String format, Object... arguments) {
+        if (logger.isLoggable(java.util.logging.Level.FINEST)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
+            doLog(java.util.logging.Level.FINEST, ft);
+        }
+    }
+
+    @Override
+    public void trace(String msg, Throwable t) {
+        if (logger.isLoggable(java.util.logging.Level.FINEST)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(msg, EMPTY_ARRAY, t);
+            doLog(java.util.logging.Level.FINEST, ft);
+        }
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return this.logger.isLoggable(java.util.logging.Level.FINE);
+    }
+
+    @Override
+    public void debug(String format, Object... arguments) {
+        if (logger.isLoggable(java.util.logging.Level.FINE)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
+            doLog(java.util.logging.Level.FINE, ft);
+        }
+    }
+
+    @Override
+    public void debug(String msg, Throwable t) {
+        if (logger.isLoggable(java.util.logging.Level.FINE)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(msg, EMPTY_ARRAY, t);
+            doLog(java.util.logging.Level.FINE, ft);
+        }
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return this.logger.isLoggable(java.util.logging.Level.INFO);
+    }
+
+    @Override
+    public void info(String format, Object... arguments) {
+        if (logger.isLoggable(java.util.logging.Level.INFO)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
+            doLog(java.util.logging.Level.INFO, ft);
+        }
+    }
+
+    @Override
+    public void info(String msg, Throwable t) {
+        if (logger.isLoggable(java.util.logging.Level.INFO)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(msg, EMPTY_ARRAY, t);
+            doLog(java.util.logging.Level.INFO, ft);
+        }
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return this.logger.isLoggable(java.util.logging.Level.WARNING);
+    }
+
+    @Override
+    public void warn(String format, Object... arguments) {
+        if (logger.isLoggable(java.util.logging.Level.WARNING)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
+            doLog(java.util.logging.Level.WARNING, ft);
+        }
+    }
+
+    @Override
+    public void warn(String msg, Throwable t) {
+        if (logger.isLoggable(java.util.logging.Level.WARNING)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(msg, EMPTY_ARRAY, t);
+            doLog(java.util.logging.Level.WARNING, ft);
+        }
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return this.logger.isLoggable(java.util.logging.Level.SEVERE);
+    }
+
+    @Override
+    public void error(String format, Object... arguments) {
+        if (logger.isLoggable(java.util.logging.Level.SEVERE)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
+            doLog(java.util.logging.Level.SEVERE, ft);
+        }
+    }
+
+    @Override
+    public void error(String msg, Throwable t) {
+        if (logger.isLoggable(java.util.logging.Level.SEVERE)) {
+            FormattingTuple ft = MessageFormatter.arrayFormat(msg, EMPTY_ARRAY, t);
+            doLog(java.util.logging.Level.SEVERE, ft);
+        }
+    }
+
+    private void doLog(java.util.logging.Level level, FormattingTuple ft) {
+        logger.logrb(level, null, null, (ResourceBundle) null, ft.getMessage(), ft.getThrowable());
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLoggerFactoryAdaptor.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLoggerFactoryAdaptor.java
new file mode 100644
index 0000000..45ae726
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/JdkLoggerFactoryAdaptor.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public class JdkLoggerFactoryAdaptor implements LoggerFactoryAdaptor {
+
+    @Override
+    public Logger getLogger(String name) {
+        return new JdkLogger(java.util.logging.Logger.getLogger(name));
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Logger.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Logger.java
new file mode 100644
index 0000000..a9298ab
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Logger.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public interface Logger {
+
+    String getName();
+
+    boolean isTraceEnabled();
+
+    void trace(String format, Object... arguments);
+
+    void trace(String msg, Throwable t);
+
+    boolean isDebugEnabled();
+
+    void debug(String format, Object... arguments);
+
+    void debug(String msg, Throwable t);
+
+    boolean isInfoEnabled();
+
+    void info(String format, Object... arguments);
+
+    void info(String msg, Throwable t);
+
+    boolean isWarnEnabled();
+
+    void warn(String format, Object... arguments);
+
+    void warn(String msg, Throwable t);
+
+    boolean isErrorEnabled();
+
+    void error(String format, Object... arguments);
+
+    void error(String msg, Throwable t);
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactory.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactory.java
new file mode 100644
index 0000000..0ea6ae7
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public class LoggerFactory {
+
+    private static LoggerFactoryAdaptor adaptor;
+
+    static {
+        try {
+            if (org.slf4j.LoggerFactory.getILoggerFactory() != null) {
+                adaptor = new Slf4jLoggerFactoryAdaptor();
+            }
+        } catch (Throwable ignore) {
+            adaptor = new JdkLoggerFactoryAdaptor();
+        }
+    }
+
+    public static Logger getLogger(Class<?> clazz) {
+        return adaptor.getLogger(clazz);
+    }
+
+    public static Logger getLogger(String name) {
+        return getLogger(name);
+    }
+
+    // for testing
+    public static void setAdaptor(LoggerFactoryAdaptor adaptor) {
+        LoggerFactory.adaptor = adaptor;
+    }
+
+    private LoggerFactory() {
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactoryAdaptor.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactoryAdaptor.java
new file mode 100644
index 0000000..55f5f5f
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/LoggerFactoryAdaptor.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public interface LoggerFactoryAdaptor {
+
+    Logger getLogger(String name);
+
+    default Logger getLogger(Class<?> clazz) {
+        return getLogger(clazz.getName());
+    }
+
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/MessageFormatter.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/MessageFormatter.java
new file mode 100644
index 0000000..8e62c39
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/MessageFormatter.java
@@ -0,0 +1,421 @@
+/*
+ * Licensed 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.
+ *
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package com.github.housepower.jdbc.log;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+// contributors: lizongbo: proposed special treatment of array parameter values
+// Joern Huxhorn: pointed out double[] omission, suggested deep array copy
+
+/**
+ * Formats messages according to very simple substitution rules. Substitutions
+ * can be made 1, 2 or more arguments.
+ *
+ * <p>
+ * For example,
+ *
+ * <pre>
+ * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;)
+ * </pre>
+ *
+ * will return the string "Hi there.".
+ * <p>
+ * The {} pair is called the <em>formatting anchor</em>. It serves to designate
+ * the location where arguments need to be substituted within the message
+ * pattern.
+ * <p>
+ * In case your message contains the '{' or the '}' character, you do not have
+ * to do anything special unless the '}' character immediately follows '{'. For
+ * example,
+ *
+ * <pre>
+ * MessageFormatter.format(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
+ * </pre>
+ *
+ * will return the string "Set {1,2,3} is not equal to 1,2.".
+ *
+ * <p>
+ * If for whatever reason you need to place the string "{}" in the message
+ * without its <em>formatting anchor</em> meaning, then you need to escape the
+ * '{' character with '\', that is the backslash character. Only the '{'
+ * character should be escaped. There is no need to escape the '}' character.
+ * For example,
+ *
+ * <pre>
+ * MessageFormatter.format(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
+ * </pre>
+ *
+ * will return the string "Set {} is not equal to 1,2.".
+ *
+ * <p>
+ * The escaping behavior just described can be overridden by escaping the escape
+ * character '\'. Calling
+ *
+ * <pre>
+ * MessageFormatter.format(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
+ * </pre>
+ *
+ * will return the string "File name is C:\file.zip".
+ *
+ * <p>
+ * The formatting conventions are different than those of {@link MessageFormat}
+ * which ships with the Java platform. This is justified by the fact that
+ * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.
+ * This local performance difference is both measurable and significant in the
+ * larger context of the complete logging processing chain.
+ *
+ * <p>
+ * See also {@link #format(String, Object)},
+ * {@link #format(String, Object, Object)} and
+ * {@link #arrayFormat(String, Object[])} methods for more details.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Joern Huxhorn
+ */
+final public class MessageFormatter {
+    static final char DELIM_START = '{';
+    static final char DELIM_STOP = '}';
+    static final String DELIM_STR = "{}";
+    private static final char ESCAPE_CHAR = '\\';
+
+    /**
+     * Performs single argument substitution for the 'messagePattern' passed as
+     * parameter.
+     * <p>
+     * For example,
+     *
+     * <pre>
+     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);
+     * </pre>
+     *
+     * will return the string "Hi there.".
+     * <p>
+     *
+     * @param messagePattern
+     *          The message pattern which will be parsed and formatted
+     * @param arg
+     *          The argument to be substituted in place of the formatting anchor
+     * @return The formatted message
+     */
+    public static FormattingTuple format(String messagePattern, Object arg) {
+        return arrayFormat(messagePattern, new Object[] { arg });
+    }
+
+    /**
+     *
+     * Performs a two argument substitution for the 'messagePattern' passed as
+     * parameter.
+     * <p>
+     * For example,
+     *
+     * <pre>
+     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
+     * </pre>
+     *
+     * will return the string "Hi Alice. My name is Bob.".
+     *
+     * @param messagePattern
+     *          The message pattern which will be parsed and formatted
+     * @param arg1
+     *          The argument to be substituted in place of the first formatting
+     *          anchor
+     * @param arg2
+     *          The argument to be substituted in place of the second formatting
+     *          anchor
+     * @return The formatted message
+     */
+    public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {
+        return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
+    }
+
+
+    static Throwable getThrowableCandidate(Object[] argArray) {
+        if (argArray == null || argArray.length == 0) {
+            return null;
+        }
+
+        final Object lastEntry = argArray[argArray.length - 1];
+        if (lastEntry instanceof Throwable) {
+            return (Throwable) lastEntry;
+        }
+        return null;
+    }
+
+    public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
+        Throwable throwableCandidate = getThrowableCandidate(argArray);
+        Object[] args = argArray;
+        if (throwableCandidate != null) {
+            args = trimmedCopy(argArray);
+        }
+        return arrayFormat(messagePattern, args, throwableCandidate);
+    }
+
+    private static Object[] trimmedCopy(Object[] argArray) {
+        if (argArray == null || argArray.length == 0) {
+            throw new IllegalStateException("non-sensical empty or null argument array");
+        }
+        final int trimemdLen = argArray.length - 1;
+        Object[] trimmed = new Object[trimemdLen];
+        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
+        return trimmed;
+    }
+
+    public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {
+
+        if (messagePattern == null) {
+            return new FormattingTuple(null, argArray, throwable);
+        }
+
+        if (argArray == null) {
+            return new FormattingTuple(messagePattern);
+        }
+
+        int i = 0;
+        int j;
+        // use string builder for better multicore performance
+        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
+
+        int L;
+        for (L = 0; L < argArray.length; L++) {
+
+            j = messagePattern.indexOf(DELIM_STR, i);
+
+            if (j == -1) {
+                // no more variables
+                if (i == 0) { // this is a simple string
+                    return new FormattingTuple(messagePattern, argArray, throwable);
+                } else { // add the tail string which contains no variables and return
+                    // the result.
+                    sbuf.append(messagePattern, i, messagePattern.length());
+                    return new FormattingTuple(sbuf.toString(), argArray, throwable);
+                }
+            } else {
+                if (isEscapedDelimeter(messagePattern, j)) {
+                    if (!isDoubleEscaped(messagePattern, j)) {
+                        L--; // DELIM_START was escaped, thus should not be incremented
+                        sbuf.append(messagePattern, i, j - 1);
+                        sbuf.append(DELIM_START);
+                        i = j + 1;
+                    } else {
+                        // The escape character preceding the delimiter start is
+                        // itself escaped: "abc x:\\{}"
+                        // we have to consume one backward slash
+                        sbuf.append(messagePattern, i, j - 1);
+                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());
+                        i = j + 2;
+                    }
+                } else {
+                    // normal case
+                    sbuf.append(messagePattern, i, j);
+                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());
+                    i = j + 2;
+                }
+            }
+        }
+        // append the characters following the last {} pair.
+        sbuf.append(messagePattern, i, messagePattern.length());
+        return new FormattingTuple(sbuf.toString(), argArray, throwable);
+    }
+
+    static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
+
+        if (delimeterStartIndex == 0) {
+            return false;
+        }
+        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
+        return potentialEscape == ESCAPE_CHAR;
+    }
+
+    static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
+        return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
+    }
+
+    // special treatment of array values was suggested by 'lizongbo'
+    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
+        if (o == null) {
+            sbuf.append("null");
+            return;
+        }
+        if (!o.getClass().isArray()) {
+            safeObjectAppend(sbuf, o);
+        } else {
+            // check for primitive array types because they
+            // unfortunately cannot be cast to Object[]
+            if (o instanceof boolean[]) {
+                booleanArrayAppend(sbuf, (boolean[]) o);
+            } else if (o instanceof byte[]) {
+                byteArrayAppend(sbuf, (byte[]) o);
+            } else if (o instanceof char[]) {
+                charArrayAppend(sbuf, (char[]) o);
+            } else if (o instanceof short[]) {
+                shortArrayAppend(sbuf, (short[]) o);
+            } else if (o instanceof int[]) {
+                intArrayAppend(sbuf, (int[]) o);
+            } else if (o instanceof long[]) {
+                longArrayAppend(sbuf, (long[]) o);
+            } else if (o instanceof float[]) {
+                floatArrayAppend(sbuf, (float[]) o);
+            } else if (o instanceof double[]) {
+                doubleArrayAppend(sbuf, (double[]) o);
+            } else {
+                objectArrayAppend(sbuf, (Object[]) o, seenMap);
+            }
+        }
+    }
+
+    private static void safeObjectAppend(StringBuilder sbuf, Object o) {
+        try {
+            String oAsString = o.toString();
+            sbuf.append(oAsString);
+        } catch (Throwable t) {
+            Util.report("LOG: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t);
+            sbuf.append("[FAILED toString()]");
+        }
+
+    }
+
+    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
+        sbuf.append('[');
+        if (!seenMap.containsKey(a)) {
+            seenMap.put(a, null);
+            final int len = a.length;
+            for (int i = 0; i < len; i++) {
+                deeplyAppendParameter(sbuf, a[i], seenMap);
+                if (i != len - 1)
+                    sbuf.append(", ");
+            }
+            // allow repeats in siblings
+            seenMap.remove(a);
+        } else {
+            sbuf.append("...");
+        }
+        sbuf.append(']');
+    }
+
+    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void charArrayAppend(StringBuilder sbuf, char[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void intArrayAppend(StringBuilder sbuf, int[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void longArrayAppend(StringBuilder sbuf, long[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
+        sbuf.append('[');
+        final int len = a.length;
+        for (int i = 0; i < len; i++) {
+            sbuf.append(a[i]);
+            if (i != len - 1)
+                sbuf.append(", ");
+        }
+        sbuf.append(']');
+    }
+
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLogger.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLogger.java
new file mode 100644
index 0000000..bb40da3
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLogger.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public class Slf4jLogger implements Logger {
+
+    private final org.slf4j.Logger logger;
+
+    public Slf4jLogger(org.slf4j.Logger logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    public String getName() {
+        return this.logger.getName();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return this.logger.isTraceEnabled();
+    }
+
+    @Override
+    public void trace(String format, Object... arguments) {
+        this.logger.trace(format, arguments);
+    }
+
+    @Override
+    public void trace(String msg, Throwable t) {
+        this.logger.trace(msg, t);
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return this.logger.isDebugEnabled();
+    }
+
+    @Override
+    public void debug(String format, Object... arguments) {
+        this.logger.debug(format, arguments);
+    }
+
+    @Override
+    public void debug(String msg, Throwable t) {
+        logger.debug(msg, t);
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return this.logger.isInfoEnabled();
+    }
+
+    @Override
+    public void info(String format, Object... arguments) {
+        this.logger.info(format, arguments);
+    }
+
+    @Override
+    public void info(String msg, Throwable t) {
+        this.logger.info(msg, t);
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return this.logger.isWarnEnabled();
+    }
+
+    @Override
+    public void warn(String format, Object... arguments) {
+        this.logger.warn(format, arguments);
+    }
+
+    @Override
+    public void warn(String msg, Throwable t) {
+        this.logger.warn(msg, t);
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return this.logger.isErrorEnabled();
+    }
+
+    @Override
+    public void error(String format, Object... arguments) {
+        this.logger.error(format, arguments);
+    }
+
+    @Override
+    public void error(String msg, Throwable t) {
+        this.logger.error(msg, t);
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLoggerFactoryAdaptor.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLoggerFactoryAdaptor.java
new file mode 100644
index 0000000..2b1983c
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Slf4jLoggerFactoryAdaptor.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+public class Slf4jLoggerFactoryAdaptor implements LoggerFactoryAdaptor {
+
+    @Override
+    public Logger getLogger(String name) {
+        return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name));
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Util.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Util.java
new file mode 100644
index 0000000..18526ee
--- /dev/null
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/log/Util.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed 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.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package com.github.housepower.jdbc.log;
+
+/**
+ * An internal utility class.
+ *
+ * @author Alexander Dorokhine
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class Util {
+
+    private Util() {
+    }
+
+    public static String safeGetSystemProperty(String key) {
+        if (key == null)
+            throw new IllegalArgumentException("null input");
+
+        String result = null;
+        try {
+            result = System.getProperty(key);
+        } catch (SecurityException sm) {
+            // ignore
+        }
+        return result;
+    }
+
+    public static boolean safeGetBooleanSystemProperty(String key) {
+        String value = safeGetSystemProperty(key);
+        if (value == null)
+            return false;
+        else
+            return value.equalsIgnoreCase("true");
+    }
+
+    /**
+     * In order to call {@link SecurityManager#getClassContext()}, which is a
+     * protected method, we add this wrapper which allows the method to be visible
+     * inside this package.
+     */
+    private static final class ClassContextSecurityManager extends SecurityManager {
+        protected Class<?>[] getClassContext() {
+            return super.getClassContext();
+        }
+    }
+
+    private static ClassContextSecurityManager SECURITY_MANAGER;
+    private static boolean SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED = false;
+
+    private static ClassContextSecurityManager getSecurityManager() {
+        if (SECURITY_MANAGER != null)
+            return SECURITY_MANAGER;
+        else if (SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED)
+            return null;
+        else {
+            SECURITY_MANAGER = safeCreateSecurityManager();
+            SECURITY_MANAGER_CREATION_ALREADY_ATTEMPTED = true;
+            return SECURITY_MANAGER;
+        }
+    }
+
+    private static ClassContextSecurityManager safeCreateSecurityManager() {
+        try {
+            return new ClassContextSecurityManager();
+        } catch (SecurityException sm) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the name of the class which called the invoking method.
+     *
+     * @return the name of the class which called the invoking method.
+     */
+    public static Class<?> getCallingClass() {
+        ClassContextSecurityManager securityManager = getSecurityManager();
+        if (securityManager == null)
+            return null;
+        Class<?>[] trace = securityManager.getClassContext();
+        String thisClassName = Util.class.getName();
+
+        // Advance until Util is found
+        int i;
+        for (i = 0; i < trace.length; i++) {
+            if (thisClassName.equals(trace[i].getName()))
+                break;
+        }
+
+        // trace[i] = Util; trace[i+1] = caller; trace[i+2] = caller's caller
+        if (i >= trace.length || i + 2 >= trace.length) {
+            throw new IllegalStateException("Failed to find its caller in the stack; " + "this should not happen");
+        }
+
+        return trace[i + 2];
+    }
+
+    static public void report(String msg, Throwable t) {
+        System.err.println(msg);
+        System.err.println("Reported exception:");
+        t.printStackTrace();
+    }
+
+    static public void report(String msg) {
+        System.err.println("LOG: " + msg);
+    }
+}
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/serde/BinaryDeserializer.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/serde/BinaryDeserializer.java
index cb48ad8..3d8a9b2 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/serde/BinaryDeserializer.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/serde/BinaryDeserializer.java
@@ -16,20 +16,17 @@ package com.github.housepower.jdbc.serde;
 
 import com.github.housepower.jdbc.buffer.BuffedReader;
 import com.github.housepower.jdbc.buffer.CompressedBuffedReader;
-import com.github.housepower.jdbc.buffer.SocketBuffedReader;
 import com.github.housepower.jdbc.misc.Container;
 
 import java.io.IOException;
-import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 
 public class BinaryDeserializer {
 
     private final Container<BuffedReader> container;
 
-    public BinaryDeserializer(Socket socket) throws IOException {
-        SocketBuffedReader socketReader = new SocketBuffedReader(socket);
-        container = new Container<>(socketReader, new CompressedBuffedReader(socketReader));
+    public BinaryDeserializer(BuffedReader buffedReader) {
+        container = new Container<>(buffedReader, new CompressedBuffedReader(buffedReader));
     }
 
     public long readVarInt() throws IOException {
diff --git a/dev/checkstyle.xml b/dev/checkstyle.xml
index 4d61c34..b17b7a5 100644
--- a/dev/checkstyle.xml
+++ b/dev/checkstyle.xml
@@ -81,6 +81,12 @@
             <property name="option" value="nospace"/>
             <property name="allowLineBreaks" value="false"/>
         </module>
+
+        <module name="IllegalImport">
+            <property name="illegalPkgs" value="
+                org.slf4j,
+                java.util.logging" />
+        </module>
     </module>
 
     <module name="SuppressionFilter">
diff --git a/examples/src/main/resources/simplelogger.properties b/examples/src/main/resources/simplelogger.properties
index 4bed641..0b45bd1 100644
--- a/examples/src/main/resources/simplelogger.properties
+++ b/examples/src/main/resources/simplelogger.properties
@@ -3,4 +3,4 @@ org.slf4j.simpleLogger.log.com.github.housepower.jdbc.connect.NativeClient=TRACE
 org.slf4j.simpleLogger.showDateTime=true
 org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
 org.slf4j.simpleLogger.showThreadName=true
-org.slf4j.simpleLogger.showShortLogName=true
\ No newline at end of file
+org.slf4j.simpleLogger.showShortLogName=true


Test patch:
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/IssueReproduceITest.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/IssueReproduceITest.java
index 443742f..976c3f5 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/IssueReproduceITest.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/IssueReproduceITest.java
@@ -14,21 +14,21 @@
 
 package com.github.housepower.jdbc;
 
+import com.github.housepower.jdbc.annotation.Issue;
 import com.google.common.base.Strings;
-
 import org.junit.jupiter.api.Test;
 
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.Statement;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-/**
- *
- */
 public class IssueReproduceITest extends AbstractITest {
+
     @Test
+    @Issue("63")
     public void testIssue63() throws Exception {
         withNewConnection(connection -> {
             int columnNum = 36;
@@ -60,5 +60,4 @@ public class IssueReproduceITest extends AbstractITest {
             statement.executeQuery("DROP TABLE IF EXISTS test");
         });
     }
-
 }
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
index 7cdea3c..2ded112 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
@@ -91,4 +91,4 @@ public class AbstractIBenchmark {
         NATIVE, HTTP
     }
 
-}
\ No newline at end of file
+}
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/Profiler.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/Profiler.java
index 423dce9..0e99616 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/Profiler.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/benchmark/Profiler.java
@@ -98,4 +98,4 @@ public class Profiler {
                 + usedMemoryStr + ", time elapsed: " + totalElapsedMillisStr
                 + "(+" + millisPassedFromLastLogStr + " ms)");
     }
-}
\ No newline at end of file
+}
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/JdkLoggerTest.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/JdkLoggerTest.java
new file mode 100644
index 0000000..86c1dd5
--- /dev/null
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/JdkLoggerTest.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 com.github.housepower.jdbc.log;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class JdkLoggerTest {
+
+    @Test
+    @Disabled("suppress error log")
+    public void testLogWithThrowable() {
+        LoggerFactoryAdaptor adaptor = new JdkLoggerFactoryAdaptor();
+        Logger log = adaptor.getLogger("test");
+        log.warn("error message, {}", "1", new RuntimeException("abc"));
+    }
+}
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/MessageFormatterTest.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/MessageFormatterTest.java
new file mode 100755
index 0000000..0b1d214
--- /dev/null
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/log/MessageFormatterTest.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed 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.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (c) 2004-2011 QOS.ch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free  of charge, to any person obtaining
+ * a  copy  of this  software  and  associated  documentation files  (the
+ * "Software"), to  deal in  the Software without  restriction, including
+ * without limitation  the rights to  use, copy, modify,  merge, publish,
+ * distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ * permit persons to whom the Software  is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The  above  copyright  notice  and  this permission  notice  shall  be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+package com.github.housepower.jdbc.log;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Ceki Gulcu
+ */
+public class MessageFormatterTest {
+
+    Integer i1 = 1;
+    Integer i2 = 2;
+    Integer i3 = 3;
+    Integer[] ia0 = new Integer[]{i1, i2, i3};
+    Integer[] ia1 = new Integer[]{10, 20, 30};
+
+    String result;
+
+    @Test
+    public void testNull() {
+        result = MessageFormatter.format(null, i1).getMessage();
+        assertNull(result);
+    }
+
+    @Test
+    public void nullParametersShouldBeHandledWithoutBarfing() {
+        result = MessageFormatter.format("Value is {}.", null).getMessage();
+        assertEquals("Value is null.", result);
+
+        result = MessageFormatter.format("Val1 is {}, val2 is {}.", null, null).getMessage();
+        assertEquals("Val1 is null, val2 is null.", result);
+
+        result = MessageFormatter.format("Val1 is {}, val2 is {}.", i1, null).getMessage();
+        assertEquals("Val1 is 1, val2 is null.", result);
+
+        result = MessageFormatter.format("Val1 is {}, val2 is {}.", null, i2).getMessage();
+        assertEquals("Val1 is null, val2 is 2.", result);
+
+        result = MessageFormatter.arrayFormat("Val1 is {}, val2 is {}, val3 is {}", new Integer[]{null, null, null}).getMessage();
+        assertEquals("Val1 is null, val2 is null, val3 is null", result);
+
+        result = MessageFormatter.arrayFormat("Val1 is {}, val2 is {}, val3 is {}", new Integer[]{null, i2, i3}).getMessage();
+        assertEquals("Val1 is null, val2 is 2, val3 is 3", result);
+
+        result = MessageFormatter.arrayFormat("Val1 is {}, val2 is {}, val3 is {}", new Integer[]{null, null, i3}).getMessage();
+        assertEquals("Val1 is null, val2 is null, val3 is 3", result);
+    }
+
+    @Test
+    public void verifyOneParameterIsHandledCorrectly() {
+        result = MessageFormatter.format("Value is {}.", i3).getMessage();
+        assertEquals("Value is 3.", result);
+
+        result = MessageFormatter.format("Value is {", i3).getMessage();
+        assertEquals("Value is {", result);
+
+        result = MessageFormatter.format("{} is larger than 2.", i3).getMessage();
+        assertEquals("3 is larger than 2.", result);
+
+        result = MessageFormatter.format("No subst", i3).getMessage();
+        assertEquals("No subst", result);
+
+        result = MessageFormatter.format("Incorrect {subst", i3).getMessage();
+        assertEquals("Incorrect {subst", result);
+
+        result = MessageFormatter.format("Value is {bla} {}", i3).getMessage();
+        assertEquals("Value is {bla} 3", result);
+
+        result = MessageFormatter.format("Escaped \\{} subst", i3).getMessage();
+        assertEquals("Escaped {} subst", result);
+
+        result = MessageFormatter.format("{Escaped", i3).getMessage();
+        assertEquals("{Escaped", result);
+
+        result = MessageFormatter.format("\\{}Escaped", i3).getMessage();
+        assertEquals("{}Escaped", result);
+
+        result = MessageFormatter.format("File name is {{}}.", "App folder.zip").getMessage();
+        assertEquals("File name is {App folder.zip}.", result);
+
+        // escaping the escape character
+        result = MessageFormatter.format("File name is C:\\\\{}.", "App folder.zip").getMessage();
+        assertEquals("File name is C:\\App folder.zip.", result);
+    }
+
+    @Test
+    public void testTwoParameters() {
+        result = MessageFormatter.format("Value {} is smaller than {}.", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than 2.", result);
+
+        result = MessageFormatter.format("Value {} is smaller than {}", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than 2", result);
+
+        result = MessageFormatter.format("{}{}", i1, i2).getMessage();
+        assertEquals("12", result);
+
+        result = MessageFormatter.format("Val1={}, Val2={", i1, i2).getMessage();
+        assertEquals("Val1=1, Val2={", result);
+
+        result = MessageFormatter.format("Value {} is smaller than \\{}", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than {}", result);
+
+        result = MessageFormatter.format("Value {} is smaller than \\{} tail", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than {} tail", result);
+
+        result = MessageFormatter.format("Value {} is smaller than \\{", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than \\{", result);
+
+        result = MessageFormatter.format("Value {} is smaller than {tail", i1, i2).getMessage();
+        assertEquals("Value 1 is smaller than {tail", result);
+
+        result = MessageFormatter.format("Value \\{} is smaller than {}", i1, i2).getMessage();
+        assertEquals("Value {} is smaller than 1", result);
+    }
+
+    @Test
+    @Disabled("suppress log")
+    public void testExceptionIn_toString() {
+        Object o = new Object() {
+            public String toString() {
+                throw new IllegalStateException("a");
+            }
+        };
+        result = MessageFormatter.format("Troublesome object {}", o).getMessage();
+        assertEquals("Troublesome object [FAILED toString()]", result);
+    }
+
+    @Test
+    public void testNullArray() {
+        String msg0 = "msg0";
+        String msg1 = "msg1 {}";
+        String msg2 = "msg2 {} {}";
+        String msg3 = "msg3 {} {} {}";
+
+        Object[] args = null;
+
+        result = MessageFormatter.arrayFormat(msg0, args).getMessage();
+        assertEquals(msg0, result);
+
+        result = MessageFormatter.arrayFormat(msg1, args).getMessage();
+        assertEquals(msg1, result);
+
+        result = MessageFormatter.arrayFormat(msg2, args).getMessage();
+        assertEquals(msg2, result);
+
+        result = MessageFormatter.arrayFormat(msg3, args).getMessage();
+        assertEquals(msg3, result);
+    }
+
+    // tests the case when the parameters are supplied in a single array
+    @Test
+    public void testArrayFormat() {
+        result = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia0).getMessage();
+        assertEquals("Value 1 is smaller than 2 and 3.", result);
+
+        result = MessageFormatter.arrayFormat("{}{}{}", ia0).getMessage();
+        assertEquals("123", result);
+
+        result = MessageFormatter.arrayFormat("Value {} is smaller than {}.", ia0).getMessage();
+        assertEquals("Value 1 is smaller than 2.", result);
+
+        result = MessageFormatter.arrayFormat("Value {} is smaller than {}", ia0).getMessage();
+        assertEquals("Value 1 is smaller than 2", result);
+
+        result = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia0).getMessage();
+        assertEquals("Val=1, {, Val=2", result);
+
+        result = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia0).getMessage();
+        assertEquals("Val=1, {, Val=2", result);
+
+        result = MessageFormatter.arrayFormat("Val1={}, Val2={", ia0).getMessage();
+        assertEquals("Val1=1, Val2={", result);
+    }
+
+    @Test
+    public void testArrayValues() {
+        Integer p0 = i1;
+        Integer[] p1 = new Integer[]{i2, i3};
+
+        result = MessageFormatter.format("{}{}", p0, p1).getMessage();
+        assertEquals("1[2, 3]", result);
+
+        // Integer[]
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", p1}).getMessage();
+        assertEquals("a[2, 3]", result);
+
+        // byte[]
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", new byte[]{1, 2}}).getMessage();
+        assertEquals("a[1, 2]", result);
+
+        // int[]
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", new int[]{1, 2}}).getMessage();
+        assertEquals("a[1, 2]", result);
+
+        // float[]
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", new float[]{1, 2}}).getMessage();
+        assertEquals("a[1.0, 2.0]", result);
+
+        // double[]
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", new double[]{1, 2}}).getMessage();
+        assertEquals("a[1.0, 2.0]", result);
+
+    }
+
+    @Test
+    public void testMultiDimensionalArrayValues() {
+        Integer[][] multiIntegerA = new Integer[][]{ia0, ia1};
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", multiIntegerA}).getMessage();
+        assertEquals("a[[1, 2, 3], [10, 20, 30]]", result);
+
+        int[][] multiIntA = new int[][]{{1, 2}, {10, 20}};
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", multiIntA}).getMessage();
+        assertEquals("a[[1, 2], [10, 20]]", result);
+
+        float[][] multiFloatA = new float[][]{{1, 2}, {10, 20}};
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", multiFloatA}).getMessage();
+        assertEquals("a[[1.0, 2.0], [10.0, 20.0]]", result);
+
+        Object[][] multiOA = new Object[][]{ia0, ia1};
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", multiOA}).getMessage();
+        assertEquals("a[[1, 2, 3], [10, 20, 30]]", result);
+
+        Object[][][] _3DOA = new Object[][][]{multiOA, multiOA};
+        result = MessageFormatter.arrayFormat("{}{}", new Object[]{"a", _3DOA}).getMessage();
+        assertEquals("a[[[1, 2, 3], [10, 20, 30]], [[1, 2, 3], [10, 20, 30]]]", result);
+    }
+
+    @Test
+    public void testCyclicArrays() {
+        {
+            Object[] cyclicA = new Object[1];
+            cyclicA[0] = cyclicA;
+            assertEquals("[[...]]", MessageFormatter.arrayFormat("{}", cyclicA).getMessage());
+        }
+        {
+            Object[] a = new Object[2];
+            a[0] = i1;
+            Object[] c = new Object[]{i3, a};
+            Object[] b = new Object[]{i2, c};
+            a[1] = b;
+            assertEquals("1[2, [3, [1, [...]]]]", MessageFormatter.arrayFormat("{}{}", a).getMessage());
+        }
+    }
+
+    @Test
+    public void testArrayThrowable() {
+        FormattingTuple ft;
+        Throwable t = new Throwable();
+        Object[] ia = new Object[]{i1, i2, i3, t};
+        Object[] iaWitness = new Object[]{i1, i2, i3};
+
+        ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia);
+        assertEquals("Value 1 is smaller than 2 and 3.", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("{}{}{}", ia);
+        assertEquals("123", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Value {} is smaller than {}.", ia);
+        assertEquals("Value 1 is smaller than 2.", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Value {} is smaller than {}", ia);
+        assertEquals("Value 1 is smaller than 2", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia);
+        assertEquals("Val=1, {, Val=2", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Val={}, \\{, Val={}", ia);
+        assertEquals("Val=1, \\{, Val=2", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Val1={}, Val2={", ia);
+        assertEquals("Val1=1, Val2={", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia);
+        assertEquals("Value 1 is smaller than 2 and 3.", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("{}{}{}{}", ia);
+        assertEquals("123{}", ft.getMessage());
+        assertTrue(Arrays.equals(iaWitness, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+
+        ft = MessageFormatter.arrayFormat("1={}", new Object[]{i1}, t);
+        assertEquals("1=1", ft.getMessage());
+        assertTrue(Arrays.equals(new Object[]{i1}, ft.getArgArray()));
+        assertEquals(t, ft.getThrowable());
+    }
+}
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/misc/CollectionUtilTest.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/misc/CollectionUtilTest.java
index 6ea222c..0f90f34 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/misc/CollectionUtilTest.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/misc/CollectionUtilTest.java
@@ -68,4 +68,4 @@ class CollectionUtilTest {
         CollectionUtil.mergeMapInPlaceKeepLast(map1Copy2, map2);
         assertEquals(keepLastMap, map1Copy2);
     }
-}
\ No newline at end of file
+}
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/EmbeddedDriver.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/EmbeddedDriver.java
index 0ab2077..a54861c 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/EmbeddedDriver.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/EmbeddedDriver.java
@@ -18,7 +18,6 @@ import org.mockito.Mockito;
 
 import java.sql.*;
 import java.util.Properties;
-import java.util.logging.Logger;
 
 public class EmbeddedDriver implements Driver {
 
@@ -56,7 +55,7 @@ public class EmbeddedDriver implements Driver {
     }
 
     @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
         return null;
     }
 }
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/TestHarness.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/TestHarness.java
index d338a5b..d21e513 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/TestHarness.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/tool/TestHarness.java
@@ -16,8 +16,8 @@ package com.github.housepower.jdbc.tool;
 
 import com.github.housepower.jdbc.AbstractITest;
 import com.google.common.base.Joiner;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.github.housepower.jdbc.log.Logger;
+import com.github.housepower.jdbc.log.LoggerFactory;
 
 import java.sql.*;
 import java.util.ArrayList;
diff --git a/examples/src/test/java/examples/DruidITest.java b/examples/src/test/java/examples/DruidITest.java
index 6b87527..0e758f6 100644
--- a/examples/src/test/java/examples/DruidITest.java
+++ b/examples/src/test/java/examples/DruidITest.java
@@ -38,4 +38,4 @@ public class DruidITest extends DataSourceITest {
             runSql(ds);
         }
     }
-}
\ No newline at end of file
+}