Moocar / logback-gelf

Logback plugin to send GELF messages to graylog2 server
Apache License 2.0
147 stars 59 forks source link

Feature Request: JMS or AMQP transport #52

Closed exabrial closed 9 years ago

exabrial commented 9 years ago

Now that there is native support for AMPQ support in GrayLog, it would be really neat to be able to log messages using this technology.

JMS and AMPQ both have their advantages and disadvantages... JMS is protocol independent, so it'd run on a wide variety of Java platforms and servers. The other advantage is that this could like be implemented using provided dependencies, meaning that logback-gelf wouldn't bring in any transitive baggage. Since the server manages the connections, no connection information is specified in the configuration, just a target queue. The disadvantage, well its JNDI lookups. They're terrible. Example: http://grepcode.com/file/repo1.maven.org/maven2/ch.qos.logback/logback-classic/0.9.19/ch/qos/logback/classic/net/JMSQueueAppender.java#92

AMQP is a wire level protocol, so while would likely need a compile dependency on a Java AMQP library, this frees you from having to use a traditional Java server like tomcat, Glassfish, TomEE, Wildfly. You could connect straight to the server, but that means logging information is stored on a per application basis, which for some environments may pose a flexibility problem.

Thanks for listening :) And great work on this implementation, it's awesome.

Moocar commented 9 years ago

Yes! We should definitely add support for JMS and AMPQ. The nice thing about the new architecture in version 0.2 is that appenders are entirely pluggable. In fact, if you look at how TCP is implemented, you'll notice that it's a separate library that implements the Appender interface and is configured in the logback.xml. So Pulling in JMS or AMPQ should be as simple as depending on the appropriate library and configuring it.

Unfortunately, it's not that simple :(. Logback ships with a JMS appender but it includes its own serializer instead of allowing you to inject your own Layout. So every log event is serialized to a java object instead of GELF. So for JMS it would seem, we'll need to write our own Appender and release it in its own library.

The story for AMPQ however seems a bit happier. It looks like there is spring AMPQ appender so it should be possible to drop it in. Mind trying it out?

Moocar commented 9 years ago

Added an ADR for AMQP support

exabrial commented 9 years ago

I didn't see a commit attached to this issue, so not sure why it was closed... but here is the start of a JMS appender. The nice thing about JMS is it's protocol agnostic, so if you broker speaks AMPQ, it can connect to Graylog directly:

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import ch.qos.logback.core.OutputStreamAppender;

public class GelfJmsAppender<E> extends OutputStreamAppender<E> {
    private Context ctx;
    private Connection connection;
    private Session session;
    private MessageProducer producer;
    private String connectionFactoryJndiLoc = "openejb:Resource/jms/connectionFactory";
    private String queueName = "org.logback.logs";

    @Override
    public void start() {
        try {
            ctx = new InitialContext();
            ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup(connectionFactoryJndiLoc);
            Connection connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            Queue queue = session.createQueue(queueName);
            producer = session.createProducer(queue);
            encoder.start();
            this.started = true;
        } catch (NamingException | JMSException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void stop() {
        try {
            connection.close();
            ctx.close();
        } catch (NamingException | JMSException e) {
            throw new RuntimeException(e);
        } finally {
            encoder.stop();
        }
    }

    @Override
    protected void append(E eventObject) {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            setOutputStream(os);
            encoder.doEncode(eventObject);
            Message message = session.createTextMessage(new String(os.toByteArray()));
            producer.send(message);
        } catch (JMSException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String getConnectionFactoryJndiLoc() {
        return connectionFactoryJndiLoc;
    }

    public void setConnectionFactoryJndiLoc(String connectionFactoryJndiLoc) {
        this.connectionFactoryJndiLoc = connectionFactoryJndiLoc;
    }

    public String getQueueName() {
        return queueName;
    }

    public void setQueueName(String queueName) {
        this.queueName = queueName;
    }
}