├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── ptgoetz │ └── logback │ └── kafka │ ├── KafkaAppender.java │ └── formatter │ ├── Formatter.java │ ├── JsonFormatter.java │ └── MessageFormatter.java └── test ├── java └── com │ └── github │ └── ptgoetz │ └── logback │ └── kafka │ └── formatter │ ├── JsonFormatterTest.java │ ├── MockLoggingEvent.java │ └── SimpleApp.java └── resources ├── logback-formatter.xml └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logback-kafka 2 | 3 | 4 | Logback appenders for logging data to Apache Kafka 5 | 6 | 7 | ## Maven Dependency 8 | To use logback-kafka in your project add to following to your pom.xml: 9 | 10 | ```xml 11 | 12 | com.github.ptgoetz 13 | logback-kafka 14 | 0.1.0 15 | 16 | ``` 17 | 18 | ## Configuration 19 | 20 | To configure your application to log to kafka, add an appender entry in your logback configuration file, and specify 21 | a zookeeper host string, and kafka topic name to log to. 22 | 23 | 24 | ```xml 25 | 26 | 27 | 29 | mytopic 30 | localhost:2181 31 | 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | ## Overriding Default Behavior 39 | By default, the Kafka appender will simply write the received log message to the kafka queue. You can override this 40 | behavior by specifying a custom formatter class: 41 | 42 | ```xml 43 | 44 | 45 | 47 | foo 48 | localhost:2181 49 | 50 | 51 | 57 | true 58 | 59 | 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | 67 | 68 | Formatters simply need to implement the `com.github.ptgoetz.logback.kafka.formatter.Formatter` interface: 69 | 70 | ```java 71 | package com.github.ptgoetz.logback.kafka.formatter; 72 | 73 | import ch.qos.logback.classic.spi.ILoggingEvent; 74 | 75 | public interface Formatter { 76 | String format(ILoggingEvent event); 77 | } 78 | ``` 79 | 80 | You can find the `ch.qos.logback.classic.spi.ILoggingEvent` javadoc [here](http://logback.qos.ch/apidocs/ch/qos/logback/classic/spi/ILoggingEvent.html). 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.github.ptgoetz 5 | logback-kafka 6 | 0.1.1-SNAPSHOT 7 | jar 8 | 9 | 10 | org.sonatype.oss 11 | oss-parent 12 | 7 13 | 14 | 15 | logback-kafka 16 | http://maven.apache.org 17 | 18 | 19 | 20 | Eclipse Public License - v 1.0 21 | http://www.eclipse.org/legal/epl-v10.html 22 | repo 23 | 24 | 25 | 26 | scm:git:git@github.com:ptgoetz/logback-kafka.git 27 | scm:git:git@github.com:ptgoetz/logback-kafka.git 28 | :git@github.com:ptgoetz/logback-kafka.git 29 | 30 | 31 | 32 | 33 | ptgoetz 34 | P. Taylor Goetz 35 | ptgoetz@gmail.com 36 | 37 | 38 | 39 | 40 | UTF-8 41 | 42 | 43 | 44 | 45 | junit 46 | junit 47 | 4.10 48 | test 49 | 50 | 51 | ch.qos.logback 52 | logback-classic 53 | 1.0.13 54 | 55 | 56 | kafka 57 | kafka 58 | 0.7.1 59 | 60 | 61 | org.scala-lang 62 | scala-library 63 | 2.8.0 64 | 65 | 66 | com.github.sgroschupf 67 | zkclient 68 | 0.1 69 | 70 | 71 | slf4j-api 72 | org.slf4j 73 | 1.7.5 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/com/github/ptgoetz/logback/kafka/KafkaAppender.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka; 2 | 3 | import java.util.Properties; 4 | 5 | import kafka.javaapi.producer.Producer; 6 | import kafka.javaapi.producer.ProducerData; 7 | import kafka.producer.ProducerConfig; 8 | import ch.qos.logback.classic.spi.ILoggingEvent; 9 | import ch.qos.logback.core.AppenderBase; 10 | 11 | import com.github.ptgoetz.logback.kafka.formatter.Formatter; 12 | import com.github.ptgoetz.logback.kafka.formatter.MessageFormatter; 13 | 14 | public class KafkaAppender extends AppenderBase { 15 | 16 | private String topic; 17 | private String zookeeperHost; 18 | private Producer producer; 19 | private Formatter formatter; 20 | 21 | public String getTopic() { 22 | return topic; 23 | } 24 | 25 | public void setTopic(String topic) { 26 | this.topic = topic; 27 | } 28 | 29 | public String getZookeeperHost() { 30 | return zookeeperHost; 31 | } 32 | 33 | public void setZookeeperHost(String zookeeperHost) { 34 | this.zookeeperHost = zookeeperHost; 35 | } 36 | 37 | public Formatter getFormatter() { 38 | return formatter; 39 | } 40 | 41 | public void setFormatter(Formatter formatter) { 42 | this.formatter = formatter; 43 | } 44 | 45 | @Override 46 | public void start() { 47 | if (this.formatter == null) { 48 | this.formatter = new MessageFormatter(); 49 | } 50 | super.start(); 51 | Properties props = new Properties(); 52 | props.put("zk.connect", this.zookeeperHost); 53 | props.put("serializer.class", "kafka.serializer.StringEncoder"); 54 | ProducerConfig config = new ProducerConfig(props); 55 | this.producer = new Producer(config); 56 | } 57 | 58 | @Override 59 | public void stop() { 60 | super.stop(); 61 | this.producer.close(); 62 | } 63 | 64 | @Override 65 | protected void append(ILoggingEvent event) { 66 | String payload = this.formatter.format(event); 67 | ProducerData data = new ProducerData(this.topic, payload); 68 | this.producer.send(data); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/ptgoetz/logback/kafka/formatter/Formatter.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | 5 | public interface Formatter { 6 | String format(ILoggingEvent event); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/github/ptgoetz/logback/kafka/formatter/JsonFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | 5 | public class JsonFormatter implements Formatter { 6 | private static final String QUOTE = "\""; 7 | private static final String COLON = ":"; 8 | private static final String COMMA = ","; 9 | 10 | private boolean expectJson = false; 11 | 12 | public String format(ILoggingEvent event) { 13 | StringBuilder sb = new StringBuilder(); 14 | sb.append("{"); 15 | fieldName("level", sb); 16 | quote(event.getLevel().levelStr, sb); 17 | sb.append(COMMA); 18 | fieldName("logger", sb); 19 | quote(event.getLoggerName(), sb); 20 | sb.append(COMMA); 21 | fieldName("timestamp", sb); 22 | sb.append(event.getTimeStamp()); 23 | sb.append(COMMA); 24 | fieldName("message", sb); 25 | if (this.expectJson) { 26 | sb.append(event.getFormattedMessage()); 27 | } else { 28 | quote(event.getFormattedMessage(), sb); 29 | } 30 | 31 | sb.append("}"); 32 | return sb.toString(); 33 | } 34 | 35 | private static void fieldName(String name, StringBuilder sb) { 36 | quote(name, sb); 37 | sb.append(COLON); 38 | } 39 | 40 | private static void quote(String value, StringBuilder sb) { 41 | sb.append(QUOTE); 42 | sb.append(value); 43 | sb.append(QUOTE); 44 | } 45 | 46 | public boolean isExpectJson() { 47 | return expectJson; 48 | } 49 | 50 | public void setExpectJson(boolean expectJson) { 51 | this.expectJson = expectJson; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/github/ptgoetz/logback/kafka/formatter/MessageFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | 5 | /** 6 | * 7 | * Formatter implementation that simply returns the logback message. 8 | * 9 | * @author tgoetz 10 | * 11 | */ 12 | public class MessageFormatter implements Formatter { 13 | 14 | public String format(ILoggingEvent event) { 15 | return event.getFormattedMessage(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/github/ptgoetz/logback/kafka/formatter/JsonFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import org.junit.Test; 4 | 5 | import static junit.framework.Assert.*; 6 | import junit.framework.TestCase; 7 | 8 | public class JsonFormatterTest extends TestCase { 9 | 10 | @Test 11 | public void testJsonFormat() { 12 | String nonJsonMessage = "{\"level\":\"INFO\",\"logger\":\"test\",\"timestamp\":1370918376296,\"message\":\"foobar\"}"; 13 | String jsonMessage = "{\"level\":\"INFO\",\"logger\":\"test\",\"timestamp\":1370918376296,\"message\":{\"foo\":\"bar\"}}"; 14 | 15 | // non-JSON 16 | MockLoggingEvent event = new MockLoggingEvent(false); 17 | JsonFormatter formatter = new JsonFormatter(); 18 | formatter.setExpectJson(false); 19 | String json = formatter.format(event); 20 | assertEquals(nonJsonMessage, json); 21 | 22 | // JSON 23 | event = new MockLoggingEvent(true); 24 | formatter.setExpectJson(true); 25 | json = formatter.format(event); 26 | assertEquals(jsonMessage, json); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/github/ptgoetz/logback/kafka/formatter/MockLoggingEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import java.util.Map; 4 | 5 | import org.slf4j.Marker; 6 | 7 | import ch.qos.logback.classic.Level; 8 | import ch.qos.logback.classic.spi.ILoggingEvent; 9 | import ch.qos.logback.classic.spi.IThrowableProxy; 10 | import ch.qos.logback.classic.spi.LoggerContextVO; 11 | 12 | public class MockLoggingEvent implements ILoggingEvent { 13 | private boolean jsonMessage = false; 14 | 15 | public MockLoggingEvent(boolean jsonMessage) { 16 | this.jsonMessage = jsonMessage; 17 | } 18 | 19 | public Object[] getArgumentArray() { 20 | // TODO Auto-generated method stub 21 | return null; 22 | } 23 | 24 | public StackTraceElement[] getCallerData() { 25 | // TODO Auto-generated method stub 26 | return null; 27 | } 28 | 29 | public String getFormattedMessage() { 30 | if (this.jsonMessage) { 31 | return "{\"foo\":\"bar\"}"; 32 | } else { 33 | return "foobar"; 34 | } 35 | } 36 | 37 | public Level getLevel() { 38 | return Level.INFO; 39 | } 40 | 41 | public LoggerContextVO getLoggerContextVO() { 42 | // TODO Auto-generated method stub 43 | return null; 44 | } 45 | 46 | public String getLoggerName() { 47 | // TODO Auto-generated method stub 48 | return "test"; 49 | } 50 | 51 | public Map getMDCPropertyMap() { 52 | // TODO Auto-generated method stub 53 | return null; 54 | } 55 | 56 | public Marker getMarker() { 57 | // TODO Auto-generated method stub 58 | return null; 59 | } 60 | 61 | public Map getMdc() { 62 | // TODO Auto-generated method stub 63 | return null; 64 | } 65 | 66 | public String getMessage() { 67 | // TODO Auto-generated method stub 68 | return null; 69 | } 70 | 71 | public String getThreadName() { 72 | // TODO Auto-generated method stub 73 | return null; 74 | } 75 | 76 | public IThrowableProxy getThrowableProxy() { 77 | // TODO Auto-generated method stub 78 | return null; 79 | } 80 | 81 | public long getTimeStamp() { 82 | // TODO Auto-generated method stub 83 | return 1370918376296L; 84 | } 85 | 86 | public boolean hasCallerData() { 87 | // TODO Auto-generated method stub 88 | return false; 89 | } 90 | 91 | public void prepareForDeferredProcessing() { 92 | // TODO Auto-generated method stub 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/github/ptgoetz/logback/kafka/formatter/SimpleApp.java: -------------------------------------------------------------------------------- 1 | package com.github.ptgoetz.logback.kafka.formatter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class SimpleApp { 7 | public static final Logger LOG = LoggerFactory.getLogger(SimpleApp.class); 8 | 9 | public static void main(String[] args) { 10 | LOG.info("Hello {}", "World!"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/logback-formatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | foo 6 | localhost:2181 7 | 8 | 9 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | mytopic 6 | localhost:2181 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------