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

net.logstash.logback.mask.PathBasedFieldMasker Maven / Gradle / Ivy

Go to download

Provides logback encoders, layouts, and appenders to log in JSON and other formats supported by Jackson

There is a newer version: 8.0
Show newest version
/*
 * Copyright 2013-2021 the original author or authors.
 *
 * 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 net.logstash.logback.mask;

import java.util.Objects;

import com.fasterxml.jackson.core.JsonStreamContext;

/**
 * Masks values of an absolute or partial path within a JSON stream.
 *
 * 

Values for paths that match a given path string will be replaced with a given mask string.

* *

Path String Format

* * The path string to match follows a format similar to (but not exactly the same as) a * JSON Pointer string, * with the differences being: *
    *
  • At least one reference token is required (e.g. "" and "/" are not allowed)
  • *
  • The path string does not need to start with {@value #TOKEN_SEPARATOR}. * If a path string starts with {@value #TOKEN_SEPARATOR} it is interpreted as an absolute path. * Otherwise, it is a partial path.
  • *
  • A wildcard token ({@value WILDCARD_TOKEN}) is supported.
  • *
  • The path string must end with a field name (not an array index)
  • *
* *

Absolute Paths

* * Absolute paths start with {@value #TOKEN_SEPARATOR}, followed by one or more * reference tokens separated by {@value #TOKEN_SEPARATOR}. * Absolute paths must match the full path from the root of the streaming context. * *

For example, given the following JSON: * *

 * {
 *     "aaa": {
 *         "bbb": [
 *             {
 *                 "ccc": "ddd"
 *             }
 *         ]
 *     },
 *     "bbb": [
 *         {
 *             "eee": "fff"
 *         }
 *     ]
 * }
 * 
* * Then the following matches occur: * *
    *
  • /aaa matches { "bbb" : [ { "ccc" : "ddd" } ] }
  • *
  • /aaa/bbb matches [ { "ccc" : "ddd" } ]
  • *
  • /aaa/bbb/0/ccc matches "ddd"
  • *
* *

Partial Paths

* * Partial paths do NOT start with {@value #TOKEN_SEPARATOR}, and contain * one or more reference tokens separated by {@value #TOKEN_SEPARATOR}. * Partial paths mask a partial path anywhere in the stream. * *

For example, given the following JSON: * *

 * {
 *     "aaa": {
 *         "bbb": [
 *             {
 *                 "ccc": "ddd"
 *             }
 *         ]
 *     },
 *     "bbb": [
 *         {
 *             "eee": "fff"
 *         }
 *     ]
 * }
 * 
* * Then the following matches occur: * *
    *
  • aaa matches { "bbb" : [ { "ccc" : "ddd" } ] }
  • *
  • aaa/bbb matches [ { "ccc" : "ddd" } ]
  • *
  • aaa/bbb/0/ccc matches "ddd"
  • *
  • bbb matches [ { "ccc" : "ddd" } ] and [ { "eee" : "fff" } ]
  • *
  • bbb/0/ccc matches "ddd"
  • *
  • 0/ccc matches "ddd"
  • *
  • ccc matches "ddd"
  • *
* * For single field values (e.g. partial paths with only one token), consider * using a {@link FieldNameBasedFieldMasker} instead. * A single {@link FieldNameBasedFieldMasker} configured with many field names, * is much more efficient than having a {@link PathBasedFieldMasker} per field name. * *

Wildcard Tokens

* * The wildcard value ({@value WILDCARD_TOKEN}) can be used as a token in the path string. * The wildcard token will match any token. * *

For example, given the following JSON: * *

 * {
 *     "aaa": {
 *         "bbb": {
 *             "ccc": "ddd"
 *         },
 *         "eee": {
 *             "ccc": "hhh",
 *         },
 *     },
 *     "iii": {
 *         "jjj": {
 *             "ccc": "lll"
 *         },
 *     },
 *     "ccc": "mmm"
 * }
 * 
* * Then the following matches occur: * *
    *
  • aaa/*/ccc matches "ddd" and "hhh"
  • *
  • */ccc matches "ddd" and "hhh" and "lll"
  • *
* *

Escaping

* * JSON Pointer escaping can be used to escape '/' and '~' within tokens. Specifically, use: *
    *
  • '~1' to represent '/' within a token
  • *
  • '~0' to represent '~' within a token
  • *
* */ public class PathBasedFieldMasker implements FieldMasker { public static final String TOKEN_SEPARATOR = "/"; public static final String WILDCARD_TOKEN = "*"; private final boolean isAbsolutePath; private final String[] tokens; private final Object mask; /** * @param pathToMask the absolute or partial path to mask (see class javadoc) * @param mask the value to write for any paths that match the pathToMask */ public PathBasedFieldMasker(String pathToMask, Object mask) { validatePathToMask(pathToMask); isAbsolutePath = pathToMask.startsWith(TOKEN_SEPARATOR); if (isAbsolutePath) { pathToMask = pathToMask.substring(TOKEN_SEPARATOR.length()); } tokens = pathToMask.split(TOKEN_SEPARATOR); for (int i = 0; i < tokens.length; i++) { tokens[i] = unescapeJsonPointerToken(tokens[i]); } this.mask = mask; } static void validatePathToMask(String pathToMask) { Objects.requireNonNull(pathToMask, "pathToMask must not be null"); if (pathToMask.isEmpty()) { throw new IllegalArgumentException("pathToMask must not be empty"); } if (pathToMask.equals(TOKEN_SEPARATOR)) { throw new IllegalArgumentException("pathToMask must contain at least one token"); } } @Override public Object mask(JsonStreamContext context) { JsonStreamContext currentContext = context; for (int i = tokens.length; --i >= 0; currentContext = currentContext.getParent()) { if (!currentLeafMatches(currentContext, tokens[i])) { return null; } } return (currentContext != null && (!isAbsolutePath || currentContext.getParent() == null || currentContext.getParent().inRoot())) ? mask : null; } private boolean currentLeafMatches(JsonStreamContext context, String leafName) { if (context != null) { if (WILDCARD_TOKEN.equals(leafName)) { return true; } if (context.hasCurrentName()) { return context.getCurrentName().equals(leafName); } if (context.hasCurrentIndex()) { return Integer.toString(context.getCurrentIndex()).equals(leafName); } } return false; } /** * Returns true if the given path represents a single field name (e.g. not a multi token or wildcard path). * * @param path the path to check * @return true if the given path represents a single field name (e.g. not a multi token or wildcard path). */ static boolean isSingleFieldName(String path) { return !path.contains(PathBasedFieldMasker.TOKEN_SEPARATOR) && !path.contains(PathBasedFieldMasker.WILDCARD_TOKEN); } /** * Unescapes "~1" as "/", and "~0" as "~" from a JSON pointer token. * * @param token the JSON pointer token to unescape * @return the unescaped token value. */ static String unescapeJsonPointerToken(String token) { return token // As per JSON Pointer string spec, ~1 is used to escape "/" .replace("~1", "/") // As per JSON Pointer string spec, ~0 is used to escape "~" .replace("~0", "~"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy