diff --git a/README.md b/README.md index 11ff793a..dc1ec95c 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ The structure of the output, and the data it contains, is fully configurable. * [Omit Common Frames](#omit-common-frames) * [Truncate after Regex](#truncate-after-regex) * [Exclude Frames per Regex](#exclude-frames-per-regex) + * [Exclude Messages From Stacktrace](#exclude-messages-from-stacktrace) * [Maximum Depth per Throwable](#maximum-depth-per-throwable) * [Maximum Trace Size (bytes)](#maximum-trace-size) * [Classname Shortening](#classname-shortening) @@ -2095,6 +2096,38 @@ Alternatively, multiple exclusion patterns can be specified at once using the `< Using the `` configuration option can be useful when using an environment variable to specify the actual patterns at deployment time. +### Exclude Messages From Stacktrace +In the event you wish to exclude all messages from the stackrace, then use ShortenedThrowableConverter.SANITIZE in you log statement. +This feature will exclude all messages in the entire stacktrace from appearing. + +**Example** +``` +Exception in thread "main" com.myproject.module.MyProjectFooBarException: Customer ssn of 12345678 was not registered + at com.myproject.module.MyProject.anotherMethod(MyProject.java:19) + at com.myproject.module.MyProject.someMethod(MyProject.java:12) + at com.myproject.module.MyProject.main(MyProject.java:8) +Caused by: java.lang.ArithmeticException: Could not generate userId for Customer with phone number 111-111-1111 + at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143) + at com.myproject.module.MyProject.anotherMethod(MyProject.java:17) + ... 2 more +``` + +If the above is logged utilizing the ShortenedThrowableConverter.SANITIZE marker, then all messages will be suppressed in the logging output. +```java + log.error(SANITIZE, "An exception was thrown but I want to make sure no customer data is shown in stacktrace", e); +``` +Will produce: +``` +Exception in thread "main" com.myproject.module.MyProjectFooBarException: + at com.myproject.module.MyProject.anotherMethod(MyProject.java:19) + at com.myproject.module.MyProject.someMethod(MyProject.java:12) + at com.myproject.module.MyProject.main(MyProject.java:8) +Caused by: java.lang.ArithmeticException: + at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143) + at com.myproject.module.MyProject.anotherMethod(MyProject.java:17) + ... 2 more +``` +This enables devs to still see the type of exception thrown and where it occurred, **without** exposing sensitive data. ### Maximum Depth per Throwable diff --git a/src/main/java/net/logstash/logback/stacktrace/ShortenedThrowableConverter.java b/src/main/java/net/logstash/logback/stacktrace/ShortenedThrowableConverter.java index 7de06369..b55c96bb 100644 --- a/src/main/java/net/logstash/logback/stacktrace/ShortenedThrowableConverter.java +++ b/src/main/java/net/logstash/logback/stacktrace/ShortenedThrowableConverter.java @@ -43,6 +43,8 @@ import ch.qos.logback.core.boolex.EventEvaluator; import ch.qos.logback.core.joran.spi.DefaultClass; import ch.qos.logback.core.status.ErrorStatus; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; /** * A {@link ThrowableHandlingConverter} (similar to logback's {@link ThrowableProxyConverter}) @@ -75,7 +77,7 @@ *
  • shortenedClassNameLength = "full" or "short" or an integer value
  • *
  • maxLength = "full" or "short" or an integer value
  • * - * + * * The other options can be listed in any order and are interpreted as follows: * - * + * *

    * For example, *

    @@ -131,6 +133,8 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
         private static final int OPTION_INDEX_SHORTENED_CLASS_NAME = 1;
         private static final int OPTION_INDEX_MAX_LENGTH = 2;
     
    +    public static final Marker SANITIZE = MarkerFactory.getMarker("SANITIZE");
    +
         /**
          * String sequence to use to delimit lines instead of {@link CoreConstants#LINE_SEPARATOR}
          * when inline is active
    @@ -154,7 +158,7 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
          * Initialized during {@link #start()}.
          */
         private Abbreviator abbreviator = new DefaultTargetLengthAbbreviator();
    -    
    +
         /**
          * Patterns used to determine which stacktrace elements to exclude.
          *
    @@ -168,12 +172,12 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
     
         /**
          * Patterns used to determine after which element the stack trace must be truncated.
    -     * 
    +     *
          * The strings being matched against are in the form "fullyQualifiedClassName.methodName"
          * (e.g. "java.lang.Object.toString").
          */
         private List truncateAfterPatterns = new ArrayList<>();
    -    
    +
         /**
          * True to print the root cause first.  False to print exceptions normally (root cause last).
          */
    @@ -188,7 +192,7 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
          * True to omit common frames
          */
         private boolean omitCommonFrames = true;
    -    
    +
         /** line delimiter */
         private String lineSeparator = CoreConstants.LINE_SEPARATOR;
     
    @@ -197,7 +201,7 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
         private StackHasher stackHasher;
     
         private StackElementFilter truncateAfterFilter;
    -    
    +
         /**
          * Evaluators that determine if the stacktrace should be logged.
          */
    @@ -234,7 +238,7 @@ public void stop() {
             super.stop();
             LogbackUtils.stop(this.abbreviator);
         }
    -    
    +
         private void parseOptions() {
             List optionList = getOptionList();
     
    @@ -269,31 +273,31 @@ private void parseOptions() {
                             case OPTION_VALUE_ROOT_FIRST:
                                 setRootCauseFirst(true);
                                 break;
    -                        
    +
                             case OPTION_VALUE_INLINE_HASH:
                                 setInlineHash(true);
                                 break;
    -                            
    +
                             case OPTION_VALUE_INLINE_STACK:
                                 setLineSeparator(DEFAULT_INLINE_SEPARATOR);
                                 break;
    -                        
    +
                             case OPTION_VALUE_OMITCOMMONFRAMES:
                                 setOmitCommonFrames(true);
                                 break;
    -                            
    +
                             case OPTION_VALUE_KEEPCOMMONFRAMES:
                                 setOmitCommonFrames(false);
                                 break;
    -                            
    +
                             default:
                                 @SuppressWarnings("rawtypes")
                                 Map evaluatorMap = (Map) getContext().getObject(CoreConstants.EVALUATOR_MAP);
                                 @SuppressWarnings("unchecked")
                                 EventEvaluator evaluator = (evaluatorMap != null)
    -                                ? (EventEvaluator) evaluatorMap.get(option)
    -                                : null;
    -    
    +                                    ? (EventEvaluator) evaluatorMap.get(option)
    +                                    : null;
    +
                                 if (evaluator != null) {
                                     addEvaluator(evaluator);
                                 } else {
    @@ -325,7 +329,7 @@ public String convert(ILoggingEvent event) {
             if (!isStarted()) {
                 throw new IllegalStateException("Converter is not started");
             }
    -        
    +
             IThrowableProxy throwableProxy = event.getThrowableProxy();
             if (throwableProxy == null || isExcludedByEvaluator(event)) {
                 return CoreConstants.EMPTY_STRING;
    @@ -343,9 +347,9 @@ public String convert(ILoggingEvent event) {
              */
             StringBuilder builder = new StringBuilder(Math.min(BUFFER_INITIAL_CAPACITY, this.maxLength + 100 > 0 ? this.maxLength + 100 : this.maxLength));
             if (rootCauseFirst) {
    -            appendRootCauseFirst(builder, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, throwableProxy, stackHashes);
    +            appendRootCauseFirst(builder, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, throwableProxy, stackHashes, event);
             } else {
    -            appendRootCauseLast(builder, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, throwableProxy, stackHashes);
    +            appendRootCauseLast(builder, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, throwableProxy, stackHashes, event);
             }
             if (builder.length() > this.maxLength) {
                 builder.setLength(this.maxLength - ELLIPSIS.length() - getLineSeparator().length());
    @@ -378,8 +382,8 @@ public void setLineSeparator(String lineSeparator) {
         public String getLineSeparator() {
             return lineSeparator;
         }
    -    
    -    
    +
    +
         /**
          * Return true if any evaluator returns true, indicating that
          * the stack trace should not be logged.
    @@ -397,11 +401,11 @@ private boolean isExcludedByEvaluator(ILoggingEvent event) {
                         addError(String.format("Exception thrown for evaluator named [%s]", evaluator.getName()), eex);
                     } else if (errors == CoreConstants.MAX_ERROR_COUNT) {
                         ErrorStatus errorStatus = new ErrorStatus(
    -                        String.format("Exception thrown for evaluator named [%s]", evaluator.getName()), this, eex);
    +                            String.format("Exception thrown for evaluator named [%s]", evaluator.getName()), this, eex);
                         errorStatus.add(new ErrorStatus(
    -                        "This was the last warning about this evaluator's errors. "
    -                            + "We don't want the StatusManager to get flooded.",
    -                        this));
    +                            "This was the last warning about this evaluator's errors. "
    +                                    + "We don't want the StatusManager to get flooded.",
    +                            this));
                         addStatus(errorStatus);
                     }
                 }
    @@ -418,24 +422,24 @@ private void appendRootCauseLast(
                 String prefix,
                 int indent,
                 IThrowableProxy throwableProxy,
    -            Deque stackHashes) {
    +            Deque stackHashes, ILoggingEvent event) {
     
             if (throwableProxy == null || builder.length() > this.maxLength) {
                 return;
             }
     
             String hash = stackHashes == null || stackHashes.isEmpty() ? null : stackHashes.removeFirst();
    -        appendFirstLine(builder, prefix, indent, throwableProxy, hash);
    +        appendFirstLine(builder, prefix, indent, throwableProxy, hash, event);
             appendStackTraceElements(builder, indent, throwableProxy);
     
             IThrowableProxy[] suppressedThrowableProxies = throwableProxy.getSuppressed();
             if (suppressedThrowableProxies != null) {
                 for (IThrowableProxy suppressedThrowableProxy : suppressedThrowableProxies) {
                     // stack hashes are not computed/inlined on suppressed errors
    -                appendRootCauseLast(builder, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT, suppressedThrowableProxy, null);
    +                appendRootCauseLast(builder, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT, suppressedThrowableProxy, null, event);
                 }
             }
    -        appendRootCauseLast(builder, CoreConstants.CAUSED_BY, indent, throwableProxy.getCause(), stackHashes);
    +        appendRootCauseLast(builder, CoreConstants.CAUSED_BY, indent, throwableProxy.getCause(), stackHashes, event);
         }
     
         /**
    @@ -447,26 +451,26 @@ private void appendRootCauseFirst(
                 String prefix,
                 int indent,
                 IThrowableProxy throwableProxy,
    -            Deque stackHashes) {
    +            Deque stackHashes, ILoggingEvent event) {
     
             if (throwableProxy == null || builder.length() > this.maxLength) {
                 return;
             }
     
             if (throwableProxy.getCause() != null) {
    -            appendRootCauseFirst(builder, prefix, indent, throwableProxy.getCause(), stackHashes);
    +            appendRootCauseFirst(builder, prefix, indent, throwableProxy.getCause(), stackHashes, event);
                 prefix = CoreConstants.WRAPPED_BY;
             }
     
             String hash = stackHashes == null || stackHashes.isEmpty() ? null : stackHashes.removeLast();
    -        appendFirstLine(builder, prefix, indent, throwableProxy, hash);
    +        appendFirstLine(builder, prefix, indent, throwableProxy, hash, event);
             appendStackTraceElements(builder, indent, throwableProxy);
     
             IThrowableProxy[] suppressedThrowableProxies = throwableProxy.getSuppressed();
             if (suppressedThrowableProxies != null) {
                 for (IThrowableProxy suppressedThrowableProxy : suppressedThrowableProxies) {
                     // stack hashes are not computed/inlined on suppressed errors
    -                appendRootCauseFirst(builder, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT, suppressedThrowableProxy, null);
    +                appendRootCauseFirst(builder, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT, suppressedThrowableProxy, null, event);
                 }
             }
         }
    @@ -481,12 +485,12 @@ private void appendStackTraceElements(StringBuilder builder, int indent, IThrowa
             }
             StackTraceElementProxy[] stackTraceElements = throwableProxy.getStackTraceElementProxyArray();
             int commonFrames = isOmitCommonFrames() ? throwableProxy.getCommonFrames() : 0;
    -        
    +
             boolean appendingExcluded = false;
             int consecutiveExcluded = 0;
             int appended = 0;
             StackTraceElementProxy previousWrittenStackTraceElement = null;
    -        
    +
             int i = 0;
             for (; i < stackTraceElements.length - commonFrames; i++) {
                 if (this.maxDepthPerThrowable > 0 && appended >= this.maxDepthPerThrowable) {
    @@ -543,7 +547,7 @@ else if (appendingExcluded) {
                 }
             }
     
    -        
    +
             /*
              * We did not process the stack up to the last element (max depth, truncate line)
              */
    @@ -558,7 +562,7 @@ else if (appendingExcluded) {
                     appendStackTraceElement(builder, indent, stackTraceElements[i], previousWrittenStackTraceElement);
                     appended++;
                 }
    -            
    +
                 if (commonFrames > 0) {
                     appendPlaceHolder(builder, indent, stackTraceElements.length - appended - consecutiveExcluded, "frames truncated (including " + commonFrames + " common frames)");
                 }
    @@ -573,7 +577,7 @@ else if (appendingExcluded) {
                      */
                     appendPlaceHolder(builder, indent, consecutiveExcluded, "frames excluded");
                 }
    -    
    +
                 if (commonFrames > 0) {
                     /*
                      * Common frames found, append a placeholder
    @@ -589,34 +593,34 @@ else if (appendingExcluded) {
         private void appendPlaceHolder(StringBuilder builder, int indent, int consecutiveExcluded, String message) {
             indent(builder, indent);
             builder.append(ELLIPSIS)
    -            .append(" ")
    -            .append(consecutiveExcluded)
    -            .append(" ")
    -            .append(message)
    -            .append(getLineSeparator());
    +                .append(" ")
    +                .append(consecutiveExcluded)
    +                .append(" ")
    +                .append(message)
    +                .append(getLineSeparator());
         }
     
         /**
          * Return {@code true} if the stack trace element is included (i.e. doesn't match any exclude patterns).
    -     * 
    +     *
          * @return {@code true} if the stacktrace element is included
          */
         private boolean isIncluded(StackTraceElementProxy step) {
             return stackElementFilter.accept(step.getStackTraceElement());
         }
     
    -    
    +
         /**
          * Return {@code true} if the stacktrace should be truncated after the element passed as argument
    -     * 
    +     *
          * @param step the stacktrace element to evaluate
          * @return {@code true} if the stacktrace should be truncated after the given element
          */
         private boolean shouldTruncateAfter(StackTraceElementProxy step) {
             return !truncateAfterFilter.accept(step.getStackTraceElement());
         }
    -    
    -    
    +
    +
         /**
          * Appends a single stack trace element.
          */
    @@ -631,15 +635,15 @@ private void appendStackTraceElement(StringBuilder builder, int indent, StackTra
             String fileName = stackTraceElement.getFileName();
             int lineNumber = stackTraceElement.getLineNumber();
             builder.append("at ")
    -            .append(abbreviator.abbreviate(stackTraceElement.getClassName()))
    -            .append(".")
    -            .append(stackTraceElement.getMethodName())
    -            .append("(")
    -            .append(fileName == null ? "Unknown Source" : fileName);
    +                .append(abbreviator.abbreviate(stackTraceElement.getClassName()))
    +                .append(".")
    +                .append(stackTraceElement.getMethodName())
    +                .append("(")
    +                .append(fileName == null ? "Unknown Source" : fileName);
     
             if (lineNumber >= 0) {
                 builder.append(":")
    -                .append(lineNumber);
    +                    .append(lineNumber);
             }
             builder.append(")");
     
    @@ -672,7 +676,7 @@ private void appendPackagingData(StringBuilder builder, StackTraceElementProxy s
         /**
          * Appends the first line containing the prefix and throwable message
          */
    -    private void appendFirstLine(StringBuilder builder, String prefix, int indent, IThrowableProxy throwableProxy, String hash) {
    +    private void appendFirstLine(StringBuilder builder, String prefix, int indent, IThrowableProxy throwableProxy, String hash, ILoggingEvent event) {
             if (builder.length() > this.maxLength) {
                 return;
             }
    @@ -685,20 +689,26 @@ private void appendFirstLine(StringBuilder builder, String prefix, int indent, I
                 builder.append("<#" + hash + "> ");
             }
             builder.append(abbreviator.abbreviate(throwableProxy.getClassName()))
    -            .append(": ")
    -            .append(throwableProxy.getMessage())
    -            .append(getLineSeparator());
    +                .append(": ")
    +                .append(getMessage(throwableProxy, event))
    +                .append(getLineSeparator());
         }
     
         private void indent(StringBuilder builder, int indent) {
             ThrowableProxyUtil.indent(builder, indent);
         }
     
    -    
    +    protected String getMessage(IThrowableProxy throwableProxy, ILoggingEvent event) {
    +        if (event.getMarkerList()!= null && event.getMarkerList().contains(SANITIZE)){
    +            return "";
    +        }
    +        return throwableProxy.getMessage();
    +    }
    +
         /**
          * Set the length to which class names should be abbreviated.
          * Cannot be used if a custom {@link Abbreviator} has been set through {@link #setClassNameAbbreviator(Abbreviator)}.
    -     * 
    +     *
          * @param length the desired maximum length or {@code -1} to disable the feature and allow for any arbitrary length.
          */
         public void setShortenedClassNameLength(int length) {
    @@ -713,7 +723,7 @@ public void setShortenedClassNameLength(int length) {
         /**
          * Get the class name abbreviation target length.
          * Cannot be used if a custom {@link Abbreviator} has been set through {@link #setClassNameAbbreviator(Abbreviator)}.
    -     * 
    +     *
          * @return the abbreviation target length
          */
         public int getShortenedClassNameLength() {
    @@ -724,27 +734,27 @@ public int getShortenedClassNameLength() {
                 throw new IllegalStateException("Cannot invoke getShortenedClassNameLength on non default abbreviator");
             }
         }
    -    
    -    
    +
    +
         /**
          * Set a custom {@link Abbreviator} used to shorten class names.
    -     * 
    +     *
          * @param abbreviator the {@link Abbreviator} to use.
          */
         @DefaultClass(DefaultTargetLengthAbbreviator.class)
         public void setClassNameAbbreviator(Abbreviator abbreviator) {
             this.abbreviator = Objects.requireNonNull(abbreviator);
         }
    -    
    +
         public Abbreviator getClassNameAbbreviator() {
             return this.abbreviator;
         }
    -    
    -    
    +
    +
         /**
          * Set a limit on the number of stackTraceElements per throwable.
          * Use {@code -1} to disable the feature and allow for an unlimited depth.
    -     * 
    +     *
          * @param maxDepthPerThrowable the maximum number of stacktrace elements per throwable or {@code -1} to
          * disable the feature and allows for an unlimited amount.
          */
    @@ -761,12 +771,12 @@ public void setMaxDepthPerThrowable(int maxDepthPerThrowable) {
         public int getMaxDepthPerThrowable() {
             return maxDepthPerThrowable;
         }
    -    
    -    
    +
    +
         /**
          * Set a hard limit on the size of the rendered stacktrace, all throwables included.
          * Use {@code -1} to disable the feature and allows for any size.
    -     * 
    +     *
          * @param maxLength the maximum size of the rendered stacktrace or {@code -1} for no limit.
          */
         public void setMaxLength(int maxLength) {
    @@ -782,20 +792,20 @@ public int getMaxLength() {
             return maxLength;
         }
     
    -    
    +
         /**
          * Control whether common frames should be omitted for nested throwables or not.
    -     * 
    +     *
          * @param omitCommonFrames {@code true} to omit common frames
          */
         public void setOmitCommonFrames(boolean omitCommonFrames) {
             this.omitCommonFrames = omitCommonFrames;
         }
    -    
    +
         public boolean isOmitCommonFrames() {
             return this.omitCommonFrames;
         }
    -    
    +
         public boolean isRootCauseFirst() {
             return rootCauseFirst;
         }
    @@ -850,14 +860,14 @@ public void addTruncateAfter(String regex) {
     
         public List getTruncateAfters() {
             return this.truncateAfterPatterns
    -                        .stream()
    -                        .map(Pattern::pattern)
    -                        .collect(Collectors.toList());
    +                .stream()
    +                .map(Pattern::pattern)
    +                .collect(Collectors.toList());
         }
    -    
    +
         /**
          * Add multiple truncate after patterns as a list of comma separated patterns.
    -     * 
    +     *
          * @param commaSeparatedPatterns list of comma separated patterns
          */
         public void addTruncateAfters(String commaSeparatedPatterns) {
    @@ -865,14 +875,14 @@ public void addTruncateAfters(String commaSeparatedPatterns) {
                 addTruncateAfter(regex);
             }
         }
    -    
    +
         public void setTruncateAfters(List patterns) {
             this.truncateAfterPatterns = new ArrayList<>(patterns.size());
             for (String pattern: patterns) {
                 addTruncateAfter(pattern);
             }
         }
    -    
    +
         public void addEvaluator(EventEvaluator evaluator) {
             evaluators.add(Objects.requireNonNull(evaluator));
         }