/*
* Copyright (c) 2011-2018 Pivotal Software Inc, All Rights Reserved.
*
* Licensed 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.
*/
/*
* Copyright (c) 2011-2018 Pivotal Software Inc, All Rights Reserved.
*
* Licensed 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 reactor.core.publisher;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Utilities around manipulating stack traces and displaying assembly traces.
Author: Simon Baslé
/**
* Utilities around manipulating stack traces and displaying assembly traces.
*
* @author Simon Baslé
*/
final class Traces {
Transform the StackTraceElement
array from an exception into a String
representation, each element being prepended with a tabulation and appended with a newline. No sanitation is performed. Params: - stackTraceElements – the array of
StackTraceElement
to convert to String
Returns: the string version of the stacktrace.
/**
* Transform the {@link StackTraceElement} array from an exception into a {@link String}
* representation, each element being prepended with a tabulation and appended with a
* newline. No sanitation is performed.
*
* @param stackTraceElements the array of {@link StackTraceElement} to convert to {@link String}
* @return the string version of the stacktrace.
*/
static String stackTraceToString(StackTraceElement[] stackTraceElements) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement e : stackTraceElements) {
String row = e.toString();
sb.append("\t")
.append(row)
.append("\n");
}
return sb.toString();
}
Return true for strings (usually from a stack trace element) that should be sanitized out by stackTraceToSanitizedString(StackTraceElement[])
. Params: - stackTraceRow – the row to check
Returns: true if it should be sanitized out, false if it should be kept
/**
* Return true for strings (usually from a stack trace element) that should be
* sanitized out by {@link #stackTraceToSanitizedString(StackTraceElement[])}.
*
* @param stackTraceRow the row to check
* @return true if it should be sanitized out, false if it should be kept
*/
static boolean shouldSanitize(String stackTraceRow) {
return stackTraceRow.trim().isEmpty()
|| stackTraceRow.contains("java.util.function")
|| stackTraceRow.contains("reactor.core.publisher.Mono.onAssembly")
|| stackTraceRow.contains("reactor.core.publisher.Flux.onAssembly")
|| stackTraceRow.contains("reactor.core.publisher.ParallelFlux.onAssembly")
|| stackTraceRow.contains("reactor.core.publisher.SignalLogger")
|| stackTraceRow.contains("FluxOnAssembly.")
|| stackTraceRow.contains("MonoOnAssembly.")
|| stackTraceRow.contains("MonoCallableOnAssembly.")
|| stackTraceRow.contains("FluxCallableOnAssembly.")
|| stackTraceRow.contains("OnOperatorDebug")
|| stackTraceRow.contains("reactor.core.publisher.Hooks")
|| stackTraceRow.contains(".junit.runner")
|| stackTraceRow.contains(".junit4.runner")
|| stackTraceRow.contains(".junit.internal")
|| stackTraceRow.contains("org.gradle.")
|| stackTraceRow.contains("sun.reflect")
|| stackTraceRow.contains("useTraceAssembly")
|| stackTraceRow.contains("java.lang.Thread.")
|| stackTraceRow.contains("ThreadPoolExecutor")
|| stackTraceRow.contains("org.apache.catalina.")
|| stackTraceRow.contains("org.apache.tomcat.")
|| stackTraceRow.contains("com.intellij.")
|| stackTraceRow.contains("java.lang.reflect");
}
Transform the StackTraceElement
array from an exception into a String
representation, each element being prepended with a tabulation and appended with a newline, unless they don't pass the sanitation filter
. Params: - stackTraceElements – the array of
StackTraceElement
to convert to String
Returns: the string version of the stacktrace.
/**
* Transform the {@link StackTraceElement} array from an exception into a {@link String}
* representation, each element being prepended with a tabulation and appended with a
* newline, unless they don't pass the {@link #shouldSanitize(String) sanitation filter}.
*
* @param stackTraceElements the array of {@link StackTraceElement} to convert to {@link String}
* @return the string version of the stacktrace.
*/
static String stackTraceToSanitizedString(StackTraceElement[] stackTraceElements) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement e : stackTraceElements) {
String row = e.toString();
if (e.getLineNumber() <= 1) {
continue;
}
if (shouldSanitize(row)) {
continue;
}
sb.append("\t")
.append(row)
.append("\n");
}
return sb.toString();
}
Extract operator information out of an assembly stack trace in String
form (see stackTraceToSanitizedString(StackTraceElement[])
). Most operators will result in a line of the form "Flux.map ⇢ user.code.Class.method(Class.java:123)"
, that is:
- The top of the stack is inspected for Reactor API references, and the deepest one is kept, since multiple API references generally denote an alias operator. (eg.
"Flux.map"
)
- The next stacktrace element is considered user code and is appended to the result with a
⇢
separator. (eg. " ⇢ user.code.Class.method(Class.java:123)"
)
- If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
- If the sanitized stack is empty, returns
"[no operator assembly information]"
Params: - source – the sanitized assembly stacktrace in String format.
Returns: a String
representing operator and operator assembly site extracted from the assembly stack trace.
/**
* Extract operator information out of an assembly stack trace in {@link String} form
* (see {@link #stackTraceToSanitizedString(StackTraceElement[])}).
* <p>
* Most operators will result in a line of the form {@code "Flux.map ⇢ user.code.Class.method(Class.java:123)"},
* that is:
* <ol>
* <li>The top of the stack is inspected for Reactor API references, and the deepest
* one is kept, since multiple API references generally denote an alias operator.
* (eg. {@code "Flux.map"})</li>
* <li>The next stacktrace element is considered user code and is appended to the
* result with a {@code ⇢} separator. (eg. {@code " ⇢ user.code.Class.method(Class.java:123)"})</li>
* <li>If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.</li>
* <li>If the sanitized stack is empty, returns {@code "[no operator assembly information]"}</li>
* </ol>
*
*
* @param source the sanitized assembly stacktrace in String format.
* @return a {@link String} representing operator and operator assembly site extracted
* from the assembly stack trace.
*/
static String extractOperatorAssemblyInformation(String source) {
return extractOperatorAssemblyInformation(source, false);
}
Extract operator information out of an assembly stack trace in String
form (see stackTraceToSanitizedString(StackTraceElement[])
) which potentially has a header line that one can skip by setting skipFirst
to true
. Most operators will result in a line of the form "Flux.map ⇢ user.code.Class.method(Class.java:123)"
, that is:
- The top of the stack is inspected for Reactor API references, and the deepest one is kept, since multiple API references generally denote an alias operator. (eg.
"Flux.map"
)
- The next stacktrace element is considered user code and is appended to the result with a
⇢
separator. (eg. " ⇢ user.code.Class.method(Class.java:123)"
)
- If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
- If the sanitized stack is empty, returns
"[no operator assembly information]"
Params: - source – the sanitized assembly stacktrace in String format.
Returns: a String
representing operator and operator assembly site extracted from the assembly stack trace.
/**
* Extract operator information out of an assembly stack trace in {@link String} form
* (see {@link #stackTraceToSanitizedString(StackTraceElement[])}) which potentially
* has a header line that one can skip by setting {@code skipFirst} to {@code true}.
* <p>
* Most operators will result in a line of the form {@code "Flux.map ⇢ user.code.Class.method(Class.java:123)"},
* that is:
* <ol>
* <li>The top of the stack is inspected for Reactor API references, and the deepest
* one is kept, since multiple API references generally denote an alias operator.
* (eg. {@code "Flux.map"})</li>
* <li>The next stacktrace element is considered user code and is appended to the
* result with a {@code ⇢} separator. (eg. {@code " ⇢ user.code.Class.method(Class.java:123)"})</li>
* <li>If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.</li>
* <li>If the sanitized stack is empty, returns {@code "[no operator assembly information]"}</li>
* </ol>
*
*
* @param source the sanitized assembly stacktrace in String format.
* @return a {@link String} representing operator and operator assembly site extracted
* from the assembly stack trace.
*/
static String extractOperatorAssemblyInformation(String source, boolean skipFirst) {
String[] uncleanTraces = source.split("\n");
final List<String> traces = Stream.of(uncleanTraces)
.map(String::trim)
.filter(s -> !s.isEmpty())
.skip(skipFirst ? 1 : 0)
.collect(Collectors.toList());
if (traces.isEmpty()) {
return "[no operator assembly information]";
}
int i = 0;
while (i < traces.size() && traces.get(i).startsWith("reactor.core.publisher") && !traces.get(i).contains("Test")) {
i++;
}
String apiLine;
String userCodeLine;
if (i == 0) {
//no line was a reactor API line
apiLine = "";
userCodeLine = traces.get(0);
}
else if (i == traces.size()) {
//we skipped ALL lines, meaning they're all reactor API lines. We'll fully display the last one
apiLine = "";
userCodeLine = traces.get(i-1).replaceFirst("reactor.core.publisher.", "");
}
else {
//currently on user code line, previous one is API
apiLine = traces.get(i - 1);
userCodeLine = traces.get(i);
}
//now we want something in the form "Flux.map ⇢ user.code.Class.method(Class.java:123)"
if (apiLine.isEmpty()) return userCodeLine;
int linePartIndex = apiLine.indexOf('(');
if (linePartIndex > 0) {
apiLine = apiLine.substring(0, linePartIndex);
}
apiLine = apiLine.replaceFirst("reactor.core.publisher.", "");
return apiLine + " ⇢ " + userCodeLine;
}
}