You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/10/14 13:49:16 UTC

[karaf] branch karaf-4.3.x updated: [KARAF-7423] Add optional logback configuration

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch karaf-4.3.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.3.x by this push:
     new 6d10adbf4b [KARAF-7423] Add optional logback configuration
6d10adbf4b is described below

commit 6d10adbf4bcc767443586c26d194cbdedf1c4b6e
Author: Ina Thiemann <in...@kisters.de>
AuthorDate: Thu Apr 14 16:04:38 2022 +0200

    [KARAF-7423] Add optional logback configuration
    
    (cherry picked from commit 9b6c7775f30c84474d2a4fe0e8bd2f9624e1f90d)
---
 assemblies/features/framework/pom.xml              |   4 +
 log/pom.xml                                        |   8 +-
 .../karaf/log/core/internal/LogServiceImpl.java    |   8 +
 .../core/internal/LogServiceLogbackXmlImpl.java    | 266 +++++++++++++++++++++
 .../core/internal/LogServiceLogbackXmlTest.java    | 104 ++++++++
 manual/src/main/asciidoc/user-guide/log.adoc       |  27 +++
 6 files changed, 416 insertions(+), 1 deletion(-)

diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml
index 779c1462b1..9117422b65 100644
--- a/assemblies/features/framework/pom.xml
+++ b/assemblies/features/framework/pom.xml
@@ -102,6 +102,10 @@
             <groupId>org.ops4j.pax.logging</groupId>
             <artifactId>pax-logging-log4j2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.logging</groupId>
+            <artifactId>pax-logging-logback</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.ops4j.pax.url</groupId>
             <artifactId>pax-url-aether</artifactId>
diff --git a/log/pom.xml b/log/pom.xml
index 11a55bc5b2..3f24caabc8 100644
--- a/log/pom.xml
+++ b/log/pom.xml
@@ -32,7 +32,7 @@
     <artifactId>org.apache.karaf.log.core</artifactId>
     <packaging>bundle</packaging>
     <name>Apache Karaf :: Log :: Core</name>
-    <description>Core Seervices and JMX MBean to manipulate the Karaf log layer</description>
+    <description>Core Services and JMX MBean to manipulate the Karaf log layer</description>
 
     <properties>
         <appendedResourcesDirectory>${basedir}/../../../etc/appended-resources/</appendedResourcesDirectory>
@@ -67,6 +67,12 @@
             <artifactId>pax-logging-log4j2</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.logging</groupId>
+            <artifactId>pax-logging-logback</artifactId>
+            <version>${pax.logging.version}</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.ops4j.pax.logging</groupId>
             <artifactId>pax-logging-api</artifactId>
diff --git a/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceImpl.java b/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceImpl.java
index f291ac5951..bf0eb76203 100644
--- a/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceImpl.java
+++ b/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceImpl.java
@@ -59,6 +59,14 @@ public class LogServiceImpl implements LogService, PaxAppender {
                 throw new IllegalStateException("Unsupported Log4j2 configuration type: " + file);
             }
         }
