New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
4.1 dev slf4j metric attributes #1275
4.1 dev slf4j metric attributes #1275
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I've left some comments.
} | ||
} | ||
|
||
private final LoggerProxy loggerProxy; | ||
private final Marker marker; | ||
private final String prefix; | ||
private final ThreadLocal<StringBuilder> threadLocalStringBuilder = ThreadLocal.withInitial(StringBuilder::new); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that using a ThreadLocal
is a premature optimization. It's not clear for me that it will provide a big performance boost over allocating a new StringBuilder
. Also, using ThreadLocal
can cause strange classloader leaks if one fail to clear a reference to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed ThreadLocal. StringBuilder has to be a local variable to the report
method since there is no guarantee the method won't be executed in parallel.
} | ||
|
||
for (Entry<String, Counter> entry : counters.entrySet()) { | ||
logCounter(entry.getKey(), entry.getValue()); | ||
if (!getDisabledMetricAttributes().contains(COUNT)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
count
is not used to filtering counters. It's used only to filter the count attribute of histograms, meters, and timers. Users can filter counters by MetricFilter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed if.
} | ||
} | ||
|
||
private void append(StringBuilder b, String key, long value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that perfomance benefits of not conveting primtives to wrappers are slim to have 4 apennd
methods. I think one method accepting an Object
should be enough, especially considering that in most cases we use Supplier
which forces auto-boxing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are worth keeping:
- they are very simple methods, easy to read, understand, change. no hidden code complexity here.
- convertDuration and convertRate return
double
so only having anappend(...,Object)
could cause extra auto-boxing - the JVM can likely optimize
primitive -> box(primitive) -> primitive
call chains - and yes, StringBuilder.append(long) is slightly more efficient than StringBuilder.append(Object) called with a Long (unless the JVM un-erases types for some core classes? -- Hotspot JVM does some surprising optimizations some times :) ).
I can remove them if you feel strongly about it but now that the code is there, I think it's worth keeping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough
import java.util.SortedMap; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Supplier; | ||
|
||
import static com.codahale.metrics.MetricAttribute.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use star imports, in the codebase we tend to use direct imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
* Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15"). | ||
* See {@link MetricAttribute}. | ||
* | ||
* @param disabledMetricAttributes a {@link MetricFilter} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/MetricFilter/a set of MetricAttribute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. fixed GraphiteReporter as well.
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
import static com.codahale.metrics.MetricAttribute.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please replace the star imports with direct imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
.filter(MetricFilter.ALL) | ||
.build(); | ||
|
||
private Set<MetricAttribute> disabledMetricAttributes = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of declaring disabledMetricAttributes as a field, I think we should it make a parameter to the infoReporter
and errorReporter
methods. Othwerise it's not clear how what's a relationship between it and the actual reporter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to use this pattern to override default values passed to the constructor of the main unit tested component. I think in the context of unit tests it makes sense since there is only 1 class that is supposed to be tested. In the context of Slf4jReporter, it should be clear that the only way of testing Slf4jReporter is to use its builder and that its builder takes an optional Set<MetricAttribute> disabledMetricAttributes
.
This pattern becomes particularly handy when there might be more than 1 default value to overwrite for some tests. The alternatives would be to either invoke the full builder with all the proper default values set but this leads to code duplication; or to add more arguments to the infoReporter
and errorReporter
factory methods to overwrite the default value but this doesn't scale well when more than a couple of variables needs their default values overwritten and the factory method may end up having as many parameters as the builder...
I'll add a comment clarifying this is used to build the reporter in each test and can be overridden. I will also move at the top of the infoReporter and errorReporter methods to hep identify the relationship.
} | ||
|
||
private void verifyError(String expectedLog) { | ||
if (expectedLog == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need conditional checks if we use it only in one place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is no longer necessary since attribute filtering doesn't apply to counters
|
||
private void verifyInfo(String expectedLog) { | ||
if (expectedLog == null) { | ||
verify(logger, never()).info(marker, expectedLog); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this branch of the execution isn't used at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the idea was to be consistent with how tests were written.
removed.
import java.util.SortedMap; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Supplier; | ||
|
||
import static com.codahale.metrics.MetricAttribute.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
* Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15"). | ||
* See {@link MetricAttribute}. | ||
* | ||
* @param disabledMetricAttributes a {@link MetricFilter} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. fixed GraphiteReporter as well.
} | ||
} | ||
|
||
private final LoggerProxy loggerProxy; | ||
private final Marker marker; | ||
private final String prefix; | ||
private final ThreadLocal<StringBuilder> threadLocalStringBuilder = ThreadLocal.withInitial(StringBuilder::new); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed ThreadLocal. StringBuilder has to be a local variable to the report
method since there is no guarantee the method won't be executed in parallel.
} | ||
|
||
for (Entry<String, Counter> entry : counters.entrySet()) { | ||
logCounter(entry.getKey(), entry.getValue()); | ||
if (!getDisabledMetricAttributes().contains(COUNT)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed if.
} | ||
} | ||
|
||
private void append(StringBuilder b, String key, long value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are worth keeping:
- they are very simple methods, easy to read, understand, change. no hidden code complexity here.
- convertDuration and convertRate return
double
so only having anappend(...,Object)
could cause extra auto-boxing - the JVM can likely optimize
primitive -> box(primitive) -> primitive
call chains - and yes, StringBuilder.append(long) is slightly more efficient than StringBuilder.append(Object) called with a Long (unless the JVM un-erases types for some core classes? -- Hotspot JVM does some surprising optimizations some times :) ).
I can remove them if you feel strongly about it but now that the code is there, I think it's worth keeping.
|
||
private void verifyInfo(String expectedLog) { | ||
if (expectedLog == null) { | ||
verify(logger, never()).info(marker, expectedLog); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the idea was to be consistent with how tests were written.
removed.
} | ||
|
||
private void verifyError(String expectedLog) { | ||
if (expectedLog == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is no longer necessary since attribute filtering doesn't apply to counters
} | ||
} | ||
|
||
private void appendLongIfEnabled(StringBuilder b, MetricAttribute metricAttribute, Supplier<Long> durationSupplier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed durationSupplier here since this isn't a duration.
} | ||
} | ||
|
||
private void appendDoubleIfEnabled(StringBuilder b, MetricAttribute metricAttribute, Supplier<Double> durationSupplier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
.filter(MetricFilter.ALL) | ||
.build(); | ||
|
||
private Set<MetricAttribute> disabledMetricAttributes = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to use this pattern to override default values passed to the constructor of the main unit tested component. I think in the context of unit tests it makes sense since there is only 1 class that is supposed to be tested. In the context of Slf4jReporter, it should be clear that the only way of testing Slf4jReporter is to use its builder and that its builder takes an optional Set<MetricAttribute> disabledMetricAttributes
.
This pattern becomes particularly handy when there might be more than 1 default value to overwrite for some tests. The alternatives would be to either invoke the full builder with all the proper default values set but this leads to code duplication; or to add more arguments to the infoReporter
and errorReporter
factory methods to overwrite the default value but this doesn't scale well when more than a couple of variables needs their default values overwritten and the factory method may end up having as many parameters as the builder...
I'll add a comment clarifying this is used to build the reporter in each test and can be overridden. I will also move at the top of the infoReporter and errorReporter methods to hep identify the relationship.
Thank you for the contribution! |
* Support for disabledMetricAttributes in Slf4jReporter * Tests for metric attribute filter in Slf4jReporter * Slf4jReporter: addressed PR feedback
@arteam Could you please release this change? It's been 8 months... |
Sure, will do a 4.1.0 release this week. |
#1273