diff --git a/settings.gradle b/settings.gradle index f12514295..5888f2742 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ include 'smack-core', 'smack-extensions', 'smack-experimental', 'smack-debug', + 'smack-debug-slf4j', 'smack-resolver-dnsjava', 'smack-resolver-minidns', 'smack-resolver-javax', diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java index 9e996058d..f2ebbc01f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java @@ -35,6 +35,7 @@ import javax.net.ssl.HostnameVerifier; import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream; import org.jivesoftware.smack.compression.XMPPInputOutputStream; +import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.initializer.SmackInitializer; import org.jivesoftware.smack.parsing.ExceptionThrowingCallback; import org.jivesoftware.smack.parsing.ParsingExceptionCallback; @@ -256,6 +257,13 @@ public final class SmackConfiguration { } } + /** + * Sets smack debugger class + */ + public static void setDebugger(Class debuggerClass) { + System.setProperty("smack.debuggerClass", debuggerClass.getCanonicalName()); + } + /** * Remove a SASL mechanism from the list to be used. * diff --git a/smack-debug-slf4j/build.gradle b/smack-debug-slf4j/build.gradle new file mode 100644 index 000000000..1bb559998 --- /dev/null +++ b/smack-debug-slf4j/build.gradle @@ -0,0 +1,10 @@ +description = """\ +Smack slf4j debugger. +Inspect the exchanged XMPP stanzas. +Connect your favourite slf4j backend of choice to get output inside of it""" + +dependencies { + compile project(':smack-core') + compile 'org.slf4j:slf4j-api:1.7.7' + testCompile project(':smack-core').sourceSets.test.runtimeClasspath +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingConnectionListener.java b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingConnectionListener.java new file mode 100644 index 000000000..3a3309cef --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingConnectionListener.java @@ -0,0 +1,62 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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 org.jivesoftware.smackx.debugger.slf4j; + +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.XMPPConnection; +import org.slf4j.Logger; + +class SLF4JLoggingConnectionListener implements ConnectionListener { + private final XMPPConnection connection; + private final Logger logger; + + public SLF4JLoggingConnectionListener(XMPPConnection connection, Logger logger) { + this.connection = Validate.notNull(connection); + this.logger = Validate.notNull(logger); + } + + @Override + public void connected(XMPPConnection connection) { + logger.debug("({}) Connection connected", connection.hashCode()); + } + + @Override + public void authenticated(XMPPConnection connection) { + logger.debug("({}) Connection authenticated as {}", connection.hashCode(), connection.getUser()); + } + + public void connectionClosed() { + logger.debug("({}) Connection closed", connection.hashCode()); + } + + public void connectionClosedOnError(Exception e) { + logger.debug("({}) Connection closed due to an exception: {}", connection.hashCode(), e); + } + + public void reconnectionFailed(Exception e) { + logger.debug("({}) Reconnection failed due to an exception: {}", connection.hashCode(), e); + } + + public void reconnectionSuccessful() { + logger.debug("({}) Connection reconnected", connection.hashCode()); + } + + public void reconnectingIn(int seconds) { + logger.debug("({}) Connection will reconnect in {}", connection.hashCode(), seconds); + } +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingPacketListener.java b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingPacketListener.java new file mode 100644 index 000000000..b6db5f281 --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JLoggingPacketListener.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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 org.jivesoftware.smackx.debugger.slf4j; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.packet.Packet; +import org.slf4j.Logger; + + +class SLF4JLoggingPacketListener implements PacketListener { + private final Logger logger; + private final String prefix; + + public SLF4JLoggingPacketListener(Logger logger, String prefix) { + this.logger = Validate.notNull(logger); + this.prefix = Validate.notNull(prefix); + } + + public void processPacket(Packet packet) { + if (SLF4JSmackDebugger.printInterpreted.get() && logger.isDebugEnabled()) { + logger.debug("{}: PKT [{}] '{}'", prefix, packet.getClass().getName(), packet.toXML()); + } + } +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JRawXmlListener.java b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JRawXmlListener.java new file mode 100644 index 000000000..c44bbcd2f --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JRawXmlListener.java @@ -0,0 +1,38 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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 org.jivesoftware.smackx.debugger.slf4j; + +import org.jivesoftware.smack.util.ReaderListener; +import org.jivesoftware.smack.util.WriterListener; +import org.slf4j.Logger; + +class SLF4JRawXmlListener implements ReaderListener, WriterListener { + private final Logger logger; + + public SLF4JRawXmlListener(Logger logger) { + this.logger = Validate.notNull(logger); + } + + public void read(String str) { + logger.debug("{}: {}", SLF4JSmackDebugger.RECEIVED_TAG, str); + } + + public void write(String str) { + logger.debug("{}: {}", SLF4JSmackDebugger.SENT_TAG, str); + } +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JSmackDebugger.java b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JSmackDebugger.java new file mode 100644 index 000000000..a95b1ce12 --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/SLF4JSmackDebugger.java @@ -0,0 +1,130 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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 org.jivesoftware.smackx.debugger.slf4j; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.debugger.SmackDebugger; +import org.jivesoftware.smack.util.ObservableReader; +import org.jivesoftware.smack.util.ObservableWriter; +import org.jxmpp.util.XmppStringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Reader; +import java.io.Writer; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * Implementation of SmackDebugger that writes log messages using SLF4J API. + * Use in conjunction with your SLF4J bindings of choice. + * See SLF4J manual for more details about bindings usage. + */ +public class SLF4JSmackDebugger implements SmackDebugger { + public static final String LOGGER_NAME = "SMACK"; + private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); + public static final AtomicBoolean printInterpreted = new AtomicBoolean(true); + + public static final String SENT_TAG = "SENT"; + public static final String RECEIVED_TAG = "RECV"; + + private final XMPPConnection connection; + + private final PacketListener receivedListener = new SLF4JLoggingPacketListener(logger, RECEIVED_TAG); + private final PacketListener sentListener = new SLF4JLoggingPacketListener(logger, SENT_TAG); + private final SLF4JRawXmlListener slf4JRawXmlListener = new SLF4JRawXmlListener(logger); + + private ObservableWriter writer; + private ObservableReader reader; + + /** + * Makes Smack use this Debugger + */ + public static void enable() { + SmackConfiguration.setDebugger(SLF4JSmackDebugger.class); + } + + /** + * Create new SLF4J Smack Debugger instance + * @param connection Smack connection to debug + * @param writer connection data writer to observe + * @param reader connection data reader to observe + */ + public SLF4JSmackDebugger(XMPPConnection connection, Writer writer, Reader reader) { + this.connection = connection; + this.writer = new ObservableWriter(writer); + this.writer.addWriterListener(slf4JRawXmlListener); + this.reader = new ObservableReader(Validate.notNull(reader)); + this.reader.addReaderListener(slf4JRawXmlListener); + this.connection.addConnectionListener(new SLF4JLoggingConnectionListener(connection, logger)); + } + + @Override + public Reader newConnectionReader(Reader newReader) { + reader.removeReaderListener(slf4JRawXmlListener); + reader = new ObservableReader(newReader); + reader.addReaderListener(slf4JRawXmlListener); + return reader; + } + + @Override + public Writer newConnectionWriter(Writer newWriter) { + writer.removeWriterListener(slf4JRawXmlListener); + writer = new ObservableWriter(newWriter); + writer.addWriterListener(slf4JRawXmlListener); + return writer; + } + + @Override + public void userHasLogged(String user) { + if (logger.isDebugEnabled()) { + String userTitle = getUserTitle(user); + logger.debug("({}) User logged in {}", connection.hashCode(), userTitle); + } + } + + private String getUserTitle(String user) { + if (("@" + connection.getServiceName()).equals(XmppStringUtils.parseBareAddress(user))) { + return "@" + connection.getServiceName(); + } else { + return user; + } + } + + @Override + public Reader getReader() { + return reader; + } + + @Override + public Writer getWriter() { + return writer; + } + + @Override + public PacketListener getReaderListener() { + return receivedListener; + } + + @Override + public PacketListener getWriterListener() { + return sentListener; + } +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/Validate.java b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/Validate.java new file mode 100644 index 000000000..501851537 --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/Validate.java @@ -0,0 +1,37 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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 org.jivesoftware.smackx.debugger.slf4j; + +/** + * This is package-level helper class to validate dependencies while initialization is in progress + */ +final class Validate { + private Validate() { /* do not create instances */ } + + public static T notNull(T instance) { + return notNull(instance, null); + } + + public static T notNull(T instance, String message) { + if (instance == null) { + throw new NullPointerException(message); + } else { + return instance; + } + } +} diff --git a/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/package.html b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/package.html new file mode 100644 index 000000000..8be0f02b6 --- /dev/null +++ b/smack-debug-slf4j/src/main/java/org/jivesoftware/smackx/debugger/slf4j/package.html @@ -0,0 +1 @@ +Smack slf4j debugger implementation. \ No newline at end of file