This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.tracer-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tracer.git
commit 02f853e01a8e43b4658f8f0e2bfa26ff457a7c53 Author: Chetan Mehrotra <[email protected]> AuthorDate: Thu Feb 11 07:15:11 2016 +0000 SLING-5505 - Allow recording of caller stacktrace with the logs Caller stack trace would be included if `caller` is set to true. One can specify the list packages which should be excluded with `callerPrefixFilter` git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/tracer@1729769 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/sling/tracer/internal/CallerFilter.java | 31 ++++++++ .../sling/tracer/internal/CallerStackReporter.java | 93 ++++++++++++++++++++++ .../sling/tracer/internal/JSONRecording.java | 23 +++++- .../sling/tracer/internal/PrefixExcludeFilter.java | 57 +++++++++++++ .../apache/sling/tracer/internal/TracerConfig.java | 19 ++++- .../apache/sling/tracer/internal/TracerSet.java | 32 +++++++- .../sling/tracer/internal/CallerFinderTest.java | 10 +-- .../tracer/internal/CallerStackReporterTest.java | 72 +++++++++++++++++ .../sling/tracer/internal/JSONRecordingTest.java | 20 ++++- .../sling/tracer/internal/TracerSetTest.java | 74 +++++++++++++++++ 10 files changed, 421 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/apache/sling/tracer/internal/CallerFilter.java b/src/main/java/org/apache/sling/tracer/internal/CallerFilter.java new file mode 100644 index 0000000..56d8ca7 --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/CallerFilter.java @@ -0,0 +1,31 @@ +/* + * 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; + +interface CallerFilter { + CallerFilter ALL = new CallerFilter() { + @Override + public boolean include(StackTraceElement ste) { + return true; + } + }; + + boolean include(StackTraceElement ste); +} diff --git a/src/main/java/org/apache/sling/tracer/internal/CallerStackReporter.java b/src/main/java/org/apache/sling/tracer/internal/CallerStackReporter.java new file mode 100644 index 0000000..59006bb --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/CallerStackReporter.java @@ -0,0 +1,93 @@ +/* + * 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.List; + +import static java.util.Arrays.asList; + +class CallerStackReporter { + /** + * Package names which should be excluded from the stack as they do not serve + * any purpose and bloat the size + */ + private static final CallerFilter FWK_EXCLUDE_FILTER = new PrefixExcludeFilter(asList( + "java.lang.Thread", + "org.apache.sling.tracer.internal", + "ch.qos.logback.classic", + "sun.reflect", + "java.lang.reflect" + )); + private final CallerFilter callerFilter; + private final int start; + private final int depth; + + public CallerStackReporter(int depth){ + this(0, depth, CallerFilter.ALL); + } + + public CallerStackReporter(int start, int depth, CallerFilter filter){ + this.start = start; + this.depth = depth; + this.callerFilter = filter; + } + + public List<StackTraceElement> report(){ + return report(Thread.currentThread().getStackTrace()); + } + + public List<StackTraceElement> report(StackTraceElement[] stack){ + List<StackTraceElement> filteredStack = fwkExcludedStack(stack); + List<StackTraceElement> result = new ArrayList<StackTraceElement>(filteredStack.size()); + + //Iterate over the filtered stack with limits applicable on that not on actual stack + for (int i = 0; i < filteredStack.size(); i++) { + StackTraceElement ste = filteredStack.get(i); + if (i >= start && i < depth + && callerFilter.include(ste)){ + result.add(ste); + } + } + return result; + } + + private List<StackTraceElement> fwkExcludedStack(StackTraceElement[] stack) { + List<StackTraceElement> filteredStack = new ArrayList<StackTraceElement>(stack.length); + for (StackTraceElement ste : stack) { + if (FWK_EXCLUDE_FILTER.include(ste)){ + filteredStack.add(ste); + } + } + return filteredStack; + } + + public CallerFilter getCallerFilter() { + return callerFilter; + } + + public int getStart() { + return start; + } + + public int getDepth() { + return depth; + } +} diff --git a/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java b/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java index f508e9e..a319a45 100644 --- a/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java +++ b/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java @@ -32,6 +32,7 @@ import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.zip.GZIPInputStream; @@ -129,7 +130,7 @@ class JSONRecording implements Recording, Comparable<JSONRecording> { if (logger.startsWith(OAK_QUERY_PKG)) { queryCollector.record(level, logger, tuple); } - logs.add(new LogEntry(level, logger, tuple)); + logs.add(new LogEntry(tc, level, logger, tuple)); } @Override @@ -233,11 +234,20 @@ class JSONRecording implements Recording, Comparable<JSONRecording> { final String logger; final FormattingTuple tuple; final long timestamp = System.currentTimeMillis(); + final List<StackTraceElement> caller; - private LogEntry(Level level, String logger, FormattingTuple tuple) { + private LogEntry(TracerConfig tc, Level level, String logger, FormattingTuple tuple) { this.level = level != null ? level : Level.INFO; this.logger = logger; this.tuple = tuple; + this.caller = getCallerData(tc); + } + + private static List<StackTraceElement> getCallerData(TracerConfig tc) { + if (tc.isReportCallerStack()){ + return tc.getCallerReporter().report(); + } + return Collections.emptyList(); } private static String toString(Object o) { @@ -274,6 +284,15 @@ class JSONRecording implements Recording, Comparable<JSONRecording> { jw.key("exception").value(getStackTraceAsString(t)); } + + if (!caller.isEmpty()){ + jw.key("caller"); + jw.array(); + for (StackTraceElement o : caller) { + jw.value(o.toString()); + } + jw.endArray(); + } } } diff --git a/src/main/java/org/apache/sling/tracer/internal/PrefixExcludeFilter.java b/src/main/java/org/apache/sling/tracer/internal/PrefixExcludeFilter.java new file mode 100644 index 0000000..8199010 --- /dev/null +++ b/src/main/java/org/apache/sling/tracer/internal/PrefixExcludeFilter.java @@ -0,0 +1,57 @@ +/* + * 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.List; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; + +/** + * Filter which returns false if the package of stack trace element + * is part of exclude list of prefixes + */ +class PrefixExcludeFilter implements CallerFilter{ + private final List<String> prefixesToExclude; + + public PrefixExcludeFilter(List<String> prefixes) { + this.prefixesToExclude = ImmutableList.copyOf(prefixes); + } + + public static PrefixExcludeFilter from(String filter){ + List<String> prefixes = Splitter.on('|').omitEmptyStrings().trimResults().splitToList(filter); + return new PrefixExcludeFilter(prefixes); + } + + @Override + public boolean include(StackTraceElement ste) { + String className = ste.getClassName(); + for (String prefix : prefixesToExclude){ + if (className.startsWith(prefix)){ + return false; + } + } + return true; + } + + public List<String> getPrefixesToExclude() { + return prefixesToExclude; + } +} diff --git a/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java b/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java index 8f7a65b..597d93c 100644 --- a/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java +++ b/src/main/java/org/apache/sling/tracer/internal/TracerConfig.java @@ -19,6 +19,9 @@ package org.apache.sling.tracer.internal; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import ch.qos.logback.classic.Level; import ch.qos.logback.core.CoreConstants; @@ -37,11 +40,17 @@ class TracerConfig implements Comparable<TracerConfig> { private final String loggerName; private final Level level; private final int depth; + private final CallerStackReporter callerReporter; public TracerConfig(String loggerName, Level level) { + this(loggerName, level, null); + } + + public TracerConfig(String loggerName, Level level,@Nullable CallerStackReporter reporter) { this.loggerName = loggerName; this.level = level; this.depth = getDepth(loggerName); + this.callerReporter = reporter; } public boolean match(String loggerName) { @@ -59,7 +68,7 @@ class TracerConfig implements Comparable<TracerConfig> { } @Override - public int compareTo(TracerConfig o) { + public int compareTo(@Nonnull TracerConfig o) { int comp = depth > o.depth ? -1 : depth < o.depth ? 1 : 0; if (comp == 0) { comp = loggerName.compareTo(o.loggerName); @@ -79,6 +88,14 @@ class TracerConfig implements Comparable<TracerConfig> { return level; } + public boolean isReportCallerStack(){ + return callerReporter != null; + } + + public CallerStackReporter getCallerReporter() { + return callerReporter; + } + private static int getDepth(String loggerName) { int depth = 0; int fromIndex = 0; diff --git a/src/main/java/org/apache/sling/tracer/internal/TracerSet.java b/src/main/java/org/apache/sling/tracer/internal/TracerSet.java index c4bcdbb..176d57a 100644 --- a/src/main/java/org/apache/sling/tracer/internal/TracerSet.java +++ b/src/main/java/org/apache/sling/tracer/internal/TracerSet.java @@ -28,6 +28,9 @@ import org.apache.sling.commons.osgi.ManifestHeader; class TracerSet { public static final String LEVEL = "level"; + public static final String CALLER = "caller"; + public static final String CALLER_PREFIX_FILTER = "callerPrefixFilter"; + private final String name; private final List<TracerConfig> configs; @@ -72,8 +75,35 @@ class TracerSet { //Defaults to Debug Level level = Level.valueOf(e.getAttributeValue(LEVEL)); - result.add(new TracerConfig(category, level)); + CallerStackReporter reporter = createReporter(e); + result.add(new TracerConfig(category, level, reporter)); } return Collections.unmodifiableList(result); } + + static CallerStackReporter createReporter(ManifestHeader.Entry e) { + String caller = e.getAttributeValue(CALLER); + if (caller == null){ + return null; + } + + if ("true".equals(caller)){ + return new CallerStackReporter(0, Integer.MAX_VALUE, CallerFilter.ALL); + } + + CallerFilter filter = CallerFilter.ALL; + int depth; + try{ + depth = Integer.parseInt(caller); + } catch (NumberFormatException ignore){ + return null; + } + + String filterValue = e.getAttributeValue(CALLER_PREFIX_FILTER); + if (filterValue != null){ + filter = PrefixExcludeFilter.from(filterValue); + } + + return new CallerStackReporter(0, depth, filter); + } } diff --git a/src/test/java/org/apache/sling/tracer/internal/CallerFinderTest.java b/src/test/java/org/apache/sling/tracer/internal/CallerFinderTest.java index 29906ef..fc104f9 100644 --- a/src/test/java/org/apache/sling/tracer/internal/CallerFinderTest.java +++ b/src/test/java/org/apache/sling/tracer/internal/CallerFinderTest.java @@ -30,7 +30,7 @@ public class CallerFinderTest { @Test public void determineCallerSingle() throws Exception{ CallerFinder cf = new CallerFinder(new String[] {"o.a.s", "o.a.j.o"}); - StackTraceElement[] stack = createStack( + StackTraceElement[] stack = asStack( "o.a.j.o.a", "o.a.j.o.b", "c.a.g.w", @@ -47,7 +47,7 @@ public class CallerFinderTest { @Test public void determineCallerMultipleApi() throws Exception{ CallerFinder cf = new CallerFinder(new String[] {"o.a.s", "o.a.j.o"}); - StackTraceElement[] stack = createStack( + StackTraceElement[] stack = asStack( "o.a.j.o.a", "o.a.j.o.b", "o.a.s.a", @@ -62,7 +62,7 @@ public class CallerFinderTest { assertNotNull(caller); assertEquals("c.a.g.w", caller.getClassName()); - stack = createStack( + stack = asStack( "o.a.j.o.a", "o.a.j.o.b", "o.a.s.a", @@ -89,7 +89,7 @@ public class CallerFinderTest { @Test public void nullCaller() throws Exception{ CallerFinder cf = new CallerFinder(new String[] {"o.a1.s", "o.a1.j.o"}); - StackTraceElement[] stack = createStack( + StackTraceElement[] stack = asStack( "o.a.j.o.a", "o.a.j.o.b", "o.a.s.a", @@ -104,7 +104,7 @@ public class CallerFinderTest { assertNull(caller); } - private static StackTraceElement[] createStack(String ... stack){ + static StackTraceElement[] asStack(String ... stack){ StackTraceElement[] result = new StackTraceElement[stack.length]; for (int i = 0; i < stack.length; i++) { result[i] = new StackTraceElement(stack[i], "foo", null, 0); diff --git a/src/test/java/org/apache/sling/tracer/internal/CallerStackReporterTest.java b/src/test/java/org/apache/sling/tracer/internal/CallerStackReporterTest.java new file mode 100644 index 0000000..8ae067e --- /dev/null +++ b/src/test/java/org/apache/sling/tracer/internal/CallerStackReporterTest.java @@ -0,0 +1,72 @@ +/* + * 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.List; + +import org.junit.Test; + +import static org.apache.sling.tracer.internal.CallerFinderTest.asStack; +import static org.junit.Assert.assertArrayEquals; + +public class CallerStackReporterTest { + + @Test + public void startAndStop() throws Exception { + StackTraceElement[] s = asStack("0", "1", "2", "3", "4", "5"); + assertArrayEquals(new String[]{"0", "1", "2", "3"}, arr(new CallerStackReporter(4).report(s))); + assertArrayEquals(new String[]{"0"}, arr(new CallerStackReporter(1).report(s))); + assertArrayEquals(new String[]{"2", "3"}, arr(new CallerStackReporter(2, 4, CallerFilter.ALL).report(s))); + } + + @Test + public void filter() throws Exception{ + StackTraceElement[] s = asStack("0", "1", "2", "3", "4", "5"); + CallerFilter f = new CallerFilter() { + @Override + public boolean include(StackTraceElement ste) { + String name = ste.getClassName(); + return name.equals("1") || name.equals("2"); + } + }; + + assertArrayEquals(new String[]{"1", "2"}, arr(new CallerStackReporter(0, 4, f).report(s))); + } + + @Test + public void prefixFilter() throws Exception{ + StackTraceElement[] s = asStack("a.b.c", "a.b.d", "f.g.h", "m.g.i", "4", "5"); + assertArrayEquals(new String[]{"a.b.c", "a.b.d", "f.g.h", "m.g.i"}, + arr(new CallerStackReporter(0, 4, CallerFilter.ALL).report(s))); + + CallerFilter f = PrefixExcludeFilter.from("a.b|f.g"); + assertArrayEquals(new String[]{"m.g.i"}, + arr(new CallerStackReporter(0, 4, f).report(s))); + } + + private static String[] arr(List<StackTraceElement> list) { + String[] result = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i).getClassName(); + } + return result; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java b/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java index 14ab480..8daf2b4 100644 --- a/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java +++ b/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java @@ -25,7 +25,6 @@ import javax.servlet.http.HttpServletRequest; import ch.qos.logback.classic.Level; import org.apache.sling.commons.json.JSONObject; -import org.junit.Ignore; import org.junit.Test; import org.slf4j.MDC; import org.slf4j.helpers.FormattingTuple; @@ -104,12 +103,31 @@ public class JSONRecordingTest { assertEquals(tp1.getMessage(), l1.getString("message")); assertEquals(1, l1.getJSONArray("params").length()); assertFalse(l1.has("exception")); + assertFalse(l1.has("caller")); assertTrue(l1.has("timestamp")); JSONObject l3 = json.getJSONArray("logs").getJSONObject(2); assertNotNull(l3.get("exception")); } + @Test + public void logsWithCaller() throws Exception{ + StringWriter sw = new StringWriter(); + final JSONRecording r = new JSONRecording("abc", request, true); + + TracerConfig config = new TracerConfig(TracerContext.QUERY_LOGGER, + Level.INFO, new CallerStackReporter(20)); + r.log(config, Level.INFO, "foo", tuple("foo")); + + r.done(); + r.render(sw); + + JSONObject json = new JSONObject(sw.toString()); + JSONObject l1 = json.getJSONArray("logs").getJSONObject(0); + assertTrue(l1.has("caller")); + assertTrue(l1.getJSONArray("caller").length() > 0); + } + private static FormattingTuple tuple(String msg){ return MessageFormatter.format(msg, null); } diff --git a/src/test/java/org/apache/sling/tracer/internal/TracerSetTest.java b/src/test/java/org/apache/sling/tracer/internal/TracerSetTest.java new file mode 100644 index 0000000..fda855c --- /dev/null +++ b/src/test/java/org/apache/sling/tracer/internal/TracerSetTest.java @@ -0,0 +1,74 @@ +/* + * 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 org.apache.sling.commons.osgi.ManifestHeader; +import org.junit.Test; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +public class TracerSetTest { + + @Test + public void nullReporter() throws Exception{ + CallerStackReporter r = TracerSet.createReporter(createEntry("foo")); + assertNull(r); + } + + @Test + public void completeStack() throws Exception{ + CallerStackReporter r = TracerSet.createReporter(createEntry("foo;caller=true")); + assertNotNull(r); + assertEquals(Integer.MAX_VALUE, r.getDepth()); + } + + @Test + public void depthSpecified() throws Exception{ + CallerStackReporter r = TracerSet.createReporter(createEntry("foo;caller=28")); + assertNotNull(r); + assertEquals(28, r.getDepth()); + } + + @Test + public void invalidDepth() throws Exception{ + CallerStackReporter r = TracerSet.createReporter(createEntry("foo;caller=abc")); + assertNull(r); + } + + @Test + public void prefixFilter() throws Exception{ + CallerStackReporter r = TracerSet.createReporter(createEntry("foo;caller=28;callerPrefixFilter=\"a|b\"")); + assertNotNull(r); + assertEquals(28, r.getDepth()); + assertTrue(r.getCallerFilter() instanceof PrefixExcludeFilter); + PrefixExcludeFilter f = (PrefixExcludeFilter) r.getCallerFilter(); + assertEquals(asList("a", "b"), f.getPrefixesToExclude()); + } + + private static ManifestHeader.Entry createEntry(String config){ + ManifestHeader parsedConfig = ManifestHeader.parse(config); + return parsedConfig.getEntries()[0]; + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
