All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.tracer.internal.LogTracer Maven / Gradle / Ivy

Go to download

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. Refer to http://sling.apache.org/documentation/bundles/log-tracers.html

There is a newer version: 1.0.4
Show newest version
/*
 * 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.annotation.Nullable;
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 javax.servlet.http.HttpServletResponse;

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.
 * 

* 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. " + "Refer to http://sling.apache.org/documentation/bundles/log-tracers.html for " + "more details", 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 *

* 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. : ;level=, 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 boolean PROP_TRACER_SERVLET_ENABLED_DEFAULT = false; @Property(label = "Servlet Enabled", description = "Enable the Tracer Servlet", boolValue = PROP_TRACER_SERVLET_ENABLED_DEFAULT ) private static final String PROP_TRACER_SERVLET_ENABLED = "servletEnabled"; static final int PROP_TRACER_SERVLET_CACHE_SIZE_DEFAULT = 50; @Property(label = "Recording Cache Size", description = "Recording cache size in MB which would be used to temporary cache the recording data", intValue = PROP_TRACER_SERVLET_CACHE_SIZE_DEFAULT ) private static final String PROP_TRACER_SERVLET_CACHE_SIZE = "recordingCacheSizeInMB"; static final long PROP_TRACER_SERVLET_CACHE_DURATION_DEFAULT = 60 * 15; @Property(label = "Recording Cache Duration", description = "Time in seconds upto which the recording data would be held in memory before expiry", longValue = PROP_TRACER_SERVLET_CACHE_DURATION_DEFAULT ) private static final String PROP_TRACER_SERVLET_CACHE_DURATION = "recordingCacheDurationInSecs"; static final boolean PROP_TRACER_SERVLET_COMPRESS_DEFAULT = true; @Property(label = "Compress Recording", description = "Enable compression for recoding held in memory", boolValue = PROP_TRACER_SERVLET_COMPRESS_DEFAULT ) private static final String PROP_TRACER_SERVLET_COMPRESS = "recordingCompressionEnabled"; static final boolean PROP_TRACER_SERVLET_GZIP_RESPONSE_DEFAULT = true; @Property(label = "GZip Response", description = "If enabled the response sent would be compressed", boolValue = PROP_TRACER_SERVLET_GZIP_RESPONSE_DEFAULT ) private static final String PROP_TRACER_SERVLET_GZIP_RESPONSE = "gzipResponse"; private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogTracer.class); private final Map tracers = new HashMap(); private BundleContext bundleContext; private ServiceRegistration slingFilterRegistration; private ServiceRegistration filterRegistration; private final AtomicReference logCollectorReg = new AtomicReference(); private final AtomicInteger logCollectorRegCount = new AtomicInteger(); private static final ThreadLocal requestContextHolder = new ThreadLocal(); @Nullable private TracerLogServlet logServlet; private TraceLogRecorder recorder = TraceLogRecorder.DEFAULT; @Activate private void activate(Map 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); boolean servletEnabled = PropertiesUtil.toBoolean(config.get(PROP_TRACER_SERVLET_ENABLED), PROP_TRACER_SERVLET_ENABLED_DEFAULT); if (servletEnabled) { int cacheSize = PropertiesUtil.toInteger(config.get(PROP_TRACER_SERVLET_CACHE_SIZE), PROP_TRACER_SERVLET_CACHE_SIZE_DEFAULT); long cacheDuration = PropertiesUtil.toLong(config.get(PROP_TRACER_SERVLET_CACHE_DURATION), PROP_TRACER_SERVLET_CACHE_DURATION_DEFAULT); boolean compressionEnabled = PropertiesUtil.toBoolean(config.get(PROP_TRACER_SERVLET_COMPRESS), PROP_TRACER_SERVLET_COMPRESS_DEFAULT); boolean gzipResponse = PropertiesUtil.toBoolean(config.get(PROP_TRACER_SERVLET_GZIP_RESPONSE), PROP_TRACER_SERVLET_GZIP_RESPONSE_DEFAULT); this.logServlet = new TracerLogServlet(context, cacheSize, cacheDuration, compressionEnabled, gzipResponse); recorder = logServlet; LOG.info("Tracer recoding enabled with cacheSize {} MB, expiry {} secs, compression {}, gzip response {}", cacheSize, cacheDuration, compressionEnabled, gzipResponse); } LOG.info("Log tracer enabled. Required filters registered. Tracer servlet enabled {}", servletEnabled); } } @Deactivate private void deactivate() { if (logServlet != null) { logServlet.unregister(); } 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, Recording recording) { //No config or tracer set name provided. So tracing not required if (tracerSetNames == null && tracerConfig == null) { return null; } List configs = new ArrayList(); List invalidNames = new ArrayList(); 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()]), recording); } private void initializeTracerSet(Map 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 slingFilterProps = new Hashtable(); 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 filterProps = new Hashtable(); 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". *

* 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; //Invoke at start so that header can be set. If done at end there is a chance //that response is committed Recording recording = recorder.startRecording(httpRequest, (HttpServletResponse) servletResponse); TracerContext tracerContext = getTracerContext(httpRequest.getHeader(HEADER_TRACER), httpRequest.getHeader(HEADER_TRACER_CONFIG), recording); try { if (tracerContext != null) { enableCollector(tracerContext); } filterChain.doFilter(servletRequest, servletResponse); } finally { if (tracerContext != null) { disableCollector(); } recorder.endRecording(httpRequest, recording); } } } /** * 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(); Recording recording = recorder.getRecordingForRequest(slingRequest); recording.registerTracker(slingRequest.getRequestProgressTracker()); 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), recording); 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; } TracerConfig tc = tracer.findMatchingConfig(logger.getName(), level); if (tc != null) { if (format == null) { return FilterReply.ACCEPT; } if (tracer.log(tc, level, logger.getName(), format, params)) { return FilterReply.ACCEPT; } } return FilterReply.NEUTRAL; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy