diff --git a/pom.xml b/pom.xml index a6d80937c..f75221331 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 1.1.3 1.5.2.Final 1.1.5.Final + 4.5.1 3.4 2.7 @@ -286,6 +287,14 @@ provided + + + org.apache.httpcomponents + httpclient + ${http-components.version} + + + junit @@ -359,6 +368,7 @@ shrinkwrap-descriptors-impl-javaee test + diff --git a/src/main/java/biz/paluch/logging/gelf/GelfUtil.java b/src/main/java/biz/paluch/logging/gelf/GelfUtil.java index 70a7ffdd0..0c08a83d6 100644 --- a/src/main/java/biz/paluch/logging/gelf/GelfUtil.java +++ b/src/main/java/biz/paluch/logging/gelf/GelfUtil.java @@ -2,9 +2,11 @@ import biz.paluch.logging.gelf.intern.GelfMessage; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author Mark Paluch @@ -93,4 +95,67 @@ public static Set getMatchingMdcNames(DynamicMdcMessageField field, Set< } return matchingMdcNames; } + + public static String addDefaultPortIfMissing(String urlString, String defaultPort) { + URL url; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + return urlString; + } + if (url.getPort() != -1) { + return urlString; + } + String regex = "http://([^/]+)"; + String found = getFirstFound(urlString, regex); + String replacer = "http://" + found + ":" + defaultPort; + + if (!isEmpty(found)) { + urlString = urlString.replaceFirst(regex, replacer); + } + return urlString; + } + + public static String getFirstFound(String contents, String regex) { + List founds = getFound(contents, regex); + if (isEmpty(founds)) { + return null; + } + return founds.get(0); + } + + public static List getFound(String contents, String regex) { + if (isEmpty(regex) || isEmpty(contents)) { + return null; + } + List results = new ArrayList(); + Pattern pattern = Pattern.compile(regex, Pattern.UNICODE_CASE); + Matcher matcher = pattern.matcher(contents); + + while (matcher.find()) { + if (matcher.groupCount() > 0) { + results.add(matcher.group(1)); + } else { + results.add(matcher.group()); + } + } + return results; + } + + public static boolean isEmpty(List list) { + if (list == null || list.size() == 0) { + return true; + } + if (list.size() == 1 && isEmpty(list.get(0))) { + return true; + } + return false; + } + + public static boolean isEmpty(String str) { + if (str != null && str.trim().length() > 0) { + return false; + } + return true; + } } diff --git a/src/main/java/biz/paluch/logging/gelf/intern/sender/DefaultGelfSenderProvider.java b/src/main/java/biz/paluch/logging/gelf/intern/sender/DefaultGelfSenderProvider.java index 157e99460..983d296da 100644 --- a/src/main/java/biz/paluch/logging/gelf/intern/sender/DefaultGelfSenderProvider.java +++ b/src/main/java/biz/paluch/logging/gelf/intern/sender/DefaultGelfSenderProvider.java @@ -6,25 +6,24 @@ import java.io.IOException; import java.net.URI; +import java.net.URL; import java.util.Map; import java.util.concurrent.TimeUnit; +import static biz.paluch.logging.gelf.GelfUtil.addDefaultPortIfMissing; + /** - * * (c) https://github.com/Batigoal/logstash-gelf.git - * */ public class DefaultGelfSenderProvider implements GelfSenderProvider { public static final int DEFAULT_PORT = 12201; - @Override - public boolean supports(String host) { + @Override public boolean supports(String host) { return host != null; } - @Override - public GelfSender create(GelfSenderConfiguration configuration) throws IOException { + @Override public GelfSender create(GelfSenderConfiguration configuration) throws IOException { String graylogHost = configuration.getHost(); int port = configuration.getPort(); @@ -39,8 +38,7 @@ public GelfSender create(GelfSenderConfiguration configuration) throws IOExcepti URI uri = URI.create(graylogHost); Map params = UriParser.parse(uri); - int connectionTimeMs = (int) UriParser.getTimeAsMs(params, GelfTCPSender.CONNECTION_TIMEOUT, - defaultTimeoutMs); + int connectionTimeMs = (int) UriParser.getTimeAsMs(params, GelfTCPSender.CONNECTION_TIMEOUT, defaultTimeoutMs); int readTimeMs = (int) UriParser.getTimeAsMs(params, GelfTCPSender.READ_TIMEOUT, defaultTimeoutMs); int deliveryAttempts = UriParser.getInt(params, GelfTCPSender.RETRIES, 1); boolean keepAlive = UriParser.getString(params, GelfTCPSender.KEEPALIVE, false); @@ -52,6 +50,11 @@ public GelfSender create(GelfSenderConfiguration configuration) throws IOExcepti URI uri = URI.create(graylogHost); String udpGraylogHost = UriParser.getHost(uri); return new GelfUDPSender(udpGraylogHost, port, configuration.getErrorReporter()); + } else if (graylogHost.startsWith("http")) { + String graylogHostWithDefaultPort = addDefaultPortIfMissing(graylogHost, String.valueOf(port)); + URL url = new URL(graylogHostWithDefaultPort); + return new GelfHTTPSender(url, configuration.getErrorReporter()); + } else { return new GelfUDPSender(graylogHost, port, configuration.getErrorReporter()); } diff --git a/src/main/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSender.java b/src/main/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSender.java new file mode 100644 index 000000000..709d23419 --- /dev/null +++ b/src/main/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSender.java @@ -0,0 +1,51 @@ +package biz.paluch.logging.gelf.intern.sender; + +import biz.paluch.logging.gelf.intern.ErrorReporter; +import biz.paluch.logging.gelf.intern.GelfMessage; +import biz.paluch.logging.gelf.intern.GelfSender; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Created by https://github.com/salex89 + */ +public class GelfHTTPSender implements GelfSender { + + final private ErrorReporter errorReporter; + final private URL url; + private HttpURLConnection connection = null; + private int HTTP_ACCEPTED_STATUS = 202; + + public GelfHTTPSender(URL url, ErrorReporter errorReporter) { + this.errorReporter = errorReporter; + this.url = url; + } + + @Override public boolean sendMessage(GelfMessage message) { + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + OutputStream outputStream = connection.getOutputStream(); + outputStream.write(message.toJson().getBytes()); + outputStream.close(); + int responseCode = connection.getResponseCode(); + if (responseCode == HTTP_ACCEPTED_STATUS) { + return true; + } else { + errorReporter.reportError("Server responded with unexpected status code: ", null); + } + } catch (IOException e) { + errorReporter.reportError("Error when sending log message", e); + } + return false; + } + + @Override public void close() { + //disconnecting HttpURLConnection here to avoid underlying premature underlying Socket being closed. + if (connection != null) + connection.disconnect(); + } +} diff --git a/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPHandler.java b/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPHandler.java new file mode 100644 index 000000000..a750ab615 --- /dev/null +++ b/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPHandler.java @@ -0,0 +1,81 @@ +package biz.paluch.logging.gelf; + +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; +import org.json.simple.JSONValue; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by aleksandar on 3/12/16. + */ +public class GelfInboundHTTPHandler extends SimpleChannelInboundHandler { + private List values = new ArrayList(); + private HttpRequest httpRequest; + private HttpContent httpContent; + private HttpResponseStatus responseStatus = HttpResponseStatus.ACCEPTED; + + private final StringBuilder contentBuffer = new StringBuilder(); + + @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object message) throws Exception { + + resetState(); + if (message instanceof HttpRequest) { + httpRequest = (HttpRequest) message; + } + if (message instanceof HttpContent) { + httpContent = (HttpContent) message; + contentBuffer.append(httpContent.content().toString(CharsetUtil.UTF_8)); + if (message instanceof LastHttpContent) { + Object parsedContent = JSONValue.parse(contentBuffer.toString()); + synchronized (values) { + values.add(parsedContent); + } + writeResponse(channelHandlerContext); + closeConnection(channelHandlerContext); + } + } + + } + + private void resetState() { + this.values.clear(); + this.httpContent = null; + this.httpRequest = null; + } + + private void closeConnection(ChannelHandlerContext channelHandlerContext) { + channelHandlerContext.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } + + private void writeResponse(ChannelHandlerContext ctx) { + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus); + ctx.write(response); + } + + public List getValues() { + return values; + } + + public String getUri() { + return httpRequest.getUri(); + } + + public HttpMethod getMethod() { + return httpRequest.getMethod(); + } + + public void setReturnStatus(HttpResponseStatus status) { + this.responseStatus = status; + } +} diff --git a/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPInitializer.java b/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPInitializer.java new file mode 100644 index 000000000..8b27e0257 --- /dev/null +++ b/src/test/java/biz/paluch/logging/gelf/GelfInboundHTTPInitializer.java @@ -0,0 +1,30 @@ +package biz.paluch.logging.gelf; + +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.*; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; + +/** + * Created by aleksandar on 3/12/16. + */ +public class GelfInboundHTTPInitializer extends ChannelInitializer { + protected GelfInboundHTTPHandler handler; + + public GelfInboundHTTPInitializer() { + handler = new GelfInboundHTTPHandler(); + } + + @Override protected void initChannel(SocketChannel socketChannel) throws Exception { + ChannelPipeline pipeline = socketChannel.pipeline(); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(1048576)); + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(handler); + } + + public GelfInboundHTTPHandler getHandler() { + return handler; + } +} diff --git a/src/test/java/biz/paluch/logging/gelf/GelfUtilTest.java b/src/test/java/biz/paluch/logging/gelf/GelfUtilTest.java index f71c63e5a..0a1949ecb 100644 --- a/src/test/java/biz/paluch/logging/gelf/GelfUtilTest.java +++ b/src/test/java/biz/paluch/logging/gelf/GelfUtilTest.java @@ -1,6 +1,7 @@ package biz.paluch.logging.gelf; import static org.junit.Assert.assertEquals; + import biz.paluch.logging.gelf.intern.GelfMessage; import biz.paluch.logging.gelf.jboss7.JBoss7JulLogEvent; import org.jboss.logmanager.ExtLogRecord; @@ -12,8 +13,7 @@ public class GelfUtilTest { - @Test - public void testProfilingString() throws Exception { + @Test public void testProfilingString() throws Exception { Map mdcMap = new HashMap(); mdcMap.put(GelfUtil.MDC_REQUEST_START_MS, "" + (System.currentTimeMillis() - 12000)); @@ -27,8 +27,7 @@ public void testProfilingString() throws Exception { } - @Test - public void testProfilingLong() throws Exception { + @Test public void testProfilingLong() throws Exception { Map mdcMap = new HashMap(); mdcMap.put(GelfUtil.MDC_REQUEST_START_MS, (System.currentTimeMillis() - 12000)); @@ -41,4 +40,11 @@ public void testProfilingLong() throws Exception { assertEquals("12sec", message.getAdditonalFields().get(GelfUtil.MDC_REQUEST_DURATION)); } + + @Test public void addDefaultPortIfMissing() { + String url = GelfUtil.addDefaultPortIfMissing("http://example.com/foo", String.valueOf(1234)); + assertEquals("http://example.com:1234/foo", url); + String url2 = GelfUtil.addDefaultPortIfMissing("http://example.com:8080/foo", String.valueOf(1234)); + assertEquals("http://example.com:8080/foo", url2); + } } diff --git a/src/test/java/biz/paluch/logging/gelf/NettyLocalHTTPServer.java b/src/test/java/biz/paluch/logging/gelf/NettyLocalHTTPServer.java new file mode 100644 index 000000000..71dbbc1b7 --- /dev/null +++ b/src/test/java/biz/paluch/logging/gelf/NettyLocalHTTPServer.java @@ -0,0 +1,52 @@ +package biz.paluch.logging.gelf; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpResponseStatus; + +import java.util.List; + +/** + * @author Mark Paluch + * @since 10.11.13 10:30 + */ +public class NettyLocalHTTPServer { + + private int port = 19393; + private EventLoopGroup group = new NioEventLoopGroup(); + private GelfInboundHTTPInitializer handlerInitializer = new GelfInboundHTTPInitializer(); + private Class channelClass = NioServerSocketChannel.class; + + private ChannelFuture f; + + public NettyLocalHTTPServer() { + } + + public void run() throws Exception { + + ServerBootstrap b = new ServerBootstrap(); + b.group(group); + b.channel((Class) channelClass).childHandler(handlerInitializer); + f = b.bind(port).sync(); + } + + public void close() { + f.channel().close(); + f = null; + } + + public List getJsonValues() { + return handlerInitializer.getHandler().getValues(); + } + + public void setReturnStatus(HttpResponseStatus status) { + handlerInitializer.getHandler().setReturnStatus(status); + } + + public GelfInboundHTTPInitializer getHandlerInitializer() { + return handlerInitializer; + } +} diff --git a/src/test/java/biz/paluch/logging/gelf/NettyLocalServer.java b/src/test/java/biz/paluch/logging/gelf/NettyLocalServer.java index 8af88f9a7..9b97735f5 100644 --- a/src/test/java/biz/paluch/logging/gelf/NettyLocalServer.java +++ b/src/test/java/biz/paluch/logging/gelf/NettyLocalServer.java @@ -2,12 +2,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.AdaptiveRecvByteBufAllocator; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.ServerChannel; +import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import java.util.List; diff --git a/src/test/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSenderTest.java b/src/test/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSenderTest.java new file mode 100644 index 000000000..d29fc916c --- /dev/null +++ b/src/test/java/biz/paluch/logging/gelf/intern/sender/GelfHTTPSenderTest.java @@ -0,0 +1,99 @@ +package biz.paluch.logging.gelf.intern.sender; + +import biz.paluch.logging.gelf.GelfTestSender; +import biz.paluch.logging.gelf.NettyLocalHTTPServer; +import biz.paluch.logging.gelf.intern.ErrorReporter; +import biz.paluch.logging.gelf.intern.GelfMessage; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.json.simple.JSONObject; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.MDC; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +/** + * (c) Alekandar - https://github.com/salex89 + */ + +public class GelfHTTPSenderTest { + + private NettyLocalHTTPServer server; + private GelfHTTPSender sender; + @Mock ErrorReporter errorReporter; + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + server = new NettyLocalHTTPServer(); + server.run(); + String uri = "http://127.0.0.1:19393"; + sender = new GelfHTTPSender(new URL(uri), errorReporter); + + } + + @After public void tearDown() { + server.close(); + sender.close(); + } + + @Test public void sendMessageTest() throws IOException { + + server.setReturnStatus(HttpResponseStatus.ACCEPTED); + + GelfMessage gelfMessage = new GelfMessage("shortMessage", "fullMessage", 12121l, "WARNING"); + boolean success = sender.sendMessage(gelfMessage); + assertTrue(success); + verifyZeroInteractions(errorReporter); + List jsonValues = server.getJsonValues(); + assertEquals(1, jsonValues.size()); + JSONObject messageJson = (JSONObject) jsonValues.get(0); + assertEquals(gelfMessage.getShortMessage(), messageJson.get("short_message")); + assertEquals(gelfMessage.getFullMessage(), messageJson.get("full_message")); + assertEquals(gelfMessage.getTimestamp(), messageJson.get("timestamp")); + assertEquals(gelfMessage.getLevel(), messageJson.get("level")); + + } + + @Test public void sendMessageFailureTest() throws IOException { + + server.setReturnStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); + String uri = "http://127.0.0.1:19393"; + GelfHTTPSender sender = new GelfHTTPSender(new URL(uri), errorReporter); + GelfMessage gelfMessage = new GelfMessage("shortMessage", "fullMessage", 12121l, "WARNING"); + boolean success = sender.sendMessage(gelfMessage); + assertFalse(success); + verify(errorReporter, times(1)).reportError(anyString(), any(Exception.class)); + } + + @Test public void testWithLoggingContext() throws JoranException { + LoggerContext lc = new LoggerContext(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(lc); + URL xmlConfigFile = getClass().getResource("/logback-gelf-with-http.xml"); + configurator.doConfigure(xmlConfigFile); + Logger testLogger = lc.getLogger("testLogger"); + testLogger.error("Hi there"); + List jsonValues = server.getJsonValues(); + String uri = server.getHandlerInitializer().getHandler().getUri(); + assertEquals("/foo/bar", uri); + assertEquals(1, jsonValues.size()); + JSONObject jsonObject = (JSONObject) jsonValues.get(0); + assertEquals("Hi there", jsonObject.get("short_message")); + + } + +} diff --git a/src/test/resources/logback-gelf-with-http.xml b/src/test/resources/logback-gelf-with-http.xml new file mode 100644 index 000000000..708fe4838 --- /dev/null +++ b/src/test/resources/logback-gelf-with-http.xml @@ -0,0 +1,28 @@ + + + + + test + + + + http://localhost:19393/foo/bar + 19393 + java-test + true + true + true + yyyy-MM-dd HH:mm:ss,SSSS + 8192 + fieldName1=fieldValue1,fieldName2=fieldValue2,myOriginHost=${HOSTNAME} + mdcField1,mdcField2 + 1.2.3.4 + + INFO + + + + + + +