This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tracer.git
commit deacd50d7dac8055788a41ad7e4ae9b4be0749e2 Author: Chetan Mehrotra <[email protected]> AuthorDate: Fri May 22 09:29:32 2015 +0000 SLING-4739 - Log Tracer - To enable logs for specific category at specific level and only for specific request git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1681054 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 115 +++++++ .../apache/sling/tracer/internal/LogTracer.java | 362 +++++++++++++++++++++ .../apache/sling/tracer/internal/TracerConfig.java | 108 ++++++ .../sling/tracer/internal/TracerContext.java | 140 ++++++++ .../apache/sling/tracer/internal/TracerSet.java | 79 +++++ .../sling/tracer/internal/LogTracerTest.java | 92 ++++++ 6 files changed, 896 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..866fb1c --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<!-- + 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. +--> +<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"> + + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>22</version> + </parent> + + <artifactId>org.apache.sling.tracer</artifactId> + <packaging>bundle</packaging> + <version>1.0.0-SNAPSHOT</version> + + <name>Apache Sling Log Tracer</name> + <description> + Tracer provides support for enabling the logs for specific category at specific level and + only for specific request. It provides a very fine level of control via config provided + as part of HTTP request around how the logging should be performed for given category. + </description> + + <properties> + <sling.java.version>6</sling.java.version> + </properties> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/tracer</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/tracer</developerConnection> + <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer</url> + </scm> + + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <!-- the used logback version is only compatible with SLF4J 1.6 --> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.6.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.0.13</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.2.0</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.1.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>4.3.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <version>4.3.1</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/tracer/internal/LogTracer.java b/src/main/java/org/apache/sling/tracer/internal/LogTracer.java new file mode 100644 index 0000000..82490bb --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/LogTracer.java @@ -0,0 +1,362 @@ +/* + * 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.tracer.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.turbo.TurboFilter; +import ch.qos.logback.core.spi.FilterReply; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; + +/** + * Tracer provides support for enabling the logs for specific category at specific level and + * only for specific request. It provides a very fine level of control via config provided + * as part of HTTP request around how the logging should be performed for given category. + * <p/> + * This is specially useful for those parts of the system which are involved in every request. + * For such parts enabling the log at global level would flood the logs and create lots of noise. + * Using Tracer one can enable log for that request which is required to be probed + */ +@Component( + label = "Apache Sling Log Tracer", + description = "Provides support for enabling log for specific loggers on per request basis", + policy = ConfigurationPolicy.REQUIRE, + metatype = true +) +public class LogTracer { + /** + * Request parameter name having comma separated value to determine list of tracers to + * enable + */ + public static final String PARAM_TRACER = "tracers"; + + /** + * Request param used to determine tracer config as part of request itself. Like + * <p/> + * org.apache.sling;level=trace,org.apache.jackrabbit + */ + public static final String PARAM_TRACER_CONFIG = "tracerConfig"; + + public static final String HEADER_TRACER_CONFIG = "Sling-Tracer-Config"; + + public static final String HEADER_TRACER = "Sling-Tracers"; + + @Property(label = "Tracer Sets", + description = "Default list of tracer sets configured. Tracer Set config confirms " + + "to following format. <set name> : <logger name>;level=<level name>, other loggers", + unbounded = PropertyUnbounded.ARRAY, + value = { + "oak-query : org.apache.jackrabbit.oak.query.QueryEngineImpl;level=debug", + "oak-writes : org.apache.jackrabbit.oak.jcr.operations.writes;level=trace" + } + ) + private static final String PROP_TRACER_SETS = "tracerSets"; + + private static final boolean PROP_TRACER_ENABLED_DEFAULT = false; + @Property(label = "Enabled", + description = "Enable the Tracer", + boolValue = PROP_TRACER_ENABLED_DEFAULT + ) + private static final String PROP_TRACER_ENABLED = "enabled"; + + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogTracer.class); + + private final Map<String, TracerSet> tracers = new HashMap<String, TracerSet>(); + + private BundleContext bundleContext; + + private ServiceRegistration slingFilterRegistration; + + private ServiceRegistration filterRegistration; + + private final AtomicReference<ServiceRegistration> logCollectorReg + = new AtomicReference<ServiceRegistration>(); + + private final AtomicInteger logCollectorRegCount = new AtomicInteger(); + + private static final ThreadLocal<TracerContext> requestContextHolder = new ThreadLocal<TracerContext>(); + + @Activate + private void activate(Map<String, ?> config, BundleContext context) { + this.bundleContext = context; + initializeTracerSet(config); + boolean enabled = PropertiesUtil.toBoolean(config.get(PROP_TRACER_ENABLED), PROP_TRACER_ENABLED_DEFAULT); + if (enabled) { + registerFilters(context); + LOG.info("Log tracer enabled. Required filters registered"); + } + } + + @Deactivate + private void deactivate() { + if (slingFilterRegistration != null) { + slingFilterRegistration.unregister(); + slingFilterRegistration = null; + } + + if (filterRegistration != null) { + filterRegistration.unregister(); + filterRegistration = null; + } + + ServiceRegistration reg = logCollectorReg.getAndSet(null); + if (reg != null) { + reg.unregister(); + } + + requestContextHolder.remove(); + } + + TracerContext getTracerContext(String tracerSetNames, String tracerConfig) { + //No config or tracer set name provided. So tracing not required + if (tracerSetNames == null && tracerConfig == null) { + return null; + } + + List<TracerConfig> configs = new ArrayList<TracerConfig>(); + + List<String> invalidNames = new ArrayList<String>(); + if (tracerSetNames != null) { + for (String tracerSetName : tracerSetNames.split(",")) { + TracerSet ts = tracers.get(tracerSetName.toLowerCase(Locale.ENGLISH)); + if (ts != null) { + configs.addAll(ts.getConfigs()); + } else { + invalidNames.add(tracerSetName); + } + } + } + + if (!invalidNames.isEmpty()) { + LOG.warn("Invalid tracer set names passed [{}] as part of [{}]", invalidNames, tracerSetNames); + } + + if (tracerConfig != null) { + TracerSet ts = new TracerSet("custom", tracerConfig); + configs.addAll(ts.getConfigs()); + } + + return new TracerContext(configs.toArray(new TracerConfig[configs.size()])); + } + + private void initializeTracerSet(Map<String, ?> config) { + String[] tracerSetConfigs = PropertiesUtil.toStringArray(config.get(PROP_TRACER_SETS), new String[0]); + + for (String tracerSetConfig : tracerSetConfigs) { + TracerSet tc = new TracerSet(tracerSetConfig); + tracers.put(tc.getName(), tc); + } + } + + private void registerFilters(BundleContext context) { + Dictionary<String, Object> slingFilterProps = new Hashtable<String, Object>(); + slingFilterProps.put("filter.scope", "REQUEST"); + slingFilterProps.put(Constants.SERVICE_DESCRIPTION, "Sling Filter required for Log Tracer"); + slingFilterRegistration = context.registerService(Filter.class.getName(), + new SlingTracerFilter(), slingFilterProps); + + Dictionary<String, Object> filterProps = new Hashtable<String, Object>(); + filterProps.put("pattern", "/.*"); + filterProps.put(Constants.SERVICE_DESCRIPTION, "Servlet Filter required for Log Tracer"); + filterRegistration = context.registerService(Filter.class.getName(), + new TracerFilter(), filterProps); + } + + /** + * TurboFilters causes slowness as they are executed on critical path + * Hence care is taken to only register the filter only when required + * Logic below ensures that filter is only registered for the duration + * or request which needs to be "monitored". + * <p/> + * If multiple such request are performed then also only one filter gets + * registered + */ + private void registerLogCollector() { + synchronized (logCollectorRegCount) { + int count = logCollectorRegCount.getAndIncrement(); + if (count == 0) { + ServiceRegistration reg = bundleContext.registerService(TurboFilter.class.getName(), + new LogCollector(), null); + logCollectorReg.set(reg); + } + } + } + + private void unregisterLogCollector() { + synchronized (logCollectorRegCount) { + int count = logCollectorRegCount.decrementAndGet(); + if (count == 0) { + ServiceRegistration reg = logCollectorReg.getAndSet(null); + reg.unregister(); + } + } + } + + private abstract class AbstractFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + + } + + protected void enableCollector(TracerContext tracerContext) { + requestContextHolder.set(tracerContext); + registerLogCollector(); + } + + protected void disableCollector() { + requestContextHolder.remove(); + unregisterLogCollector(); + } + } + + /** + * Filter which registers at root and check for Tracer related params. If found to + * be enabled then perform required setup for the logs to be captured. + */ + private class TracerFilter extends AbstractFilter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + + //At generic filter level we just check for tracer hint via Header (later Cookie) + //and not touch the request parameter to avoid eager initialization of request + //parameter map + + HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; + TracerContext tracerContext = getTracerContext(httpRequest.getHeader(HEADER_TRACER), + httpRequest.getHeader(HEADER_TRACER_CONFIG)); + try { + if (tracerContext != null) { + enableCollector(tracerContext); + } + filterChain.doFilter(servletRequest, servletResponse); + } finally { + if (tracerContext != null) { + disableCollector(); + } + } + } + + + } + + /** + * Sling level filter to extract the RequestProgressTracker and passes that to current + * thread's TracerContent + */ + private class SlingTracerFilter extends AbstractFilter { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest; + TracerContext tracerContext = requestContextHolder.get(); + + boolean createdContext = false; + + //Check if the global filter created context based on HTTP headers. If not + //then check from request params + if (tracerContext == null) { + tracerContext = getTracerContext(slingRequest.getParameter(PARAM_TRACER), + slingRequest.getParameter(PARAM_TRACER_CONFIG)); + if (tracerContext != null) { + createdContext = true; + } + } + + try { + if (tracerContext != null) { + tracerContext.registerProgressTracker(slingRequest.getRequestProgressTracker()); + + //if context created in this filter then enable the collector + if (createdContext) { + enableCollector(tracerContext); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } finally { + if (tracerContext != null) { + tracerContext.done(); + + if (createdContext) { + disableCollector(); + } + } + } + } + } + + private static class LogCollector extends TurboFilter { + @Override + public FilterReply decide(Marker marker, Logger logger, Level level, + String format, Object[] params, Throwable t) { + TracerContext tracer = requestContextHolder.get(); + if (tracer == null) { + return FilterReply.NEUTRAL; + } + + if (tracer.shouldLog(logger.getName(), level)) { + if (format == null) { + return FilterReply.ACCEPT; + } + if (tracer.log(logger.getName(), format, params)) { + return FilterReply.ACCEPT; + } + } + + return FilterReply.NEUTRAL; + } + } + +} diff --git a/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java b/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java new file mode 100644 index 0000000..8f7a65b --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java @@ -0,0 +1,108 @@ +/* + * 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.tracer.internal; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.core.CoreConstants; + +class TracerConfig implements Comparable<TracerConfig> { + enum MatchResult { + MATCH_LOG, + /** + * Logger category matched but level not. So logging should + * not be performed and no further TracerConfig should be matched for this + */ + MATCH_NO_LOG, + + NO_MATCH + } + + private final String loggerName; + private final Level level; + private final int depth; + + public TracerConfig(String loggerName, Level level) { + this.loggerName = loggerName; + this.level = level; + this.depth = getDepth(loggerName); + } + + public boolean match(String loggerName) { + return loggerName.startsWith(this.loggerName); + } + + public MatchResult match(String loggerName, Level level) { + if (loggerName.startsWith(this.loggerName)) { + if (level.isGreaterOrEqual(this.level)) { + return MatchResult.MATCH_LOG; + } + return MatchResult.MATCH_NO_LOG; + } + return MatchResult.NO_MATCH; + } + + @Override + public int compareTo(TracerConfig o) { + int comp = depth > o.depth ? -1 : depth < o.depth ? 1 : 0; + if (comp == 0) { + comp = loggerName.compareTo(o.loggerName); + } + return comp; + } + + public int getDepth() { + return depth; + } + + public String getLoggerName() { + return loggerName; + } + + public Level getLevel() { + return level; + } + + private static int getDepth(String loggerName) { + int depth = 0; + int fromIndex = 0; + while (true) { + int index = getSeparatorIndexOf(loggerName, fromIndex); + depth++; + if (index == -1) { + break; + } + fromIndex = index + 1; + } + return depth; + } + + /* + * Taken from LoggerNameUtil. Though its accessible Logback is might not maintain + * strict backward compatibility for such util classes. So copy the logic + */ + private static int getSeparatorIndexOf(String name, int fromIndex) { + int i = name.indexOf(CoreConstants.DOT, fromIndex); + if (i != -1) { + return i; + } else { + return name.indexOf(CoreConstants.DOLLAR, fromIndex); + } + } +} diff --git a/src/main/java/org/apache/sling/tracer/internal/TracerContext.java b/src/main/java/org/apache/sling/tracer/internal/TracerContext.java new file mode 100644 index 0000000..b3405c4 --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/TracerContext.java @@ -0,0 +1,140 @@ +/* + * 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.tracer.internal; + +import java.util.Arrays; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.core.helpers.CyclicBuffer; +import org.apache.sling.api.request.RequestProgressTracker; +import org.slf4j.helpers.MessageFormatter; + +class TracerContext { + private static final String QUERY_LOGGER = "org.apache.jackrabbit.oak.query.QueryEngineImpl"; + + /** + * Following queries are internal to Oak and are fired for login/access control + * etc. They should be ignored. With Oak 1.2+ such queries are logged at trace + * level (OAK-2304) + */ + private static final String[] IGNORABLE_QUERIES = { + "SELECT * FROM [nt:base] WHERE [jcr:uuid] = $id", + "SELECT * FROM [nt:base] WHERE PROPERTY([rep:members], 'WeakReference') = $uuid", + "SELECT * FROM [rep:Authorizable]WHERE [rep:principalName] = $principalName", + }; + + private static final int LOG_BUFFER_SIZE = 50; + /* + * In memory buffer to store logs till RequestProgressTracker is registered. + * This would be required for those case where TracerContext is created at + * normal Filter level which gets invoked before Sling layer is hit. + * + * Later when Sling layer is hit and SlingTracerFilter is invoked + * then it would register the RequestProgressTracker and then these inmemory logs + * would be dumped there + */ + private CyclicBuffer<String> buffer; + private RequestProgressTracker progressTracker; + private int queryCount; + private final TracerConfig[] tracers; + + public TracerContext(TracerConfig[] tracers) { + this.tracers = tracers; + + //Say if the list is like com.foo;level=trace,com.foo.bar;level=info. + // Then first config would result in a match and later config would + // not be able to suppress the logs from a child category + //To handle such cases we sort the config. With having more depth i.e. more specific + //coming first and others later + Arrays.sort(tracers); + } + + public boolean shouldLog(String logger, Level level) { + for (TracerConfig tc : tracers) { + TracerConfig.MatchResult mr = tc.match(logger, level); + if (mr == TracerConfig.MatchResult.MATCH_LOG) { + return true; + } else if (mr == TracerConfig.MatchResult.MATCH_NO_LOG) { + return false; + } + } + return false; + } + + public boolean log(String logger, String format, Object[] params) { + if (QUERY_LOGGER.equals(logger) + && params != null && params.length == 2) { + return logQuery((String) params[1]); + } + return logWithLoggerName(logger, format, params); + } + + public void done() { + if (queryCount > 0) { + progressTracker.log("JCR Query Count {0}", queryCount); + } + } + + /** + * Registers the progress tracker and also logs all the in memory logs + * collected so far to the tracker + */ + public void registerProgressTracker(RequestProgressTracker requestProgressTracker) { + this.progressTracker = requestProgressTracker; + if (buffer != null) { + for (String msg : buffer.asList()) { + progressTracker.log(msg); + } + buffer = null; + } + } + + private boolean logWithLoggerName(String loggerName, String format, Object... params) { + String msg = MessageFormatter.arrayFormat(format, params).getMessage(); + msg = "[" + loggerName + "] " + msg; + if (progressTracker == null) { + if (buffer == null) { + buffer = new CyclicBuffer<String>(LOG_BUFFER_SIZE); + } + buffer.add(msg); + } else { + progressTracker.log(msg); + } + return true; + } + + private boolean logQuery(String query) { + if (ignorableQuery(query)) { + return false; + } + queryCount++; + logWithLoggerName("JCR", " Query {}", query); + return true; + } + + private boolean ignorableQuery(String msg) { + for (String ignorableQuery : IGNORABLE_QUERIES) { + if (msg.contains(ignorableQuery)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/org/apache/sling/tracer/internal/TracerSet.java b/src/main/java/org/apache/sling/tracer/internal/TracerSet.java new file mode 100644 index 0000000..c4bcdbb --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/TracerSet.java @@ -0,0 +1,79 @@ +/* + * 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.tracer.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ch.qos.logback.classic.Level; +import org.apache.sling.commons.osgi.ManifestHeader; + +class TracerSet { + public static final String LEVEL = "level"; + private final String name; + private final List<TracerConfig> configs; + + public TracerSet(String config) { + int indexOfColon = config.indexOf(':'); + if (indexOfColon == -1) { + throw new IllegalArgumentException("Invalid tracer config format. TracerSet " + + "name cannot be determined " + config); + } + + name = config.substring(0, indexOfColon).toLowerCase().trim(); + configs = parseTracerConfigs(config.substring(indexOfColon + 1)); + } + + public TracerSet(String name, String config) { + this.name = name; + this.configs = parseTracerConfigs(config); + } + + public TracerConfig getConfig(String category) { + for (TracerConfig tc : configs) { + if (tc.match(category)) { + return tc; + } + } + return null; + } + + public List<TracerConfig> getConfigs() { + return configs; + } + + public String getName() { + return name; + } + + private static List<TracerConfig> parseTracerConfigs(String config) { + ManifestHeader parsedConfig = ManifestHeader.parse(config); + List<TracerConfig> result = new ArrayList<TracerConfig>(parsedConfig.getEntries().length); + for (ManifestHeader.Entry e : parsedConfig.getEntries()) { + String category = e.getValue(); + + //Defaults to Debug + Level level = Level.valueOf(e.getAttributeValue(LEVEL)); + result.add(new TracerConfig(category, level)); + } + return Collections.unmodifiableList(result); + } +} diff --git a/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java b/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java new file mode 100644 index 0000000..9895561 --- /dev/null +++ b/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java @@ -0,0 +1,92 @@ +/* + * 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.tracer.internal; + +import java.util.Arrays; + +import ch.qos.logback.classic.Level; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class LogTracerTest { + + @Test(expected = IllegalArgumentException.class) + public void parseInvalidConfig() throws Exception { + new TracerSet("foo"); + } + + @Test + public void parseTracerSet() throws Exception { + TracerSet a = new TracerSet("foo : com.foo, com.bar;level=INFO"); + assertEquals("foo", a.getName()); + TracerConfig tcfoo = a.getConfig("com.foo"); + assertNotNull(tcfoo); + assertEquals(Level.DEBUG, tcfoo.getLevel()); + + assertNotNull("Config for parent should match for child", a.getConfig("com.foo.bar")); + + + TracerConfig tcbar = a.getConfig("com.bar"); + assertNotNull(tcbar); + assertEquals(Level.INFO, tcbar.getLevel()); + } + + @Test + public void childLoggerLevelDiff() throws Exception { + TracerSet ts = new TracerSet("foo : a.b;level=trace, a.b.c;level=info"); + TracerContext tc = getContext(ts); + + assertTrue(tc.shouldLog("a.b", Level.TRACE)); + assertTrue(tc.shouldLog("a.b.d", Level.TRACE)); + assertFalse(tc.shouldLog("a.b.c", Level.TRACE)); + } + + @Test + public void tracerConfigTest() throws Exception { + TracerConfig tc = new TracerConfig("a.b.c", Level.DEBUG); + assertEquals(3, tc.getDepth()); + assertEquals(TracerConfig.MatchResult.MATCH_LOG, tc.match("a.b.c.d", Level.DEBUG)); + assertEquals(TracerConfig.MatchResult.MATCH_NO_LOG, tc.match("a.b.c.d", Level.TRACE)); + assertEquals(TracerConfig.MatchResult.NO_MATCH, tc.match("a.b.d", Level.TRACE)); + } + + @Test + public void tracerConfigSort() throws Exception { + TracerConfig[] configs = new TracerConfig[]{ + new TracerConfig("a.b.c.d", Level.DEBUG), + new TracerConfig("a", Level.DEBUG), + new TracerConfig("a.b.e", Level.DEBUG), + }; + + Arrays.sort(configs); + assertEquals("a.b.c.d", configs[0].getLoggerName()); + assertEquals("a.b.e", configs[1].getLoggerName()); + assertEquals("a", configs[2].getLoggerName()); + + } + + private static TracerContext getContext(TracerSet ts) { + return new TracerContext(ts.getConfigs().toArray(new TracerConfig[ts.getConfigs().size()])); + } + +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
