VariableSubstitutionsHelper.java
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v2.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core.model.util;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
import ch.qos.logback.core.spi.ScanException;
import ch.qos.logback.core.util.OptionHelper;
import java.util.HashMap;
import java.util.Map;
/**
* Helper methods to deal with properties.
*
* <p>This class acts as a small container for substitution properties and
* delegates actual variable substitution to {@link OptionHelper#substVars}.
* It also offers a convenience method to mask confidential property values
* (for example passwords) by returning a blurred placeholder.</p>
*
* @since 1.5.1
*/
public class VariableSubstitutionsHelper extends ContextAwareBase implements ContextAwarePropertyContainer {
static final String PASSWORD = "password";
static final String SECRET = "secret";
static final String CONFIDENTIAL = "confidential";
static final String BLURRED_STR = "******";
protected Map<String, String> propertiesMap;
/**
* Create a helper backed by an empty property map.
*
* @param context the logback context to associate with this helper; may be null
*/
public VariableSubstitutionsHelper(Context context) {
this.setContext(context);
this.propertiesMap = new HashMap<>();
}
/**
* Create a helper pre-populated with the contents of {@code otherMap}.
* The provided map is copied and further modifications do not affect the
* original map.
*
* @param context the logback context to associate with this helper; may be null
* @param otherMap initial properties to copy; if null an empty map is created
*/
public VariableSubstitutionsHelper(Context context, Map<String, String> otherMap) {
this.setContext(context);
this.propertiesMap = new HashMap<>(otherMap);
}
/**
* Perform variable substitution on the provided reference string.
*
* <p>Returns {@code null} if {@code ref} is {@code null}. On parse errors
* the original input string is returned and an error is logged.</p>
*
* @param ref the string possibly containing variables to substitute
* @return the string with substitutions applied, or {@code null} if {@code ref} was {@code null}
*/
@Override
public String subst(String ref) {
if (ref == null) {
return null;
}
try {
return OptionHelper.substVars(ref, this, context);
} catch (ScanException | IllegalArgumentException e) {
addError("Problem while parsing [" + ref + "]", e);
return ref;
}
}
/**
* Return a blurred placeholder for confidential properties.
*
* <p>If the property name {@code ref} contains any of the case-insensitive
* substrings {@code "password"}, {@code "secret"} or {@code "confidential"}
* this method returns a fixed blurred string ("******"). Otherwise, the
* supplied {@code substituted} value is returned unchanged.</p>
*
* @param ref the property name to inspect; must not be {@code null}
* @param substituted the substituted value to return when the property is not confidential
* @return a blurred placeholder when the property appears confidential, otherwise {@code substituted}
* @throws IllegalArgumentException when {@code ref} is {@code null}
*/
public String sanitizeIfConfidential(String ref, String substituted) {
if(ref == null) {
throw new IllegalArgumentException("ref cannot be null");
}
String lowerCaseRef = ref.toLowerCase();
if(lowerCaseRef.contains(PASSWORD) || lowerCaseRef.contains(SECRET) || lowerCaseRef.contains(CONFIDENTIAL)) {
return BLURRED_STR;
} else
return substituted;
}
/**
* Add or overwrite a substitution property.
*
* <p>Null keys or values are ignored. Values are trimmed before storing
* to avoid surprises caused by leading or trailing whitespace.</p>
*
* @param key the property name; ignored if {@code null}
* @param value the property value; ignored if {@code null}
*/
@Override
public void addSubstitutionProperty(String key, String value) {
if (key == null || value == null) {
return;
}
// values with leading or trailing spaces are bad. We remove them now.
value = value.trim();
propertiesMap.put(key, value);
}
/**
* Retrieve a property value by name.
*
* @param key the property name
* @return the property value or {@code null} if not present
*/
@Override
public String getProperty(String key) {
return propertiesMap.get(key);
}
/**
* Return a shallow copy of the internal property map.
*
* <p>The returned map is a copy and modifications to it do not affect the
* internal state of this helper.</p>
*
* @return a copy of the property map
*/
@Override
public Map<String, String> getCopyOfPropertyMap() {
return new HashMap<>(propertiesMap);
}
}