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 ad4924aad2f..89f41aec9a7 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.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 getLevel(String logger) { try { Document doc = loadConfig(path); + Map properties = getProperties(doc); Map loggers = getLoggers(doc); Map levels = new TreeMap<>(); for (Map.Entry 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 Map getLevel(String logger) { } } + static String resolveValue(String value, Map properties, Properties systemProperties, Map 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 @@ else if (!Level.isDefault(level)) { static void insertIndented(Element parent, Element element) { NodeList taggedElements = parent.getElementsByTagName("*"); //only use direct descendants of parent element to insert next to - ArrayList childElements = new ArrayList(); + ArrayList 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 @@ static void insertIndented(Element parent, Element element) { 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 @@ private Map getLoggers(Document doc) { 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 getProperties(Document doc) { + Map 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 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 ce6a770e79c..1b0815cdf74 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 = "\n" + - "\t\n" + - "\t\n" + + String xml = "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + ""; String out = insertIndented(xml, false); assertEquals( - "\n" + - "\t\n" + - "\t\t\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", out); out = insertIndented(xml, true); assertEquals( - "\n" + - "\t\n" + - "\t\t\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", out); } @Test public void testInsertIndentedSpaces() throws Exception { - String xml = "\n" + - " \n" + - " \n" + + String xml = "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + ""; String out = insertIndented(xml, false); assertEquals( - "\n" + - " \n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", out); out = insertIndented(xml, true); assertEquals( - "\n" + - " \n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", out); } @Test public void testInsertIndentedTabsWithRoot() throws Exception { - String xml = "\n" + - "\t\n" + - "\t\t\n" + - "\t\n" + + String xml = "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + ""; String out = insertIndented(xml, false); assertEquals( - "\n" + - "\t\n" + - "\t\t\n" + - "\t\t\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", out); out = insertIndented(xml, true); assertEquals( - "\n" + - "\t\n" + - "\t\t\n" + - "\t\t\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", out); } @Test public void testInsertIndentedSpacesWithRoot() throws Exception { - String xml = "\n" + - " \n" + - " \n" + - " \n" + + String xml = "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + ""; - + String out = insertIndented(xml, false); assertEquals( - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", out); out = insertIndented(xml, true); assertEquals( - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", 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 70b62571a63..e3d8a537a1f 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.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 = "\n" + + String xml = "" + System.lineSeparator() + ""; String out = insertIndented(xml); assertEquals( - "\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", 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 = "\n" + - " \n" + + String xml = "" + System.lineSeparator() + + " " + System.lineSeparator() + ""; String out = insertIndented(xml); assertEquals( - "\n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", out); } @Test public void testInsertIndentedTabsWithRoot() throws Exception { - String xml = "\n" + - "\t\n" + + String xml = "" + System.lineSeparator() + + "\t" + System.lineSeparator() + ""; String out = insertIndented(xml); assertEquals( - "\n" + - "\t\n" + - "\t\n" + + "" + System.lineSeparator() + + "\t" + System.lineSeparator() + + "\t" + System.lineSeparator() + "", out); } @Test public void testInsertIndentedSpacesWithRoot() throws Exception { - String xml = "\n" + - " \n" + + String xml = "" + System.lineSeparator() + + " " + System.lineSeparator() + ""; String out = insertIndented(xml); assertEquals( - "\n" + - " \n" + - " \n" + + "" + System.lineSeparator() + + " " + System.lineSeparator() + + " " + System.lineSeparator() + "", out); } @@ -91,7 +114,7 @@ private String insertIndented(String xml) throws Exception { 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 @@ private String insertIndented(String xml) throws Exception { 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 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 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 props = new HashMap<>(); + props.put(LOG_LEVEL_TOKEN, "DEBUG"); + Map 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 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 00000000000..fe3dfe991fa --- /dev/null +++ b/log/src/test/resources/logback.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file