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

net.logstash.logback.pattern.AbstractJsonPatternParser 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
/**
 * 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.pattern;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ch.qos.logback.core.pattern.PatternLayoutBase;
import ch.qos.logback.core.spi.ContextAware;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;

/**
 * Parser that takes a JSON pattern, resolves all the conversion specifiers and returns an instance
 * of NodeWriter that, when its write() method is invoked, produces JSON defined by the parsed pattern.
 *
 * @param  - type of the event (ILoggingEvent, IAccessEvent)
 *
 * @author Dmitry Andrianov
 */
public abstract class AbstractJsonPatternParser {

    public static final Pattern OPERATION_PATTERN = Pattern.compile("\\# (\\w+) (?: \\{ (.*) \\} )?", Pattern.COMMENTS);
    
    private final ContextAware contextAware;
    private final JsonFactory jsonFactory;
    
    private final Map operations = new HashMap<>();

    /**
     * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
     * will be omitted from json output.
     */
    private boolean omitEmptyFields;

    public AbstractJsonPatternParser(final ContextAware contextAware, final JsonFactory jsonFactory) {
        this.contextAware = contextAware;
        this.jsonFactory = jsonFactory;
        addOperation(new AsLongOperation());
        addOperation(new AsDoubleOperation());
        addOperation(new AsJsonOperation());
        addOperation(new TryJsonOperation());
    }
    
    protected void addOperation(Operation operation) {
        this.operations.put(operation.getName(), operation);
    }

    protected abstract class Operation {
        private final String name;
        private final boolean requiresData;

        public Operation(String name, boolean requiresData) {
            this.name = name;
            this.requiresData = requiresData;
        }
        
        public String getName() {
            return name;
        }
        
        public boolean requiresData() {
            return requiresData;
        }
        
        public abstract ValueGetter createValueGetter(String data);
        
    }
    
    protected class AsLongOperation extends Operation {

        public AsLongOperation() {
            super("asLong", true);
        }
        
        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsLongValueTransformer<>(makeLayoutValueGetter(data));
        }
    }

    protected class AsDoubleOperation extends Operation {

        public AsDoubleOperation() {
            super("asDouble", true);
        }
        
        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsDoubleValueTransformer<>(makeLayoutValueGetter(data));
        }
    }

    protected class AsJsonOperation extends Operation {

        public AsJsonOperation() {
            super("asJson", true);
        }

        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsJsonValueTransformer(makeLayoutValueGetter(data));
        }
    }
    
    protected class TryJsonOperation extends Operation {

        public TryJsonOperation() {
            super("tryJson", true);
        }

        @Override
        public ValueGetter createValueGetter(String data) {
            return new TryJsonValueTransformer(makeLayoutValueGetter(data));
        }
    }

    protected static class LayoutValueGetter implements ValueGetter {

        private final PatternLayoutBase layout;

        LayoutValueGetter(final PatternLayoutBase layout) {
            this.layout = layout;
        }

        @Override
        public String getValue(final Event event) {
            return layout.doLayout(event);
        }
    }

    protected static abstract class AbstractAsObjectTransformer implements ValueGetter {

        private final ValueGetter generator;

        AbstractAsObjectTransformer(final ValueGetter generator) {
            this.generator = generator;
        }

        @Override
        public T getValue(final Event event) {
            final String value = generator.getValue(event);
            if (value == null || value.isEmpty()) {
                return null;
            }
            try {
                return transform(value);
            } catch (Exception e) {
                return null;
            }
        }

        abstract protected T transform(final String value) throws NumberFormatException, IOException;
    }

    protected static abstract class AbstractAsNumberTransformer implements ValueGetter {

        private final ValueGetter generator;

        AbstractAsNumberTransformer(final ValueGetter generator) {
            this.generator = generator;
        }

        @Override
        public T getValue(final Event event) {
            final String value = generator.getValue(event);
            if (value == null || value.isEmpty()) {
                return null;
            }
            try {
                return transform(value);
            } catch (NumberFormatException e) {
                return null;
            }
        }

        abstract protected T transform(final String value) throws NumberFormatException;
    }

    protected static class AsLongValueTransformer extends AbstractAsNumberTransformer {
        public AsLongValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected Long transform(final String value) throws NumberFormatException {
            return Long.parseLong(value);
        }
    }

    protected static class AsDoubleValueTransformer extends AbstractAsNumberTransformer {
        public AsDoubleValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected Double transform(final String value)  throws NumberFormatException {
            return Double.parseDouble(value);
        }
    }

    protected class AsJsonValueTransformer extends AbstractAsObjectTransformer {

        public AsJsonValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected JsonNode transform(final String value) throws IOException {
            return jsonFactory.getCodec().readTree(jsonFactory.createParser(value));
        }
    }
    
    protected class TryJsonValueTransformer extends AbstractAsObjectTransformer {

        public TryJsonValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected Object transform(final String value) throws IOException {
            try {
                final String trimmedValue = value.trim();
                final JsonParser parser = jsonFactory.createParser(trimmedValue);
                final TreeNode tree = jsonFactory.getCodec().readTree(parser);
                if (parser.getCurrentLocation().getCharOffset() < trimmedValue.length()) {
                    /*
                     * If the full trimmed string was not read, then the full trimmed string contains a json value plus other text.
                     * For example, trimmedValue = '10 foobar', or 'true foobar', or '{"foo","bar"} baz'.
                     * In these cases readTree will only read the first part, and will not read the remaining text.
                     */
                    return value;
                }
                return tree;
            } catch (JsonParseException e) {
                return value;
            }
        }
    }

    protected interface FieldWriter extends NodeWriter {
    }

    protected class ConstantValueWriter implements NodeWriter {
        private final Object value;

        public ConstantValueWriter(final Object value) {
            this.value = value;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeObject(value);
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            return !omitEmptyFields
                    || (value != null
                            && !(value instanceof JsonNode && ((JsonNode) value).getNodeType() == JsonNodeType.NULL));
        }
    }

    protected class ListWriter implements NodeWriter {
        private final List> items;

        public ListWriter(final List> items) {
            this.items = items;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeStartArray();
            for (NodeWriter item : items) {
                if (item.shouldWrite(generator, event)) {
                    item.write(generator, event);
                }
            }
            generator.writeEndArray();
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            if (!omitEmptyFields) {
                return true;
            }

            for (NodeWriter item : items) {
                if (item.shouldWrite(generator, event)) {
                    return true;
                }
            }

            return false;
        }
    }

    protected class ComputableValueWriter implements NodeWriter {

        private final ValueGetter getter;

        public ComputableValueWriter(final ValueGetter getter) {
            this.getter = getter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            Object value = getter.getValue(event);
            generator.writeObject(value);
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            return !omitEmptyFields || getter.getValue(event) != null;
        }
    }

    protected class DelegatingObjectFieldWriter implements FieldWriter {

        private final String name;
        private final NodeWriter delegate;

        public DelegatingObjectFieldWriter(final String name, final NodeWriter delegate) {
            this.name = name;
            this.delegate = delegate;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            if (delegate.shouldWrite(generator, event)) {
                generator.writeFieldName(name);
                delegate.write(generator, event);
            }
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            return delegate.shouldWrite(generator, event);
        }
    }

    protected class ComputableObjectFieldWriter implements FieldWriter {

        private final String name;
        private final ValueGetter getter;

        public ComputableObjectFieldWriter(final String name, final ValueGetter getter) {
            this.name = name;
            this.getter = getter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            Object value = getter.getValue(event);
            if (!omitEmptyFields || value != null) {
                generator.writeFieldName(name);
                generator.writeObject(value);
            }
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            return !omitEmptyFields || !isEmptyValue(getter.getValue(event));
        }
    }

    protected class ObjectWriter implements NodeWriter {

        private final ChildrenWriter childrenWriter;
        
        public ObjectWriter(ChildrenWriter childrenWriter) {
            this.childrenWriter = childrenWriter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            if (childrenWriter.shouldWrite(generator, event)) {
                generator.writeStartObject();
                this.childrenWriter.write(generator, event);
                generator.writeEndObject();
            }
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            return childrenWriter.shouldWrite(generator, event);
        }
    }

    protected class ChildrenWriter implements NodeWriter {

        private final List> items;

        public ChildrenWriter(final List> items) {
            this.items = items;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            for (FieldWriter item : items) {
                if (item.shouldWrite(generator, event)) {
                    item.write(generator, event);
                }
            }
        }

        @Override
        public boolean shouldWrite(JsonGenerator generator, Event event) {
            if (!omitEmptyFields) {
                return true;
            }

            for (FieldWriter item : items) {
                if (item.shouldWrite(generator, event)) {
                    return true;
                }
            }
            return false;
        }
    }

    protected PatternLayoutBase buildLayout(String format) {
        PatternLayoutBase layout = createLayout();
        layout.setContext(contextAware.getContext());
        layout.setPattern(format);
        layout.setPostCompileProcessor(null); // Remove EnsureLineSeparation which is there by default
        layout.start();

        return layout;
    }

    protected abstract PatternLayoutBase createLayout();

    private ValueGetter makeComputableValueGetter(String pattern) {

        Matcher matcher = OPERATION_PATTERN.matcher(pattern);

        if (matcher.matches()) {
            String operationName = matcher.group(1);
            String operationData = matcher.groupCount() > 1
                    ? matcher.group(2)
                    : null;
                    
            Operation operation = this.operations.get(operationName);
            if (operation != null) {
                if (operation.requiresData() && operationData == null) {
                    contextAware.addError("No parameter provided to operation: " + operation.getName());
                } else {
                    return operation.createValueGetter(operationData);
                }
            }
        } 
        return makeLayoutValueGetter(pattern);
    }

    protected LayoutValueGetter makeLayoutValueGetter(final String data) {
        return new LayoutValueGetter(buildLayout(data));
    }

    private NodeWriter parseValue(JsonNode node) {
        if (node.isTextual()) {
            ValueGetter getter = makeComputableValueGetter(node.asText());
            return new ComputableValueWriter(getter);
        } else if (node.isArray()) {
            return parseArray(node);
        } else if (node.isObject()) {
            return parseObject(node);
        } else {
            // Anything else, we will be just writing as is (nulls, numbers, booleans and whatnot)
            return new ConstantValueWriter(node);
        }
    }

    private ListWriter parseArray(JsonNode node) {

        List> children = new ArrayList<>();
        for (JsonNode item : node) {
            children.add(parseValue(item));
        }

        return new ListWriter<>(children);
    }

    private ObjectWriter parseObject(JsonNode node) {

        return new ObjectWriter<>(parseChildren(node));
    }

    private ChildrenWriter parseChildren(JsonNode node) {
        List> children = new ArrayList<>();
        for (Iterator> nodeFields = node.fields(); nodeFields.hasNext(); ) {
            Map.Entry field = nodeFields.next();

            String key = field.getKey();
            JsonNode value = field.getValue();

            if (value.isTextual()) {
                ValueGetter getter = makeComputableValueGetter(value.asText());
                children.add(new ComputableObjectFieldWriter<>(key, getter));
            } else {
                children.add(new DelegatingObjectFieldWriter<>(key, parseValue(value)));
            }
        }
        return new ChildrenWriter<>(children);
    }

    public NodeWriter parse(String pattern) {

        if (pattern == null) {
            contextAware.addError("No pattern specified");
            return null;
        }

        JsonNode node;
        try {
            node = jsonFactory.createParser(pattern).readValueAsTree();
        } catch (IOException e) {
            contextAware.addError("Failed to parse pattern [" + pattern + "]", e);
            return null;
        }

        if (node == null) {
            contextAware.addError("Empty JSON pattern");
            return null;
        }

        if (!node.isObject()) {
            contextAware.addError("Invalid pattern JSON - must be an object");
            return null;
        }

        return parseChildren(node);
    }

    /**
     * Return true if the given value is considered to be "empty".
     * @param value value to inspect
     * @return true if the given value is considered to be "empty".
     */
    private boolean isEmptyValue(Object value) {
        if (value == null) {
            return true;
        }
        if (value instanceof String && ((String) value).isEmpty()) {
            return true;
        }
        if (value instanceof Collection && ((Collection) value).isEmpty()) {
            return true;
        }
        if (value instanceof Map && ((Map) value).isEmpty()) {
            return true;
        }

        if (value instanceof JsonNode) {
            JsonNode node = (JsonNode) value;
            if (node.getNodeType() == JsonNodeType.NULL) {
                return true;
            }
            if (node.isTextual() && node.textValue().isEmpty()) {
                return true;
            }
            if (node.isContainerNode() && node.size() == 0) {
                return true;
            }
        }
        return false;

    }

    /**
     * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
     * will be omitted from json output.
     */
    public boolean isOmitEmptyFields() {
        return omitEmptyFields;
    }

    /**
     * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
     * will be omitted from json output.
     */
    public void setOmitEmptyFields(boolean omitEmptyFields) {
        this.omitEmptyFields = omitEmptyFields;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy