1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2024, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.classic.pattern;
16  
17  
18  import ch.qos.logback.classic.spi.ILoggingEvent;
19  import ch.qos.logback.core.CoreConstants;
20  import org.slf4j.event.KeyValuePair;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import static ch.qos.logback.classic.pattern.KeyValuePairConverter.*;
26  
27  /**
28   * Similar to  {@link KeyValuePairConverter} with the added ability to mask the values of specified keys.
29   * <p>
30   * Assuming the specified key is k2, and the kvp list of an event contains {k1, v1}, {k2, v2}, the String output
31   * will be "k1=v1 k2=XXX", without the quotes.
32   *
33   * Value quotes can be specified as the first option, e.g %maskedKvp{SINGLE, k1}
34   *
35   * @author Ceki G&uuml;lc&uuml;
36   * @since 1.5.7
37   */
38  
39  
40  public class MaskedKeyValuePairConverter extends ClassicConverter {
41      public static final String MASK = "XXX";
42      List<String> optionList;
43      List<String> maskList = new ArrayList<>();
44      KeyValuePairConverter.ValueQuoteSpecification valueQuoteSpec = KeyValuePairConverter.ValueQuoteSpecification.DOUBLE;
45  
46      public void start() {
47          this.optionList = getOptionList();
48          KeyValuePairConverter.ValueQuoteSpecification extractedSpec = extractSpec(this.optionList);
49          if (extractedSpec == null) {
50              maskList = optionList;
51          } else {
52              valueQuoteSpec = extractedSpec;
53              maskList = optionList.subList(1, optionList.size());
54          }
55  
56          checkMaskListForExtraQuoteSpecs(maskList);
57  
58          super.start();
59      }
60  
61      private void checkMaskListForExtraQuoteSpecs(List<String> maskList) {
62          if(maskList == null || maskList.isEmpty())
63              return;
64          if(maskList.contains(DOUBLE_OPTION_STR)) {
65              addWarn("quote spec "+DOUBLE_OPTION_STR+ " found in the wrong order");
66          }
67          if(maskList.contains(SINGLE_OPTION_STR)) {
68              addWarn("extra quote spec "+SINGLE_OPTION_STR+ " found in the wrong order");
69          }
70          if(maskList.contains(NONE_OPTION_STR)) {
71              addWarn("extra quote spec "+NONE_OPTION_STR+ " found in the wrong order");
72          }
73      }
74  
75  
76      KeyValuePairConverter.ValueQuoteSpecification extractSpec(List<String> optionList) {
77  
78          if (optionList == null || optionList.isEmpty()) {
79              return null;
80          }
81  
82          String firstOption = optionList.get(0);
83  
84          if (DOUBLE_OPTION_STR.equalsIgnoreCase(firstOption)) {
85              return KeyValuePairConverter.ValueQuoteSpecification.DOUBLE;
86          } else if (SINGLE_OPTION_STR.equalsIgnoreCase(firstOption)) {
87              return KeyValuePairConverter.ValueQuoteSpecification.SINGLE;
88          } else if (NONE_OPTION_STR.equalsIgnoreCase(firstOption)) {
89              return KeyValuePairConverter.ValueQuoteSpecification.NONE;
90          } else {
91              return null;
92          }
93      }
94  
95      @Override
96      public String convert(ILoggingEvent event) {
97  
98          List<KeyValuePair> kvpList = event.getKeyValuePairs();
99          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 }