001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2024, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.classic.pattern;
016
017
018import ch.qos.logback.classic.spi.ILoggingEvent;
019import ch.qos.logback.core.CoreConstants;
020import org.slf4j.event.KeyValuePair;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import static ch.qos.logback.classic.pattern.KeyValuePairConverter.*;
026
027/**
028 * Similar to  {@link KeyValuePairConverter} with the added ability to mask the values of specified keys.
029 * <p>
030 * Assuming the specified key is k2, and the kvp list of an event contains {k1, v1}, {k2, v2}, the String output
031 * will be "k1=v1 k2=XXX", without the quotes.
032 *
033 * Value quotes can be specified as the first option, e.g %maskedKvp{SINGLE, k1}
034 *
035 * @author Ceki G&uuml;lc&uuml;
036 * @since 1.5.7
037 */
038
039
040public class MaskedKeyValuePairConverter extends ClassicConverter {
041    public static final String MASK = "XXX";
042    List<String> optionList;
043    List<String> maskList = new ArrayList<>();
044    KeyValuePairConverter.ValueQuoteSpecification valueQuoteSpec = KeyValuePairConverter.ValueQuoteSpecification.DOUBLE;
045
046    public void start() {
047        this.optionList = getOptionList();
048        KeyValuePairConverter.ValueQuoteSpecification extractedSpec = extractSpec(this.optionList);
049        if (extractedSpec == null) {
050            maskList = optionList;
051        } else {
052            valueQuoteSpec = extractedSpec;
053            maskList = optionList.subList(1, optionList.size());
054        }
055
056        checkMaskListForExtraQuoteSpecs(maskList);
057
058        super.start();
059    }
060
061    private void checkMaskListForExtraQuoteSpecs(List<String> maskList) {
062        if(maskList == null || maskList.isEmpty())
063            return;
064        if(maskList.contains(DOUBLE_OPTION_STR)) {
065            addWarn("quote spec "+DOUBLE_OPTION_STR+ " found in the wrong order");
066        }
067        if(maskList.contains(SINGLE_OPTION_STR)) {
068            addWarn("extra quote spec "+SINGLE_OPTION_STR+ " found in the wrong order");
069        }
070        if(maskList.contains(NONE_OPTION_STR)) {
071            addWarn("extra quote spec "+NONE_OPTION_STR+ " found in the wrong order");
072        }
073    }
074
075
076    KeyValuePairConverter.ValueQuoteSpecification extractSpec(List<String> optionList) {
077
078        if (optionList == null || optionList.isEmpty()) {
079            return null;
080        }
081
082        String firstOption = optionList.get(0);
083
084        if (DOUBLE_OPTION_STR.equalsIgnoreCase(firstOption)) {
085            return KeyValuePairConverter.ValueQuoteSpecification.DOUBLE;
086        } else if (SINGLE_OPTION_STR.equalsIgnoreCase(firstOption)) {
087            return KeyValuePairConverter.ValueQuoteSpecification.SINGLE;
088        } else if (NONE_OPTION_STR.equalsIgnoreCase(firstOption)) {
089            return KeyValuePairConverter.ValueQuoteSpecification.NONE;
090        } else {
091            return null;
092        }
093    }
094
095    @Override
096    public String convert(ILoggingEvent event) {
097
098        List<KeyValuePair> kvpList = event.getKeyValuePairs();
099        if (kvpList == null || kvpList.isEmpty()) {
100            return CoreConstants.EMPTY_STRING;
101        }
102
103        StringBuilder sb = new StringBuilder();
104        for (int i = 0; i < kvpList.size(); i++) {
105            KeyValuePair kvp = kvpList.get(i);
106            if (i != 0)
107                sb.append(' ');
108            sb.append(String.valueOf(kvp.key));
109            sb.append('=');
110            Character quoteChar = valueQuoteSpec.asChar();
111            if (quoteChar != null)
112                sb.append(quoteChar);
113            if (maskList.contains(kvp.key))
114                sb.append(MASK);
115            else
116                sb.append(String.valueOf(kvp.value));
117            if (quoteChar != null)
118                sb.append(quoteChar);
119        }
120
121        return sb.toString();
122    }
123}