diff --git a/hawtio-log-logback/pom.xml b/hawtio-log-logback/pom.xml
new file mode 100644
index 0000000000..b0a73540a1
--- /dev/null
+++ b/hawtio-log-logback/pom.xml
@@ -0,0 +1,52 @@
+
+
+
+ io.hawt
+ project
+ 2.15-SNAPSHOT
+
+
+ 4.0.0
+ hawtio-log-logback
+ ${project.artifactId}
+ hawtio :: hawtio-log-logback
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot-version}
+ pom
+ import
+
+
+
+
+
+
+ io.hawt
+ hawtio-log
+ ${project.version}
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ junit
+ junit
+ ${junit-version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core-version}
+
+
+
+
diff --git a/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQuery.java b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQuery.java
new file mode 100644
index 0000000000..1799885cc6
--- /dev/null
+++ b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQuery.java
@@ -0,0 +1,153 @@
+package io.hawt.log.logback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.spi.AppenderAttachable;
+import io.hawt.log.LogEvent;
+import io.hawt.log.LogFilter;
+import io.hawt.log.LogResults;
+import io.hawt.log.support.LogQueryBase;
+import io.hawt.log.support.LruList;
+import io.hawt.log.support.Predicate;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of {@link LogbackLogQueryMBean}.
+ */
+public class LogbackLogQuery extends LogQueryBase implements LogbackLogQueryMBean {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(LogbackLogQuery.class);
+
+ private static final String APPENDER_NAME = "LogQuery";
+
+ private int size = 2000;
+ private LruList events;
+
+ private final Appender appender = new HawtioAppender();
+
+ private final LoggingEventMapper eventMapper;
+
+ public LogbackLogQuery() {
+ super();
+ eventMapper = new LoggingEventMapper(getHostName());
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ appender.start();
+ reconnectAppender();
+ }
+
+ @Override
+ public void reconnectAppender() {
+ ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
+ Logger root = loggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+
+ if (root instanceof AppenderAttachable) {
+ appender.setContext((Context) loggerFactory);
+ @SuppressWarnings("unchecked")
+ AppenderAttachable attachable = (AppenderAttachable) root;
+ attachable.addAppender(appender);
+ LOG.info("Connected to Logback appender to trap logs with Hawtio log plugin");
+ } else {
+ LOG.warn("No appender-attachable root logger found so cannot attach Hawtio log appender!");
+ }
+ }
+
+ @Override
+ public void logMessage(LoggingEvent record) {
+ getEvents().add(record);
+ }
+
+ @Override
+ public LogResults getLogResults(int count) {
+ return filterLogResults(null, count);
+ }
+
+ @Override
+ public LogResults queryLogResults(LogFilter filter) {
+ Predicate predicate = createPredicate(filter);
+ int count = filter == null ? -1 : filter.getCount();
+ return filterLogResults(predicate, count);
+ }
+
+ protected LogResults filterLogResults(Predicate predicate, int maxCount) {
+ int matched = 0;
+ long from = Long.MAX_VALUE;
+ long to = Long.MIN_VALUE;
+ List list = new ArrayList<>();
+ Iterable elements = getEvents().getElements();
+ for (LoggingEvent element : elements) {
+ LogEvent logEvent = eventMapper.toLogEvent(element);
+ long timestamp = element.getTimeStamp();
+ if (timestamp > to) {
+ to = timestamp;
+ }
+ if (timestamp < from) {
+ from = timestamp;
+ }
+ if (predicate == null || predicate.matches(logEvent)) {
+ list.add(logEvent);
+ matched += 1;
+ if (maxCount > 0 && matched >= maxCount) {
+ break;
+ }
+ }
+ }
+
+ LogResults results = new LogResults();
+ results.setEvents(list);
+ if (from < Long.MAX_VALUE) {
+ results.setFromTimestamp(from);
+ }
+ if (to > Long.MIN_VALUE) {
+ results.setToTimestamp(to);
+ }
+ LOG.debug("Requested {} logging items, returning {} event(s) from possible {}",
+ maxCount, results.getEvents().size(), getEvents().size());
+ return results;
+ }
+
+ // -------------------------------------------------------------------------
+ // Properties
+ // -------------------------------------------------------------------------
+
+ @Override
+ public int getSize() {
+ return size;
+ }
+
+ @Override
+ public void setSize(int size) {
+ this.size = size;
+ if (events != null) {
+ events.resize(size);
+ }
+ }
+
+ public LruList getEvents() {
+ if (events == null) {
+ events = new LruList<>(LoggingEvent.class, getSize());
+ }
+ return events;
+ }
+
+ private class HawtioAppender extends AppenderBase {
+ public HawtioAppender() {
+ setName(APPENDER_NAME);
+ }
+
+ @Override
+ protected void append(LoggingEvent event) {
+ logMessage(event);
+ }
+ }
+}
diff --git a/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQueryMBean.java b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQueryMBean.java
new file mode 100644
index 0000000000..35bb08b62d
--- /dev/null
+++ b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQueryMBean.java
@@ -0,0 +1,22 @@
+package io.hawt.log.logback;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import io.hawt.log.support.LogQuerySupportMBean;
+
+/**
+ * The MBean operations for {@link LogbackLogQuery}
+ */
+public interface LogbackLogQueryMBean extends LogQuerySupportMBean {
+ /**
+ * Provides a hook you can call if the underlying log4j
+ * configuration is reloaded so that you can force the appender
+ * to get re-registered.
+ */
+ void reconnectAppender();
+
+ void logMessage(LoggingEvent record);
+
+ int getSize();
+
+ void setSize(int size);
+}
diff --git a/hawtio-log-logback/src/main/java/io/hawt/log/logback/LoggingEventMapper.java b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LoggingEventMapper.java
new file mode 100644
index 0000000000..07c9b637ca
--- /dev/null
+++ b/hawtio-log-logback/src/main/java/io/hawt/log/logback/LoggingEventMapper.java
@@ -0,0 +1,58 @@
+package io.hawt.log.logback;
+
+import java.util.Date;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import io.hawt.log.LogEvent;
+import io.hawt.log.support.ThrowableFormatter;
+
+public class LoggingEventMapper {
+
+ private String hostName;
+
+ public LoggingEventMapper(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public LogEvent toLogEvent(LoggingEvent loggingEvent) {
+ LogEvent answer = new LogEvent();
+ answer.setClassName(loggingEvent.getLoggerName());
+
+ ThrowableProxy throwable = (ThrowableProxy) loggingEvent.getThrowableProxy();
+ if (throwable != null) {
+ ThrowableFormatter formatter = new ThrowableFormatter();
+ String[] stack = formatter.doRender(throwable.getThrowable());
+ if (stack != null) {
+ answer.setException(stack);
+ }
+ }
+
+ StackTraceElement[] callerData = loggingEvent.getCallerData();
+ if (callerData != null && callerData.length > 0) {
+ StackTraceElement ste = callerData[0];
+ answer.setFileName(ste.getFileName());
+ answer.setClassName(ste.getClassName());
+ answer.setMethodName(ste.getMethodName());
+ answer.setLineNumber(String.valueOf(ste.getLineNumber()));
+ }
+
+ Level level = loggingEvent.getLevel();
+ if (level != null) {
+ answer.setLevel(level.toString());
+ }
+ answer.setLogger(loggingEvent.getLoggerName());
+ String message = loggingEvent.getFormattedMessage();
+ if (message != null) {
+ answer.setMessage(message);
+ }
+ answer.setProperties(loggingEvent.getMDCPropertyMap());
+ answer.setSeq(loggingEvent.getTimeStamp());
+ answer.setTimestamp(new Date(loggingEvent.getTimeStamp()));
+ answer.setThread(loggingEvent.getThreadName());
+ answer.setHost(hostName);
+
+ return answer;
+ }
+}
diff --git a/hawtio-log-logback/src/test/java/io/hawt/log/logback/LogbackLogQueryTest.java b/hawtio-log-logback/src/test/java/io/hawt/log/logback/LogbackLogQueryTest.java
new file mode 100644
index 0000000000..2bee167a42
--- /dev/null
+++ b/hawtio-log-logback/src/test/java/io/hawt/log/logback/LogbackLogQueryTest.java
@@ -0,0 +1,69 @@
+package io.hawt.log.logback;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import io.hawt.log.LogEvent;
+import io.hawt.log.LogFilter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LogbackLogQueryTest {
+ private static final transient Logger LOG = LoggerFactory.getLogger(LogbackLogQueryTest.class);
+
+ private LogbackLogQuery logQuery;
+
+ @Before
+ public void setUp() {
+ logQuery = new LogbackLogQuery();
+ logQuery.start();
+ }
+
+ @After
+ public void tearDown() {
+ logQuery.stop();
+ }
+
+ @Test
+ public void testGetLogResults() throws Exception {
+ // Given
+ String message = "testGetLogResults - Hello Hawtio";
+ IntStream.range(0, 10).forEach(i -> LOG.info(message + " #{}", i));
+
+ // When
+ List events = logQuery.getLogResults(10).getEvents();
+
+ // Then
+ assertThat(events.size()).isEqualTo(10);
+ List messages = events.stream()
+ .map(LogEvent::getMessage)
+ .collect(Collectors.toList());
+ assertThat(messages).contains(message + " #5");
+ }
+
+ @Test
+ public void testQueryLogResults() throws Exception {
+ // Given
+ String message = "testQueryLogResults - Hello Hawtio";
+ IntStream.range(0, 10).forEach(i -> LOG.info(message + " #{}", i));
+
+ // When
+ LogFilter filter = new LogFilter();
+ filter.setMatchesText(message);
+ filter.setCount(10);
+ List events = logQuery.queryLogResults(filter).getEvents();
+
+ // Then
+ assertThat(events.size()).isEqualTo(10);
+ events.stream()
+ .map(LogEvent::getMessage)
+ .forEach(m -> assertThat(m).contains(message));
+ }
+
+}
diff --git a/hawtio-log/src/main/java/io/hawt/log/LogEvent.java b/hawtio-log/src/main/java/io/hawt/log/LogEvent.java
index 4dcbd493ce..7a6ddb4cb1 100644
--- a/hawtio-log/src/main/java/io/hawt/log/LogEvent.java
+++ b/hawtio-log/src/main/java/io/hawt/log/LogEvent.java
@@ -7,20 +7,20 @@
import java.util.Date;
import java.util.Map;
-@JsonIgnoreProperties(ignoreUnknown=true)
+@JsonIgnoreProperties(ignoreUnknown = true)
public class LogEvent implements Comparable, Serializable {
private static final long serialVersionUID = 1L;
private static String defaultContainerName;
- private String host;
- private Long seq;
- private Date timestamp;
- private String level;
- private String logger;
- private String thread;
- private String message;
- private String[] exception;
- private Map properties;
+ private String host;
+ private Long seq;
+ private Date timestamp;
+ private String level;
+ private String logger;
+ private String thread;
+ private String message;
+ private String[] exception;
+ private Map properties;
private String className;
private String fileName;
private String methodName;
@@ -32,11 +32,11 @@ public class LogEvent implements Comparable, Serializable {
}
public static LogEvent toLogEvent(Object element) {
- if (element instanceof LogEvent) {
- return (LogEvent) element;
- }
- return null;
- }
+ if (element instanceof LogEvent) {
+ return (LogEvent) element;
+ }
+ return null;
+ }
public LogEvent() {
this.containerName = getDefaultContainerName();
@@ -50,7 +50,8 @@ public boolean equals(Object o) {
LogEvent logEvent = (LogEvent) o;
if (host != null ? !host.equals(logEvent.host) : logEvent.host != null) return false;
- if (containerName != null ? !containerName.equals(logEvent.containerName) : logEvent.containerName != null) return false;
+ if (containerName != null ? !containerName.equals(logEvent.containerName) : logEvent.containerName != null)
+ return false;
if (logger != null ? !logger.equals(logEvent.logger) : logEvent.logger != null) return false;
if (message != null ? !message.equals(logEvent.message) : logEvent.message != null) return false;
if (seq != null ? !seq.equals(logEvent.seq) : logEvent.seq != null) return false;
@@ -105,12 +106,12 @@ public String toString() {
}
public String getHost() {
- return host;
- }
+ return host;
+ }
- public void setHost(String host) {
- this.host = host;
- }
+ public void setHost(String host) {
+ this.host = host;
+ }
public String getContainerName() {
return containerName;
@@ -121,68 +122,68 @@ public void setContainerName(String containerName) {
}
public Long getSeq() {
- return seq;
- }
+ return seq;
+ }
- public void setSeq(Long seq) {
- this.seq = seq;
- }
+ public void setSeq(Long seq) {
+ this.seq = seq;
+ }
- public Date getTimestamp() {
- return timestamp;
- }
+ public Date getTimestamp() {
+ return timestamp;
+ }
- public void setTimestamp(Date timestamp) {
- this.timestamp = timestamp;
- }
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
- public String getLevel() {
- return level;
- }
+ public String getLevel() {
+ return level;
+ }
- public void setLevel(String level) {
- this.level = level;
- }
+ public void setLevel(String level) {
+ this.level = level;
+ }
- public String getLogger() {
- return logger;
- }
+ public String getLogger() {
+ return logger;
+ }
- public void setLogger(String logger) {
- this.logger = logger;
- }
+ public void setLogger(String logger) {
+ this.logger = logger;
+ }
- public String getThread() {
- return thread;
- }
+ public String getThread() {
+ return thread;
+ }
- public void setThread(String thread) {
- this.thread = thread;
- }
+ public void setThread(String thread) {
+ this.thread = thread;
+ }
- public String getMessage() {
- return message;
- }
+ public String getMessage() {
+ return message;
+ }
- public void setMessage(String message) {
- this.message = message;
- }
+ public void setMessage(String message) {
+ this.message = message;
+ }
- public Map getProperties() {
- return properties;
- }
+ public Map getProperties() {
+ return properties;
+ }
- public void setProperties(Map properties) {
- this.properties = properties;
- }
+ public void setProperties(Map properties) {
+ this.properties = properties;
+ }
- public String[] getException() {
- return exception;
- }
+ public String[] getException() {
+ return exception;
+ }
- public void setException(String[] exception) {
- this.exception = exception;
- }
+ public void setException(String[] exception) {
+ this.exception = exception;
+ }
public String getClassName() {
return className;
diff --git a/hawtio-log/src/main/java/io/hawt/log/log4j/Log4jLogQuery.txt b/hawtio-log/src/main/java/io/hawt/log/log4j/Log4jLogQuery.txt
new file mode 100644
index 0000000000..aa45742a11
--- /dev/null
+++ b/hawtio-log/src/main/java/io/hawt/log/log4j/Log4jLogQuery.txt
@@ -0,0 +1,398 @@
+package io.hawt.log.log4j;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import io.hawt.log.LogEvent;
+import io.hawt.log.LogFilter;
+import io.hawt.log.LogResults;
+import io.hawt.log.support.LogQuerySupport;
+import io.hawt.log.support.LruList;
+import io.hawt.log.support.Predicate;
+import org.apache.log4j.Appender;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static io.hawt.log.support.Objects.contains;
+
+// TODO: re-enable mvn resolver (requires code to be ported from fabric8 v1)
+
+/**
+ * A log4j adapter for LogQueryMBean
+ */
+public class Log4jLogQuery extends LogQuerySupport implements Log4jLogQueryMBean {
+ private static final transient Logger LOG = LoggerFactory.getLogger(Log4jLogQuery.class);
+
+ private int size = 2000;
+ private LruList events;
+ private boolean addMavenCoordinates = false;
+// private AetherBasedResolver resolver;
+// private MavenConfigurationImpl config;
+ private Properties properties = new Properties();
+ private final Appender appender = new AppenderSkeleton() {
+ protected void append(LoggingEvent loggingEvent) {
+ logMessage(loggingEvent);
+ }
+
+ public void close() {
+ }
+
+ public boolean requiresLayout() {
+ return true;
+ }
+ };
+
+
+ public void start() {
+ super.start();
+
+ reconnectAppender();
+ }
+
+ @Override
+ public void reconnectAppender() {
+ ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
+
+ boolean added = false;
+ if (loggerFactory instanceof AppenderAttachable) {
+ AppenderAttachable appenderAttachable = (AppenderAttachable) loggerFactory;
+ appender.setName("LogQuery");
+ appenderAttachable.addAppender(appender);
+ LOG.info("Connected to Log4j appender to trap logs with hawtio log plugin");
+ added = true;
+ }
+
+ org.apache.log4j.Logger root = LogManager.getRootLogger();
+ if (root != null) {
+ appender.setName("LogQuery");
+ root.addAppender(appender);
+ LOG.info("Connected to Log4j appender to trap logs with hawtio log plugin");
+ added = true;
+ }
+
+ if (!added) {
+ LOG.warn("No ILoggerFactory or RootLogger found so cannot attach hatwio log appender!");
+ }
+ }
+
+ public void stop() {
+ super.stop();
+ }
+
+ public LogResults getLogResults(int maxCount) throws IOException {
+ return filterLogResults(null, maxCount);
+ }
+
+ public LogResults queryLogResults(LogFilter filter) {
+ Predicate predicate = createPredicate(filter);
+ int maxCount = -1;
+ if (filter != null) {
+ maxCount = filter.getCount();
+ }
+ return filterLogResults(predicate, maxCount);
+ }
+
+ private Predicate createPredicate(LogFilter filter) {
+ if (filter == null) {
+ return null;
+ }
+ final List> predicates = new ArrayList>();
+
+ final Set levels = filter.getLevelsSet();
+ if (levels.size() > 0) {
+ predicates.add(new Predicate() {
+ @Override
+ public boolean matches(LogEvent event) {
+ String level = event.getLevel();
+ return level != null && levels.contains(level.toString());
+ }
+ });
+ }
+ final Long before = filter.getBeforeTimestamp();
+ if (before != null) {
+ final Date date = new Date(before);
+ predicates.add(new Predicate() {
+ @Override
+ public boolean matches(LogEvent event) {
+ Date time = event.getTimestamp();
+ return time != null && time.before(date);
+ }
+ });
+ }
+ final Long after = filter.getAfterTimestamp();
+ if (after != null) {
+ final Date date = new Date(after);
+ predicates.add(new Predicate() {
+ @Override
+ public boolean matches(LogEvent event) {
+ Date time = event.getTimestamp();
+ return time != null && time.after(date);
+ }
+ });
+ }
+
+ final String matchesText = filter.getMatchesText();
+ if (matchesText != null && matchesText.length() > 0) {
+ predicates.add(new Predicate() {
+ @Override
+ public boolean matches(LogEvent event) {
+ if (contains(matchesText, event.getClassName(), event.getMessage(), event.getLogger(), event.getThread())) {
+ return true;
+ }
+ String[] throwableStrRep = event.getException();
+ if (throwableStrRep != null && contains(matchesText, throwableStrRep)) {
+ return true;
+ }
+ Map properties = event.getProperties();
+ if (properties != null && contains(matchesText, properties.toString())) {
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ if (predicates.size() == 0) {
+ return null;
+ } else if (predicates.size() == 1) {
+ return predicates.get(0);
+ } else {
+ return new Predicate() {
+ @Override
+ public String toString() {
+ return "AndPredicate" + predicates;
+ }
+
+ @Override
+ public boolean matches(LogEvent event) {
+ for (Predicate predicate : predicates) {
+ if (!predicate.matches(event)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ }
+ }
+
+ protected LogResults filterLogResults(Predicate predicate, int maxCount) {
+ int matched = 0;
+ long from = Long.MAX_VALUE;
+ long to = Long.MIN_VALUE;
+ List list = new ArrayList();
+ Iterable elements = getEvents().getElements();
+ for (LoggingEvent element : elements) {
+ LogEvent logEvent = toLogEvent(element);
+ long timestamp = element.getTimeStamp();
+ if (timestamp > to) {
+ to = timestamp;
+ }
+ if (timestamp < from) {
+ from = timestamp;
+ }
+ if (logEvent != null) {
+ if (predicate == null || predicate.matches(logEvent)) {
+ list.add(logEvent);
+ matched += 1;
+ if (maxCount > 0 && matched >= maxCount) {
+ break;
+ }
+ }
+ }
+ }
+ LogResults results = new LogResults();
+ results.setEvents(list);
+ if (from < Long.MAX_VALUE) {
+ results.setFromTimestamp(from);
+ }
+ if (to > Long.MIN_VALUE) {
+ results.setToTimestamp(to);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Requested " + maxCount + " logging items. returning "
+ + results.getEvents().size() + " event(s) from a possible " + getEvents().size());
+
+ }
+ return results;
+ }
+
+ protected LogEvent toLogEvent(LoggingEvent element) {
+ LogEvent answer = new LogEvent();
+ answer.setClassName(element.getFQNOfLoggerClass());
+ // TODO
+ //answer.setContainerName(element.get);
+ ThrowableInformation throwableInformation = element.getThrowableInformation();
+ if (throwableInformation != null) {
+ ThrowableFormatter renderer = new ThrowableFormatter();
+ String[] stack = renderer.doRender(throwableInformation.getThrowable());
+ if (stack == null) {
+ stack = element.getThrowableStrRep();
+ }
+ answer.setException(stack);
+ }
+ LocationInfo locationInformation = element.getLocationInformation();
+ if (locationInformation != null) {
+ answer.setFileName(locationInformation.getFileName());
+ answer.setClassName(locationInformation.getClassName());
+ answer.setMethodName(locationInformation.getMethodName());
+ answer.setLineNumber(locationInformation.getLineNumber());
+ }
+ Level level = element.getLevel();
+ if (level != null) {
+ answer.setLevel(level.toString());
+ }
+ // TODO
+ answer.setLogger(element.getLoggerName());
+ Category logger = element.getLogger();
+ Object message = element.getMessage();
+ if (message != null) {
+ // TODO marshal differently?
+ answer.setMessage(message.toString());
+ }
+ answer.setProperties(element.getProperties());
+ // TODO
+ answer.setSeq(element.getTimeStamp());
+ answer.setTimestamp(new Date(element.getTimeStamp()));
+ answer.setThread(element.getThreadName());
+ answer.setHost(getHostName());
+ return answer;
+ }
+
+ protected String filterLogEvents(LogFilter filter) throws IOException {
+ // TODO
+ return null;
+ }
+
+
+ protected void appendMavenCoordinates(LoggingEvent loggingEvent) {
+ LocationInfo information = loggingEvent.getLocationInformation();
+ if (information != null) {
+ String coordinates = MavenCoordHelper.getMavenCoordinates(information.getClassName());
+ if (coordinates != null) {
+ loggingEvent.setProperty("maven.coordinates", coordinates);
+ }
+ }
+ }
+
+ protected String loadCoords(String coords, String filePath, String classifier) throws IOException {
+ String[] split = coords.split("/");
+ if (split != null && split.length > 2) {
+ String groupId = split[0];
+ String artifactId = split[1];
+ String version = split[2];
+ /*if (resolver == null) {
+ Properties defaultProperties = getDefaultProperties();
+ Properties systemProperties = System.getProperties();
+ if (config == null) {
+ Properties combined = new Properties();
+ combined.putAll(defaultProperties);
+ combined.putAll(systemProperties);
+ if (properties != null) {
+ combined.putAll(properties);
+ }
+ config = new MavenConfigurationImpl(new PropertiesPropertyResolver(combined), ServiceConstants.PID);
+ }
+ resolver = new AetherBasedResolver(config);
+ }
+ File file = resolver.resolveFile(groupId, artifactId, classifier, "jar", version);
+ if (file.exists() && file.isFile()) {
+ if (isRoot(filePath)) {
+ return jarIndex(file);
+ }
+ String fileUri = file.toURI().toString();
+ URL url = new URL("jar:" + fileUri + "!" + filePath);
+ return loadString(url);
+ } */
+ }
+ return null;
+ }
+
+ protected Properties getDefaultProperties() {
+ Properties defaultProperties = new Properties();
+ defaultProperties.setProperty("org.ops4j.pax.url.mvn.repositories",
+ "http://repo1.maven.org/maven2@id=maven.central.repo, " +
+ "https://repo.fusesource.com/nexus/content/repositories/releases@id=fusesource.release.repo, " +
+ "https://repo.fusesource.com/nexus/content/groups/ea@id=fusesource.ea.repo, " +
+ "http://svn.apache.org/repos/asf/servicemix/m2-repo@id=servicemix.repo, " +
+ "http://repository.springsource.com/maven/bundles/release@id=springsource.release.repo, " +
+ "http://repository.springsource.com/maven/bundles/external@id=springsource.external.repo, " +
+ "https://oss.sonatype.org/content/groups/scala-tools@id=scala.repo");
+ return defaultProperties;
+ }
+
+ // Properties
+ //-------------------------------------------------------------------------
+ public LruList getEvents() {
+ if (events == null) {
+ events = new LruList(LoggingEvent.class, getSize());
+ }
+ return events;
+ }
+
+ public void setEvents(LruList events) {
+ this.events = events;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public boolean isAddMavenCoordinates() {
+ return addMavenCoordinates;
+ }
+
+ public void setAddMavenCoordinates(boolean addMavenCoordinates) {
+ this.addMavenCoordinates = addMavenCoordinates;
+ }
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public void setProperties(Properties properties) {
+ this.properties = properties;
+ }
+
+// public MavenConfigurationImpl getConfig() {
+// return config;
+// }
+//
+// public void setConfig(MavenConfigurationImpl config) {
+// this.config = config;
+// }
+//
+// public AetherBasedResolver getResolver() {
+// return resolver;
+// }
+//
+// public void setResolver(AetherBasedResolver resolver) {
+// this.resolver = resolver;
+// }
+
+ @Override
+ public void logMessage(LoggingEvent record) {
+ if (addMavenCoordinates) {
+ appendMavenCoordinates(record);
+ }
+ getEvents().add(record);
+ }
+}
diff --git a/hawtio-log/src/main/java/io/hawt/log/support/LogQueryBase.java b/hawtio-log/src/main/java/io/hawt/log/support/LogQueryBase.java
new file mode 100644
index 0000000000..f876a02777
--- /dev/null
+++ b/hawtio-log/src/main/java/io/hawt/log/support/LogQueryBase.java
@@ -0,0 +1,72 @@
+package io.hawt.log.support;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.hawt.log.LogEvent;
+import io.hawt.log.LogFilter;
+
+import static io.hawt.log.support.Objects.contains;
+
+public abstract class LogQueryBase extends LogQuerySupport {
+
+ protected Predicate createPredicate(LogFilter filter) {
+ if (filter == null) {
+ return null;
+ }
+ List> predicates = new ArrayList<>();
+
+ Set levels = filter.getLevelsSet();
+ if (levels.size() > 0) {
+ predicates.add(e -> e.getLevel() != null && levels.contains(e.getLevel()));
+ }
+ Long before = filter.getBeforeTimestamp();
+ if (before != null) {
+ Date date = new Date(before);
+ predicates.add(e -> e.getTimestamp() != null && e.getTimestamp().before(date));
+ }
+ Long after = filter.getAfterTimestamp();
+ if (after != null) {
+ Date date = new Date(after);
+ predicates.add(e -> e.getTimestamp() != null && e.getTimestamp().after(date));
+ }
+
+ String matchesText = filter.getMatchesText();
+ if (matchesText != null && matchesText.length() > 0) {
+ predicates.add(e -> matches(e, matchesText));
+ }
+
+ if (predicates.isEmpty()) {
+ return null;
+ } else if (predicates.size() == 1) {
+ return predicates.get(0);
+ } else {
+ return new Predicate() {
+ @Override
+ public String toString() {
+ return "AndPredicate" + predicates;
+ }
+
+ @Override
+ public boolean matches(LogEvent event) {
+ return predicates.stream().allMatch(p -> p.matches(event));
+ }
+ };
+ }
+ }
+
+ private boolean matches(LogEvent event, String text) {
+ if (contains(text, event.getClassName(), event.getMessage(), event.getLogger(), event.getThread())) {
+ return true;
+ }
+ String[] throwableStrRep = event.getException();
+ if (throwableStrRep != null && contains(text, throwableStrRep)) {
+ return true;
+ }
+ Map properties = event.getProperties();
+ return properties != null && contains(text, properties.toString());
+ }
+}
diff --git a/hawtio-log/src/main/java/io/hawt/log/support/LogQuerySupport.java b/hawtio-log/src/main/java/io/hawt/log/support/LogQuerySupport.java
index a6bb84bd5a..51a576857e 100644
--- a/hawtio-log/src/main/java/io/hawt/log/support/LogQuerySupport.java
+++ b/hawtio-log/src/main/java/io/hawt/log/support/LogQuerySupport.java
@@ -13,6 +13,7 @@
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
@@ -54,9 +55,8 @@ protected static String loadString(URL url) throws IOException {
StringWriter writer = new StringWriter();
final char[] buffer = new char[4096];
int n;
- while ( -1 != ( n = reader.read( buffer ) ) )
- {
- writer.write( buffer, 0, n );
+ while (-1 != (n = reader.read(buffer))) {
+ writer.write(buffer, 0, n);
}
writer.flush();
return writer.toString();
@@ -223,7 +223,7 @@ protected String getArtifactFile(String mavenCoords, String filePath, String cla
String coords = mavenCoords.replace(':', '/');
String[] array = coords.split("\\s+");
- if (array == null || array.length < 2) {
+ if (array.length < 2) {
return loadCoords(coords, filePath, classifier);
} else {
// lets enumerate all values if space separated
@@ -294,8 +294,6 @@ protected void addJarEntryToIndex(JarEntry entry, StringBuilder buffer) {
}
}
-
-
/**
* Returns true if the file path is "/" or empty
*/
diff --git a/hawtio-log/src/main/java/io/hawt/log/support/LruList.java b/hawtio-log/src/main/java/io/hawt/log/support/LruList.java
index aec84a853c..e1eeed8f7c 100644
--- a/hawtio-log/src/main/java/io/hawt/log/support/LruList.java
+++ b/hawtio-log/src/main/java/io/hawt/log/support/LruList.java
@@ -1,14 +1,16 @@
package io.hawt.log.support;
import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.stream.StreamSupport;
/**
* A simple LRU list that stores a fixed size
*/
public class LruList {
private final Class klass;
- private final int size;
+ private int size;
private T[] elements;
private transient int start = 0;
private transient int end = 0;
@@ -17,14 +19,28 @@ public class LruList {
public LruList(Class klass, int size) {
this.klass = klass;
- this.size = size;
if (size <= 0) {
throw new IllegalArgumentException("The size must be greater than 0");
}
+ this.size = size;
elements = createArray(size);
maxElements = elements.length;
}
+ public synchronized void resize(int size) {
+ if (size <= 0) {
+ throw new IllegalArgumentException("The size must be greater than 0");
+ }
+ this.size = size;
+ // existing entries
+ Iterable elements = getElements();
+ // initialise list with new size
+ clear();
+ for (T element : elements) {
+ add(element);
+ }
+ }
+
public synchronized int size() {
int size = 0;
if (end < start) {
@@ -45,7 +61,7 @@ public synchronized void clear() {
public synchronized void add(T element) {
if (null == element) {
- throw new NullPointerException("Attempted to add null object to buffer");
+ throw new NullPointerException("Attempted to add null object to buffer");
}
if (size() == maxElements) {
Object e = elements[start];
@@ -70,18 +86,18 @@ public synchronized Iterable getElements() {
return getElements(size());
}
- public synchronized Iterable getElements(int nb) {
+ public synchronized Iterable getElements(int number) {
int s = size();
- nb = Math.min(Math.max(0, nb), s);
- T[] e = createArray(nb);
- for (int i = 0; i < nb; i++) {
- e[i] = elements[(i + s - nb + start) % maxElements];
+ int n = Math.min(Math.max(0, number), s);
+ T[] e = createArray(n);
+ for (int i = 0; i < n; i++) {
+ e[i] = elements[(i + s - n + start) % maxElements];
}
return Arrays.asList(e);
}
+ @SuppressWarnings("unchecked")
private T[] createArray(int size) {
return (T[]) Array.newInstance(klass, size);
}
-
}
diff --git a/hawtio-log/src/main/java/io/hawt/log/log4j/MavenCoordHelper.java b/hawtio-log/src/main/java/io/hawt/log/support/MavenCoordHelper.java
similarity index 97%
rename from hawtio-log/src/main/java/io/hawt/log/log4j/MavenCoordHelper.java
rename to hawtio-log/src/main/java/io/hawt/log/support/MavenCoordHelper.java
index 56b96664cc..9666b94cf3 100644
--- a/hawtio-log/src/main/java/io/hawt/log/log4j/MavenCoordHelper.java
+++ b/hawtio-log/src/main/java/io/hawt/log/support/MavenCoordHelper.java
@@ -1,7 +1,5 @@
-package io.hawt.log.log4j;
+package io.hawt.log.support;
-import io.hawt.log.support.MavenCoordinates;
-import io.hawt.log.support.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -94,7 +92,6 @@ public static String getMavenCoordinates(Class cls) throws IOException {
return buffer.toString();
}
-
/**
* Find class given class name.
*
diff --git a/hawtio-log/src/main/java/io/hawt/log/support/Objects.java b/hawtio-log/src/main/java/io/hawt/log/support/Objects.java
index 066cc850ee..4a6bbb368a 100644
--- a/hawtio-log/src/main/java/io/hawt/log/support/Objects.java
+++ b/hawtio-log/src/main/java/io/hawt/log/support/Objects.java
@@ -1,21 +1,13 @@
package io.hawt.log.support;
+import java.util.Arrays;
+
public final class Objects {
public static boolean isBlank(String text) {
return text == null || text.trim().length() == 0;
}
- /**
- * A helper method for comparing objects for equality while handling nulls
- */
- public static boolean equal(Object a, Object b) {
- if (a == b) {
- return true;
- }
- return a != null && b != null && a.equals(b);
- }
-
/**
* A helper method for performing an ordered comparison on the objects
* handling nulls and objects which do not handle sorting gracefully
@@ -35,8 +27,7 @@ public static int compare(Object a, Object b) {
return 1;
}
if (a instanceof Comparable) {
- Comparable comparable = (Comparable)a;
- return comparable.compareTo(b);
+ return ((Comparable) a).compareTo(b);
}
int answer = a.getClass().getName().compareTo(b.getClass().getName());
if (answer == 0) {
@@ -46,11 +37,8 @@ public static int compare(Object a, Object b) {
}
public static boolean contains(String matchesText, String... values) {
- for (String v : values) {
- if (v != null && v.contains(matchesText)) {
- return true;
- }
- }
- return false;
+ return Arrays.stream(values)
+ .filter(java.util.Objects::nonNull)
+ .anyMatch(v -> v.contains(matchesText));
}
}
diff --git a/hawtio-log/src/main/java/io/hawt/log/log4j/ThrowableFormatter.java b/hawtio-log/src/main/java/io/hawt/log/support/ThrowableFormatter.java
similarity index 96%
rename from hawtio-log/src/main/java/io/hawt/log/log4j/ThrowableFormatter.java
rename to hawtio-log/src/main/java/io/hawt/log/support/ThrowableFormatter.java
index 8a8576c4d5..ee28f52eb8 100644
--- a/hawtio-log/src/main/java/io/hawt/log/log4j/ThrowableFormatter.java
+++ b/hawtio-log/src/main/java/io/hawt/log/support/ThrowableFormatter.java
@@ -1,4 +1,4 @@
-package io.hawt.log.log4j;
+package io.hawt.log.support;
import java.io.File;
import java.lang.reflect.Method;
@@ -7,8 +7,6 @@
import java.util.HashMap;
import java.util.Map;
-import io.hawt.log.support.Objects;
-
/**
* Code modified from log4j to format exceptions
*/
@@ -22,7 +20,6 @@ public class ThrowableFormatter {
*/
private Method getClassNameMethod;
-
/**
* Construct new instance.
*/
@@ -43,7 +40,7 @@ public String[] doRender(final Throwable throwable) {
if (getStackTraceMethod != null) {
try {
Object[] noArgs = null;
- Object[] elements = (Object[])getStackTraceMethod.invoke(throwable, noArgs);
+ Object[] elements = (Object[]) getStackTraceMethod.invoke(throwable, noArgs);
String[] lines = new String[elements.length + 1];
lines[0] = throwable.toString();
Map classMap = new HashMap();
@@ -68,7 +65,7 @@ private String formatElement(final Object element, final Map classMap) {
StringBuffer buf = new StringBuffer("\tat ");
buf.append(element);
try {
- String className = getClassNameMethod.invoke(element, (Object[])null).toString();
+ String className = getClassNameMethod.invoke(element, (Object[]) null).toString();
Object classDetails = classMap.get(className);
if (classDetails != null) {
buf.append(classDetails);
diff --git a/platforms/springboot/pom.xml b/platforms/springboot/pom.xml
index f86bb02847..0ab198097a 100644
--- a/platforms/springboot/pom.xml
+++ b/platforms/springboot/pom.xml
@@ -38,6 +38,16 @@
hawtio-system
${project.version}
+
+ io.hawt
+ hawtio-log
+ ${project.version}
+
+
+ io.hawt
+ hawtio-log-logback
+ ${project.version}
+
org.springframework.boot
spring-boot-actuator
@@ -90,7 +100,7 @@
org.mockito
mockito-core
- 2.20.0
+ ${mockito-core-version}
test
diff --git a/platforms/springboot/src/main/java/io/hawt/springboot/SpringHawtioContextListener.java b/platforms/springboot/src/main/java/io/hawt/springboot/SpringHawtioContextListener.java
index dd1e3fdbfc..61559c522b 100755
--- a/platforms/springboot/src/main/java/io/hawt/springboot/SpringHawtioContextListener.java
+++ b/platforms/springboot/src/main/java/io/hawt/springboot/SpringHawtioContextListener.java
@@ -5,23 +5,27 @@
import javax.servlet.ServletContextEvent;
import io.hawt.HawtioContextListener;
+import io.hawt.log.logback.LogbackLogQuery;
import io.hawt.system.ConfigManager;
import io.hawt.system.HawtioProperty;
public class SpringHawtioContextListener extends HawtioContextListener {
public final String servletPath;
+ private final LogbackLogQuery logQuery;
public SpringHawtioContextListener(final ConfigManager configManager,
- final String servletPath) {
+ final String servletPath) {
super(configManager);
this.servletPath = Objects.requireNonNull(servletPath);
+ this.logQuery = new LogbackLogQuery();
}
@Override
public void contextInitialized(final ServletContextEvent servletContextEvent) {
super.contextInitialized(servletContextEvent);
+ logQuery.start();
servletContextEvent.getServletContext()
- .setAttribute(HawtioProperty.SERVLET_PATH, this.servletPath);
+ .setAttribute(HawtioProperty.SERVLET_PATH, this.servletPath);
}
}
diff --git a/pom.xml b/pom.xml
index d879efd526..a2e2a77da0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,9 +57,10 @@
1.1.0
1.12
- 5.3.14
+ 5.3.17
2.6.2
+ 3.22.0
1.15
2.11.0
1.2
@@ -74,7 +75,7 @@
9.4.43.v20210629
${jetty-version}
1.7.0
- 4.12
+ 4.13.2
4.3.3
[4.0,5)
11.0.2
@@ -86,6 +87,7 @@
2.6
3.0.0-M1
3.2.3
+ 4.3.1
20171018
5.0.0
3.1.0
@@ -118,6 +120,7 @@
hawtio-ide
hawtio-local-jvm-mbean
hawtio-log
+ hawtio-log-logback
hawtio-log-osgi
hawtio-plugin-mbean
hawtio-system
@@ -249,7 +252,7 @@
org.apache.maven.plugins
maven-failsafe-plugin
${maven-failsafe-plugin-version}
-
+
@@ -283,6 +286,7 @@
org.apache.maven.plugins
maven-gpg-plugin
+
${gpg.passphrase}