All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolver Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * 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.logging.log4j.layout.template.json.resolver;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.status.StatusLogger;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Exception resolver.
 * 

* Note that this resolver is toggled by {@link * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean) stackTraceEnabled} * layout configuration, which is by default populated from log4j.layout.jsonTemplate.stackTraceEnabled * system property. * *

Configuration

* *
 * config              = field , [ stringified ] , [ stackTrace ]
 * field               = "field" -> ( "className" | "message" | "stackTrace" )
 *
 * stackTrace          = "stackTrace" -> (
 *                         [ stringified ]
 *                       , [ elementTemplate ]
 *                       )
 *
 * stringified         = "stringified" -> ( boolean | truncation )
 * truncation          = "truncation" -> (
 *                         [ suffix ]
 *                       , [ pointMatcherStrings ]
 *                       , [ pointMatcherRegexes ]
 *                       )
 * suffix              = "suffix" -> string
 * pointMatcherStrings = "pointMatcherStrings" -> string[]
 * pointMatcherRegexes = "pointMatcherRegexes" -> string[]
 *
 * elementTemplate     = "elementTemplate" -> object
 * 
* * stringified is set to false by default. * stringified at the root level is deprecated in favor of * stackTrace.stringified, which has precedence if both are provided. *

* pointMatcherStrings and pointMatcherRegexes enable the * truncation of stringified stack traces after the given matching point. If * both parameters are provided, pointMatcherStrings will be checked * first. *

* If a stringified stack trace truncation takes place, it will be indicated * with suffix, which by default is set to the configured * truncatedStringSuffix in the layout, unless explicitly provided. *