+        else if (config.get("org.ops4j.pax.logging.logback.config.file") != null) {
+            String file = config.get("org.ops4j.pax.logging.logback.config.file").toString();
+            if (file.endsWith(".xml")) {
+                return new LogServiceLogbackXmlImpl(file);
+            } else {
+                throw new IllegalStateException("Unsupported Logback configuration type: " + file);
+            }
+        }
         else {
             throw new IllegalStateException("Unrecognized configuration");
         }
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
new file mode 100644
index 0000000000..ad4924aad2
--- /dev/null
+++ b/log/src/main/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlImpl.java
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+package org.apache.karaf.log.core.internal;
+
+import org.apache.karaf.log.core.Level;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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;
+
+public class LogServiceLogbackXmlImpl implements LogServiceInternal {
+
+    private static final String ELEMENT_ROOT = "root";
+    private static final String ELEMENT_LOGGER = "logger";
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_LEVEL = "level";
+    private static final String ELEMENT_CONFIGURATION = "configuration";
+
+    private final Path path;
+
+    LogServiceLogbackXmlImpl(String file) {
+        this.path = Paths.get(file);
+    }
+
+    public Map<String, String> getLevel(String logger) {
+        try {
+            Document doc = loadConfig(path);
+            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 (ALL_LOGGER.equals(logger)) {
+                return levels;
+            }
+            String l = logger;
+            String val;
+            for (; ; ) {
+                val = levels.get(l != null ? l : ROOT_LOGGER);
+                if (val != null || l == null) {
+                    return Collections.singletonMap(logger, val);
+                }
+                int idx = l.lastIndexOf('.');
+                if (idx < 0) {
+                    l = null;
+                } else {
+                    l = l.substring(0, idx);
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to retrieve level for logger", e);
+        }
+    }
+
+    public void setLevel(String logger, String level) {
+        try {
+            Document doc = loadConfig(path);
+            Map<String, Element> loggers = getLoggers(doc);
+
+            Element element = loggers.get(logger);
+            if (element != null) {
+                if (Level.isDefault(level)) {
+                    element.removeAttribute(ATTRIBUTE_LEVEL);
+                } else {
+                    element.setAttribute(ATTRIBUTE_LEVEL, level);
+                }
+            }
+            else if (!Level.isDefault(level)) {
+                Element docE = doc.getDocumentElement();
+                boolean root = ROOT_LOGGER.equals(logger);
+                if (root) {
+                    element = doc.createElement(ELEMENT_ROOT);
+                    element.setAttribute(ATTRIBUTE_LEVEL, level);
+                } else {
+                    element = doc.createElement(ELEMENT_LOGGER);
+                    element.setAttribute(ATTRIBUTE_NAME, logger);
+                    element.setAttribute(ATTRIBUTE_LEVEL, level);
+                }
+                insertIndented(docE, element);
+            } else {
+                return;
+            }
+            try (OutputStream os = Files.newOutputStream(path, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+                TransformerFactory tFactory = TransformerFactory.newInstance();
+                tFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
+                try {
+                    tFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+                    tFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+                } catch (IllegalArgumentException e) {
+                    // ignore
+                }
+
+                Transformer transformer = tFactory.newTransformer();
+                transformer.transform(new DOMSource(doc), new StreamResult(os));
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to set level for logger", e);
+        }
+    }
+
+    /**
+     * Insert the given node into the parent element,
+     * indenting it as needed.
+     */
+    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>();
+        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;
+            if (insertAfter != null) {
+                if (insertAfter.getPreviousSibling() != null && insertAfter.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
+                    String indent = insertAfter.getPreviousSibling().getTextContent();
+                    Node node = parent.getOwnerDocument().createTextNode(indent);
+                    if (insertAfter.getNextSibling() != null) {
+                        parent.insertBefore(node, insertAfter.getNextSibling());
+                        insertAfter = node;
+                    } else {
+                        parent.appendChild(node);
+                    }
+                }
+                if (insertAfter.getNextSibling() != null ) {
+                    parent.insertBefore(element, insertAfter.getNextSibling());
+                } else {
+                    parent.appendChild(element);
+                }
+            } else {
+                String indent;
+                String prev;
+                if (parent.getPreviousSibling() != null && parent.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
+                    indent = parent.getPreviousSibling().getTextContent();
+                    prev = indent;
+                    if (indent.endsWith("\t")) {
+                        indent += "\t";
+                    } else {
+                        int nl = indent.lastIndexOf('\n');
+                        if (nl >= 0) {
+                            indent = indent + indent.substring(nl + 1);
+                        } else {
+                            indent += "\t";
+                        }
+                    }
+                    if (parent.getFirstChild() != null && parent.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
+                        parent.removeChild(parent.getFirstChild());
+                    }
+                } else {
+                    indent = "\t";
+                    prev = "\n";
+                }
+                parent.appendChild(parent.getOwnerDocument().createTextNode(indent));
+                parent.appendChild(element);
+                parent.appendChild(parent.getOwnerDocument().createTextNode(prev));
+            }
+    }
+
+    static Document loadConfig(Path path) throws Exception {
+        try (InputStream is = Files.newInputStream(path)) {
+            return loadConfig(path.toString(), is);
+        }
+    }
+
+    static Document loadConfig(String id, InputStream is) throws ParserConfigurationException, SAXException, IOException {
+        final InputSource source = new InputSource(is);
+        source.setPublicId(id);
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setValidating(false);
+        factory.setExpandEntityReferences(false);
+
+        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        setFeature(factory, "http://xml.org/sax/features/external-general-entities", false);
+        setFeature(factory, "http://xml.org/sax/features/external-parameter-entities", false);
+        setFeature(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        setFeature(factory, "http://apache.org/xml/features/xinclude/fixup-base-uris", true);
+        setFeature(factory, "http://apache.org/xml/features/xinclude/fixup-language", true);
+        tryCall(() -> factory.setXIncludeAware(true));
+        DocumentBuilder documentBuilder = factory.newDocumentBuilder();
+        return documentBuilder.parse(source);
+    }
+
+    private static void setFeature(DocumentBuilderFactory factory, String name, boolean b) {
+        tryCall(() -> factory.setFeature(name, b));
+    }
+
+    interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    private static void tryCall(RunnableWithException c) {
+        try {
+            c.run();
+        } catch (Exception e) {
+            // Ignore
+        }
+    }
+
+    private Map<String, Element> getLoggers(Document doc) {
+        Map<String, Element> loggers = new TreeMap<>();
+        Element docE = doc.getDocumentElement();
+        if (!ELEMENT_CONFIGURATION.equals(docE.getLocalName())) {
+            throw new IllegalArgumentException("Xml root document should be " + ELEMENT_CONFIGURATION);
+        }
+        NodeList loggersList = docE.getElementsByTagName(ELEMENT_LOGGER);
+        for (int i = 0; i < loggersList.getLength(); i++) {
+            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())) {
+                    String name = e.getAttribute(ATTRIBUTE_NAME);
+                    if (name != null) {
+                        loggers.put(name, e);
+                    }
+                }
+            }
+        }
+        return loggers;
+    }
+
+}
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
new file mode 100644
index 0000000000..70b62571a6
--- /dev/null
+++ b/log/src/test/java/org/apache/karaf/log/core/internal/LogServiceLogbackXmlTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+package org.apache.karaf.log.core.internal;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+
+import static org.junit.Assert.assertEquals;
+
+public class LogServiceLogbackXmlTest {
+
+    @Test
+    public void testInsertIndentedTabs() throws Exception {
+        String xml = "<configuration>\n" +
+                "</configuration>";
+
+        String out = insertIndented(xml);
+        assertEquals(
+                "<configuration>\n" +
+                        "\t<logger/>\n" +
+                        "</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" +
+                "</configuration>";
+
+        String out = insertIndented(xml);
+        assertEquals(
+                "<configuration>\n" +
+                        "  <logger/>\n" +
+                        "  <logger/>\n" +
+                        "</configuration>", out);
+    }
+
+    @Test
+    public void testInsertIndentedTabsWithRoot() throws Exception {
+        String xml = "<configuration>\n" +
+                "\t<root/>\n" +
+                "</configuration>";
+
+        String out = insertIndented(xml);
+        assertEquals(
+                "<configuration>\n" +
+                        "\t<root/>\n" +
+                        "\t<logger/>\n" +
+                        "</configuration>", out);
+    }
+
+    @Test
+    public void testInsertIndentedSpacesWithRoot() throws Exception {
+        String xml = "<configuration>\n" +
+                "  <root/>\n" +
+                "</configuration>";
+
+        String out = insertIndented(xml);
+        assertEquals(
+                "<configuration>\n" +
+                        "  <root/>\n" +
+                        "  <logger/>\n" +
+                        "</configuration>", out);
+    }
+
+    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(),
+                element);
+        try (StringWriter os = new StringWriter()) {
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            Transformer transformer = tFactory.newTransformer();
+            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+            transformer.transform(new DOMSource(doc, "the.xml"), new StreamResult(os));
+            return os.toString();
+        }
+    }
+}
diff --git a/manual/src/main/asciidoc/user-guide/log.adoc b/manual/src/main/asciidoc/user-guide/log.adoc
index 21e5da7e8d..7edfb38ead 100644
--- a/manual/src/main/asciidoc/user-guide/log.adoc
+++ b/manual/src/main/asciidoc/user-guide/log.adoc
@@ -160,6 +160,33 @@ A default configuration in `etc/log4j2.xml` could be:
 </Configuration>
 ----
 
