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ülcü 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}