* elementTemplate is an object describing the template to be used * while resolving the {@link StackTraceElement} array. If stringified * is set to true, elementTemplate will be discarded. By * default, elementTemplate is set to null and rather * populated from the layout configuration. That is, the stack trace element * template can also be provided using {@link JsonTemplateLayout.Builder#setStackTraceElementTemplate(String) stackTraceElementTemplate} * and {@link JsonTemplateLayout.Builder#setStackTraceElementTemplateUri(String) setStackTraceElementTemplateUri} * layout configuration parameters. The template to be employed is determined * in the following order: *

    *
  1. elementTemplate provided in the resolver configuration *
  2. stackTraceElementTemplate parameter from layout configuration * (the default is populated from log4j.layout.jsonTemplate.stackTraceElementTemplate * system property) *
  3. stackTraceElementTemplateUri parameter from layout configuration * (the default is populated from log4j.layout.jsonTemplate.stackTraceElementTemplateUri * system property) *
*

* See {@link StackTraceElementResolver} * for the list of available resolvers in a stack trace element template. * *

Examples

* * Resolve logEvent.getThrown().getClass().getCanonicalName(): * *
 *  {
 *   "$resolver": "exception",
 *   "field": "className"
 * }
 * 
* * Resolve the stack trace into a list of StackTraceElement objects: * *
 *  {
 *   "$resolver": "exception",
 *   "field": "stackTrace"
 * }
 * 
* * Resolve the stack trace into a string field: * *
 *  {
 *   "$resolver": "exception",
 *   "field": "stackTrace",
 *   "stackTrace": {
 *     "stringified": true
 *   }
 * }
 * 
* * Resolve the stack trace into a string field * such that the content will be truncated after the given point matcher: * *
 *  {
 *   "$resolver": "exception",
 *   "field": "stackTrace",
 *   "stackTrace": {
 *     "stringified": {
 *       "truncation": {
 *         "suffix": ">",
 *         "pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
 *       }
 *     }
 *   }
 * }
 * 
* * Resolve the stack trace into an object described by the provided stack trace * element template: * *
 *  {
 *   "$resolver": "exception",
 *   "field": "stackTrace",
 *   "stackTrace": {
 *     "elementTemplate": {
 *       "class": {
 *         "$resolver": "stackTraceElement",
 *         "field": "className"
 *       },
 *       "method": {
 *         "$resolver": "stackTraceElement",
 *         "field": "methodName"
 *       },
 *       "file": {
 *         "$resolver": "stackTraceElement",
 *         "field": "fileName"
 *       },
 *       "line": {
 *         "$resolver": "stackTraceElement",
 *         "field": "lineNumber"
 *       }
 *     }
 *   }
 * }
 * 
* * @see JsonTemplateLayout.Builder#getTruncatedStringSuffix() * @see JsonTemplateLayoutDefaults#getTruncatedStringSuffix() * @see JsonTemplateLayout.Builder#getStackTraceElementTemplate() * @see JsonTemplateLayoutDefaults#getStackTraceElementTemplate() * @see JsonTemplateLayout.Builder#getStackTraceElementTemplateUri() * @see JsonTemplateLayoutDefaults#getStackTraceElementTemplateUri() * @see ExceptionRootCauseResolver * @see StackTraceElementResolver */ public class ExceptionResolver implements EventResolver { private static final Logger LOGGER = StatusLogger.getLogger(); private static final EventResolver NULL_RESOLVER = (ignored, jsonGenerator) -> jsonGenerator.writeNull(); private final boolean stackTraceEnabled; private final EventResolver internalResolver; ExceptionResolver( final EventResolverContext context, final TemplateResolverConfig config) { this.stackTraceEnabled = context.isStackTraceEnabled(); this.internalResolver = createInternalResolver(context, config); } EventResolver createInternalResolver( final EventResolverContext context, final TemplateResolverConfig config) { final String fieldName = config.getString("field"); if ("className".equals(fieldName)) { return createClassNameResolver(); } else if ("message".equals(fieldName)) { return createMessageResolver(); } else if ("stackTrace".equals(fieldName)) { return createStackTraceResolver(context, config); } throw new IllegalArgumentException("unknown field: " + config); } private EventResolver createClassNameResolver() { return (final LogEvent logEvent, final JsonWriter jsonWriter) -> { final Throwable exception = extractThrowable(logEvent); if (exception == null) { jsonWriter.writeNull(); } else { String exceptionClassName = exception.getClass().getCanonicalName(); jsonWriter.writeString(exceptionClassName); } }; } private EventResolver createMessageResolver() { return (final LogEvent logEvent, final JsonWriter jsonWriter) -> { final Throwable exception = extractThrowable(logEvent); if (exception == null) { jsonWriter.writeNull(); } else { String exceptionMessage = exception.getMessage(); jsonWriter.writeString(exceptionMessage); } }; } private EventResolver createStackTraceResolver( final EventResolverContext context, final TemplateResolverConfig config) { if (!context.isStackTraceEnabled()) { return NULL_RESOLVER; } final boolean stringified = isStackTraceStringified(config); return stringified ? createStackTraceStringResolver(context, config) : createStackTraceObjectResolver(context, config); } private static boolean isStackTraceStringified( final TemplateResolverConfig config) { final Boolean stringifiedOld = config.getBoolean("stringified"); if (stringifiedOld != null) { LOGGER.warn( "\"stringified\" flag at the root level of an exception " + "[root cause] resolver is deprecated in favor of " + "\"stackTrace.stringified\""); } final Object stringifiedNew = config.getObject(new String[]{"stackTrace", "stringified"}); if (stringifiedOld == null && stringifiedNew == null) { return false; } else if (stringifiedNew == null) { return stringifiedOld; } else { return !(stringifiedNew instanceof Boolean) || (boolean) stringifiedNew; } } private EventResolver createStackTraceStringResolver( final EventResolverContext context, final TemplateResolverConfig config) { // Read the configuration. final String truncationSuffix = readTruncationSuffix(context, config); final List truncationPointMatcherStrings = readTruncationPointMatcherStrings(config); final List truncationPointMatcherRegexes = readTruncationPointMatcherRegexes(config); // Create the resolver. final StackTraceStringResolver resolver = new StackTraceStringResolver( context, truncationSuffix, truncationPointMatcherStrings, truncationPointMatcherRegexes); // Create the null-protected resolver. return (final LogEvent logEvent, final JsonWriter jsonWriter) -> { final Throwable exception = extractThrowable(logEvent); if (exception == null) { jsonWriter.writeNull(); } else { resolver.resolve(exception, jsonWriter); } }; } private static String readTruncationSuffix( final EventResolverContext context, final TemplateResolverConfig config) { final String suffix = config.getString( new String[]{"stackTrace", "stringified", "truncation", "suffix"}); return suffix != null ? suffix : context.getTruncatedStringSuffix(); } private static List readTruncationPointMatcherStrings( final TemplateResolverConfig config) { List strings = config.getList( new String[]{"stackTrace", "stringified", "truncation", "pointMatcherStrings"}, String.class); if (strings == null) { strings = Collections.emptyList(); } return strings; } private static List readTruncationPointMatcherRegexes( final TemplateResolverConfig config) { // Extract the regexes. List regexes = config.getList( new String[]{"stackTrace", "stringified", "truncation", "pointMatcherRegexes"}, String.class); if (regexes == null) { regexes = Collections.emptyList(); } // Check the regex syntax. for (int i = 0; i < regexes.size(); i++) { final String regex = regexes.get(i); try { Pattern.compile(regex); } catch (final PatternSyntaxException error) { final String message = String.format( "invalid truncation point matcher regex at index %d: %s", i, regex); throw new IllegalArgumentException(message, error); } } // Return the extracted regexes. return regexes; } private static final Map STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME; static { final StackTraceElementResolverFactory stackTraceElementResolverFactory = StackTraceElementResolverFactory.getInstance(); STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME = Collections.singletonMap( stackTraceElementResolverFactory.getName(), stackTraceElementResolverFactory); } private EventResolver createStackTraceObjectResolver( final EventResolverContext context, final TemplateResolverConfig config) { final TemplateResolver stackTraceElementResolver = createStackTraceElementResolver(context, config); final StackTraceObjectResolver stackTraceResolver = new StackTraceObjectResolver(stackTraceElementResolver); return (final LogEvent logEvent, final JsonWriter jsonWriter) -> { final Throwable throwable = extractThrowable(logEvent); if (throwable == null) { jsonWriter.writeNull(); } else { stackTraceResolver.resolve(throwable, jsonWriter); } }; } private static TemplateResolver createStackTraceElementResolver( final EventResolverContext context, final TemplateResolverConfig config) { final StackTraceElementResolverStringSubstitutor substitutor = new StackTraceElementResolverStringSubstitutor( context.getSubstitutor().getInternalSubstitutor()); final StackTraceElementResolverContext stackTraceElementResolverContext = StackTraceElementResolverContext .newBuilder() .setResolverFactoryByName(STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME) .setSubstitutor(substitutor) .setJsonWriter(context.getJsonWriter()) .build(); final String stackTraceElementTemplate = findEffectiveStackTraceElementTemplate(context, config); return TemplateResolvers.ofTemplate( stackTraceElementResolverContext, stackTraceElementTemplate); } private static String findEffectiveStackTraceElementTemplate( final EventResolverContext context, final TemplateResolverConfig config) { // First, check the template configured in the resolver configuration. final Object stackTraceElementTemplateObject = config.getObject(new String[]{"stackTrace", "elementTemplate"}); if (stackTraceElementTemplateObject != null) { final JsonWriter jsonWriter = context.getJsonWriter(); return jsonWriter.use(() -> jsonWriter.writeValue(stackTraceElementTemplateObject)); } // Otherwise, use the template provided in the context. return context.getStackTraceElementTemplate(); } Throwable extractThrowable(final LogEvent logEvent) { return logEvent.getThrown(); } static String getName() { return "exception"; } @Override public boolean isResolvable() { return stackTraceEnabled; } @Override public boolean isResolvable(final LogEvent logEvent) { return stackTraceEnabled && logEvent.getThrown() != null; } @Override public void resolve( final LogEvent logEvent, final JsonWriter jsonWriter) { internalResolver.resolve(logEvent, jsonWriter); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy