You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:23:54 UTC
[sling-org-apache-sling-tracer] 01/06: SLING-4739 - Log Tracer - To
enable logs for specific category at specific level and only for specific
request
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.tracer-0.0.2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tracer.git
commit 8e6b5d0d11cff3c0974c46ca5123b19537e0106f
Author: Chetan Mehrotra <ch...@apache.org>
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/contrib/extensions/tracer@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
"commits@sling.apache.org" <co...@sling.apache.org>.