+==== Logback support
+
+Xml based configurations for logback are supported.
+To use logback you have to:
+. Edit `etc/startup.properties` to replace the line `org/ops4j/pax/logging/pax-logging-service/1.8.4/pax-logging-service-1.8.4.jar=8` with `org/ops4j/pax/logging/pax-logging-service/pax-logging-logback/2.0.14 = 8`
+. Add pax-logging-logback jar file in `system/org/ops4j/pax/logging/pax-logging-logback/x.x.x/pax-logging-logback-x.x.x.jar where x.x.x is the version as defined in `etc/startup.properties`
+. Alternatively to steps 1 and 2, if using maven with karaf as a dependency, you can add configuration for including logback in the pom.xml like so:
+----
+    <dependency>
+      <groupId>org.apache.karaf.features</groupId>
+        <version>${karaf.version}</version>
+        <configuration>
+          <framework>framework-logback</framework>
+        </configuration>
+    </dependency>
+----
+. Edit `etc/org.ops4j.pax.logging.cfg` configuration file and replace log4j configuration with `org.ops4j.pax.logging.logback.config.file = ${karaf.etc}/logback.xml`
+. Add the `etc/logback.xml` configuration file. You need to add a PaxAppender to your configuartion and include it in root. Add the following to logback.xml:
+----
+  <appender class="org.ops4j.pax.logging.logback.internal.bridges.PaxAppenderDelegate" name="VmLogAppender"/>
+  <root level="INFO">
+	<appender-ref ref="VmLogAppender"/>
+	{...}
+  </root>
+----
+
+
 ==== `karaf.log.console` property
 
 Before Karaf starts _proper_ logging facilities (pax-logging), it may configure `java.util.logging`. Standard