You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2020/12/11 08:51:29 UTC

[mina-sshd] 04/15: [SSHD-1109] Provide full slf4j logger capabilities to CliLogger + use it in all CLI classes

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit b844cdf48ed3e21ba7438ae0bc99221037a78e8a
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Thu Dec 10 07:36:49 2020 +0200

    [SSHD-1109] Provide full slf4j logger capabilities to CliLogger + use it in all CLI classes
---
 CHANGES.md                                         |   1 +
 .../main/java/org/apache/sshd/cli/CliLogger.java   | 147 +++++++++++++++-
 .../main/java/org/apache/sshd/cli/CliSupport.java  | 185 ++++-----------------
 .../org/apache/sshd/cli/client/ScpCommandMain.java |  36 ++--
 .../apache/sshd/cli/client/SftpCommandMain.java    |  17 +-
 .../sshd/cli/client/SshClientCliSupport.java       |  27 +--
 .../org/apache/sshd/cli/client/SshClientMain.java  |  31 ++--
 .../sshd/cli/server/SshServerCliSupport.java       |  23 ++-
 .../org/apache/sshd/cli/server/SshServerMain.java  |  16 +-
 .../helper/ScpCommandTransferEventListener.java    |  37 +++--
 .../server/helper/ServerEventListenerHelper.java   |  43 +----
 .../helper/ServerPortForwardingEventListener.java  |  37 +++--
 .../helper/SftpServerSubSystemEventListener.java   |  33 ++--
 .../apache/sshd/cli/client/ChannelExecMain.java    |   4 +-
 .../org/apache/sshd/cli/server/SshFsMounter.java   |   3 +-
 .../sshd/common/util/io/NullPrintStream.java       | 172 +++++++++++++++++++
 .../common/util/logging/AbstractLoggingBean.java   |  12 +-
 .../sshd/common/util/logging/LoggerSkeleton.java   | 169 +++++++++++++++++++
 .../sshd/common/util/logging/LoggingUtils.java     |  19 +++
 .../util/logging/SimplifiedLoggerSkeleton.java     | 103 ++++++++++++
 sshd-mina/pom.xml                                  |   1 +
 21 files changed, 818 insertions(+), 298 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 054fcf4..10cf108 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -18,6 +18,7 @@
 
 * [SSHD-1085](https://issues.apache.org/jira/browse/SSHD-1085) Added `CliLogger` + more verbosity on `SshClientMain`
 * [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Route tests JUL logging via SLF4JBridgeHandler
+* [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Provide full slf4j logger capabilities to CliLogger + use it in all CLI classes
 
 ## Behavioral changes and enhancements
 
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
index 904bde8..04c2019 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
@@ -20,24 +20,38 @@
 package org.apache.sshd.cli;
 
 import java.io.PrintStream;
+import java.io.PrintWriter;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Objects;
 import java.util.logging.Level;
 
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.ConfigFileReaderSupport;
+import org.apache.sshd.common.config.LogLevelValue;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NullPrintStream;
 import org.apache.sshd.common.util.logging.SimplifiedLog;
+import org.apache.sshd.common.util.logging.SimplifiedLoggerSkeleton;
+import org.slf4j.Logger;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class CliLogger implements SimplifiedLog {
+public class CliLogger extends SimplifiedLoggerSkeleton {
     public static final DateFormat LOG_TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
 
+    private static final long serialVersionUID = -3785762030194772776L;
+    private static final NullPrintStream NULL_PRINT_STREAM = new NullPrintStream();
+
     protected final Level threshold;
     protected final PrintStream logStream;
 
-    public CliLogger(Level threshold, PrintStream logStream) {
+    protected CliLogger(String name, Level threshold, PrintStream logStream) {
+        super(name);
+
         this.threshold = threshold;
         this.logStream = logStream;
     }
@@ -49,10 +63,16 @@ public class CliLogger implements SimplifiedLog {
 
     @Override
     public void log(Level level, Object msg, Throwable err) {
-        if (!isEnabledLevel(level)) {
-            return;
+        if (isEnabledLevel(level)) {
+            log(logStream, level, msg, err);
         }
+    }
 
+    public static void log(PrintStream logStream, Level level, Object msg) {
+        log(logStream, level, msg, null);
+    }
+
+    public static void log(PrintStream logStream, Level level, Object msg, Throwable err) {
         Date now = new Date();
         String time;
         synchronized (LOG_TIME_FORMATTER) {
@@ -63,8 +83,123 @@ public class CliLogger implements SimplifiedLog {
                 .append(' ').append(Thread.currentThread().getName())
                 .append(' ').append(Objects.toString(msg))
                 .println();
-        if (err != null) {
-            err.printStackTrace(logStream);
+        printStackTrace(logStream, err);
+    }
+
+    /**
+     * Looks for the {@link ConfigFileReaderSupport#LOG_LEVEL_CONFIG_PROP} in the options. If found, then uses it as the
+     * result. Otherwise, invokes {@link #resolveLoggingVerbosity(String...)}
+     *
+     * @param  resolver The {@code -o} options specified by the user
+     * @param  args     The command line arguments
+     * @return          The resolved verbosity level
+     */
+    public static Level resolveLoggingVerbosity(PropertyResolver resolver, String... args) {
+        String levelValue = PropertyResolverUtils.getString(
+                resolver, ConfigFileReaderSupport.LOG_LEVEL_CONFIG_PROP);
+        if (GenericUtils.isEmpty(levelValue)) {
+            return resolveLoggingVerbosity(args);
         }
+
+        LogLevelValue level = LogLevelValue.fromName(levelValue);
+        if (level == null) {
+            throw new IllegalArgumentException(
+                    "Unknown " + ConfigFileReaderSupport.LOG_LEVEL_CONFIG_PROP + " option value: " + levelValue);
+        }
+
+        return level.getLoggingLevel();
+    }
+
+    public static Level resolveLoggingVerbosity(String... args) {
+        return resolveLoggingVerbosity(args, GenericUtils.length(args));
+    }
+
+    public static Level resolveLoggingVerbosity(String[] args, int maxIndex) {
+        for (int index = 0; index < maxIndex; index++) {
+            String argName = args[index];
+            if ("-v".equals(argName)) {
+                return Level.INFO;
+            } else if ("-vv".equals(argName)) {
+                return Level.FINE;
+            } else if ("-vvv".equals(argName)) {
+                return Level.FINEST;
+            }
+        }
+
+        return Level.CONFIG;
+    }
+
+    public static Logger resolveSystemLogger(Class<?> clazz, Level threshold) {
+        return resolveSystemLogger(clazz.getName(), threshold);
+    }
+
+    public static Logger resolveSystemLogger(String name, Level threshold) {
+        return resolveLogger(name, threshold, System.out, System.err);
+    }
+
+    public static Logger resolveLogger(Class<?> clazz, Level threshold, PrintStream stdout, PrintStream stderr) {
+        return resolveLogger(clazz.getSimpleName(), threshold, stdout, stderr);
+    }
+
+    public static Logger resolveLogger(String name, Level threshold, PrintStream stdout, PrintStream stderr) {
+        PrintStream logStream = resolvePrintStream(threshold, stdout, stderr);
+        return getLogger(name, threshold, logStream);
+    }
+
+    public static boolean showError(PrintStream stderr, String message) {
+        stderr.append("ERROR: ").println(message);
+        return true;
+    }
+
+    public static boolean isEnabledVerbosityLogging(Level level) {
+        if ((level == null) || Level.OFF.equals(level) || Level.CONFIG.equals(level)
+                || Level.SEVERE.equals(level) || Level.WARNING.equals(level)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static PrintStream resolvePrintStream(Level threshold, PrintStream stdout, PrintStream stderr) {
+        if (isEnabledVerbosityLogging(threshold)) {
+            return Level.INFO.equals(threshold) ? stderr : stdout;
+        } else {
+            return NULL_PRINT_STREAM;
+        }
+    }
+
+    public static <T extends Throwable> T printStackTrace(Appendable out, T reason) {
+        if ((reason == null) || (out == null)) {
+            return reason;
+        }
+
+        if (out instanceof PrintStream) {
+            reason.printStackTrace((PrintStream) out);
+        } else if (out instanceof PrintWriter) {
+            reason.printStackTrace((PrintWriter) out);
+        }
+
+        return reason;
+    }
+
+    public static Logger getSystemLogger(Class<?> clazz, Level threshold) {
+        return getSystemLogger(clazz.getName(), threshold);
+    }
+
+    public static Logger getSystemLogger(String name, Level threshold) {
+        return getLogger(name, threshold, resolveSystemPrintStream(threshold));
+    }
+
+    public static PrintStream resolveSystemPrintStream(Level threshold) {
+        return resolvePrintStream(threshold, System.out, System.err);
+    }
+
+    public static Logger getLogger(Class<?> clazz, Level threshold, PrintStream logStream) {
+        return getLogger(clazz.getSimpleName(), threshold, logStream);
+    }
+
+    public static Logger getLogger(String name, Level threshold, PrintStream logStream) {
+        return ((threshold == null) || Level.OFF.equals(threshold))
+                ? SimplifiedLoggerSkeleton.EMPTY : new CliLogger(name, threshold, logStream);
     }
 }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
index aafce0c..f7306e7 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
@@ -20,14 +20,12 @@ package org.apache.sshd.cli;
 
 import java.io.IOException;
 import java.io.PrintStream;
-import java.io.PrintWriter;
 import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.logging.Level;
 
 import org.apache.sshd.common.AttributeRepository;
@@ -44,7 +42,6 @@ import org.apache.sshd.common.compression.BuiltinCompressions;
 import org.apache.sshd.common.compression.Compression;
 import org.apache.sshd.common.config.CompressionConfigValue;
 import org.apache.sshd.common.config.ConfigFileReaderSupport;
-import org.apache.sshd.common.config.LogLevelValue;
 import org.apache.sshd.common.helpers.AbstractFactoryManager;
 import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
 import org.apache.sshd.common.io.IoAcceptor;
@@ -58,6 +55,7 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.util.GenericUtils;
+import org.slf4j.Logger;
 
 /**
  * Provides common utilities for SSH client/server execution from the CLI
@@ -71,20 +69,6 @@ public abstract class CliSupport {
         super();
     }
 
-    public static boolean showError(PrintStream stderr, String message) {
-        stderr.append("ERROR: ").println(message);
-        return true;
-    }
-
-    public static boolean isEnabledVerbosityLogging(Level level) {
-        if ((level == null) || Level.OFF.equals(level) || Level.CONFIG.equals(level)
-                || Level.SEVERE.equals(level) || Level.WARNING.equals(level)) {
-            return false;
-        }
-
-        return true;
-    }
-
     public static <
             S extends SessionContext,
             M extends UserAuthInstance<S>, F extends UserAuthMethodFactory<S, M>,
@@ -160,39 +144,22 @@ public abstract class CliSupport {
 
         manager.setIoServiceFactoryFactory(factory.create());
 
-        if (!isEnabledVerbosityLogging(level)) {
-            return manager;
+        Logger logger = CliLogger.resolveLogger(CliSupport.class, level, stdout, stderr);
+        if (logger.isInfoEnabled()) {
+            manager.setIoServiceEventListener(createLoggingIoServiceEventListener(logger));
+            manager.addSessionListener(createLoggingSessionListener(logger));
         }
-
-        PrintStream out = Level.INFO.equals(level) ? stderr : stdout;
-        manager.setIoServiceEventListener(createLoggingIoServiceEventListener(out));
-        manager.addSessionListener(createLoggingSessionListener(out));
         return manager;
     }
 
-    public static void printStackTrace(Appendable out, Throwable reason) {
-        if ((reason == null) || (out == null)) {
-            return;
-        }
-
-        if (out instanceof PrintStream) {
-            reason.printStackTrace((PrintStream) out);
-        } else if (out instanceof PrintWriter) {
-            reason.printStackTrace((PrintWriter) out);
-        }
-    }
-
     @SuppressWarnings("checkstyle:anoninnerlength")
-    public static IoServiceEventListener createLoggingIoServiceEventListener(Appendable out) {
+    public static IoServiceEventListener createLoggingIoServiceEventListener(Logger logger) {
         return new IoServiceEventListener() {
             @Override
             public void connectionEstablished(
                     IoConnector connector, SocketAddress local, AttributeRepository context, SocketAddress remote)
                     throws IOException {
-                out.append("Connection established via ").append(Objects.toString(connector))
-                        .append("- local=").append(Objects.toString(local))
-                        .append(", remote=").append(Objects.toString(remote))
-                        .append(System.lineSeparator());
+                logger.info("Connection established via {} - local={}, remote={}", connector, local, remote);
             }
 
             @Override
@@ -200,13 +167,11 @@ public abstract class CliSupport {
                     IoConnector connector, SocketAddress local, AttributeRepository context,
                     SocketAddress remote, Throwable reason)
                     throws IOException {
-                out.append("Abort established connection ").append(Objects.toString(connector))
-                        .append(" - local=").append(Objects.toString(local))
-                        .append(", remote=").append(Objects.toString(remote))
-                        .append(": (").append(reason.getClass().getSimpleName()).append(')')
-                        .append(' ').append(reason.getMessage())
-                        .append(System.lineSeparator());
-                printStackTrace(out, reason);
+                logger.info("Abort established connection {}  - local={}, remote={}", connector, local, remote);
+                if (reason != null) {
+                    logger.warn("     {}: {}", reason.getClass().getSimpleName(), reason.getMessage());
+                    logger.error(reason.getClass().getSimpleName(), reason);
+                }
             }
 
             @Override
@@ -214,11 +179,7 @@ public abstract class CliSupport {
                     IoAcceptor acceptor, SocketAddress local,
                     SocketAddress remote, SocketAddress service)
                     throws IOException {
-                out.append("Connection accepted via ").append(Objects.toString(acceptor))
-                        .append(" - local=").append(Objects.toString(local))
-                        .append(", remote=").append(Objects.toString(remote))
-                        .append(", service=").append(Objects.toString(service))
-                        .append(System.lineSeparator());
+                logger.info("Connection accepted via {} - local={}, remote={}, service={}", acceptor, local, remote, service);
             }
 
             @Override
@@ -226,35 +187,26 @@ public abstract class CliSupport {
                     IoAcceptor acceptor, SocketAddress local, SocketAddress remote,
                     SocketAddress service, Throwable reason)
                     throws IOException {
-                out.append("Abort accepted connection ").append(Objects.toString(acceptor))
-                        .append(" - local=").append(Objects.toString(local))
-                        .append(", remote=").append(Objects.toString(remote))
-                        .append(", service=").append(Objects.toString(service))
-                        .append(": (").append(reason.getClass().getSimpleName()).append(')')
-                        .append(' ').append(reason.getMessage())
-                        .append(System.lineSeparator());
-                printStackTrace(out, reason);
+                logger.info("Abort accepted connection {} - local={}, remote={}, service={}", acceptor, local, remote, service);
+                if (reason != null) {
+                    logger.warn("     {}: {}", reason.getClass().getSimpleName(), reason.getMessage());
+                    logger.error(reason.getClass().getSimpleName(), reason);
+                }
             }
         };
     }
 
     @SuppressWarnings("checkstyle:anoninnerlength")
-    public static SessionListener createLoggingSessionListener(Appendable out) {
+    public static SessionListener createLoggingSessionListener(Logger logger) {
         return new SessionListener() {
             @Override
             public void sessionPeerIdentificationReceived(
                     Session session, String version, List<String> extraLines) {
-                try {
-                    out.append(Objects.toString(session))
-                            .append(" peer identification=").append(version)
-                            .append(System.lineSeparator());
-                    if (GenericUtils.isNotEmpty(extraLines)) {
-                        for (String l : extraLines) {
-                            out.append("    => ").append(l).append(System.lineSeparator());
-                        }
+                logger.info("{} peer identification={}", session, version);
+                if (GenericUtils.isNotEmpty(extraLines)) {
+                    for (String l : extraLines) {
+                        logger.info("    => {}", l);
                     }
-                } catch (IOException e) {
-                    // ignored
                 }
             }
 
@@ -269,90 +221,25 @@ public abstract class CliSupport {
                     return;
                 }
 
-                try {
-                    out.append(Objects.toString(session))
-                            .append(" KEX negotiation results:")
-                            .append(System.lineSeparator());
-                    for (KexProposalOption opt : KexProposalOption.VALUES) {
-                        String value = negotiatedOptions.get(opt);
-                        out.append("    ").append(opt.getDescription())
-                                .append(": ").append(value)
-                                .append(System.lineSeparator());
-                    }
-                } catch (IOException e) {
-                    // ignored
+                logger.info("{} KEX negotiation results:", session);
+                for (KexProposalOption opt : KexProposalOption.VALUES) {
+                    logger.info("    {}: {}", opt.getDescription(), negotiatedOptions.get(opt));
                 }
             }
 
             @Override
             public void sessionException(Session session, Throwable t) {
-                try {
-                    out.append(Objects.toString(session))
-                            .append(' ').append(t.getClass().getSimpleName())
-                            .append(": ").append(t.getMessage())
-                            .append(System.lineSeparator());
-                    printStackTrace(out, t);
-                } catch (IOException e) {
-                    // ignored
-                }
+                logger.error("{} {}: {}", session, t.getClass().getSimpleName(), t.getMessage());
+                logger.error(t.getClass().getSimpleName(), t);
             }
 
             @Override
             public void sessionClosed(Session session) {
-                try {
-                    out.append(Objects.toString(session))
-                            .append(" closed")
-                            .append(System.lineSeparator());
-                } catch (IOException e) {
-                    // ignored
-                }
+                logger.info("{} closed", session);
             }
         };
     }
 
-    public static Level resolveLoggingVerbosity(String... args) {
-        return resolveLoggingVerbosity(args, GenericUtils.length(args));
-    }
-
-    public static Level resolveLoggingVerbosity(String[] args, int maxIndex) {
-        for (int index = 0; index < maxIndex; index++) {
-            String argName = args[index];
-            if ("-v".equals(argName)) {
-                return Level.INFO;
-            } else if ("-vv".equals(argName)) {
-                return Level.FINE;
-            } else if ("-vvv".equals(argName)) {
-                return Level.FINEST;
-            }
-        }
-
-        return Level.CONFIG;
-    }
-
-    /**
-     * Looks for the {@link ConfigFileReaderSupport#LOG_LEVEL_CONFIG_PROP} in the options. If found, then uses it as the
-     * result. Otherwise, invokes {@link #resolveLoggingVerbosity(String...)}
-     *
-     * @param  resolver The {@code -o} options specified by the user
-     * @param  args     The command line arguments
-     * @return          The resolved verbosity level
-     */
-    public static Level resolveLoggingVerbosity(PropertyResolver resolver, String... args) {
-        String levelValue = PropertyResolverUtils.getString(
-                resolver, ConfigFileReaderSupport.LOG_LEVEL_CONFIG_PROP);
-        if (GenericUtils.isEmpty(levelValue)) {
-            return resolveLoggingVerbosity(args);
-        }
-
-        LogLevelValue level = LogLevelValue.fromName(levelValue);
-        if (level == null) {
-            throw new IllegalArgumentException(
-                    "Unknown " + ConfigFileReaderSupport.LOG_LEVEL_CONFIG_PROP + " option value: " + levelValue);
-        }
-
-        return level.getLoggingLevel();
-    }
-
     public static List<NamedFactory<Compression>> setupCompressions(PropertyResolver options, PrintStream stderr) {
         String argVal = PropertyResolverUtils.getString(
                 options, ConfigFileReaderSupport.COMPRESSION_PROP);
@@ -362,7 +249,7 @@ public abstract class CliSupport {
 
         NamedFactory<Compression> value = CompressionConfigValue.fromName(argVal);
         if (value == null) {
-            showError(stderr, "Unknown compression configuration value: " + argVal);
+            CliLogger.showError(stderr, "Unknown compression configuration value: " + argVal);
             return null;
         }
 
@@ -372,14 +259,14 @@ public abstract class CliSupport {
     public static List<NamedFactory<Compression>> setupCompressions(
             String argName, String argVal, List<NamedFactory<Compression>> current, PrintStream stderr) {
         if (GenericUtils.size(current) > 0) {
-            showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+            CliLogger.showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
             return null;
         }
 
         BuiltinCompressions.ParseResult result = BuiltinCompressions.parseCompressionsList(argVal);
         Collection<? extends NamedFactory<Compression>> available = result.getParsedFactories();
         if (GenericUtils.isEmpty(available)) {
-            showError(stderr, "No known compressions in " + argVal);
+            CliLogger.showError(stderr, "No known compressions in " + argVal);
             return null;
         }
 
@@ -403,14 +290,14 @@ public abstract class CliSupport {
     public static List<NamedFactory<Mac>> setupMacs(
             String argName, String argVal, List<NamedFactory<Mac>> current, PrintStream stderr) {
         if (GenericUtils.size(current) > 0) {
-            showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+            CliLogger.showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
             return null;
         }
 
         BuiltinMacs.ParseResult result = BuiltinMacs.parseMacsList(argVal);
         Collection<? extends NamedFactory<Mac>> available = result.getParsedFactories();
         if (GenericUtils.isEmpty(available)) {
-            showError(stderr, "No known MACs in " + argVal);
+            CliLogger.showError(stderr, "No known MACs in " + argVal);
             return null;
         }
 
@@ -435,14 +322,14 @@ public abstract class CliSupport {
     public static List<NamedFactory<Cipher>> setupCiphers(
             String argName, String argVal, List<NamedFactory<Cipher>> current, PrintStream stderr) {
         if (GenericUtils.size(current) > 0) {
-            showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+            CliLogger.showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
             return null;
         }
 
         BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(argVal);
         Collection<? extends NamedFactory<Cipher>> available = result.getParsedFactories();
         if (GenericUtils.isEmpty(available)) {
-            showError(stderr, "WARNING: No known ciphers in " + argVal);
+            CliLogger.showError(stderr, "WARNING: No known ciphers in " + argVal);
             return null;
         }
 
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
index 3990cd9..8b14d4b 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
@@ -37,7 +37,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.logging.Level;
 
-import org.apache.sshd.cli.CliSupport;
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
 import org.apache.sshd.client.config.hosts.HostConfigEntry;
@@ -58,6 +58,7 @@ import org.apache.sshd.scp.common.helpers.ScpAckInfo;
 import org.apache.sshd.scp.common.helpers.ScpReceiveDirCommandDetails;
 import org.apache.sshd.scp.common.helpers.ScpReceiveFileCommandDetails;
 import org.apache.sshd.scp.common.helpers.ScpTimestampCommandDetails;
+import org.slf4j.Logger;
 
 /**
  * @see    <A HREF="https://man7.org/linux/man-pages/man1/scp.1.html">SCP(1) - manual page</A>
@@ -95,7 +96,7 @@ public class ScpCommandMain extends SshClientCliSupport {
             if (isArgumentedOption(SCP_PORT_OPTION, argName) || "-creator".equals(argName)) {
                 index++;
                 if (index >= numArgs) {
-                    error = showError(stderr, "option requires an argument: " + argName);
+                    error = CliLogger.showError(stderr, "option requires an argument: " + argName);
                     break;
                 }
 
@@ -109,32 +110,32 @@ public class ScpCommandMain extends SshClientCliSupport {
                 threeWay = true;
                 effective.add(argName);
             } else if (argName.charAt(0) == '-') {
-                error = showError(stderr, "Unknown option: " + argName);
+                error = CliLogger.showError(stderr, "Unknown option: " + argName);
                 break;
             } else {
                 index++;
                 if (index >= numArgs) {
-                    error = showError(stderr, "Not enough arguments");
+                    error = CliLogger.showError(stderr, "Not enough arguments");
                     break;
                 }
 
                 ScpLocation source = new ScpLocation(argName);
                 ScpLocation target = new ScpLocation(args[index]);
                 if (index < (numArgs - 1)) {
-                    error = showError(stderr, "Unexpected extra arguments");
+                    error = CliLogger.showError(stderr, "Unexpected extra arguments");
                     break;
                 }
 
                 if (threeWay) {
                     if (source.isLocal() || target.isLocal()) {
-                        error = showError(stderr, "Both targets must be remote for the 3-way copy option");
+                        error = CliLogger.showError(stderr, "Both targets must be remote for the 3-way copy option");
                         break;
                     }
 
                     adjustRemoteTargetArguments(source, source, target, effective);
                 } else {
                     if (source.isLocal() == target.isLocal()) {
-                        error = showError(stderr, "Both targets are either remote or local");
+                        error = CliLogger.showError(stderr, "Both targets are either remote or local");
                         break;
                     }
 
@@ -176,7 +177,7 @@ public class ScpCommandMain extends SshClientCliSupport {
             if ("-creator".equals(argName)) {
                 index++;
                 if (index >= numArgs) {
-                    showError(stderr, "option requires an argument: " + argName);
+                    CliLogger.showError(stderr, "option requires an argument: " + argName);
                     return null;
                 }
 
@@ -260,6 +261,8 @@ public class ScpCommandMain extends SshClientCliSupport {
         try {
             if (!quiet) {
                 creator.setScpTransferEventListener(new ScpTransferEventListener() {
+                    private final Logger log = CliLogger.resolveLogger(ScpCommandMain.class, level, stdout, stderr);
+
                     @Override
                     public void startFolderEvent(
                             Session session, FileOperation op, Path file, Set<PosixFilePermission> perms) {
@@ -297,21 +300,14 @@ public class ScpCommandMain extends SshClientCliSupport {
                     private void logEvent(
                             String name, Session session, FileOperation op, Path file, long length,
                             Collection<PosixFilePermission> perms, Throwable thrown) {
-                        PrintStream ps = (thrown == null) ? stdout : stderr;
-                        ps.append("    ").append(name)
-                                .append('[').append(session.toString()).append(']')
-                                .append('[').append(op.name()).append(']')
-                                .append(' ').append(file.toString());
-                        if (length > 0L) {
-                            ps.append(' ').append("length=").append(Long.toString(length));
+                        if (!log.isInfoEnabled()) {
+                            return;
                         }
-                        ps.append(' ').append(String.valueOf(perms));
 
+                        log.info("{} - [{}][{}] (length={}) {} {}", name, session, op, file, length, perms);
                         if (thrown != null) {
-                            ps.append(" - ").append(thrown.getClass().getSimpleName()).append(": ")
-                                    .append(thrown.getMessage());
+                            log.error("{} -   {}: {}", name, thrown.getClass().getSimpleName(), thrown.getMessage());
                         }
-                        ps.println();
                     }
                 });
             }
@@ -452,7 +448,7 @@ public class ScpCommandMain extends SshClientCliSupport {
             int numArgs = GenericUtils.length(args);
             // see the way normalizeCommandArguments works...
             if (numArgs >= 2) {
-                level = CliSupport.resolveLoggingVerbosity(args, numArgs - 2);
+                level = CliLogger.resolveLoggingVerbosity(args, numArgs - 2);
                 logStream = resolveLoggingTargetStream(stdout, stderr, args, numArgs - 2);
                 if (logStream != null) {
                     setupLogging(level, stdout, stderr, logStream);
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index 42a4d1f..eb560dd 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -42,7 +42,7 @@ import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 
-import org.apache.sshd.cli.CliSupport;
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.cli.client.helper.SftpFileTransferProgressOutputStream;
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.session.ClientSession;
@@ -79,6 +79,7 @@ import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.common.SftpException;
 import org.apache.sshd.sftp.common.extensions.ParserUtils;
 import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser;
+import org.slf4j.Logger;
 
 /**
  * TODO Add javadoc
@@ -317,7 +318,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
         PrintStream stderr = System.err;
         OutputStream logStream = stderr;
         try (BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
-            Level level = CliSupport.resolveLoggingVerbosity(args);
+            Level level = CliLogger.resolveLoggingVerbosity(args);
             logStream = resolveLoggingTargetStream(stdout, stderr, args);
             if (logStream != null) {
                 setupLogging(level, stdout, stderr, logStream);
@@ -336,13 +337,17 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
 
             try {
                 SftpClientFactory clientFactory = resolveSftpClientFactory(session);
-                if ((level != null) && (level.intValue() <= Level.INFO.intValue())) {
-                    stdout.append("Using factory=").println(clientFactory.getClass().getSimpleName());
+                Logger logger = (logStream != null)
+                        ? CliLogger.getLogger(SftpCommandMain.class, level,
+                                (logStream instanceof PrintStream) ? (PrintStream) logStream : new PrintStream(logStream))
+                        : CliLogger.resolveSystemLogger(SftpCommandMain.class, level);
+                if (logger.isInfoEnabled()) {
+                    logger.info("Using factory={}", clientFactory.getClass().getSimpleName());
                 }
 
                 SftpVersionSelector versionSelector = resolveVersionSelector(session);
-                if ((level != null) && (level.intValue() <= Level.INFO.intValue())) {
-                    stdout.append("Using version selector=").println(versionSelector);
+                if (logger.isInfoEnabled()) {
+                    logger.info("Using version selector={}", versionSelector);
                 }
 
                 try (SftpClient sftpClient = clientFactory.createSftpClient(session, versionSelector);
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
index 8947388..9fbd052 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -43,6 +43,7 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.cli.CliSupport;
 import org.apache.sshd.client.ClientAuthenticationManager;
 import org.apache.sshd.client.ClientBuilder;
@@ -135,7 +136,7 @@ public abstract class SshClientCliSupport extends CliSupport {
             if (isArgumentedOption(portOption, argName)) {
                 i++;
                 if (i >= numArgs) {
-                    error = showError(stderr, "option requires an argument: " + argName);
+                    error = CliLogger.showError(stderr, "option requires an argument: " + argName);
                     break;
                 }
 
@@ -144,24 +145,24 @@ public abstract class SshClientCliSupport extends CliSupport {
 
             if (portOption.equals(argName)) {
                 if (port > 0) {
-                    error = showError(stderr, argName + " option value re-specified: " + port);
+                    error = CliLogger.showError(stderr, argName + " option value re-specified: " + port);
                     break;
                 }
 
                 port = Integer.parseInt(argVal);
                 if (port <= 0) {
-                    error = showError(stderr, "Bad option value for " + argName + ": " + port);
+                    error = CliLogger.showError(stderr, "Bad option value for " + argName + ": " + port);
                     break;
                 }
             } else if ("-J".equals(argName)) {
                 if (proxyJump != null) {
-                    error = showError(stderr, argName + " option value re-specified: " + proxyJump);
+                    error = CliLogger.showError(stderr, argName + " option value re-specified: " + proxyJump);
                     break;
                 }
                 proxyJump = argVal;
             } else if ("-w".equals(argName)) {
                 if (GenericUtils.length(password) > 0) {
-                    error = showError(stderr, argName + " option value re-specified: " + password);
+                    error = CliLogger.showError(stderr, argName + " option value re-specified: " + password);
                     break;
                 }
                 password = argVal;
@@ -190,7 +191,7 @@ public abstract class SshClientCliSupport extends CliSupport {
                 String opt = argVal;
                 int idx = opt.indexOf('=');
                 if (idx <= 0) {
-                    error = showError(stderr, "bad syntax for option: " + opt);
+                    error = CliLogger.showError(stderr, "bad syntax for option: " + opt);
                     break;
                 }
 
@@ -204,7 +205,7 @@ public abstract class SshClientCliSupport extends CliSupport {
                 }
             } else if ("-l".equals(argName)) {
                 if (login != null) {
-                    error = showError(stderr, argName + " option value re-specified: " + port);
+                    error = CliLogger.showError(stderr, argName + " option value re-specified: " + port);
                     break;
                 }
 
@@ -221,7 +222,7 @@ public abstract class SshClientCliSupport extends CliSupport {
                         login = host.substring(0, pos);
                         host = host.substring(pos + 1);
                     } else {
-                        error = showError(stderr, "Login already specified using -l option (" + login + "): " + host);
+                        error = CliLogger.showError(stderr, "Login already specified using -l option (" + login + "): " + host);
                         break;
                     }
                 }
@@ -229,7 +230,7 @@ public abstract class SshClientCliSupport extends CliSupport {
         }
 
         if ((!error) && GenericUtils.isEmpty(host)) {
-            error = showError(stderr, "Hostname not specified");
+            error = CliLogger.showError(stderr, "Hostname not specified");
         }
 
         if (error) {
@@ -461,7 +462,7 @@ public abstract class SshClientCliSupport extends CliSupport {
             try {
                 setupSessionIdentities(client, identities, stdin, stdout, stderr);
             } catch (Throwable t) { // show but do not fail the setup - maybe a password can be used
-                showError(stderr, t.getClass().getSimpleName() + " while loading user keys: " + t.getMessage());
+                CliLogger.showError(stderr, t.getClass().getSimpleName() + " while loading user keys: " + t.getMessage());
             }
 
             setupServerKeyVerifier(client, resolver, stdin, stdout, stderr);
@@ -476,7 +477,7 @@ public abstract class SshClientCliSupport extends CliSupport {
             }
             return client;
         } catch (Throwable t) {
-            showError(stderr, "Failed (" + t.getClass().getSimpleName() + ") to setup client: " + t.getMessage());
+            CliLogger.showError(stderr, "Failed (" + t.getClass().getSimpleName() + ") to setup client: " + t.getMessage());
             client.close();
             return null;
         }
@@ -649,7 +650,7 @@ public abstract class SshClientCliSupport extends CliSupport {
             String argName = args[index];
             if ("-E".equals(argName)) {
                 if ((index + 1) >= maxIndex) {
-                    showError(stderr, "Missing " + argName + " option argument");
+                    CliLogger.showError(stderr, "Missing " + argName + " option argument");
                     return null;
                 }
 
@@ -662,7 +663,7 @@ public abstract class SshClientCliSupport extends CliSupport {
                     Path path = Paths.get(argVal).normalize().toAbsolutePath();
                     return Files.newOutputStream(path);
                 } catch (IOException e) {
-                    showError(stderr,
+                    CliLogger.showError(stderr,
                             "Failed (" + e.getClass().getSimpleName() + ") to open " + argVal + ": " + e.getMessage());
                     return null;
                 }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
index 89740ce..20f86fa 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
@@ -32,7 +32,6 @@ import java.util.Map;
 import java.util.logging.Level;
 
 import org.apache.sshd.cli.CliLogger;
-import org.apache.sshd.cli.CliSupport;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.channel.ChannelShell;
 import org.apache.sshd.client.channel.ClientChannel;
@@ -44,6 +43,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.slf4j.Logger;
 
 /**
  * TODO Add javadoc
@@ -74,7 +74,7 @@ public class SshClientMain extends SshClientCliSupport {
             // handled by 'setupClientSession'
             if (GenericUtils.isEmpty(command) && isArgumentedOption("-p", argName)) {
                 if ((i + 1) >= numArgs) {
-                    error = showError(stderr, "option requires an argument: " + argName);
+                    error = CliLogger.showError(stderr, "option requires an argument: " + argName);
                     break;
                 }
 
@@ -89,17 +89,17 @@ public class SshClientMain extends SshClientCliSupport {
 
             if (GenericUtils.isEmpty(command) && "-D".equals(argName)) {
                 if ((i + 1) >= numArgs) {
-                    error = showError(stderr, "option requires an argument: " + argName);
+                    error = CliLogger.showError(stderr, "option requires an argument: " + argName);
                     break;
                 }
                 if (socksPort > 0) {
-                    error = showError(stderr, argName + " option value re-specified: " + socksPort);
+                    error = CliLogger.showError(stderr, argName + " option value re-specified: " + socksPort);
                     break;
                 }
 
                 socksPort = Integer.parseInt(args[++i]);
                 if (socksPort <= 0) {
-                    error = showError(stderr, "Bad option value for " + argName + ": " + socksPort);
+                    error = CliLogger.showError(stderr, "Bad option value for " + argName + ": " + socksPort);
                     break;
                 }
             } else if (GenericUtils.isEmpty(command) && "-A".equals(argName)) {
@@ -107,7 +107,7 @@ public class SshClientMain extends SshClientCliSupport {
             } else if (GenericUtils.isEmpty(command) && "-a".equals(argName)) {
                 agentForward = false;
             } else {
-                level = CliSupport.resolveLoggingVerbosity(args, i);
+                level = CliLogger.resolveLoggingVerbosity(args, i);
                 logStream = resolveLoggingTargetStream(stdout, stderr, args, i);
                 if (logStream == null) {
                     error = true;
@@ -147,7 +147,7 @@ public class SshClientMain extends SshClientCliSupport {
                 return;
             }
 
-            CliLogger logger = new CliLogger(level, System.err);
+            Logger logger = CliLogger.resolveSystemLogger(SshClientMain.class, level);
             boolean verbose = logger.isInfoEnabled();
             try (SshClient client = (SshClient) session.getFactoryManager()) {
                 /*
@@ -160,8 +160,7 @@ public class SshClientMain extends SshClientCliSupport {
                 try {
                     if (socksPort >= 0) {
                         if (verbose) {
-                            logger.info(
-                                    "Start dynamic port forwarding to " + SshdSocketAddress.LOCALHOST_NAME + ":" + socksPort);
+                            logger.info("Start dynamic port forwarding to {}:{}", SshdSocketAddress.LOCALHOST_NAME, socksPort);
                         }
 
                         session.startDynamicPortForwarding(
@@ -183,8 +182,8 @@ public class SshClientMain extends SshClientCliSupport {
                         }
 
                         if (logger.isDebugEnabled()) {
-                            logger.debug("PTY=" + ptyConfig + " for command=" + cmdValue);
-                            logger.debug("ENV=" + env + " for command=" + cmdValue);
+                            logger.debug("PTY={} for command={}", ptyConfig, cmdValue);
+                            logger.debug("ENV={} for command={}", env, cmdValue);
                         }
 
                         try (OutputStream channelOut = new NoCloseOutputStream(System.out);
@@ -194,21 +193,21 @@ public class SshClientMain extends SshClientCliSupport {
 
                             Duration maxWait = CliClientModuleProperties.CHANNEL_OPEN_TIMEOUT.getRequired(channel);
                             if (verbose) {
-                                logger.info("Wait " + maxWait + " for open channel for command=" + cmdValue);
+                                logger.info("Wait {} for open channel for command={}", maxWait, cmdValue);
                             }
                             channel.open().verify(maxWait);
                             if (verbose) {
-                                logger.info("Channel opened for command=" + cmdValue);
+                                logger.info("Channel opened for command={}", cmdValue);
                             }
 
                             Collection<ClientChannelEvent> result = channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
                             if (verbose) {
-                                logger.info("command=" + cmdValue + " - waitFor result=" + result);
+                                logger.info("command={} - waitFor result={}", cmdValue, result);
                                 if (result.contains(ClientChannelEvent.EXIT_SIGNAL)) {
-                                    logger.info("    " + ClientChannelEvent.EXIT_SIGNAL + "=" + channel.getExitSignal());
+                                    logger.info("    {}={}", ClientChannelEvent.EXIT_SIGNAL, channel.getExitSignal());
                                 }
                                 if (result.contains(ClientChannelEvent.EXIT_STATUS)) {
-                                    logger.info("    " + ClientChannelEvent.EXIT_STATUS + "=" + channel.getExitStatus());
+                                    logger.info("    {}={}", ClientChannelEvent.EXIT_STATUS, channel.getExitStatus());
                                 }
                             }
                         } finally {
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
index 6b3340b..0c00435 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
@@ -37,6 +37,7 @@ import java.util.logging.Level;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.cli.CliSupport;
 import org.apache.sshd.cli.server.helper.ScpCommandTransferEventListener;
 import org.apache.sshd.cli.server.helper.ServerPortForwardingEventListener;
@@ -54,6 +55,7 @@ import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.scp.common.ScpTransferEventListener;
 import org.apache.sshd.scp.server.ScpCommandFactory;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
@@ -68,6 +70,7 @@ import org.apache.sshd.server.subsystem.SubsystemFactory;
 import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.server.SftpEventListener;
 import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.slf4j.Logger;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -155,8 +158,9 @@ public abstract class SshServerCliSupport extends CliSupport {
             SshServer server, Level level, PrintStream stdout, PrintStream stderr, PropertyResolver options) {
         ForwardingFilter forwardFilter = SshServerConfigFileReader.resolveServerForwarding(options);
         server.setForwardingFilter(forwardFilter);
-        if (isEnabledVerbosityLogging(level)) {
-            server.addPortForwardingEventListener(new ServerPortForwardingEventListener(stdout, stderr));
+        if (CliLogger.isEnabledVerbosityLogging(level)) {
+            Logger logger = CliLogger.resolveLogger(SshServerCliSupport.class, level, stdout, stderr);
+            server.addPortForwardingEventListener(new ServerPortForwardingEventListener(logger));
         }
         return forwardFilter;
     }
@@ -202,7 +206,8 @@ public abstract class SshServerCliSupport extends CliSupport {
         if (SftpConstants.SFTP_SUBSYSTEM_NAME.equalsIgnoreCase(nameList)) {
             SubsystemFactory factory = registerSubsystemFactoryListeners(
                     server, level, stdout, stderr, options, new SftpSubsystemFactory());
-            stdout.println("Using built-in SFTP subsystem");
+            PrintStream logStream = CliLogger.resolvePrintStream(level, stdout, stderr);
+            CliLogger.log(logStream, level, "Using built-in SFTP subsystem");
             return Collections.singletonList(factory);
         }
 
@@ -232,8 +237,9 @@ public abstract class SshServerCliSupport extends CliSupport {
             F factory)
             throws Exception {
         if (factory instanceof SftpSubsystemFactory) {
-            if (isEnabledVerbosityLogging(level)) {
-                SftpEventListener listener = new SftpServerSubSystemEventListener(stdout, stderr);
+            if (CliLogger.isEnabledVerbosityLogging(level)) {
+                Logger logger = CliLogger.resolveLogger(SftpEventListener.class, level, stdout, stderr);
+                SftpEventListener listener = new SftpServerSubSystemEventListener(logger);
                 ((SftpSubsystemFactory) factory).addSftpEventListener(listener);
             }
 
@@ -309,12 +315,13 @@ public abstract class SshServerCliSupport extends CliSupport {
     }
 
     public static ScpCommandFactory createScpCommandFactory(
-            Level level, Appendable stdout, Appendable stderr, ShellFactory delegateShellFactory) {
+            Level level, PrintStream stdout, PrintStream stderr, ShellFactory delegateShellFactory) {
         ScpCommandFactory.Builder scp = new ScpCommandFactory.Builder()
                 .withDelegate(ProcessShellCommandFactory.INSTANCE)
                 .withDelegateShellFactory(delegateShellFactory);
-        if (isEnabledVerbosityLogging(level)) {
-            scp.addEventListener(new ScpCommandTransferEventListener(stdout, stderr));
+        if (CliLogger.isEnabledVerbosityLogging(level)) {
+            Logger logger = CliLogger.resolveLogger(ScpTransferEventListener.class, level, stdout, stderr);
+            scp.addEventListener(new ScpCommandTransferEventListener(logger));
         }
 
         return scp.build();
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
index 27e48b6..69b417e 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
@@ -19,6 +19,7 @@
 
 package org.apache.sshd.cli.server;
 
+import java.io.PrintStream;
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -29,6 +30,7 @@ import java.util.TreeMap;
 import java.util.logging.Level;
 import java.util.stream.Collectors;
 
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
@@ -46,6 +48,7 @@ import org.apache.sshd.server.config.keys.ServerIdentity;
 import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
 import org.apache.sshd.server.shell.ShellFactory;
 import org.apache.sshd.server.subsystem.SubsystemFactory;
+import org.slf4j.Logger;
 
 /**
  * TODO Add javadoc
@@ -158,7 +161,8 @@ public class SshServerMain extends SshServerCliSupport {
         }
 
         PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
-        Level level = resolveLoggingVerbosity(resolver, args);
+        Level level = CliLogger.resolveLoggingVerbosity(resolver, args);
+        Logger logger = CliLogger.resolveSystemLogger(SshServerMain.class, level);
         SshServer sshd = error
                 ? null
                 : setupIoServiceFactory(
@@ -192,7 +196,9 @@ public class SshServerMain extends SshServerCliSupport {
 
         ShellFactory shellFactory = resolveShellFactory(level, System.out, System.err, resolver);
         if (shellFactory != null) {
-            System.out.append("Using shell=").println(shellFactory.getClass().getName());
+            if (logger.isInfoEnabled()) {
+                logger.info("Using shell={}", shellFactory.getClass().getName());
+            }
             sshd.setShellFactory(shellFactory);
         }
 
@@ -204,7 +210,9 @@ public class SshServerMain extends SshServerCliSupport {
 
         List<SubsystemFactory> subsystems = resolveServerSubsystems(sshd, level, System.out, System.err, resolver);
         if (GenericUtils.isNotEmpty(subsystems)) {
-            System.out.append("Setup subsystems=").println(NamedResource.getNames(subsystems));
+            if (logger.isInfoEnabled()) {
+                logger.info("Setup subsystems={}", NamedResource.getNames(subsystems));
+            }
             sshd.setSubsystemFactories(subsystems);
         }
 
@@ -215,7 +223,7 @@ public class SshServerMain extends SshServerCliSupport {
     }
 
     private static CommandFactory setupCommandFactory(
-            SshServer sshd, Level level, Appendable stdout, Appendable stderr, ShellFactory shellFactory) {
+            SshServer sshd, Level level, PrintStream stdout, PrintStream stderr, ShellFactory shellFactory) {
         ScpCommandFactory scpFactory;
         if (shellFactory instanceof ScpCommandFactory) {
             scpFactory = (ScpCommandFactory) shellFactory;
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ScpCommandTransferEventListener.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ScpCommandTransferEventListener.java
index 85b9261..9dfed05 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ScpCommandTransferEventListener.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ScpCommandTransferEventListener.java
@@ -26,7 +26,9 @@ import java.util.Set;
 
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.scp.common.ScpTransferEventListener;
+import org.apache.sshd.scp.common.helpers.ScpAckInfo;
 import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.slf4j.Logger;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -34,15 +36,17 @@ import org.apache.sshd.scp.server.ScpCommandFactory;
 public class ScpCommandTransferEventListener
         extends ServerEventListenerHelper
         implements ScpTransferEventListener {
-    public ScpCommandTransferEventListener(Appendable stdout, Appendable stderr) {
-        super(ScpCommandFactory.SCP_FACTORY_NAME, stdout, stderr);
+    public ScpCommandTransferEventListener(Logger logger) {
+        super(ScpCommandFactory.SCP_FACTORY_NAME, logger);
     }
 
     @Override
     public void startFileEvent(
             Session session, FileOperation op, Path file, long length, Set<PosixFilePermission> perms)
             throws IOException {
-        outputDebugMessage("startFileEvent(%s)[%s] len=%d, perms=%s: %s", session, op, length, perms, file);
+        if (log.isInfoEnabled()) {
+            log.info("startFileEvent({})[{}] len={}, perms={}: {}", session, op, length, perms, file);
+        }
     }
 
     @Override
@@ -50,17 +54,19 @@ public class ScpCommandTransferEventListener
             Session session, FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown)
             throws IOException {
         if (thrown != null) {
-            outputErrorMessage("endFileEvent(%s)[%s] failed (%s) len=%d, perms=%s [%s]: %s",
+            log.error("endFileEvent({})[{}] failed ({}) len={}, perms={} [{}]: {}",
                     session, op, thrown.getClass().getSimpleName(), length, perms, file, thrown.getMessage());
-        } else {
-            outputDebugMessage("endFileEvent(%s)[%s] len=%d, perms=%s: %s", session, op, length, perms, file);
+        } else if (log.isInfoEnabled()) {
+            log.info("endFileEvent({})[{}] len={}, perms={}: {}", session, op, length, perms, file);
         }
     }
 
     @Override
     public void startFolderEvent(Session session, FileOperation op, Path file, Set<PosixFilePermission> perms)
             throws IOException {
-        outputDebugMessage("startFolderEvent(%s)[%s] perms=%s: %s", session, op, perms, file);
+        if (log.isInfoEnabled()) {
+            log.info("startFolderEvent({})[{}] perms={}: {}", session, op, perms, file);
+        }
     }
 
     @Override
@@ -68,10 +74,21 @@ public class ScpCommandTransferEventListener
             Session session, FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown)
             throws IOException {
         if (thrown != null) {
-            outputErrorMessage("endFolderEvent(%s)[%s] failed (%s) perms=%s [%s]: %s",
+            log.error("endFolderEvent({})[{}] failed ({}) perms={} [{}]: {}",
                     session, op, thrown.getClass().getSimpleName(), perms, file, thrown.getMessage());
-        } else {
-            outputDebugMessage("endFolderEvent(%s)[%s] lperms=%s: %s", session, op, perms, file);
+        } else if (log.isInfoEnabled()) {
+            log.info("endFolderEvent({})[{}] perms={}: {}", session, op, perms, file);
+        }
+    }
+
+    @Override
+    public void handleFileEventAckInfo(
+            Session session, FileOperation op, Path file, long length,
+            Set<PosixFilePermission> perms, ScpAckInfo ackInfo)
+            throws IOException {
+        if (log.isInfoEnabled()) {
+            log.info("handleFileEventAckInfo({})[{}] perms={}, length={}, ACK={}: {}",
+                    session, op, perms, length, ackInfo, file);
         }
     }
 }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerEventListenerHelper.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerEventListenerHelper.java
index f3417ea..b79bccd 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerEventListenerHelper.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerEventListenerHelper.java
@@ -19,52 +19,21 @@
 
 package org.apache.sshd.cli.server.helper;
 
-import java.io.Flushable;
-import java.io.IOException;
-import java.util.Objects;
-
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.slf4j.Logger;
 
-public abstract class ServerEventListenerHelper implements NamedResource {
+public abstract class ServerEventListenerHelper extends AbstractLoggingBean implements NamedResource {
     private final String name;
-    private final Appendable stdout;
-    private final Appendable stderr;
 
-    public ServerEventListenerHelper(String name, Appendable stdout, Appendable stderr) {
+    public ServerEventListenerHelper(String name, Logger logger) {
+        super(logger);
+
         this.name = name;
-        this.stdout = Objects.requireNonNull(stdout, "No output target");
-        this.stderr = Objects.requireNonNull(stderr, "No error target");
     }
 
     @Override
     public String getName() {
         return name;
     }
-
-    public Appendable getStdout() {
-        return stdout;
-    }
-
-    public Appendable getStderr() {
-        return stderr;
-    }
-
-    protected String outputErrorMessage(String format, Object... args) throws IOException {
-        return outputMessage(getStderr(), format, args);
-    }
-
-    protected String outputDebugMessage(String format, Object... args) throws IOException {
-        return outputMessage(getStdout(), format, args);
-    }
-
-    protected String outputMessage(Appendable out, String format, Object... args) throws IOException {
-        String message = String.format(format, args);
-        out.append(getName())
-                .append(": ").append(message)
-                .append(System.lineSeparator());
-        if (out instanceof Flushable) {
-            ((Flushable) out).flush();
-        }
-        return message;
-    }
 }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerPortForwardingEventListener.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerPortForwardingEventListener.java
index c6afab4..cdcd976 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerPortForwardingEventListener.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/ServerPortForwardingEventListener.java
@@ -24,10 +24,11 @@ import java.io.IOException;
 import org.apache.sshd.common.forward.PortForwardingEventListener;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.slf4j.Logger;
 
 public class ServerPortForwardingEventListener extends ServerEventListenerHelper implements PortForwardingEventListener {
-    public ServerPortForwardingEventListener(Appendable stdout, Appendable stderr) {
-        super("PORT-FWD", stdout, stderr);
+    public ServerPortForwardingEventListener(Logger logger) {
+        super("PORT-FWD", logger);
     }
 
     @Override
@@ -36,11 +37,13 @@ public class ServerPortForwardingEventListener extends ServerEventListenerHelper
             boolean localForwarding, SshdSocketAddress boundAddress, Throwable reason)
             throws IOException {
         if (reason == null) {
-            outputDebugMessage("Estalibshed explicit tunnel for session=%s: local=%s, remote=%s, bound=%s, localForward=%s",
-                    session, local, remote, boundAddress, localForwarding);
+            if (log.isInfoEnabled()) {
+                log.info("Estalibshed explicit tunnel for session={}: local={}, remote={}, bound={}, localForward={}",
+                        session, local, remote, boundAddress, localForwarding);
+            }
         } else {
-            outputErrorMessage(
-                    "Failed (%s) to establish explicit tunnel for session=%s, local=%s, remote=%s, bound=%s, localForward=%s: %s",
+            log.error(
+                    "Failed ({}) to establish explicit tunnel for session={}, local={}, remote={}, bound={}, localForward={}: {}",
                     reason.getClass().getSimpleName(), session, local, remote, boundAddress, localForwarding,
                     reason.getMessage());
         }
@@ -52,11 +55,13 @@ public class ServerPortForwardingEventListener extends ServerEventListenerHelper
             Throwable reason)
             throws IOException {
         if (reason == null) {
-            outputDebugMessage("Torn down explicit tunnel for session=%s: address=%s, remote=%s, localForward=%s",
-                    session, address, remoteAddress, localForwarding);
+            if (log.isInfoEnabled()) {
+                log.info("Torn down explicit tunnel for session={}: address={}, remote={}, localForward={}",
+                        session, address, remoteAddress, localForwarding);
+            }
         } else {
-            outputErrorMessage(
-                    "Failed (%s) to tear down explicit tunnel for session=%s, address=%s, remote=%s, localForward=%s: %s",
+            log.error(
+                    "Failed ({}) to tear down explicit tunnel for session={}, address={}, remote={}, localForward={}: {}",
                     reason.getClass().getSimpleName(), session, address, remoteAddress, localForwarding, reason.getMessage());
         }
     }
@@ -66,9 +71,11 @@ public class ServerPortForwardingEventListener extends ServerEventListenerHelper
             Session session, SshdSocketAddress local, SshdSocketAddress boundAddress, Throwable reason)
             throws IOException {
         if (reason == null) {
-            outputDebugMessage("Estalibshed dynamic tunnel for session=%s: local=%s,  bound=%s", session, local, boundAddress);
+            if (log.isInfoEnabled()) {
+                log.info("Estalibshed dynamic tunnel for session={}: local={},  bound={}", session, local, boundAddress);
+            }
         } else {
-            outputErrorMessage("Failed (%s) to establish dynamic tunnel for session=%s, bound=%s: %s",
+            log.error("Failed ({}) to establish dynamic tunnel for session={}, bound={}: {}",
                     reason.getClass().getSimpleName(), session, local, boundAddress, reason.getMessage());
         }
     }
@@ -78,9 +85,11 @@ public class ServerPortForwardingEventListener extends ServerEventListenerHelper
             Session session, SshdSocketAddress address, Throwable reason)
             throws IOException {
         if (reason == null) {
-            outputDebugMessage("Tornd down dynamic tunnel for session=%s: address=%s", session);
+            if (log.isInfoEnabled()) {
+                log.info("Torn down dynamic tunnel for session={}: address={}", session);
+            }
         } else {
-            outputErrorMessage("Failed (%s) to tear down dynamic tunnel for session=%s, address=%s: %s",
+            log.error("Failed ({}) to tear down dynamic tunnel for session={}, address={}: {}",
                     reason.getClass().getSimpleName(), session, address, reason.getMessage());
         }
     }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/SftpServerSubSystemEventListener.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/SftpServerSubSystemEventListener.java
index 280a674..c534379 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/SftpServerSubSystemEventListener.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/helper/SftpServerSubSystemEventListener.java
@@ -28,23 +28,28 @@ import java.util.Map;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.server.SftpEventListener;
+import org.slf4j.Logger;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class SftpServerSubSystemEventListener extends ServerEventListenerHelper implements SftpEventListener {
-    public SftpServerSubSystemEventListener(Appendable stdout, Appendable stderr) {
-        super(SftpConstants.SFTP_SUBSYSTEM_NAME, stdout, stderr);
+    public SftpServerSubSystemEventListener(Logger logger) {
+        super(SftpConstants.SFTP_SUBSYSTEM_NAME, logger);
     }
 
     @Override
     public void initialized(ServerSession session, int version) throws IOException {
-        outputDebugMessage("Session %s initialized - version=%d", session, version);
+        if (log.isInfoEnabled()) {
+            log.info("Session {} initialized - version={}", session, version);
+        }
     }
 
     @Override
     public void destroying(ServerSession session) throws IOException {
-        outputDebugMessage("Session destroyed: %s", session);
+        if (log.isInfoEnabled()) {
+            log.info("Session destroyed: {}", session);
+        }
     }
 
     @Override
@@ -52,9 +57,11 @@ public class SftpServerSubSystemEventListener extends ServerEventListenerHelper
             ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown)
             throws IOException {
         if (thrown == null) {
-            outputDebugMessage("Session %s created directory %s with attributes=%s", session, path, attrs);
+            if (log.isInfoEnabled()) {
+                log.info("Session {} created directory {} with attributes={}", session, path, attrs);
+            }
         } else {
-            outputErrorMessage("Failed (%s) to create directory %s in session %s: %s",
+            log.error("Failed ({}) to create directory {} in session {}: {}",
                     thrown.getClass().getSimpleName(), path, session, thrown.getMessage());
         }
     }
@@ -64,10 +71,12 @@ public class SftpServerSubSystemEventListener extends ServerEventListenerHelper
             ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown)
             throws IOException {
         if (thrown == null) {
-            outputDebugMessage("Session %s moved %s to %s with options=%s",
-                    session, srcPath, dstPath, opts);
+            if (log.isInfoEnabled()) {
+                log.info("Session {} moved {} to {} with options={}",
+                        session, srcPath, dstPath, opts);
+            }
         } else {
-            outputErrorMessage("Failed (%s) to move %s to %s using options=%s in session %s: %s",
+            log.error("Failed ({}) to move {} to {} using options={} in session {}: {}",
                     thrown.getClass().getSimpleName(), srcPath, dstPath, opts, session, thrown.getMessage());
         }
     }
@@ -75,9 +84,11 @@ public class SftpServerSubSystemEventListener extends ServerEventListenerHelper
     @Override
     public void removed(ServerSession session, Path path, boolean isDirectory, Throwable thrown) throws IOException {
         if (thrown == null) {
-            outputDebugMessage("Session %s removed %s", session, path);
+            if (log.isInfoEnabled()) {
+                log.info("Session {} removed {}", session, path);
+            }
         } else {
-            outputErrorMessage("Failed (%s) to remove %s in session %s: %s",
+            log.error("Failed ({}) to remove {} in session {}: {}",
                     thrown.getClass().getSimpleName(), path, session, thrown.getMessage());
         }
     }
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
index 45424d4..e1cfc31 100644
--- a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
@@ -24,7 +24,7 @@ import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
 
-import org.apache.sshd.cli.CliSupport;
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
@@ -79,7 +79,7 @@ public class ChannelExecMain extends BaseTestSupport {
         try (BufferedReader stdin = new BufferedReader(
                 new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
             ClientSession session = SshClientCliSupport.setupClientSession("-P", stdin,
-                    CliSupport.resolveLoggingVerbosity(args), stdout, stderr, args);
+                    CliLogger.resolveLoggingVerbosity(args), stdout, stderr, args);
             if (session == null) {
                 System.err.println("usage: channelExec [-i identity] [-l login] [-P port] [-o option=value]"
                                    + " [-J proxyJump] [-w password] [-c cipherlist]  [-m maclist] [-C] hostname/user@host");
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
index d42ff7d..d7e29b7 100644
--- a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
@@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.logging.Level;
 
+import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.cli.CliSupport;
 import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
@@ -297,7 +298,7 @@ public final class SshFsMounter extends SshServerCliSupport {
         }
 
         PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
-        Level level = resolveLoggingVerbosity(resolver, args);
+        Level level = CliLogger.resolveLoggingVerbosity(resolver, args);
         SshServer sshd = error
                 ? null : setupIoServiceFactory(
                         CoreTestSupportUtils.setupTestServer(SshFsMounter.class), resolver,
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java
new file mode 100644
index 0000000..a21c5d2
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java
@@ -0,0 +1,172 @@
+/*
+ * 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.sshd.common.util.io;
+
+import java.io.PrintStream;
+import java.util.Locale;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NullPrintStream extends PrintStream {
+    public NullPrintStream() {
+        super(new NullOutputStream());
+    }
+
+    @Override
+    public void write(int b) {
+        // ignored
+    }
+
+    @Override
+    public void write(byte[] buf, int off, int len) {
+        // ignored
+    }
+
+    @Override
+    public void print(boolean b) {
+        // ignored
+    }
+
+    @Override
+    public void print(char c) {
+        append(c);
+    }
+
+    @Override
+    public void print(int i) {
+        print((long) i);
+    }
+
+    @Override
+    public void print(long l) {
+        // ignored
+    }
+
+    @Override
+    public void print(float f) {
+        print((double) f);
+    }
+
+    @Override
+    public void print(double d) {
+        // ignored
+    }
+
+    @Override
+    public void print(char[] s) {
+        // ignored
+    }
+
+    @Override
+    public void print(String s) {
+        // ignored
+    }
+
+    @Override
+    public void print(Object obj) {
+        // ignored
+    }
+
+    @Override
+    public void println() {
+        // ignored
+    }
+
+    @Override
+    public void println(boolean x) {
+        // ignored
+    }
+
+    @Override
+    public void println(char x) {
+        // ignored
+    }
+
+    @Override
+    public void println(int x) {
+        // ignored
+    }
+
+    @Override
+    public void println(long x) {
+        // ignored
+    }
+
+    @Override
+    public void println(float x) {
+        // ignored
+    }
+
+    @Override
+    public void println(double x) {
+        // ignored
+    }
+
+    @Override
+    public void println(char[] x) {
+        // ignored
+    }
+
+    @Override
+    public void println(String x) {
+        // ignored
+    }
+
+    @Override
+    public void println(Object x) {
+        // ignored
+    }
+
+    @Override
+    public PrintStream printf(String format, Object... args) {
+        return printf(Locale.getDefault(), format, args);
+    }
+
+    @Override
+    public PrintStream printf(Locale l, String format, Object... args) {
+        return format(l, format, args);
+    }
+
+    @Override
+    public PrintStream format(String format, Object... args) {
+        return format(Locale.getDefault(), format, args);
+    }
+
+    @Override
+    public PrintStream format(Locale l, String format, Object... args) {
+        return this;
+    }
+
+    @Override
+    public PrintStream append(CharSequence csq) {
+        return append(csq, 0, csq.length());
+    }
+
+    @Override
+    public PrintStream append(CharSequence csq, int start, int end) {
+        return this;
+    }
+
+    @Override
+    public PrintStream append(char c) {
+        return this;
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java
index 3f31e81..08a5230 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java
@@ -37,9 +37,11 @@ public abstract class AbstractLoggingBean {
 
     /**
      * Default constructor - creates a logger using the full class name
+     *
+     * @see #AbstractLoggingBean(Logger)
      */
     protected AbstractLoggingBean() {
-        this("");
+        this((Logger) null);
     }
 
     /**
@@ -56,6 +58,14 @@ public abstract class AbstractLoggingBean {
         log = LoggerFactory.getLogger(name);
     }
 
+    /**
+     * @param logger The {@link Logger} instance to use - if {@code null} then one is retrieved using the full class
+     *               name
+     */
+    protected AbstractLoggingBean(Logger logger) {
+        log = (logger == null) ? LoggerFactory.getLogger(getClass()) : logger;
+    }
+
     protected SimplifiedLog getSimplifiedLogger() {
         SimplifiedLog logger;
         synchronized (simplifiedLog) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggerSkeleton.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggerSkeleton.java
new file mode 100644
index 0000000..50b320a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggerSkeleton.java
@@ -0,0 +1,169 @@
+/*
+ * 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.sshd.common.util.logging;
+
+import org.slf4j.helpers.MarkerIgnoringBase;
+
+/**
+ * Provides some more default implementations for {@link org.slf4j.Logger} interface methods
+ * 
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class LoggerSkeleton extends MarkerIgnoringBase {
+    private static final long serialVersionUID = 1129569061632973648L;
+
+    protected LoggerSkeleton(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public void error(String format, Object arg) {
+        if (isErrorEnabled()) {
+            error(format, new Object[] { arg });
+        }
+    }
+
+    @Override
+    public void error(String format, Object arg1, Object arg2) {
+        if (isErrorEnabled()) {
+            error(format, new Object[] { arg1, arg2 });
+        }
+    }
+
+    @Override
+    public void error(String format, Object... arguments) {
+        if (isErrorEnabled()) {
+            error(LoggingUtils.formatMessage(format, arguments));
+        }
+    }
+
+    @Override
+    public void error(String msg) {
+        error(msg, (Throwable) null);
+    }
+
+    @Override
+    public void warn(String format, Object arg) {
+        if (isWarnEnabled()) {
+            warn(format, new Object[] { arg });
+        }
+    }
+
+    @Override
+    public void warn(String format, Object arg1, Object arg2) {
+        if (isWarnEnabled()) {
+            warn(format, new Object[] { arg1, arg2 });
+        }
+    }
+
+    @Override
+    public void warn(String format, Object... arguments) {
+        if (isWarnEnabled()) {
+            warn(LoggingUtils.formatMessage(format, arguments));
+        }
+    }
+
+    @Override
+    public void warn(String msg) {
+        warn(msg, (Throwable) null);
+    }
+
+    @Override
+    public void info(String format, Object arg) {
+        if (isInfoEnabled()) {
+            info(format, new Object[] { arg });
+        }
+    }
+
+    @Override
+    public void info(String format, Object arg1, Object arg2) {
+        if (isInfoEnabled()) {
+            info(format, new Object[] { arg1, arg2 });
+        }
+    }
+
+    @Override
+    public void info(String format, Object... arguments) {
+        if (isInfoEnabled()) {
+            info(LoggingUtils.formatMessage(format, arguments));
+        }
+    }
+
+    @Override
+    public void info(String msg) {
+        if (isInfoEnabled()) {
+            info(msg, (Throwable) null);
+        }
+    }
+
+    @Override
+    public void debug(String format, Object arg) {
+        if (isDebugEnabled()) {
+            debug(format, new Object[] { arg });
+        }
+    }
+
+    @Override
+    public void debug(String format, Object arg1, Object arg2) {
+        if (isDebugEnabled()) {
+            debug(format, new Object[] { arg1, arg2 });
+        }
+    }
+
+    @Override
+    public void debug(String format, Object... arguments) {
+        if (isDebugEnabled()) {
+            debug(LoggingUtils.formatMessage(format, arguments));
+        }
+    }
+
+    @Override
+    public void debug(String msg) {
+        if (isDebugEnabled()) {
+            debug(msg, (Throwable) null);
+        }
+    }
+
+    @Override
+    public void trace(String format, Object arg) {
+        if (isTraceEnabled()) {
+            trace(format, new Object[] { arg });
+        }
+    }
+
+    @Override
+    public void trace(String format, Object arg1, Object arg2) {
+        if (isTraceEnabled()) {
+            trace(format, new Object[] { arg1, arg2 });
+        }
+    }
+
+    @Override
+    public void trace(String format, Object... arguments) {
+        if (isTraceEnabled()) {
+            trace(LoggingUtils.formatMessage(format, arguments));
+        }
+    }
+
+    @Override
+    public void trace(String msg) {
+        trace(msg, (Throwable) null);
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
index 3d592b5..263924b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
@@ -38,6 +38,8 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.slf4j.Logger;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -505,6 +507,23 @@ public final class LoggingUtils {
         };
     }
 
+    /**
+     * Formats an {@code slf4j} message using its formatting structure - mainly the usage of <U>positional</U> arguments
+     * - e.g., &quot;Value1={}, Value2={}, ...&quot;
+     *
+     * @param  format    The formatting instructions - ignored if {@code null}/empty
+     * @param  arguments The formatting arguments - ignored if {@code null}/empty
+     * @return           The formatted message - or the format itself if no arguments or no format string
+     */
+    public static String formatMessage(String format, Object... arguments) {
+        if (GenericUtils.isEmpty(format) || GenericUtils.isEmpty(arguments)) {
+            return format;
+        }
+
+        FormattingTuple tuple = MessageFormatter.arrayFormat(format, arguments, null);
+        return tuple.getMessage();
+    }
+
     public static void debug(Logger log, String message, Object o1, Object o2, Throwable t) {
         if (log.isTraceEnabled() && (t != null)) {
             log.debug(message, o1, o2, t);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLoggerSkeleton.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLoggerSkeleton.java
new file mode 100644
index 0000000..c890696
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLoggerSkeleton.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sshd.common.util.logging;
+
+import java.util.logging.Level;
+
+/**
+ * Routes the effective logging to the {@link SimplifiedLog} methods.
+ *
+ * <B>Note:</B> we need the explicit overrides even though they are defined in {@link SimplifiedLog} as {@code default}
+ * since they are defined as {@code abstract} in the {@code slf4j Logger} interface
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class SimplifiedLoggerSkeleton extends LoggerSkeleton implements SimplifiedLog {
+    public static final SimplifiedLoggerSkeleton EMPTY = new SimplifiedLoggerSkeleton("EMPTY") {
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public boolean isEnabledLevel(Level level) {
+            return false;
+        }
+
+        @Override
+        public void log(Level level, Object message, Throwable t) {
+            return;
+        }
+
+    };
+
+    private static final long serialVersionUID = 9207771015837755402L;
+
+    protected SimplifiedLoggerSkeleton(String name) {
+        super(name);
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return SimplifiedLog.super.isErrorEnabled();
+    }
+
+    @Override
+    public void error(String msg, Throwable err) {
+        SimplifiedLog.super.error(msg, err);
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return SimplifiedLog.super.isWarnEnabled();
+    }
+
+    @Override
+    public void warn(String msg, Throwable err) {
+        SimplifiedLog.super.warn(msg, err);
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return SimplifiedLog.super.isInfoEnabled();
+    }
+
+    @Override
+    public void info(String msg, Throwable err) {
+        SimplifiedLog.super.info(msg, err);
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return SimplifiedLog.super.isDebugEnabled();
+    }
+
+    @Override
+    public void debug(String msg, Throwable err) {
+        SimplifiedLog.super.debug(msg, err);
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return SimplifiedLog.super.isTraceEnabled();
+    }
+
+    @Override
+    public void trace(String msg, Throwable err) {
+        SimplifiedLog.super.trace(msg, err);
+    }
+}
diff --git a/sshd-mina/pom.xml b/sshd-mina/pom.xml
index d2f0db8..1d8e4f2 100644
--- a/sshd-mina/pom.xml
+++ b/sshd-mina/pom.xml
@@ -126,6 +126,7 @@
                         <exclude>**/CipherTest.java</exclude>
                         <exclude>**/CompressionTest.java</exclude>
                         <exclude>**/NoServerNoClientTest.java</exclude>
+                        <exclude>**/OpenSSHCertificateTest.java</exclude>
                         <!-- exclude>**/PortForwardingTest.java</exclude -->
                         <exclude>**/MacTest.java</exclude>
                         <exclude>**/SpringConfigTest.java</exclude>