Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2ffb989
commit c448456
Showing
8 changed files
with
330 additions
and
9 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
9 changes: 9 additions & 0 deletions
9
dropwizard-logging/src/main/java/io/dropwizard/logging/AsyncAppenderBaseProxy.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 | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,9 @@ | |||
package io.dropwizard.logging; | |||
|
|||
import ch.qos.logback.core.AsyncAppenderBase; | |||
import ch.qos.logback.core.spi.DeferredProcessingAware; | |||
|
|||
public interface AsyncAppenderBaseProxy<E extends DeferredProcessingAware> { | |||
|
|||
AsyncAppenderBase<E> getAppender(); | |||
} |
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
133 changes: 133 additions & 0 deletions
133
dropwizard-logging/src/main/java/io/dropwizard/logging/ThrottlingAppenderWrapper.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 | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,133 @@ | |||
package io.dropwizard.logging; | |||
|
|||
import ch.qos.logback.core.Appender; | |||
import ch.qos.logback.core.AsyncAppenderBase; | |||
import ch.qos.logback.core.Context; | |||
import ch.qos.logback.core.LogbackException; | |||
import ch.qos.logback.core.filter.Filter; | |||
import ch.qos.logback.core.spi.DeferredProcessingAware; | |||
import ch.qos.logback.core.spi.FilterReply; | |||
import ch.qos.logback.core.status.Status; | |||
import com.google.common.util.concurrent.RateLimiter; | |||
import io.dropwizard.util.Duration; | |||
|
|||
import java.util.List; | |||
import java.util.concurrent.TimeUnit; | |||
|
|||
/** | |||
* An {@link AsyncAppenderBase} that applies throttling to a proxied appender. | |||
* Throttling is defined by an average duration between messages. | |||
* Throttled messages are discarded. | |||
*/ | |||
class ThrottlingAppenderWrapper<E extends DeferredProcessingAware> implements Appender<E>, AsyncAppenderBaseProxy<E> { | |||
|
|||
private final AsyncAppenderBase<E> appender; | |||
private final RateLimiter rateLimiter; | |||
|
|||
public ThrottlingAppenderWrapper(AsyncAppenderBase<E> delegate, Duration messageRate) { | |||
this.appender = delegate; | |||
this.rateLimiter = RateLimiter.create(1_000_000_000.0 / messageRate.toNanoseconds()); | |||
} | |||
|
|||
@Override | |||
public AsyncAppenderBase<E> getAppender() { | |||
return appender; | |||
} | |||
|
|||
@Override | |||
public void start() { | |||
appender.start(); | |||
} | |||
|
|||
@Override | |||
public void stop() { | |||
appender.stop(); | |||
} | |||
|
|||
@Override | |||
public boolean isStarted() { | |||
return appender.isStarted(); | |||
} | |||
|
|||
@Override | |||
public void doAppend(E event) throws LogbackException { | |||
if (rateLimiter.tryAcquire()) { | |||
appender.doAppend(event); | |||
} | |||
} | |||
|
|||
@Override | |||
public String getName() { | |||
return appender.getName(); | |||
} | |||
|
|||
@Override | |||
public void setName(String name) { | |||
appender.setName(name); | |||
} | |||
|
|||
@Override | |||
public Context getContext() { | |||
return appender.getContext(); | |||
} | |||
|
|||
@Override | |||
public void setContext(Context context) { | |||
appender.setContext(context); | |||
} | |||
|
|||
@Override | |||
public void addStatus(Status status) { | |||
appender.addStatus(status); | |||
} | |||
|
|||
@Override | |||
public void addInfo(String msg) { | |||
appender.addInfo(msg); | |||
} | |||
|
|||
@Override | |||
public void addInfo(String msg, Throwable ex) { | |||
appender.addInfo(msg, ex); | |||
} | |||
|
|||
@Override | |||
public void addWarn(String msg) { | |||
appender.addWarn(msg); | |||
} | |||
|
|||
@Override | |||
public void addWarn(String msg, Throwable ex) { | |||
appender.addWarn(msg, ex); | |||
} | |||
|
|||
@Override | |||
public void addError(String msg) { | |||
appender.addError(msg); | |||
} | |||
|
|||
@Override | |||
public void addError(String msg, Throwable ex) { | |||
appender.addError(msg, ex); | |||
} | |||
|
|||
@Override | |||
public void addFilter(Filter<E> newFilter) { | |||
appender.addFilter(newFilter); | |||
} | |||
|
|||
@Override | |||
public void clearAllFilters() { | |||
appender.clearAllFilters(); | |||
} | |||
|
|||
@Override | |||
public List<Filter<E>> getCopyOfAttachedFiltersList() { | |||
return appender.getCopyOfAttachedFiltersList(); | |||
} | |||
|
|||
@Override | |||
public FilterReply getFilterChainDecision(E event) { | |||
return appender.getFilterChainDecision(event); | |||
} | |||
} |
130 changes: 130 additions & 0 deletions
130
dropwizard-logging/src/test/java/io/dropwizard/logging/ThrottlingAppenderTest.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 | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,130 @@ | |||
package io.dropwizard.logging; | |||
|
|||
import com.codahale.metrics.MetricRegistry; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.io.Resources; | |||
import io.dropwizard.configuration.ConfigurationValidationException; | |||
import io.dropwizard.configuration.FileConfigurationSourceProvider; | |||
import io.dropwizard.configuration.SubstitutingSourceProvider; | |||
import io.dropwizard.configuration.YamlConfigurationFactory; | |||
import io.dropwizard.jackson.Jackson; | |||
import io.dropwizard.validation.BaseValidator; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.commons.text.StrSubstitutor; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
|
|||
import java.io.File; | |||
import java.io.IOException; | |||
import java.net.URISyntaxException; | |||
import java.nio.file.Files; | |||
import java.util.List; | |||
|
|||
import static org.assertj.core.api.Assertions.assertThat; | |||
|
|||
public class ThrottlingAppenderTest { | |||
private final ObjectMapper objectMapper = Jackson.newObjectMapper(); | |||
private final YamlConfigurationFactory<DefaultLoggingFactory> factory = new YamlConfigurationFactory<>( | |||
DefaultLoggingFactory.class, | |||
BaseValidator.newValidator(), | |||
objectMapper, "dw"); | |||
|
|||
|
|||
private static File loadResource(String resourceName) throws URISyntaxException { | |||
return new File(Resources.getResource(resourceName).toURI()); | |||
} | |||
|
|||
@Test(expected = ConfigurationValidationException.class) | |||
public void appenderWithZeroMessageRate() throws Exception { | |||
final YamlConfigurationFactory<ConsoleAppenderFactory> factory = new YamlConfigurationFactory<>( | |||
ConsoleAppenderFactory.class, BaseValidator.newValidator(), Jackson.newObjectMapper(), "dw"); | |||
final ConsoleAppenderFactory appender = factory.build(loadResource("yaml/appender_with_zero_message_rate.yml")); | |||
} | |||
|
|||
@Test(expected = ConfigurationValidationException.class) | |||
public void appenderWithInvalidMessageRate() throws Exception { | |||
final YamlConfigurationFactory<ConsoleAppenderFactory> factory = new YamlConfigurationFactory<>( | |||
ConsoleAppenderFactory.class, BaseValidator.newValidator(), Jackson.newObjectMapper(), "dw"); | |||
final ConsoleAppenderFactory appender = factory.build(loadResource("yaml/appender_with_invalid_message_rate.yml")); | |||
} | |||
|
|||
@Rule | |||
public TemporaryFolder folder = new TemporaryFolder(); | |||
|
|||
private File newLog() throws IOException { | |||
return folder.newFile("throttling.log"); | |||
} | |||
|
|||
private DefaultLoggingFactory setup(File defaultLog, String messageRate) throws Exception { | |||
StrSubstitutor substitutor = new StrSubstitutor(ImmutableMap.of( | |||
"default", StringUtils.removeEnd(defaultLog.getAbsolutePath(), ".log"), | |||
"messageRate", messageRate | |||
)); | |||
DefaultLoggingFactory config = factory.build( | |||
new SubstitutingSourceProvider(new FileConfigurationSourceProvider(), substitutor), | |||
loadResource("yaml/logging-message-rate.yml").getPath()); | |||
config.configure(new MetricRegistry(), "test-logger"); | |||
return config; | |||
} | |||
|
|||
@Test | |||
public void overThrottlingLimit() throws Exception { | |||
File defaultLog = newLog(); | |||
DefaultLoggingFactory config = setup(defaultLog, "100ms"); | |||
Logger logger = LoggerFactory.getLogger("com.example.app"); | |||
Thread.sleep(1000); | |||
for (int i = 0; i < 100; i++) { | |||
logger.info("Application log {}", i); | |||
} | |||
config.stop(); | |||
assertThat(Files.readAllLines(defaultLog.toPath())).containsOnly( | |||
"INFO com.example.app: Application log 0", | |||
"INFO com.example.app: Application log 1", | |||
"INFO com.example.app: Application log 2", | |||
"INFO com.example.app: Application log 3", | |||
"INFO com.example.app: Application log 4", | |||
"INFO com.example.app: Application log 5", | |||
"INFO com.example.app: Application log 6", | |||
"INFO com.example.app: Application log 7", | |||
"INFO com.example.app: Application log 8", | |||
"INFO com.example.app: Application log 9", | |||
"INFO com.example.app: Application log 10"); | |||
} | |||
|
|||
@Test | |||
public void belowThrottlingLimit() throws Exception { | |||
File defaultLog = newLog(); | |||
DefaultLoggingFactory config = setup(defaultLog, "1ms"); | |||
Logger logger = LoggerFactory.getLogger("com.example.app"); | |||
Thread.sleep(1000); | |||
for (int i = 0; i < 1000; i++) { | |||
logger.info("Application log {}", i); | |||
} | |||
config.stop(); | |||
assertThat(Files.readAllLines(defaultLog.toPath())).size().isEqualTo(1000); | |||
} | |||
|
|||
@Test | |||
public void overThrottlingLimit2Seconds() throws Exception { | |||
File defaultLog = newLog(); | |||
DefaultLoggingFactory config = setup(defaultLog, "100ms"); | |||
Logger logger = LoggerFactory.getLogger("com.example.app"); | |||
Thread.sleep(1000); | |||
for (int i = 0; i < 100; i++) { | |||
if (i == 50) { | |||
Thread.sleep(1000); | |||
} | |||
logger.info("Application log {}", i); | |||
} | |||
config.stop(); | |||
List<String> lines = Files.readAllLines(defaultLog.toPath()); | |||
assertThat(lines).hasSize(21); | |||
assertThat(lines.get(0)).isEqualTo("INFO com.example.app: Application log 0"); | |||
assertThat(lines.get(20)).isEqualTo("INFO com.example.app: Application log 59"); | |||
} | |||
|
|||
} |
2 changes: 2 additions & 0 deletions
2
dropwizard-logging/src/test/resources/yaml/appender_with_invalid_message_rate.yml
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 | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,2 @@ | |||
type: console | |||
messageRate: 1d |
Oops, something went wrong.