/**
* Copyright 2015 StreamSets Inc.
*
* Licensed under 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 com.streamsets.pipeline.lib.parser.log;
import com.streamsets.pipeline.api.OnRecordError;
import com.streamsets.pipeline.api.Record;
import com.streamsets.pipeline.api.Stage;
import com.streamsets.pipeline.config.LogMode;
import com.streamsets.pipeline.config.OnParseError;
import com.streamsets.pipeline.lib.parser.DataParser;
import com.streamsets.pipeline.lib.parser.DataParserException;
import com.streamsets.pipeline.lib.parser.DataParserFactory;
import com.streamsets.pipeline.lib.parser.DataParserFactoryBuilder;
import com.streamsets.pipeline.lib.parser.DataParserFormat;
import com.streamsets.pipeline.sdk.ContextInfoCreator;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
public class TestLog4jParser {
private static final String LOG_LINE = "2015-03-20 15:53:31,161 DEBUG PipelineConfigurationValidator - " +
"Pipeline 'test:preview' validation. valid=true, canPreview=true, issuesCount=0";
private static final String DATE_LEVEL_CLASS =
"2015-03-24 17:49:16,808 ERROR ExceptionToHttpErrorProvider - ";
private static final String ERROR_MSG_WITH_STACK_TRACE = "REST API call error: LOG_PARSER_01 - Error parsing log line '2015-03-24 12:38:05,206 DEBUG LogConfigurator - Log starting, from configuration: /Users/harikiran/Documents/workspace/streamsets/dev/dist/target/streamsets-datacollector-1.0.0b2-SNAPSHOT/streamsets-datacollector-1.0.0b2-SNAPSHOT/etc/log4j.properties', reason : 'LOG_PARSER_03 - Log line 2015-03-24 12:38:05,206 DEBUG LogConfigurator - Log starting, from configuration: /Users/harikiran/Documents/workspace/streamsets/dev/dist/target/streamsets-datacollector-1.0.0b2-SNAPSHOT/streamsets-datacollector-1.0.0b2-SNAPSHOT/etc/log4j.properties does not confirm to Log4j Log Format'\n" +
"com.streamsets.pipeline.lib.parser.DataParserException: LOG_PARSER_01 - Error parsing log line '2015-03-24 12:38:05,206 DEBUG LogConfigurator - Log starting, from configuration: /Users/harikiran/Documents/workspace/streamsets/dev/dist/target/streamsets-datacollector-1.0.0b2-SNAPSHOT/streamsets-datacollector-1.0.0b2-SNAPSHOT/etc/log4j.properties', reason : 'LOG_PARSER_03 - Log line 2015-03-24 12:38:05,206 DEBUG LogConfigurator - Log starting, from configuration: /Users/harikiran/Documents/workspace/streamsets/dev/dist/target/streamsets-datacollector-1.0.0b2-SNAPSHOT/streamsets-datacollector-1.0.0b2-SNAPSHOT/etc/log4j.properties does not confirm to Log4j Log Format'\n" +
"\tat com.streamsets.pipeline.lib.parser.log.LogDataParser.parse(LogDataParser.java:69)\n" +
"\tat com.streamsets.pipeline.stage.origin.spooldir.SpoolDirSource.produce(SpoolDirSource.java:566)\n" +
"\tat com.streamsets.pipeline.stage.origin.spooldir.SpoolDirSource.produce(SpoolDirSource.java:535)\n" +
"\tat com.streamsets.pipeline.configurablestage.DSource.produce(DSource.java:24)\n" +
"\tat com.streamsets.pipeline.runner.StageRuntime.execute(StageRuntime.java:149)\n" +
"\tat com.streamsets.pipeline.runner.StagePipe.process(StagePipe.java:106)\n" +
"\tat com.streamsets.pipeline.runner.preview.PreviewPipelineRunner.run(PreviewPipelineRunner.java:85)\n" +
"\tat com.streamsets.pipeline.runner.Pipeline.run(Pipeline.java:98)\n" +
"\tat com.streamsets.pipeline.runner.preview.PreviewPipeline.run(PreviewPipeline.java:38)\n" +
"\tat com.streamsets.pipeline.restapi.PreviewResource.previewWithOverride(PreviewResource.java:105)\n" +
"\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
"\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\n" +
"\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" +
"\tat java.lang.reflect.Method.invoke(Method.java:606)\n" +
"\tat org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)\n" +
"\tat org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:151)\n" +
"\tat org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:171)\n" +
"\tat org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:152)\n" +
"\tat org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:104)\n" +
"\tat org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:384)\n" +
"\tat org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:342)\n" +
"\tat org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:101)\n" +
"\tat org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:271)\n" +
"\tat org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)\n" +
"\tat org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)\n" +
"\tat org.glassfish.jersey.internal.Errors.process(Errors.java:315)\n" +
"\tat org.glassfish.jersey.internal.Errors.process(Errors.java:297)\n" +
"\tat org.glassfish.jersey.internal.Errors.process(Errors.java:267)\n" +
"\tat org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:297)\n" +
"\tat org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:254)\n" +
"\tat org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1030)\n" +
"\tat org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)\n" +
"\tat org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)\n" +
"\tat org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)\n" +
"\tat org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)\n" +
"\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:769)\n" +
"\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1667)\n" +
"\tat com.streamsets.pipeline.http.LocaleDetectorFilter.doFilter(LocaleDetectorFilter.java:29)\n" +
"\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1650)\n" +
"\tat org.eclipse.jetty.servlets.UserAgentFilter.doFilter(UserAgentFilter.java:83)\n" +
"\tat org.eclipse.jetty.servlets.GzipFilter.doFilter(GzipFilter.java:300)\n" +
"\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1650)\n" +
"\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:583)\n" +
"\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n" +
"\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:542)\n" +
"\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)\n" +
"\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1125)\n" +
"\tat org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)\n" +
"\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)\n" +
"\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1059)\n" +
"\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n" +
"\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n" +
"\tat org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:309)\n" +
"\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)\n" +
"\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n" +
"\tat org.eclipse.jetty.server.Server.handle(Server.java:497)\n" +
"\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)\n" +
"\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:248)\n" +
"\tat org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\n" +
"\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:610)\n" +
"\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:539)\n" +
"\tat java.lang.Thread.run(Thread.java:745)\n" +
"Caused by: com.streamsets.pipeline.lib.parser.DataParserException: LOG_PARSER_03 - Log line 2015-03-24 12:38:05,206 DEBUG LogConfigurator - Log starting, from configuration: /Users/harikiran/Documents/workspace/streamsets/dev/dist/target/streamsets-datacollector-1.0.0b2-SNAPSHOT/streamsets-datacollector-1.0.0b2-SNAPSHOT/etc/log4j.properties does not confirm to Log4j Log Format\n" +
"\tat com.streamsets.pipeline.lib.parser.log.Log4jParser.handleNoMatch(Log4jParser.java:30)\n" +
"\tat com.streamsets.pipeline.lib.parser.log.GrokParser.parseLogLine(GrokParser.java:51)\n" +
"\tat com.streamsets.pipeline.lib.parser.log.LogDataParser.parse(LogDataParser.java:67)\n" +
"\t... 61 more";
private static final String LOG_LINE_WITH_STACK_TRACE = DATE_LEVEL_CLASS + ERROR_MSG_WITH_STACK_TRACE;
private Stage.Context getContext() {
return ContextInfoCreator.createSourceContext("i", false, OnRecordError.TO_ERROR,
Collections.<String>emptyList());
}
@Test
public void testParse() throws Exception {
DataParser parser = getDataParser(LOG_LINE, 1000, 0);
Assert.assertEquals(0, Long.parseLong(parser.getOffset()));
Record record = parser.parse();
Assert.assertNotNull(record);
Assert.assertEquals("id::0", record.getHeader().getSourceId());
Assert.assertEquals(LOG_LINE, record.get().getValueAsMap().get("originalLine").getValueAsString());
Assert.assertFalse(record.has("/truncated"));
Assert.assertEquals(141, Long.parseLong(parser.getOffset()));
Assert.assertTrue(record.has("/" + Constants.TIMESTAMP));
Assert.assertEquals("2015-03-20 15:53:31,161", record.get("/" + Constants.TIMESTAMP).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.SEVERITY));
Assert.assertEquals("DEBUG", record.get("/" + Constants.SEVERITY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.CATEGORY));
Assert.assertEquals("PipelineConfigurationValidator", record.get("/" + Constants.CATEGORY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.MESSAGE));
Assert.assertEquals("Pipeline 'test:preview' validation. valid=true, canPreview=true, issuesCount=0",
record.get("/" + Constants.MESSAGE).getValueAsString());
parser.close();
}
@Test
public void testParseWithOffset() throws Exception {
DataParser parser = getDataParser("Hello\n" + LOG_LINE, 1000, 6);
Assert.assertEquals(6, Long.parseLong(parser.getOffset()));
Record record = parser.parse();
Assert.assertNotNull(record);
Assert.assertEquals("id::6", record.getHeader().getSourceId());
Assert.assertEquals(LOG_LINE, record.get().getValueAsMap().get("originalLine").getValueAsString());
Assert.assertFalse(record.has("/truncated"));
Assert.assertEquals(147, Long.parseLong(parser.getOffset()));
Assert.assertTrue(record.has("/" + Constants.TIMESTAMP));
Assert.assertEquals("2015-03-20 15:53:31,161", record.get("/" + Constants.TIMESTAMP).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.SEVERITY));
Assert.assertEquals("DEBUG", record.get("/" + Constants.SEVERITY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.CATEGORY));
Assert.assertEquals("PipelineConfigurationValidator", record.get("/" + Constants.CATEGORY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.MESSAGE));
Assert.assertEquals("Pipeline 'test:preview' validation. valid=true, canPreview=true, issuesCount=0",
record.get("/" + Constants.MESSAGE).getValueAsString());
record = parser.parse();
Assert.assertNull(record);
Assert.assertEquals(-1, Long.parseLong(parser.getOffset()));
parser.close();
}
@Test(expected = IOException.class)
public void testClose() throws Exception {
DataParser parser = getDataParser("Hello\nByte", 1000, 0);
parser.close();
parser.parse();
}
@Test(expected = DataParserException.class)
public void testTruncate() throws Exception {
DataParser parser = getDataParser(LOG_LINE, 25, 0);
Assert.assertEquals(0, Long.parseLong(parser.getOffset()));
try {
parser.parse();
} finally {
parser.close();
}
}
@Test(expected = DataParserException.class)
public void testParseNonLogLine() throws Exception {
DataParser parser = getDataParser(
"127.0.0.1 ss h [10/Oct/2000:13:55:36 -0700] This is a log line that does not confirm to default log4j log format",
1000, 0);
Assert.assertEquals(0, Long.parseLong(parser.getOffset()));
try {
parser.parse();
} finally {
parser.close();
}
}
@Test
public void testParseLogLineWithStackTrace() throws Exception {
InputStream is = new ByteArrayInputStream(LOG_LINE_WITH_STACK_TRACE.getBytes());
DataParserFactoryBuilder dataParserFactoryBuilder = new DataParserFactoryBuilder(getContext(), DataParserFormat.LOG);
DataParserFactory factory = dataParserFactoryBuilder
.setMaxDataLen(10000)
.setMode(LogMode.LOG4J)
.setConfig(LogDataParserFactory.RETAIN_ORIGINAL_TEXT_KEY, true)
.setConfig(LogDataParserFactory.LOG4J_FORMAT_KEY, "%d{ISO8601} %-5p %c{1} - %m")
.setConfig(LogDataParserFactory.ON_PARSE_ERROR_KEY, OnParseError.INCLUDE_AS_STACK_TRACE)
.setConfig(LogDataParserFactory.LOG4J_TRIM_STACK_TRACES_TO_LENGTH_KEY, 100)
.build();
DataParser parser = factory.getParser("id", is, "0");
Assert.assertEquals(0, Long.parseLong(parser.getOffset()));
Record record = parser.parse();
Assert.assertNotNull(record);
Assert.assertEquals("id::0", record.getHeader().getSourceId());
Assert.assertEquals(LOG_LINE_WITH_STACK_TRACE, record.get().getValueAsMap().get("originalLine").getValueAsString());
Assert.assertFalse(record.has("/truncated"));
Assert.assertEquals(-1, Long.parseLong(parser.getOffset()));
Assert.assertTrue(record.has("/" + Constants.TIMESTAMP));
Assert.assertEquals("2015-03-24 17:49:16,808", record.get("/" + Constants.TIMESTAMP).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.SEVERITY));
Assert.assertEquals("ERROR", record.get("/" + Constants.SEVERITY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.CATEGORY));
Assert.assertEquals("ExceptionToHttpErrorProvider", record.get("/" + Constants.CATEGORY).getValueAsString());
Assert.assertTrue(record.has("/" + Constants.MESSAGE));
Assert.assertEquals(ERROR_MSG_WITH_STACK_TRACE, record.get("/" + Constants.MESSAGE).getValueAsString());
parser.close();
}
private DataParser getDataParser(String logLine, int maxObjectLength, int readerOffset) throws DataParserException {
InputStream is = new ByteArrayInputStream(logLine.getBytes());
DataParserFactoryBuilder dataParserFactoryBuilder = new DataParserFactoryBuilder(getContext(), DataParserFormat.LOG);
DataParserFactory factory = dataParserFactoryBuilder
.setMaxDataLen(maxObjectLength)
.setMode(LogMode.LOG4J)
.setOverRunLimit(1000)
.setConfig(LogDataParserFactory.RETAIN_ORIGINAL_TEXT_KEY, true)
.setConfig(LogDataParserFactory.LOG4J_FORMAT_KEY, "%d{ISO8601} %-5p %c{1} - %m")
.build();
return factory.getParser("id", is, String.valueOf(readerOffset));
}
}