Home / Class/ StandardStackTracePrinter Class — spring-boot Architecture

StandardStackTracePrinter Class — spring-boot Architecture

Architecture documentation for the StandardStackTracePrinter class in StandardStackTracePrinter.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java lines 44–486

public final class StandardStackTracePrinter implements StackTracePrinter {

	private static final String DEFAULT_LINE_SEPARATOR = System.lineSeparator();

	private static final ToIntFunction<StackTraceElement> DEFAULT_FRAME_HASHER = (frame) -> Objects
		.hash(frame.getClassName(), frame.getMethodName(), frame.getLineNumber());

	private static final int UNLIMITED = Integer.MAX_VALUE;

	private final EnumSet<Option> options;

	private final int maximumLength;

	private final String lineSeparator;

	private final Predicate<Throwable> filter;

	private final BiPredicate<Integer, StackTraceElement> frameFilter;

	private final Function<Throwable, String> formatter;

	private final Function<StackTraceElement, String> frameFormatter;

	private final @Nullable ToIntFunction<StackTraceElement> frameHasher;

	private StandardStackTracePrinter(EnumSet<Option> options, int maximumLength, @Nullable String lineSeparator,
			@Nullable Predicate<Throwable> filter, @Nullable BiPredicate<Integer, StackTraceElement> frameFilter,
			@Nullable Function<Throwable, String> formatter,
			@Nullable Function<StackTraceElement, String> frameFormatter,
			@Nullable ToIntFunction<StackTraceElement> frameHasher) {
		this.options = options;
		this.maximumLength = maximumLength;
		this.lineSeparator = (lineSeparator != null) ? lineSeparator : DEFAULT_LINE_SEPARATOR;
		this.filter = (filter != null) ? filter : (t) -> true;
		this.frameFilter = (frameFilter != null) ? frameFilter : (i, t) -> true;
		this.formatter = (formatter != null) ? formatter : Object::toString;
		this.frameFormatter = (frameFormatter != null) ? frameFormatter : Object::toString;
		this.frameHasher = frameHasher;
	}

	@Override
	public void printStackTrace(Throwable throwable, Appendable out) throws IOException {
		if (this.filter.test(throwable)) {
			Set<Throwable> seen = Collections.newSetFromMap(new IdentityHashMap<>());
			Output output = new Output(out);
			Print print = new Print("", "", output);
			printFullStackTrace(seen, print, new StackTrace(throwable), null);
		}
	}

	private void printFullStackTrace(Set<Throwable> seen, Print print, @Nullable StackTrace stackTrace,
			@Nullable StackTrace enclosing) throws IOException {
		if (stackTrace == null) {
			return;
		}
		if (!seen.add(stackTrace.throwable())) {
			String hashPrefix = stackTrace.hashPrefix(this.frameHasher);
			String throwable = this.formatter.apply(stackTrace.throwable());
			print.circularReference(hashPrefix, throwable);
			return;
		}
		StackTrace cause = stackTrace.cause();
		if (!hasOption(Option.ROOT_FIRST)) {
			printSingleStackTrace(seen, print, stackTrace, enclosing);
			printFullStackTrace(seen, print.withCausedByCaption(cause), cause, stackTrace);
		}
		else {
			printFullStackTrace(seen, print, cause, stackTrace);
			printSingleStackTrace(seen, print.withWrappedByCaption(cause), stackTrace, enclosing);
		}
	}

	private void printSingleStackTrace(Set<Throwable> seen, Print print, StackTrace stackTrace,
			@Nullable StackTrace enclosing) throws IOException {
		String hashPrefix = stackTrace.hashPrefix(this.frameHasher);
		String throwable = this.formatter.apply(stackTrace.throwable());
		print.thrown(hashPrefix, throwable);
		printFrames(print, stackTrace, enclosing);
		if (!hasOption(Option.HIDE_SUPPRESSED)) {
			StackTrace[] suppressed = stackTrace.suppressed();
			if (suppressed != null) {
				for (StackTrace suppressedStackTrace : suppressed) {
					printFullStackTrace(seen, print.withSuppressedCaption(), suppressedStackTrace, stackTrace);
				}
			}
		}
	}

