| /* |
| * 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.FormattingTuple; |
| import org.slf4j.helpers.MessageFormatter; |
| |
| class TracerContext { |
| 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; |
| private final Recording recording; |
| |
| public TracerContext(TracerConfig[] tracers, Recording recording) { |
| this.tracers = tracers; |
| this.recording = recording; |
| |
| //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); |
| } |
| |
| /** |
| * Finds and returns the matching TracerConfig for given logger and level |
| * If non null it indicates that logging should proceed |
| */ |
| public TracerConfig findMatchingConfig(String logger, Level level) { |
| for (TracerConfig tc : tracers) { |
| TracerConfig.MatchResult mr = tc.match(logger, level); |
| if (mr == TracerConfig.MatchResult.MATCH_LOG) { |
| return tc; |
| } else if (mr == TracerConfig.MatchResult.MATCH_NO_LOG) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public boolean log(TracerConfig tc, Level level, String logger, String format, Object[] params) { |
| FormattingTuple tuple = null; |
| if (QUERY_LOGGER.equals(logger) |
| && params != null && params.length == 2) { |
| if (logQuery((String) params[1])){ |
| //Get original log message |
| tuple = logWithLoggerName(logger, format, params); |
| } |
| } else { |
| tuple = logWithLoggerName(logger, format, params); |
| } |
| |
| if (tuple != null) { |
| recording.log(tc, level, logger, tuple); |
| } |
| return tuple != null; |
| } |
| |
| public void recordCategory(String loggerName) { |
| recording.recordCategory(loggerName); |
| } |
| |
| 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 FormattingTuple logWithLoggerName(String loggerName, String format, Object... params) { |
| FormattingTuple tuple = MessageFormatter.arrayFormat(format, params); |
| String msg = tuple.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 tuple; |
| } |
| |
| private boolean logQuery(String query) { |
| if (ignorableQuery(query)) { |
| return false; |
| } |
| queryCount++; |
| return true; |
| } |
| |
| private boolean ignorableQuery(String msg) { |
| for (String ignorableQuery : IGNORABLE_QUERIES) { |
| if (msg.contains(ignorableQuery)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |