This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/karaf.git
The following commit(s) were added to refs/heads/main by this push:
new 703fc1b1d5 Fix LogServiceLogback to find root logger correctly and add
support for basic variable substitution (#2172)
703fc1b1d5 is described below
commit 703fc1b1d5b9427a6aca9ae83038147fb00aeee8
Author: JB Onofré <[email protected]>
AuthorDate: Mon Dec 1 09:39:11 2025 +0100
Fix LogServiceLogback to find root logger correctly and add support for
basic variable substitution (#2172)
Co-authored-by: Antti Nevala <[email protected]>
---
.../core/internal/LogServiceLogbackXmlImpl.java | 100 ++++++++++++--
.../core/internal/LogServiceLog4j2XmlImplTest.java | 102 +++++++--------
.../core/internal/LogServiceLogbackXmlTest.java | 144 ++++++++++++++++++---
log/src/test/resources/logback.xml | 28 ++++
4 files changed, 291 insertions(+), 83 deletions(-)
diff --git
a/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlImpl.java
b/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlImpl.java
index ad4924aad2..89f41aec9a 100644
---
a/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlImpl.java
+++
b/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlImpl.java
@@ -39,17 +39,19 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class LogServiceLogbackXmlImpl implements LogServiceInternal {
private static final String ELEMENT_ROOT = "root";
private static final String ELEMENT_LOGGER = "logger";
+ private static final String ELEMENT_PROPERTY = "property";
+ private static final String ELEMENT_VARIABLE = "variable";
private static final String ATTRIBUTE_NAME = "name";
private static final String ATTRIBUTE_LEVEL = "level";
+ private static final String ATTRIBUTE_VALUE = "value";
private static final String ELEMENT_CONFIGURATION = "configuration";
private final Path path;
@@ -61,13 +63,15 @@ public class LogServiceLogbackXmlImpl implements
LogServiceInternal {
public Map<String, String> getLevel(String logger) {
try {
Document doc = loadConfig(path);
+ Map<String, String> properties = getProperties(doc);
Map<String, Element> loggers = getLoggers(doc);
Map<String, String> levels = new TreeMap<>();
for (Map.Entry<String, Element> e : loggers.entrySet()) {
+
String level = e.getValue().getAttribute(ATTRIBUTE_LEVEL);
- if (level != null && !level.isEmpty()) {
- levels.put(e.getKey(), level);
+ if (!level.isEmpty()) {
+ levels.put(e.getKey(), resolveValue(level, properties, new
Properties(System.getProperties()), new HashMap<>(System.getenv())));
}
}
@@ -93,6 +97,40 @@ public class LogServiceLogbackXmlImpl implements
LogServiceInternal {
}
}
+ static String resolveValue(String value, Map<String, String> properties,
Properties systemProperties, Map<String, String> envVariables) {
+ // Pattern to match ${variable:-default}
+ // At this moment only basic substitution is supported
+ // i.e D${my.param:-EBUG} is not supported
+ Pattern pattern = Pattern.compile("\\$\\{(.+?)(?::-(.+?))?}");
+ Matcher matcher = pattern.matcher(value);
+
+ if (matcher.matches()) {
+ String variable = matcher.group(1);
+ String defaultValue = matcher.group(2);
+ // Remove found property to prevent cyclic loops
+ // Check properties
+ String resolved = properties.remove(variable);
+ if (resolved == null) {
+ // Check system properties
+ resolved = systemProperties.getProperty(variable);
+ systemProperties.remove(variable);
+ }
+ if (resolved == null) {
+ // Check environment variables
+ resolved = envVariables.remove(variable);
+ }
+
+ if (resolved != null) {
+ // Check resolved variable again to susbstitute
+ return resolveValue(resolved, properties, systemProperties,
envVariables);
+ } else {
+ return defaultValue;
+ }
+ } else {
+ return value;
+ }
+ }
+
public void setLevel(String logger, String level) {
try {
Document doc = loadConfig(path);
@@ -146,13 +184,13 @@ public class LogServiceLogbackXmlImpl implements
LogServiceInternal {
static void insertIndented(Element parent, Element element) {
NodeList taggedElements = parent.getElementsByTagName("*");
//only use direct descendants of parent element to insert next to
- ArrayList <Node> childElements = new ArrayList<Node>();
+ ArrayList <Node> childElements = new ArrayList<>();
for (int i = 0;i < taggedElements.getLength(); i++ ){
if(taggedElements.item(i).getParentNode().equals(parent)){
childElements.add(taggedElements.item(i));
}
}
- Node insertAfter = childElements.size() > 0 ?
childElements.get(childElements.size() - 1) : null;
+ Node insertAfter = !childElements.isEmpty() ?
childElements.get(childElements.size() - 1) : null;
if (insertAfter != null) {
if (insertAfter.getPreviousSibling() != null &&
insertAfter.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
String indent =
insertAfter.getPreviousSibling().getTextContent();
@@ -185,7 +223,7 @@ public class LogServiceLogbackXmlImpl implements
LogServiceInternal {
indent += "\t";
}
}
- if (parent.getFirstChild() != null &&
parent.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
+ if (parent.getFirstChild() != null) {
parent.removeChild(parent.getFirstChild());
}
} else {
@@ -250,17 +288,53 @@ public class LogServiceLogbackXmlImpl implements
LogServiceInternal {
Node n = loggersList.item(i);
if (n instanceof Element) {
Element e = (Element) n;
- if (ELEMENT_ROOT.equals(e.getLocalName())) {
- loggers.put(ROOT_LOGGER, e);
- } else if (ELEMENT_LOGGER.equals(e.getLocalName())) {
+ if (ELEMENT_LOGGER.equals(e.getLocalName())) {
String name = e.getAttribute(ATTRIBUTE_NAME);
- if (name != null) {
+ if (!name.isEmpty()) {
loggers.put(name, e);
}
}
}
}
+ // Handle root separately
+ Node n = docE.getElementsByTagName(ELEMENT_ROOT).item(0);
+ if (n instanceof Element) {
+ Element e = (Element) n;
+ if (ELEMENT_ROOT.equals(e.getLocalName())) {
+ loggers.put(ROOT_LOGGER, e);
+ }
+ }
return loggers;
}
+ private Map<String, String> getProperties(Document doc) {
+ Map<String, String> properties = new TreeMap<>();
+ Element docE = doc.getDocumentElement();
+ if (!ELEMENT_CONFIGURATION.equals(docE.getLocalName())) {
+ throw new IllegalArgumentException("Xml root document should be "
+ ELEMENT_CONFIGURATION);
+ }
+ NodeList propertyList = docE.getElementsByTagName(ELEMENT_PROPERTY);
+ extractProperties(propertyList, ELEMENT_PROPERTY, properties);
+ NodeList variableList = docE.getElementsByTagName(ELEMENT_VARIABLE);
+ extractProperties(variableList, ELEMENT_VARIABLE, properties);
+ return properties;
+ }
+
+ private static void extractProperties(NodeList propertyList, String
elementName, Map<String, String> properties) {
+ for (int i = 0; i < propertyList.getLength(); i++) {
+ Node n = propertyList.item(i);
+ if (n instanceof Element) {
+ Element e = (Element) n;
+ if (elementName.equals(e.getLocalName())) {
+ String name = e.getAttribute(ATTRIBUTE_NAME);
+ String value = e.getAttribute(ATTRIBUTE_VALUE);
+ if (!name.isEmpty()) {
+ properties.put(name, value);
+ }
+ }
+ }
+ }
+ }
+
+
}
diff --git
a/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLog4j2XmlImplTest.java
b/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLog4j2XmlImplTest.java
index ce6a770e79..1b0815cdf7 100644
---
a/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLog4j2XmlImplTest.java
+++
b/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLog4j2XmlImplTest.java
@@ -34,99 +34,99 @@ public class LogServiceLog4j2XmlImplTest {
@Test
public void testInsertIndentedTabs() throws Exception {
- String xml = "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t</Loggers>\n" +
+ String xml = "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>";
String out = insertIndented(xml, false);
assertEquals(
- "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t\t<Logger/>\n" +
- "\t</Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t\t<Logger/>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>", out);
out = insertIndented(xml, true);
assertEquals(
- "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t\t<Logger/>\n" +
- "\t</Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t\t<Logger/>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>", out);
}
@Test
public void testInsertIndentedSpaces() throws Exception {
- String xml = "<Configuration>\n" +
- " <Loggers>\n" +
- " </Loggers>\n" +
+ String xml = "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>";
String out = insertIndented(xml, false);
assertEquals(
- "<Configuration>\n" +
- " <Loggers>\n" +
- " <Logger/>\n" +
- " </Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " <Logger/>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>", out);
out = insertIndented(xml, true);
assertEquals(
- "<Configuration>\n" +
- " <Loggers>\n" +
- " <Logger/>\n" +
- " </Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " <Logger/>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>", out);
}
@Test
public void testInsertIndentedTabsWithRoot() throws Exception {
- String xml = "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t\t<Root/>\n" +
- "\t</Loggers>\n" +
+ String xml = "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t\t<Root/>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>";
String out = insertIndented(xml, false);
assertEquals(
- "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t\t<Root/>\n" +
- "\t\t<Logger/>\n" +
- "\t</Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t\t<Root/>" + System.lineSeparator() +
+ "\t\t<Logger/>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>", out);
out = insertIndented(xml, true);
assertEquals(
- "<Configuration>\n" +
- "\t<Loggers>\n" +
- "\t\t<Logger/>\n" +
- "\t\t<Root/>\n" +
- "\t</Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ "\t<Loggers>" + System.lineSeparator() +
+ "\t\t<Logger/>" + System.lineSeparator() +
+ "\t\t<Root/>" + System.lineSeparator() +
+ "\t</Loggers>" + System.lineSeparator() +
"</Configuration>", out);
}
@Test
public void testInsertIndentedSpacesWithRoot() throws Exception {
- String xml = "<Configuration>\n" +
- " <Loggers>\n" +
- " <Root/>\n" +
- " </Loggers>\n" +
+ String xml = "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " <Root/>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>";
-
+
String out = insertIndented(xml, false);
assertEquals(
- "<Configuration>\n" +
- " <Loggers>\n" +
- " <Root/>\n" +
- " <Logger/>\n" +
- " </Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " <Root/>" + System.lineSeparator() +
+ " <Logger/>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>", out);
out = insertIndented(xml, true);
assertEquals(
- "<Configuration>\n" +
- " <Loggers>\n" +
- " <Logger/>\n" +
- " <Root/>\n" +
- " </Loggers>\n" +
+ "<Configuration>" + System.lineSeparator() +
+ " <Loggers>" + System.lineSeparator() +
+ " <Logger/>" + System.lineSeparator() +
+ " <Root/>" + System.lineSeparator() +
+ " </Loggers>" + System.lineSeparator() +
"</Configuration>", out);
}
diff --git
a/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlTest.java
b/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlTest.java
index 70b62571a6..e3d8a537a1 100644
---
a/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlTest.java
+++
b/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.karaf.log.core.internal;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -27,63 +28,85 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class LogServiceLogbackXmlTest {
+ public static final String LOG_LEVEL_TOKEN = "log.level";
+
+ private static String file;
+ @BeforeClass
+ public static void initClass() {
+ Path p;
+ try {
+ p =
Paths.get(LogServiceLogbackXmlImpl.class.getResource("/logback.xml").toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ file = p.toString();
+ }
+
@Test
public void testInsertIndentedTabs() throws Exception {
- String xml = "<configuration>\n" +
+ String xml = "<configuration>" + System.lineSeparator() +
"</configuration>";
String out = insertIndented(xml);
assertEquals(
- "<configuration>\n" +
- "\t<logger/>\n" +
+ "<configuration>" + System.lineSeparator() +
+ "\t<logger/>" + System.lineSeparator() +
"</configuration>", out);
}
@Test
public void testInsertIndentedSpaces() throws Exception {
//this one tests with one logger already added, because with no
loggers there is no indentation to decide by and the function will choose tab
- String xml = "<configuration>\n" +
- " <logger/>\n" +
+ String xml = "<configuration>" + System.lineSeparator() +
+ " <logger/>" + System.lineSeparator() +
"</configuration>";
String out = insertIndented(xml);
assertEquals(
- "<configuration>\n" +
- " <logger/>\n" +
- " <logger/>\n" +
+ "<configuration>" + System.lineSeparator() +
+ " <logger/>" + System.lineSeparator() +
+ " <logger/>" + System.lineSeparator() +
"</configuration>", out);
}
@Test
public void testInsertIndentedTabsWithRoot() throws Exception {
- String xml = "<configuration>\n" +
- "\t<root/>\n" +
+ String xml = "<configuration>" + System.lineSeparator() +
+ "\t<root/>" + System.lineSeparator() +
"</configuration>";
String out = insertIndented(xml);
assertEquals(
- "<configuration>\n" +
- "\t<root/>\n" +
- "\t<logger/>\n" +
+ "<configuration>" + System.lineSeparator() +
+ "\t<root/>" + System.lineSeparator() +
+ "\t<logger/>" + System.lineSeparator() +
"</configuration>", out);
}
@Test
public void testInsertIndentedSpacesWithRoot() throws Exception {
- String xml = "<configuration>\n" +
- " <root/>\n" +
+ String xml = "<configuration>" + System.lineSeparator() +
+ " <root/>" + System.lineSeparator() +
"</configuration>";
String out = insertIndented(xml);
assertEquals(
- "<configuration>\n" +
- " <root/>\n" +
- " <logger/>\n" +
+ "<configuration>" + System.lineSeparator() +
+ " <root/>" + System.lineSeparator() +
+ " <logger/>" + System.lineSeparator() +
"</configuration>", out);
}
@@ -91,7 +114,7 @@ public class LogServiceLogbackXmlTest {
Document doc = LogServiceLog4j2XmlImpl.loadConfig(null, new
ByteArrayInputStream(xml.getBytes()));
Element element = doc.createElement("logger");
LogServiceLogbackXmlImpl.insertIndented(
- (Element) doc.getDocumentElement(),
+ doc.getDocumentElement(),
element);
try (StringWriter os = new StringWriter()) {
TransformerFactory tFactory = TransformerFactory.newInstance();
@@ -101,4 +124,87 @@ public class LogServiceLogbackXmlTest {
return os.toString();
}
}
+
+ @Test
+ public void testBasicValue() {
+ Properties systemProps = new Properties();
+ String resolved = LogServiceLogbackXmlImpl.resolveValue("DEBUG",
Collections.emptyMap(), systemProps, Collections.emptyMap());
+ assertEquals("DEBUG", resolved);
+ }
+
+ @Test
+ public void testPropertySubstitution() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(LOG_LEVEL_TOKEN, "WARN");
+ String resolved =
LogServiceLogbackXmlImpl.resolveValue("${log.level:-DEBUG}", properties, new
Properties(), Collections.emptyMap());
+ assertEquals("WARN", resolved);
+ }
+
+ @Test
+ public void testSystemPropertiesSubstitution() {
+ Properties systemProps = new Properties();
+ systemProps.put(LOG_LEVEL_TOKEN, "WARN");
+ String resolved =
LogServiceLogbackXmlImpl.resolveValue("${log.level:-DEBUG}",
Collections.emptyMap(), systemProps, Collections.emptyMap());
+ assertEquals("WARN", resolved);
+ }
+
+ @Test
+ public void testEnvVariableSubstitution() {
+ Map<String, String> env = new HashMap<>();
+ env.put(LOG_LEVEL_TOKEN, "WARN");
+ String resolved =
LogServiceLogbackXmlImpl.resolveValue("${log.level:-DEBUG}",
Collections.emptyMap(), new Properties(), env);
+ assertEquals("WARN", resolved);
+ }
+
+ @Test
+ public void testPropertyWinsEnvSubstitution() {
+ Map<String, String> props = new HashMap<>();
+ props.put(LOG_LEVEL_TOKEN, "DEBUG");
+ Map<String, String> env = new HashMap<>();
+ env.put(LOG_LEVEL_TOKEN, "WARN");
+ String resolved =
LogServiceLogbackXmlImpl.resolveValue("${log.level}", props , new Properties(),
env);
+ assertEquals("DEBUG", resolved);
+ }
+
+ @Test
+ public void testRootLogLevel() {
+ LogServiceLogbackXmlImpl logService = getLogService();
+ assertEquals("WARN",
logService.getLevel(LogServiceInternal.ROOT_LOGGER).get(LogServiceInternal.ROOT_LOGGER));
+ }
+
+ @Test
+ public void testPropertyLogLevel() {
+ String logger = "debugPropertyLogger";
+ LogServiceLogbackXmlImpl logService = getLogService();
+ assertEquals("DEBUG", logService.getLevel(logger).get(logger));
+ }
+
+ @Test
+ public void testSystemPropertyLogLevel() {
+ String logger = "systemPropertyLogger";
+ System.setProperty("LOG_LEVEL", "DEBUG");
+ LogServiceLogbackXmlImpl logService = getLogService();
+ assertEquals("DEBUG", logService.getLevel(logger).get(logger));
+ System.clearProperty("LOG_LEVEL");
+ }
+
+ @Test
+ public void testDefaultValueLogLevel() {
+ String logger = "defaultValueLogger";
+ LogServiceLogbackXmlImpl logService = getLogService();
+ assertEquals("DEBUG", logService.getLevel(logger).get(logger));
+ }
+
+ @Test
+ public void testAllLoggerLogLevel() {
+ LogServiceLogbackXmlImpl logService = getLogService();
+ Map<String, String> levels =
logService.getLevel(LogServiceInternal.ALL_LOGGER);
+ assertEquals(4, levels.size());
+ assertTrue(levels.containsKey(LogServiceInternal.ROOT_LOGGER));
+ }
+
+ private LogServiceLogbackXmlImpl getLogService() {
+ return new LogServiceLogbackXmlImpl(file);
+ }
+
}
diff --git a/log/src/test/resources/logback.xml
b/log/src/test/resources/logback.xml
new file mode 100644
index 0000000000..fe3dfe991f
--- /dev/null
+++ b/log/src/test/resources/logback.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration>
+ <property name="system.log.level" scope="context" value="${LOG_LEVEL}"/>
+ <property name="debug.log.level" scope="context" value="DEBUG"/>
+
+ <root level="WARN"/>
+
+ <logger name="debugPropertyLogger" level="${debug.log.level}"/>
+ <logger name="systemPropertyLogger" level="${system.log.level}"/>
+ <logger name="defaultValueLogger" level="${not.existing.property:-DEBUG}"/>
+</configuration>
\ No newline at end of file