	private void printFrames(Print print, StackTrace stackTrace, @Nullable StackTrace enclosing) throws IOException {
		int commonFrames = (!hasOption(Option.SHOW_COMMON_FRAMES)) ? stackTrace.commonFramesCount(enclosing) : 0;
		int filteredFrames = 0;
		StackTraceElement[] frames = stackTrace.frames();
		if (frames != null) {
			for (int i = 0; i < frames.length - commonFrames; i++) {
				StackTraceElement element = frames[i];
				if (!this.frameFilter.test(i, element)) {
					filteredFrames++;
					continue;
				}
				print.omittedFilteredFrames(filteredFrames);
				filteredFrames = 0;
				print.at(this.frameFormatter.apply(element));
			}
		}
		print.omittedFilteredFrames(filteredFrames);
		if (commonFrames != 0) {
			print.omittedCommonFrames(commonFrames);
		}
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that will print all
	 * common frames rather the replacing them with the {@literal "... N more"} message.
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withCommonFrames() {
		return withOption(Option.SHOW_COMMON_FRAMES);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that will not print
	 * {@link Throwable#getSuppressed() suppressed} items.
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withoutSuppressed() {
		return withOption(Option.HIDE_SUPPRESSED);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that will use ellipses
	 * to truncate output longer that the specified length.
	 * @param maximumLength the maximum length that can be printed
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withMaximumLength(int maximumLength) {
		Assert.isTrue(maximumLength > 0, "'maximumLength' must be positive");
		return new StandardStackTracePrinter(this.options, maximumLength, this.lineSeparator, this.filter,
				this.frameFilter, this.formatter, this.frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that filter frames
	 * (including caused and suppressed) deeper then the specified maximum.
	 * @param maximumThrowableDepth the maximum throwable depth
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withMaximumThrowableDepth(int maximumThrowableDepth) {
		Assert.isTrue(maximumThrowableDepth > 0, "'maximumThrowableDepth' must be positive");
		return withFrameFilter((index, element) -> index < maximumThrowableDepth);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that will only include
	 * throwables (excluding caused and suppressed) that match the given predicate.
	 * @param predicate the predicate used to filter the throwable
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withFilter(Predicate<Throwable> predicate) {
		Assert.notNull(predicate, "'predicate' must not be null");
		return new StandardStackTracePrinter(this.options, this.maximumLength, this.lineSeparator,
				this.filter.and(predicate), this.frameFilter, this.formatter, this.frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that will only include
	 * frames that match the given predicate.
	 * @param predicate the predicate used to filter frames
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withFrameFilter(BiPredicate<Integer, StackTraceElement> predicate) {
		Assert.notNull(predicate, "'predicate' must not be null");
		return new StandardStackTracePrinter(this.options, this.maximumLength, this.lineSeparator, this.filter,
				this.frameFilter.and(predicate), this.formatter, this.frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that print the stack
	 * trace using the specified line separator.
	 * @param lineSeparator the line separator to use
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withLineSeparator(String lineSeparator) {
		Assert.notNull(lineSeparator, "'lineSeparator' must not be null");
		return new StandardStackTracePrinter(this.options, this.maximumLength, lineSeparator, this.filter,
				this.frameFilter, this.formatter, this.frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one uses the specified
	 * formatter to create a string representation of a throwable.
	 * @param formatter the formatter to use
	 * @return a new {@link StandardStackTracePrinter} instance
	 * @see #withLineSeparator(String)
	 */
	public StandardStackTracePrinter withFormatter(Function<Throwable, String> formatter) {
		Assert.notNull(formatter, "'formatter' must not be null");
		return new StandardStackTracePrinter(this.options, this.maximumLength, this.lineSeparator, this.filter,
				this.frameFilter, formatter, this.frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one uses the specified
	 * formatter to create a string representation of a frame.
	 * @param frameFormatter the frame formatter to use
	 * @return a new {@link StandardStackTracePrinter} instance
	 * @see #withLineSeparator(String)
	 */
	public StandardStackTracePrinter withFrameFormatter(Function<StackTraceElement, String> frameFormatter) {
		Assert.notNull(frameFormatter, "'frameFormatter' must not be null");
		return new StandardStackTracePrinter(this.options, this.maximumLength, this.lineSeparator, this.filter,
				this.frameFilter, this.formatter, frameFormatter, this.frameHasher);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that generates and
	 * prints hashes for each stacktrace.
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withHashes() {
		return withHashes(true);
	}

	/**
	 * Return a new {@link StandardStackTracePrinter} from this one that changes if hashes
	 * should be generated and printed for each stacktrace.
	 * @param hashes if hashes should be added
	 * @return a new {@link StandardStackTracePrinter} instance
	 */
	public StandardStackTracePrinter withHashes(boolean hashes) {
		return withHashes((!hashes) ? null : DEFAULT_FRAME_HASHER);
	}

	public StandardStackTracePrinter withHashes(@Nullable ToIntFunction<StackTraceElement> frameHasher) {
		return new StandardStackTracePrinter(this.options, this.maximumLength, this.lineSeparator, this.filter,
				this.frameFilter, this.formatter, this.frameFormatter, frameHasher);
	}

	private StandardStackTracePrinter withOption(Option option) {
		EnumSet<Option> options = EnumSet.copyOf(this.options);
		options.add(option);
		return new StandardStackTracePrinter(options, this.maximumLength, this.lineSeparator, this.filter,
				this.frameFilter, this.formatter, this.frameFormatter, this.frameHasher);
	}

	private boolean hasOption(Option option) {
		return this.options.contains(option);
	}

	/**
	 * Return a {@link StandardStackTracePrinter} that prints the stack trace with the
	 * root exception last (the same as {@link Throwable#printStackTrace()}).
	 * @return a {@link StandardStackTracePrinter} that prints the stack trace root last
	 */
	public static StandardStackTracePrinter rootLast() {
		return new StandardStackTracePrinter(EnumSet.noneOf(Option.class), UNLIMITED, null, null, null, null, null,
				null);
	}

	/**
	 * Return a {@link StandardStackTracePrinter} that prints the stack trace with the
	 * root exception first (the opposite of {@link Throwable#printStackTrace()}).
	 * @return a {@link StandardStackTracePrinter} that prints the stack trace root first
	 */
	public static StandardStackTracePrinter rootFirst() {
		return new StandardStackTracePrinter(EnumSet.of(Option.ROOT_FIRST), UNLIMITED, null, null, null, null, null,
				null);
	}

	/**
	 * Options supported by this printer.
	 */
	private enum Option {

		ROOT_FIRST, SHOW_COMMON_FRAMES, HIDE_SUPPRESSED

	}

	/**
	 * Prints the actual line output.
	 */
	private record Print(String indent, String caption, Output output) {

		void circularReference(String hashPrefix, String throwable) throws IOException {
			this.output.println(this.indent, this.caption + "[CIRCULAR REFERENCE: " + hashPrefix + throwable + "]");
		}

		void thrown(String hashPrefix, String throwable) throws IOException {
			this.output.println(this.indent, this.caption + hashPrefix + throwable);
		}

		void at(String frame) throws IOException {
			this.output.println(this.indent, "\tat " + frame);
		}

		void omittedFilteredFrames(int filteredFrameCount) throws IOException {
			if (filteredFrameCount > 0) {
				this.output.println(this.indent, "\t... " + filteredFrameCount + " filtered");
			}
		}

		void omittedCommonFrames(int commonFrameCount) throws IOException {
			this.output.println(this.indent, "\t... " + commonFrameCount + " more");
		}

		Print withCausedByCaption(@Nullable StackTrace causedBy) {
			return withCaption(causedBy != null, "", "Caused by: ");
		}

		Print withWrappedByCaption(@Nullable StackTrace wrappedBy) {
			return withCaption(wrappedBy != null, "", "Wrapped by: ");
		}

		public Print withSuppressedCaption() {
			return withCaption(true, "\t", "Suppressed: ");
		}

		private Print withCaption(boolean test, String extraIndent, String caption) {
			return (test) ? new Print(this.indent + extraIndent, caption, this.output) : this;
		}

	}

	/**
	 * Line-by-line output.
	 */
	private class Output {

		private static final String ELLIPSIS = "...";

		private final Appendable out;

		private int remaining;

		Output(Appendable out) {
			this.out = out;
			this.remaining = StandardStackTracePrinter.this.maximumLength - ELLIPSIS.length();
		}

		void println(String indent, String string) throws IOException {
			if (this.remaining > 0) {
				String line = indent + string + StandardStackTracePrinter.this.lineSeparator;
				if (line.length() > this.remaining) {
					line = line.substring(0, this.remaining) + ELLIPSIS;
				}
				this.out.append(line);
				this.remaining -= line.length();
			}
		}

	}

	/**
	 * Holds the stacktrace for a specific throwable and caches things that are expensive
	 * to calculate.
	 */
	private static final class StackTrace {

		private final Throwable throwable;

		private final StackTraceElement @Nullable [] frames;

		private StackTrace @Nullable [] suppressed;

		private @Nullable StackTrace cause;

		private @Nullable Integer hash;

		private @Nullable String hashPrefix;

		private StackTrace(Throwable throwable) {
			this.throwable = throwable;
			this.frames = (throwable != null) ? throwable.getStackTrace() : null;
		}

		Throwable throwable() {
			return this.throwable;
		}

		StackTraceElement @Nullable [] frames() {
			return this.frames;
		}

		int commonFramesCount(@Nullable StackTrace other) {
			if (other == null || this.frames == null || other.frames == null) {
				return 0;
			}
			int index = this.frames.length - 1;
			int otherIndex = other.frames.length - 1;
			while (index >= 0 && otherIndex >= 0 && this.frames[index].equals(other.frames[otherIndex])) {
				index--;
				otherIndex--;
			}
			return this.frames.length - 1 - index;
		}

		StackTrace @Nullable [] suppressed() {
			if (this.suppressed == null && this.throwable != null) {
				this.suppressed = Arrays.stream(this.throwable.getSuppressed())
					.map(StackTrace::new)
					.toArray(StackTrace[]::new);
			}
			return this.suppressed;
		}

		@Nullable StackTrace cause() {
			if (this.cause == null && this.throwable != null) {
				Throwable cause = this.throwable.getCause();
				this.cause = (cause != null) ? new StackTrace(cause) : null;
			}
			return this.cause;
		}

		String hashPrefix(@Nullable ToIntFunction<StackTraceElement> frameHasher) {
			if (frameHasher == null || throwable() == null) {
				return "";
			}
			this.hashPrefix = (this.hashPrefix != null) ? this.hashPrefix
					: String.format("<#%08x> ", hash(new HashSet<>(), frameHasher));
			return this.hashPrefix;
		}

		private int hash(HashSet<Throwable> seen, ToIntFunction<StackTraceElement> frameHasher) {
			if (this.hash != null) {
				return this.hash;
			}
			int hash = 0;
			StackTrace cause = cause();
			if (cause != null && seen.add(cause.throwable())) {
				hash = cause.hash(seen, frameHasher);
			}
			hash = 31 * hash + throwable().getClass().getName().hashCode();
			if (frames() != null) {
				for (StackTraceElement frame : frames()) {
					hash = 31 * hash + frameHasher.applyAsInt(frame);
				}
			}
			this.hash = hash;
			return hash;
		}

	}

}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free