001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, 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 v2.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 */ 014package ch.qos.logback.classic.sift; 015 016import ch.qos.logback.classic.spi.ILoggingEvent; 017import ch.qos.logback.core.sift.AbstractDiscriminator; 018import ch.qos.logback.core.util.BatchedFixedIntervalInvocationGate; 019import ch.qos.logback.core.util.Duration; 020import ch.qos.logback.core.util.OptionHelper; 021 022import java.util.Map; 023 024/** 025 * MDCBasedDiscriminator essentially returns the value mapped to an MDC key. If 026 * the said value is null, then a default value is returned. 027 * <p/> 028 * <p> 029 * Both Key and the DefaultValue are user specified properties. 030 * </p> 031 * <p> 032 * Path characters ({@code /} and {@code \}) are removed from the discriminating 033 * value so that it is safe to use in file names or similar path segments. 034 * </p> 035 * 036 * @author Ceki Gülcü 037 */ 038public class MDCBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> { 039 040 private static final char FORWARD_SLASH = '/'; 041 private static final char BACKWARD_SLASH = '\\'; 042 static final String REQUIRED_SANITIZING_WARNING = "Required sanitizing of path characters from MDC value [%s]"; 043 private String key; 044 private String defaultValue; 045 /** Limits how often path-sanitization warnings are emitted on the hot path. */ 046 private final BatchedFixedIntervalInvocationGate invocationGate = 047 new BatchedFixedIntervalInvocationGate(4, Duration.buildByMinutes(10)); 048 049 @Override 050 public void start() { 051 int errors = 0; 052 if (OptionHelper.isNullOrEmptyOrAllSpaces(key)) { 053 errors++; 054 addError("The \"Key\" property must be set"); 055 } 056 if (OptionHelper.isNullOrEmptyOrAllSpaces(defaultValue)) { 057 errors++; 058 addError("The \"DefaultValue\" property must be set"); 059 } 060 if (errors == 0) { 061 started = true; 062 } 063 } 064 065 /** 066 * Return the value associated with an MDC entry designated by the Key property. 067 * If that value is null, then return the value assigned to the DefaultValue 068 * property. 069 * <p> 070 * Path characters ({@code /} and {@code \}) are stripped from the result. 071 * </p> 072 */ 073 public String getDiscriminatingValue(ILoggingEvent event) { 074 // http://jira.qos.ch/browse/LBCLASSIC-213 075 Map<String, String> mdcMap = event.getMDCPropertyMap(); 076 if (mdcMap == null) { 077 return defaultValue; 078 } 079 String mdcValue = mdcMap.get(key); 080 if (mdcValue == null) { 081 return defaultValue; 082 } else { 083 return sanitizePathCharacters(mdcValue, event.getTimeStamp()); 084 } 085 } 086 087 /** 088 * Removes every {@code /} and {@code \} (any number of occurrences) so the 089 * value is safe as a file-name segment. 090 * <p> 091 * Optimized for the common case where neither character is present: a single 092 * scan and no allocation. When sanitization is needed, the prefix is kept and 093 * the remainder is copied while skipping all path separators. 094 * </p> 095 */ 096 String sanitizePathCharacters(String value, long timestamp) { 097 if (value == null) { 098 return null; 099 } 100 final int len = value.length(); 101 int i = 0; 102 for (; i < len; i++) { 103 char c = value.charAt(i); 104 if (c == FORWARD_SLASH || c == BACKWARD_SLASH) { 105 break; 106 } 107 } 108 // no path separators → return original (fast path, zero allocation) 109 if (i == len) { 110 return value; 111 } 112 113 if(!invocationGate.isTooSoon(timestamp)) { 114 addWarn(String.format(REQUIRED_SANITIZING_WARNING, value)); 115 } 116 117 StringBuilder sb = new StringBuilder(len - 1); 118 sb.append(value, 0, i); 119 for (i++; i < len; i++) { 120 char c = value.charAt(i); 121 if (c != FORWARD_SLASH && c != BACKWARD_SLASH) { 122 sb.append(c); 123 } 124 } 125 return sb.toString(); 126 } 127 128 129 public String getKey() { 130 return key; 131 } 132 133 public void setKey(String key) { 134 this.key = key; 135 } 136 137 /** 138 * @return 139 * @see #setDefaultValue(String) 140 */ 141 public String getDefaultValue() { 142 return defaultValue; 143 } 144 145 /** 146 * The default MDC value in case the MDC is not set for {@link #setKey(String) 147 * mdcKey}. 148 * <p/> 149 * <p> 150 * For example, if {@link #setKey(String) Key} is set to the value "someKey", 151 * and the MDC is not set for "someKey", then this appender will use the default 152 * value, which you can set with the help of this method. 153 * 154 * @param defaultValue 155 */ 156 public void setDefaultValue(String defaultValue) { 157 this.defaultValue = defaultValue; 158 } 159}