Author: justin Date: Mon Aug 7 13:30:08 2017 New Revision: 1804334 URL: http://svn.apache.org/viewvc?rev=1804334&view=rev Log: SLING-7031 - configurable component to write subset of metrics to log file on a recurring basis.
Added: sling/trunk/bundles/commons/metrics/src/main/java/org/apache/sling/commons/metrics/internal/LogReporter.java sling/trunk/bundles/commons/metrics/src/test/java/org/apache/sling/commons/metrics/internal/LogReporterTest.java Modified: sling/trunk/bundles/commons/metrics/pom.xml Modified: sling/trunk/bundles/commons/metrics/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/metrics/pom.xml?rev=1804334&r1=1804333&r2=1804334&view=diff ============================================================================== --- sling/trunk/bundles/commons/metrics/pom.xml (original) +++ sling/trunk/bundles/commons/metrics/pom.xml Mon Aug 7 13:30:08 2017 @@ -238,6 +238,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>junit-addons</groupId> + <artifactId>junit-addons</artifactId> + <version>1.4</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.testing.paxexam</artifactId> <version>0.0.4</version> Added: sling/trunk/bundles/commons/metrics/src/main/java/org/apache/sling/commons/metrics/internal/LogReporter.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/metrics/src/main/java/org/apache/sling/commons/metrics/internal/LogReporter.java?rev=1804334&view=auto ============================================================================== --- sling/trunk/bundles/commons/metrics/src/main/java/org/apache/sling/commons/metrics/internal/LogReporter.java (added) +++ sling/trunk/bundles/commons/metrics/src/main/java/org/apache/sling/commons/metrics/internal/LogReporter.java Mon Aug 7 13:30:08 2017 @@ -0,0 +1,150 @@ +/* + * Licensed to 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 org.apache.sling.commons.metrics.internal; + +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Slf4jReporter; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +@Component(service = {}, configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = LogReporter.Config.class, factory = true) +public class LogReporter implements ServiceTrackerCustomizer<MetricRegistry, Slf4jReporter> { + + private BundleContext context; + private ServiceTracker<MetricRegistry, Slf4jReporter> tracker; + private Config config; + + @Activate + protected void activate(Config config, BundleContext context) { + this.config = config; + this.context = context; + tracker = new ServiceTracker<>(context, MetricRegistry.class, this); + tracker.open(); + } + + @Deactivate + protected void deactivate(BundleContext context) { + tracker.close(); + } + + //~---------------------------------------------< ServiceTracker > + + @Override + public Slf4jReporter addingService(ServiceReference<MetricRegistry> serviceReference) { + MetricRegistry registry = context.getService(serviceReference); + String metricRegistryName = (String) serviceReference.getProperty(MetricWebConsolePlugin.METRIC_REGISTRY_NAME); + + if (config.registryName() == null || config.registryName().length() == 0 + || config.registryName().equals(metricRegistryName)) { + Slf4jReporter.Builder builder = Slf4jReporter.forRegistry(registry). + outputTo(LoggerFactory.getLogger(config.loggerName())). + withLoggingLevel(config.level()); + + if (config.prefix() != null && config.prefix().length() > 0) { + builder.filter(new PrefixFilter(config.prefix())); + } else if (config.pattern() != null && config.pattern().length() > 0) { + builder.filter(new PatternFilter(config.pattern())); + } + + Slf4jReporter reporter = builder.build(); + reporter.start(config.period(), config.timeUnit()); + return reporter; + } else { + return null; + } + } + + @Override + public void modifiedService(ServiceReference<MetricRegistry> serviceReference, Slf4jReporter reporter) { + // NO OP + } + + @Override + public void removedService(ServiceReference<MetricRegistry> serviceReference, Slf4jReporter reporter) { + if (reporter != null) { + reporter.close(); + } + } + + private class PrefixFilter implements MetricFilter { + private final String prefix; + + private PrefixFilter(String prefix) { + this.prefix = prefix; + } + + @Override + public boolean matches(String s, Metric metric) { + return s.startsWith(prefix); + } + } + + private class PatternFilter implements MetricFilter { + private final Pattern pattern; + + private PatternFilter(String pattern) { + this.pattern = Pattern.compile(pattern); + } + + @Override + public boolean matches(String s, Metric metric) { + return pattern.matcher(s).matches(); + } + } + + @ObjectClassDefinition(name = "Apache Sling Metrics Log Reporter Configuration") + @interface Config { + + @AttributeDefinition(description = "Period at which the metrics data will be logged") + long period() default 5; + + @AttributeDefinition(description = "Unit of time for evaluating the period") + TimeUnit timeUnit() default TimeUnit.MINUTES; + + @AttributeDefinition(description = "The log level to log at.") + Slf4jReporter.LoggingLevel level() default Slf4jReporter.LoggingLevel.INFO; + + @AttributeDefinition(description = "The logger name") + String loggerName() default "metrics"; + + @AttributeDefinition(description = "If specified, only metrics whose name starts with this value are logged. If both prefix and pattern are set, prefix is used.") + String prefix() default ""; + + @AttributeDefinition(description = "If specified, only metrics whose name matches this regular expression will be logged. If both prefix and pattern are set, prefix is used.") + String pattern() default ""; + + @AttributeDefinition(description = "Restrict the metrics logged to a specifically named registry.") + String registryName() default ""; + } + +} Added: sling/trunk/bundles/commons/metrics/src/test/java/org/apache/sling/commons/metrics/internal/LogReporterTest.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/metrics/src/test/java/org/apache/sling/commons/metrics/internal/LogReporterTest.java?rev=1804334&view=auto ============================================================================== --- sling/trunk/bundles/commons/metrics/src/test/java/org/apache/sling/commons/metrics/internal/LogReporterTest.java (added) +++ sling/trunk/bundles/commons/metrics/src/test/java/org/apache/sling/commons/metrics/internal/LogReporterTest.java Mon Aug 7 13:30:08 2017 @@ -0,0 +1,449 @@ +/* + * Licensed to 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 org.apache.sling.commons.metrics.internal; + +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Slf4jReporter; +import junitx.util.PrivateAccessor; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; + + +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +import java.lang.annotation.Annotation; +import java.util.concurrent.TimeUnit; + +@RunWith(MockitoJUnitRunner.class) +public class LogReporterTest { + + @Mock + private BundleContext bundleContext; + + LogReporter reporterService = new LogReporter(); + + @Test + public void testSpecificRegistryNameInclude() { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + when(registryServiceReference.getProperty(MetricWebConsolePlugin.METRIC_REGISTRY_NAME)).thenReturn("oak"); + + LogReporter.Config config = createConfigWithRegistryName("oak"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNotNull(reporter); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testSpecificRegistryNameExclude() { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + when(registryServiceReference.getProperty(MetricWebConsolePlugin.METRIC_REGISTRY_NAME)).thenReturn("other"); + + LogReporter.Config config = createConfigWithRegistryName("oak"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNull(reporter); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testSpecificRegistryNameExcludeNullName() { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + + LogReporter.Config config = createConfigWithRegistryName("oak"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNull(reporter); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testLoggerName() throws Exception { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + + LogReporter.Config config = createConfigWithLoggerNameAndLevel("test", Slf4jReporter.LoggingLevel.WARN); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNotNull(reporter); + + Object loggerProxy = PrivateAccessor.getField(reporter, "loggerProxy"); + assertEquals("WarnLoggerProxy", loggerProxy.getClass().getSimpleName()); + + Logger logger = (Logger) PrivateAccessor.getField(loggerProxy, "logger"); + assertEquals("test", logger.getName()); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testPrefix() throws Exception { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + + LogReporter.Config config = createConfigWithPrefix("testPrefix"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNotNull(reporter); + + MetricFilter filter = (MetricFilter) PrivateAccessor.getField(reporter, "filter"); + assertEquals("PrefixFilter", filter.getClass().getSimpleName()); + assertTrue(filter.matches("testPrefixedName", null)); + assertFalse(filter.matches("testNonPrefixedName", null)); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testPattern() throws Exception { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + + LogReporter.Config config = createConfigWithPattern("[0-9]test.*"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNotNull(reporter); + + MetricFilter filter = (MetricFilter) PrivateAccessor.getField(reporter, "filter"); + assertEquals("PatternFilter", filter.getClass().getSimpleName()); + assertTrue(filter.matches("5testTest", null)); + assertFalse(filter.matches("ZtestTest", null)); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testPrefixAndPattern() throws Exception { + MetricRegistry registry = new MetricRegistry(); + ServiceReference<MetricRegistry> registryServiceReference = mock(ServiceReference.class); + when(bundleContext.getService(registryServiceReference)).thenReturn(registry); + + LogReporter.Config config = createConfigWithPrefixAndPattern("testPrefix", "[0-9]test.*"); + reporterService.activate(config, bundleContext); + + Slf4jReporter reporter = null; + try { + reporter = reporterService.addingService(registryServiceReference); + assertNotNull(reporter); + + MetricFilter filter = (MetricFilter) PrivateAccessor.getField(reporter, "filter"); + assertEquals("PrefixFilter", filter.getClass().getSimpleName()); + assertTrue(filter.matches("testPrefixedName", null)); + assertFalse(filter.matches("testNonPrefixedName", null)); + } finally { + if (reporter != null) { + reporter.close(); + } + reporterService.deactivate(bundleContext); + } + } + + @Test + public void testRemove() { + Slf4jReporter reporter = mock(Slf4jReporter.class); + reporterService.removedService(null, reporter); + verify(reporter, times(1)).close(); + } + + @Test + public void testNoOpCalls() { + // extra no-op calls for coverage + reporterService.removedService(null, null); + reporterService.modifiedService(null, null); + } + + private LogReporter.Config createConfigWithRegistryName(final String registryName) { + return new LogReporter.Config() { + @Override + public long period() { + return 5; + } + + @Override + public TimeUnit timeUnit() { + return TimeUnit.MINUTES; + } + + @Override + public Slf4jReporter.LoggingLevel level() { + return Slf4jReporter.LoggingLevel.INFO; + } + + @Override + public String loggerName() { + return "metrics"; + } + + @Override + public String prefix() { + return null; + } + + @Override + public String pattern() { + return null; + } + + @Override + public String registryName() { + return registryName; + } + + @Override + public Class<? extends Annotation> annotationType() { + return LogReporter.Config.class; + } + }; + } + + private LogReporter.Config createConfigWithLoggerNameAndLevel(final String loggerName, final Slf4jReporter.LoggingLevel level) { + return new LogReporter.Config() { + @Override + public long period() { + return 5; + } + + @Override + public TimeUnit timeUnit() { + return TimeUnit.MINUTES; + } + + @Override + public Slf4jReporter.LoggingLevel level() { + return level; + } + + @Override + public String loggerName() { + return loggerName; + } + + @Override + public String prefix() { + return null; + } + + @Override + public String pattern() { + return null; + } + + @Override + public String registryName() { + return ""; + } + + @Override + public Class<? extends Annotation> annotationType() { + return LogReporter.Config.class; + } + }; + } + + private LogReporter.Config createConfigWithPrefix(final String prefix) { + return new LogReporter.Config() { + @Override + public long period() { + return 5; + } + + @Override + public TimeUnit timeUnit() { + return TimeUnit.MINUTES; + } + + @Override + public Slf4jReporter.LoggingLevel level() { + return Slf4jReporter.LoggingLevel.INFO; + } + + @Override + public String loggerName() { + return "metrics"; + } + + @Override + public String prefix() { + return prefix; + } + + @Override + public String pattern() { + return null; + } + + @Override + public String registryName() { + return null; + } + + @Override + public Class<? extends Annotation> annotationType() { + return LogReporter.Config.class; + } + }; + } + + private LogReporter.Config createConfigWithPattern(final String pattern) { + return new LogReporter.Config() { + @Override + public long period() { + return 5; + } + + @Override + public TimeUnit timeUnit() { + return TimeUnit.MINUTES; + } + + @Override + public Slf4jReporter.LoggingLevel level() { + return Slf4jReporter.LoggingLevel.INFO; + } + + @Override + public String loggerName() { + return "metrics"; + } + + @Override + public String prefix() { + return null; + } + + @Override + public String pattern() { + return pattern; + } + + @Override + public String registryName() { + return null; + } + + @Override + public Class<? extends Annotation> annotationType() { + return LogReporter.Config.class; + } + }; + } + + private LogReporter.Config createConfigWithPrefixAndPattern(final String prefix, final String pattern) { + return new LogReporter.Config() { + @Override + public long period() { + return 5; + } + + @Override + public TimeUnit timeUnit() { + return TimeUnit.MINUTES; + } + + @Override + public Slf4jReporter.LoggingLevel level() { + return Slf4jReporter.LoggingLevel.INFO; + } + + @Override + public String loggerName() { + return "metrics"; + } + + @Override + public String prefix() { + return prefix; + } + + @Override + public String pattern() { + return pattern; + } + + @Override + public String registryName() { + return null; + } + + @Override + public Class<? extends Annotation> annotationType() { + return LogReporter.Config.class; + } + }; + } +}