Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(springboot): ENTESB-18661 implement LogQueryMBean for Logback/Sp…
…ring Boot Fix #2722
- Loading branch information
Showing
16 changed files
with
956 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<parent> | ||
<groupId>io.hawt</groupId> | ||
<artifactId>project</artifactId> | ||
<version>2.15-SNAPSHOT</version> | ||
</parent> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<artifactId>hawtio-log-logback</artifactId> | ||
<name>${project.artifactId}</name> | ||
<description>hawtio :: hawtio-log-logback</description> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-dependencies</artifactId> | ||
<version>${spring-boot-version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.hawt</groupId> | ||
<artifactId>hawtio-log</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-classic</artifactId> | ||
</dependency> | ||
|
||
<!-- testing --> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>${junit-version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<version>${assertj-core-version}</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
153 changes: 153 additions & 0 deletions
153
hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQuery.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<LoggingEvent> events; | ||
|
||
private final Appender<LoggingEvent> 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<LoggingEvent> attachable = (AppenderAttachable<LoggingEvent>) 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<LogEvent> predicate = createPredicate(filter); | ||
int count = filter == null ? -1 : filter.getCount(); | ||
return filterLogResults(predicate, count); | ||
} | ||
|
||
protected LogResults filterLogResults(Predicate<LogEvent> predicate, int maxCount) { | ||
int matched = 0; | ||
long from = Long.MAX_VALUE; | ||
long to = Long.MIN_VALUE; | ||
List<LogEvent> list = new ArrayList<>(); | ||
Iterable<LoggingEvent> 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<LoggingEvent> getEvents() { | ||
if (events == null) { | ||
events = new LruList<>(LoggingEvent.class, getSize()); | ||
} | ||
return events; | ||
} | ||
|
||
private class HawtioAppender extends AppenderBase<LoggingEvent> { | ||
public HawtioAppender() { | ||
setName(APPENDER_NAME); | ||
} | ||
|
||
@Override | ||
protected void append(LoggingEvent event) { | ||
logMessage(event); | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
hawtio-log-logback/src/main/java/io/hawt/log/logback/LogbackLogQueryMBean.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
58 changes: 58 additions & 0 deletions
58
hawtio-log-logback/src/main/java/io/hawt/log/logback/LoggingEventMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
hawtio-log-logback/src/test/java/io/hawt/log/logback/LogbackLogQueryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<LogEvent> events = logQuery.getLogResults(10).getEvents(); | ||
|
||
// Then | ||
assertThat(events.size()).isEqualTo(10); | ||
List<String> 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<LogEvent> events = logQuery.queryLogResults(filter).getEvents(); | ||
|
||
// Then | ||
assertThat(events.size()).isEqualTo(10); | ||
events.stream() | ||
.map(LogEvent::getMessage) | ||
.forEach(m -> assertThat(m).contains(message)); | ||
} | ||
|
||
} |
Oops, something went wrong.