/*
 * 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:
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:
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:

  1. 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")
  2. 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)")
  3. If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
  4. 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:

  1. 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")
  2. 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)")
  3. If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
  4. 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; } }