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