From 1f20d75477c48a0945aa87f9393e1352e09b8e97 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 19:01:45 +0200 Subject: [PATCH 01/24] Restore Apache Felix Logback module This reverts commit 22cdf0e5d03da839e81bffd15194281899d92129 so development can resume from the last pre-removal state. --- logback/doc/changelog.txt | 3 + logback/itests/base.bndrun | 8 + .../immediate-equinox-logservice/itest.bndrun | 16 + .../immediate-equinox-logservice/pom.xml | 40 +++ .../felix/logback/test/LogServiceTest.java | 77 +++++ .../immediate-felix-logservice/itest.bndrun | 19 ++ .../itests/immediate-felix-logservice/pom.xml | 39 +++ .../felix/logback/test/LogServiceTest.java | 77 +++++ logback/itests/immediate-jbl/itest.bndrun | 28 ++ logback/itests/immediate-jbl/pom.xml | 39 +++ .../apache/felix/logback/test/JBLTest.java | 37 +++ logback/itests/immediate-jcl/itest.bndrun | 27 ++ logback/itests/immediate-jcl/pom.xml | 39 +++ .../apache/felix/logback/test/JCLTest.java | 38 +++ .../itests/immediate-jul-felix/itest.bndrun | 19 ++ logback/itests/immediate-jul-felix/pom.xml | 39 +++ .../apache/felix/logback/test/JULTest.java | 39 +++ logback/itests/immediate-jul/itest.bndrun | 21 ++ logback/itests/immediate-jul/pom.xml | 39 +++ .../apache/felix/logback/test/JULTest.java | 39 +++ logback/itests/immediate-log4j1/itest.bndrun | 25 ++ logback/itests/immediate-log4j1/pom.xml | 39 +++ .../apache/felix/logback/test/Log4j1Test.java | 38 +++ logback/itests/immediate-log4j2/itest.bndrun | 27 ++ logback/itests/immediate-log4j2/pom.xml | 39 +++ .../apache/felix/logback/test/Log4j2Test.java | 38 +++ logback/itests/immediate-slf4j/itest.bndrun | 24 ++ logback/itests/immediate-slf4j/pom.xml | 39 +++ .../apache/felix/logback/test/SLF4JTest.java | 38 +++ logback/itests/itest-parent/pom.xml | 305 +++++++++++++++++ logback/itests/logback.xml | 40 +++ logback/itests/pom.xml | 55 ++++ .../standard-equinox-logservice/itest.bndrun | 19 ++ .../standard-equinox-logservice/pom.xml | 39 +++ .../felix/logback/test/LogServiceTest.java | 77 +++++ .../standard-felix-logservice/itest.bndrun | 21 ++ .../itests/standard-felix-logservice/pom.xml | 45 +++ .../felix/logback/test/LogServiceTest.java | 77 +++++ logback/itests/standard-jbl/itest.bndrun | 23 ++ logback/itests/standard-jbl/pom.xml | 39 +++ .../apache/felix/logback/test/JBLTest.java | 37 +++ logback/itests/standard-jcl/itest.bndrun | 17 + logback/itests/standard-jcl/pom.xml | 39 +++ .../apache/felix/logback/test/JCLTest.java | 38 +++ logback/itests/standard-jul/itest.bndrun | 19 ++ logback/itests/standard-jul/pom.xml | 39 +++ .../apache/felix/logback/test/JULTest.java | 39 +++ logback/itests/standard-log4j1/itest.bndrun | 15 + logback/itests/standard-log4j1/pom.xml | 39 +++ .../apache/felix/logback/test/Log4j1Test.java | 38 +++ logback/itests/standard-log4j2/itest.bndrun | 17 + logback/itests/standard-log4j2/pom.xml | 39 +++ .../apache/felix/logback/test/Activator.java | 50 +++ .../apache/felix/logback/test/Log4j2Test.java | 42 +++ logback/itests/standard-slf4j/itest.bndrun | 14 + logback/itests/standard-slf4j/pom.xml | 39 +++ .../apache/felix/logback/test/SLF4JTest.java | 38 +++ logback/itests/test-helper/pom.xml | 64 ++++ .../logback/test/helper/LogTestHelper.java | 91 +++++ .../test/helper/ls/LogServiceHelper.java | 35 ++ logback/logback/DEPENDENCIES | 22 ++ logback/logback/LICENSE | 202 ++++++++++++ logback/logback/NOTICE | 6 + logback/logback/README.md | 148 +++++++++ logback/logback/pom.xml | 150 +++++++++ .../felix/logback/internal/Activator.java | 139 ++++++++ .../logback/internal/EquinoxHookSupport.java | 34 ++ .../logback/internal/LogbackLogListener.java | 311 ++++++++++++++++++ .../resources/hookconfigurators.properties | 15 + logback/pom.xml | 44 +++ 70 files changed, 3550 insertions(+) create mode 100644 logback/doc/changelog.txt create mode 100644 logback/itests/base.bndrun create mode 100644 logback/itests/immediate-equinox-logservice/itest.bndrun create mode 100644 logback/itests/immediate-equinox-logservice/pom.xml create mode 100644 logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java create mode 100644 logback/itests/immediate-felix-logservice/itest.bndrun create mode 100644 logback/itests/immediate-felix-logservice/pom.xml create mode 100644 logback/itests/immediate-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java create mode 100644 logback/itests/immediate-jbl/itest.bndrun create mode 100644 logback/itests/immediate-jbl/pom.xml create mode 100644 logback/itests/immediate-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java create mode 100644 logback/itests/immediate-jcl/itest.bndrun create mode 100644 logback/itests/immediate-jcl/pom.xml create mode 100644 logback/itests/immediate-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java create mode 100644 logback/itests/immediate-jul-felix/itest.bndrun create mode 100644 logback/itests/immediate-jul-felix/pom.xml create mode 100644 logback/itests/immediate-jul-felix/src/main/java/org/apache/felix/logback/test/JULTest.java create mode 100644 logback/itests/immediate-jul/itest.bndrun create mode 100644 logback/itests/immediate-jul/pom.xml create mode 100644 logback/itests/immediate-jul/src/main/java/org/apache/felix/logback/test/JULTest.java create mode 100644 logback/itests/immediate-log4j1/itest.bndrun create mode 100644 logback/itests/immediate-log4j1/pom.xml create mode 100644 logback/itests/immediate-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java create mode 100644 logback/itests/immediate-log4j2/itest.bndrun create mode 100644 logback/itests/immediate-log4j2/pom.xml create mode 100644 logback/itests/immediate-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java create mode 100644 logback/itests/immediate-slf4j/itest.bndrun create mode 100644 logback/itests/immediate-slf4j/pom.xml create mode 100644 logback/itests/immediate-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java create mode 100644 logback/itests/itest-parent/pom.xml create mode 100644 logback/itests/logback.xml create mode 100644 logback/itests/pom.xml create mode 100644 logback/itests/standard-equinox-logservice/itest.bndrun create mode 100644 logback/itests/standard-equinox-logservice/pom.xml create mode 100644 logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java create mode 100644 logback/itests/standard-felix-logservice/itest.bndrun create mode 100644 logback/itests/standard-felix-logservice/pom.xml create mode 100644 logback/itests/standard-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java create mode 100644 logback/itests/standard-jbl/itest.bndrun create mode 100644 logback/itests/standard-jbl/pom.xml create mode 100644 logback/itests/standard-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java create mode 100644 logback/itests/standard-jcl/itest.bndrun create mode 100644 logback/itests/standard-jcl/pom.xml create mode 100644 logback/itests/standard-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java create mode 100644 logback/itests/standard-jul/itest.bndrun create mode 100644 logback/itests/standard-jul/pom.xml create mode 100644 logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java create mode 100644 logback/itests/standard-log4j1/itest.bndrun create mode 100644 logback/itests/standard-log4j1/pom.xml create mode 100644 logback/itests/standard-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java create mode 100644 logback/itests/standard-log4j2/itest.bndrun create mode 100644 logback/itests/standard-log4j2/pom.xml create mode 100644 logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Activator.java create mode 100644 logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java create mode 100644 logback/itests/standard-slf4j/itest.bndrun create mode 100644 logback/itests/standard-slf4j/pom.xml create mode 100644 logback/itests/standard-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java create mode 100644 logback/itests/test-helper/pom.xml create mode 100644 logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java create mode 100644 logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/ls/LogServiceHelper.java create mode 100644 logback/logback/DEPENDENCIES create mode 100644 logback/logback/LICENSE create mode 100644 logback/logback/NOTICE create mode 100644 logback/logback/README.md create mode 100644 logback/logback/pom.xml create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java create mode 100644 logback/logback/src/main/resources/hookconfigurators.properties create mode 100644 logback/pom.xml diff --git a/logback/doc/changelog.txt b/logback/doc/changelog.txt new file mode 100644 index 0000000000..d926a7124f --- /dev/null +++ b/logback/doc/changelog.txt @@ -0,0 +1,3 @@ +Version 1.0.0 +-------------- + * Initial release diff --git a/logback/itests/base.bndrun b/logback/itests/base.bndrun new file mode 100644 index 0000000000..6cb0682e49 --- /dev/null +++ b/logback/itests/base.bndrun @@ -0,0 +1,8 @@ +-resolve.effective: resolve, active + +-runproperties.base: \ + logback.configurationFile=${fileuri;${.}/logback.xml} + +-runstartlevel: \ + order=sortbynameversion,\ + begin=-1 diff --git a/logback/itests/immediate-equinox-logservice/itest.bndrun b/logback/itests/immediate-equinox-logservice/itest.bndrun new file mode 100644 index 0000000000..95424cb332 --- /dev/null +++ b/logback/itests/immediate-equinox-logservice/itest.bndrun @@ -0,0 +1,16 @@ +-include: ../base.bndrun + +-runfw: org.eclipse.osgi + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.equinox.logservice)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.logback;version=latest + +-runbundles: \ + org.apache.felix.logback.itests.immediate.equinox.logservice;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-equinox-logservice/pom.xml b/logback/itests/immediate-equinox-logservice/pom.xml new file mode 100644 index 0000000000..aa05340fe0 --- /dev/null +++ b/logback/itests/immediate-equinox-logservice/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.equinox.logservice + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + + diff --git a/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java new file mode 100644 index 0000000000..e6444af214 --- /dev/null +++ b/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -0,0 +1,77 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.felix.logback.test.helper.ls.LogServiceHelper; +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.log.LogService; +import org.osgi.service.log.Logger; + +public class LogServiceTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogServiceHelper.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + + @Test + public void service() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceRegistration registration = bundleContext.registerService(Integer.class, Integer.valueOf(25), null); + + ServiceReference reference = registration.getReference(); + String refString = reference.toString(); + + try { + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + } + finally { + registration.unregister(); + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + } + } + + @SuppressWarnings("deprecation") + @Test + public void logService() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(LogService.class); + LogService logService = bundleContext.getService(reference); + + logService.log(LogService.LOG_INFO, "Test log"); + + try { + assertLog("INFO|LogService." + getBSN() + "|Test log"); + } + finally { + bundleContext.ungetService(reference); + } + } + +} diff --git a/logback/itests/immediate-felix-logservice/itest.bndrun b/logback/itests/immediate-felix-logservice/itest.bndrun new file mode 100644 index 0000000000..f0754326d1 --- /dev/null +++ b/logback/itests/immediate-felix-logservice/itest.bndrun @@ -0,0 +1,19 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.felix.logservice)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runbundles: \ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.felix.logservice;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-felix-logservice/pom.xml b/logback/itests/immediate-felix-logservice/pom.xml new file mode 100644 index 0000000000..e0efc8c56b --- /dev/null +++ b/logback/itests/immediate-felix-logservice/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent + + + org.apache.felix.logback.itests.immediate.felix.logservice + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/immediate-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java new file mode 100644 index 0000000000..e6444af214 --- /dev/null +++ b/logback/itests/immediate-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -0,0 +1,77 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.felix.logback.test.helper.ls.LogServiceHelper; +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.log.LogService; +import org.osgi.service.log.Logger; + +public class LogServiceTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogServiceHelper.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + + @Test + public void service() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceRegistration registration = bundleContext.registerService(Integer.class, Integer.valueOf(25), null); + + ServiceReference reference = registration.getReference(); + String refString = reference.toString(); + + try { + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + } + finally { + registration.unregister(); + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + } + } + + @SuppressWarnings("deprecation") + @Test + public void logService() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(LogService.class); + LogService logService = bundleContext.getService(reference); + + logService.log(LogService.LOG_INFO, "Test log"); + + try { + assertLog("INFO|LogService." + getBSN() + "|Test log"); + } + finally { + bundleContext.ungetService(reference); + } + } + +} diff --git a/logback/itests/immediate-jbl/itest.bndrun b/logback/itests/immediate-jbl/itest.bndrun new file mode 100644 index 0000000000..5176812cd5 --- /dev/null +++ b/logback/itests/immediate-jbl/itest.bndrun @@ -0,0 +1,28 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.jbl)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runproperties.this: \ + org.jboss.logging.provider=slf4j + +-runbundles: \ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.jbl;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)' \ No newline at end of file diff --git a/logback/itests/immediate-jbl/pom.xml b/logback/itests/immediate-jbl/pom.xml new file mode 100644 index 0000000000..60fbf05322 --- /dev/null +++ b/logback/itests/immediate-jbl/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.jbl + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java b/logback/itests/immediate-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java new file mode 100644 index 0000000000..711bc55698 --- /dev/null +++ b/logback/itests/immediate-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java @@ -0,0 +1,37 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.jboss.logging.Logger; +import org.junit.Test; + +public class JBLTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = Logger.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-jcl/itest.bndrun b/logback/itests/immediate-jcl/itest.bndrun new file mode 100644 index 0000000000..3e7aae2e4d --- /dev/null +++ b/logback/itests/immediate-jcl/itest.bndrun @@ -0,0 +1,27 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=log4j.over.slf4j)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.jcl)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runbundles: \ + log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + org.apache.commons.logging;version='[1.2.0,1.2.1)',\ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.jcl;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-jcl/pom.xml b/logback/itests/immediate-jcl/pom.xml new file mode 100644 index 0000000000..e3cf061638 --- /dev/null +++ b/logback/itests/immediate-jcl/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.jcl + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java b/logback/itests/immediate-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java new file mode 100644 index 0000000000..e3b525fe36 --- /dev/null +++ b/logback/itests/immediate-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; + +public class JCLTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Log logger = LogFactory.getLog(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-jul-felix/itest.bndrun b/logback/itests/immediate-jul-felix/itest.bndrun new file mode 100644 index 0000000000..9641288c53 --- /dev/null +++ b/logback/itests/immediate-jul-felix/itest.bndrun @@ -0,0 +1,19 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.jul.felix)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runbundles: \ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.jul.felix;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-jul-felix/pom.xml b/logback/itests/immediate-jul-felix/pom.xml new file mode 100644 index 0000000000..00dc1518b6 --- /dev/null +++ b/logback/itests/immediate-jul-felix/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.jul.felix + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-jul-felix/src/main/java/org/apache/felix/logback/test/JULTest.java b/logback/itests/immediate-jul-felix/src/main/java/org/apache/felix/logback/test/JULTest.java new file mode 100644 index 0000000000..47791a0d31 --- /dev/null +++ b/logback/itests/immediate-jul-felix/src/main/java/org/apache/felix/logback/test/JULTest.java @@ -0,0 +1,39 @@ +/* + * 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.felix.logback.test; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; + +public class JULTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = Logger.getLogger(getClass().getName()); + if (logger.isLoggable(Level.INFO)) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-jul/itest.bndrun b/logback/itests/immediate-jul/itest.bndrun new file mode 100644 index 0000000000..d56c960790 --- /dev/null +++ b/logback/itests/immediate-jul/itest.bndrun @@ -0,0 +1,21 @@ +-include: ../base.bndrun + +-runfw: org.eclipse.osgi + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.jul)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runbundles: \ + org.apache.felix.logback.itests.immediate.jul;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-jul/pom.xml b/logback/itests/immediate-jul/pom.xml new file mode 100644 index 0000000000..a341a69aca --- /dev/null +++ b/logback/itests/immediate-jul/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.jul + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-jul/src/main/java/org/apache/felix/logback/test/JULTest.java b/logback/itests/immediate-jul/src/main/java/org/apache/felix/logback/test/JULTest.java new file mode 100644 index 0000000000..47791a0d31 --- /dev/null +++ b/logback/itests/immediate-jul/src/main/java/org/apache/felix/logback/test/JULTest.java @@ -0,0 +1,39 @@ +/* + * 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.felix.logback.test; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; + +public class JULTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = Logger.getLogger(getClass().getName()); + if (logger.isLoggable(Level.INFO)) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-log4j1/itest.bndrun b/logback/itests/immediate-log4j1/itest.bndrun new file mode 100644 index 0000000000..90429c8404 --- /dev/null +++ b/logback/itests/immediate-log4j1/itest.bndrun @@ -0,0 +1,25 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.log4j1)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runbundles: \ + log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.log4j1;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-log4j1/pom.xml b/logback/itests/immediate-log4j1/pom.xml new file mode 100644 index 0000000000..bc7926c4fd --- /dev/null +++ b/logback/itests/immediate-log4j1/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.log4j1 + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java b/logback/itests/immediate-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java new file mode 100644 index 0000000000..3ba9d53cac --- /dev/null +++ b/logback/itests/immediate-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.junit.Test; + +public class Log4j1Test extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogManager.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-log4j2/itest.bndrun b/logback/itests/immediate-log4j2/itest.bndrun new file mode 100644 index 0000000000..8ed22d94c2 --- /dev/null +++ b/logback/itests/immediate-log4j2/itest.bndrun @@ -0,0 +1,27 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.log4j2)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + org.apache.logging.log4j.api;version=latest,\ + org.apache.logging.log4j.to-slf4j;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.apache.logging.log4j;version=${version_cleanup;${apache.logging.log4j.version}},\ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runbundles: \ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.log4j2;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-log4j2/pom.xml b/logback/itests/immediate-log4j2/pom.xml new file mode 100644 index 0000000000..f79b92b7c2 --- /dev/null +++ b/logback/itests/immediate-log4j2/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.log4j2 + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java b/logback/itests/immediate-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java new file mode 100644 index 0000000000..4a2ade62f6 --- /dev/null +++ b/logback/itests/immediate-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Test; + +public class Log4j2Test extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogManager.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/immediate-slf4j/itest.bndrun b/logback/itests/immediate-slf4j/itest.bndrun new file mode 100644 index 0000000000..39995e228a --- /dev/null +++ b/logback/itests/immediate-slf4j/itest.bndrun @@ -0,0 +1,24 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log.extension)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.immediate.slf4j)' + +-runpath: \ + ch.qos.logback.classic;version=latest,\ + ch.qos.logback.core;version=latest,\ + slf4j.api;version=latest,\ + org.apache.felix.log;version=latest,\ + org.apache.felix.logback;version=latest + +-runsystempackages: \ + org.slf4j;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.helpers;version=${version_cleanup;${slf4j.api.version}},\ + org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} + +-runbundles: \ + org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.slf4j;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file diff --git a/logback/itests/immediate-slf4j/pom.xml b/logback/itests/immediate-slf4j/pom.xml new file mode 100644 index 0000000000..eb6611b61d --- /dev/null +++ b/logback/itests/immediate-slf4j/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.immediate.slf4j + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/immediate-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java b/logback/itests/immediate-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java new file mode 100644 index 0000000000..0c53343621 --- /dev/null +++ b/logback/itests/immediate-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SLF4JTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LoggerFactory.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/itest-parent/pom.xml b/logback/itests/itest-parent/pom.xml new file mode 100644 index 0000000000..8820043b57 --- /dev/null +++ b/logback/itests/itest-parent/pom.xml @@ -0,0 +1,305 @@ + + + + 4.0.0 + + org.apache.felix + org.apache.felix.logback.reactor + 1.0.0-SNAPSHOT + ../../pom.xml + + + Apache Felix Logback ITests Parent POM + org.apache.felix.logback.itests.parent + pom + + + scm:git:https://github.com/apache/felix-dev.git + scm:git:https://github.com/apache/felix-dev.git + https://gitbox.apache.org/repos/asf?p=felix-dev.git + + + + 6.1.0 + + 8 + 1.8 + 1.8 + UTF-8 + + 2.14.1 + 1.2.5-SNAPSHOT + + 1.7.32 + + 1.2.8 + + + true + true + true + true + true + + + + + org.slf4j + slf4j-api + ${slf4j.api.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.osgi + osgi.annotation + 8.0.1 + provided + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.osgi + org.osgi.service.log + 1.4.0 + + + org.osgi + org.osgi.util.function + 1.2.0 + + + org.osgi + org.osgi.util.promise + 1.2.0 + + + org.osgi + org.osgi.util.pushstream + 1.0.2 + + + org.apache.aries.spifly + org.apache.aries.spifly.dynamic.framework.extension + 1.3.4 + runtime + + + org.apache.felix + org.apache.felix.configadmin + 1.9.22 + runtime + + + org.apache.felix + org.apache.felix.framework + 7.0.1 + runtime + + + org.apache.felix + org.apache.felix.log + ${felix.log.version} + runtime + + + org.apache.felix + org.apache.felix.log.extension + 1.0.0 + runtime + + + org.apache.felix + org.apache.felix.logback + 1.0.3-SNAPSHOT + runtime + + + org.apache.servicemix.bundles + org.apache.servicemix.bundles.junit + 4.13.2_1 + runtime + + + org.eclipse.platform + org.eclipse.osgi + 3.17.0 + runtime + + + org.jboss.logging + jboss-logging + 3.4.2.Final + + + commons-logging + commons-logging + 1.2 + + + org.slf4j + log4j-over-slf4j + ${slf4j.api.version} + + + org.apache.logging.log4j + log4j-api + ${apache.logging.log4j.version} + + + org.apache.logging.log4j + log4j-to-slf4j + 2.14.1 + + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + + + + biz.aQute.bnd + bnd-resolver-maven-plugin + + + biz.aQute.bnd + bnd-testing-maven-plugin + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.0.0 + + + props + + run + + + + + + + + + + + + + + biz.aQute.bnd + bnd-maven-plugin + ${bnd.version} + + + bnd-process + + bnd-process + + + + + + biz.aQute.bnd + bnd-resolver-maven-plugin + ${bnd.version} + + false + false + + itest.bndrun + + + compile + runtime + test + + + + + resolve + + resolve + + package + + + + + biz.aQute.bnd + bnd-testing-maven-plugin + ${bnd.version} + + false + false + + itest.bndrun + + + compile + runtime + test + + + + + testing + + testing + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + diff --git a/logback/itests/logback.xml b/logback/itests/logback.xml new file mode 100644 index 0000000000..3c424d2a2a --- /dev/null +++ b/logback/itests/logback.xml @@ -0,0 +1,40 @@ + + + + + + true + + + + + + [%.15thread] %-5level %logger{1000}:%line - %msg%n + + + + + + + + + + + + + + diff --git a/logback/itests/pom.xml b/logback/itests/pom.xml new file mode 100644 index 0000000000..703d321d65 --- /dev/null +++ b/logback/itests/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + + Apache Felix Logback ITests Reactor + org.apache.felix.logback.itests.reactor + org.apache.felix + 1.0.0-SNAPSHOT + pom + + + scm:git:https://github.com/apache/felix-dev.git + scm:git:https://github.com/apache/felix-dev.git + https://gitbox.apache.org/repos/asf?p=felix-dev.git + + + + test-helper + immediate-equinox-logservice + immediate-felix-logservice + immediate-jbl + immediate-jcl + immediate-jul + immediate-jul-felix + immediate-log4j1 + immediate-log4j2 + immediate-slf4j + standard-equinox-logservice + standard-felix-logservice + standard-jbl + standard-jcl + standard-jul + standard-log4j1 + standard-log4j2 + standard-slf4j + + diff --git a/logback/itests/standard-equinox-logservice/itest.bndrun b/logback/itests/standard-equinox-logservice/itest.bndrun new file mode 100644 index 0000000000..dd8560d43d --- /dev/null +++ b/logback/itests/standard-equinox-logservice/itest.bndrun @@ -0,0 +1,19 @@ +-include: ../base.bndrun + +-runfw: org.eclipse.osgi + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.equinox.logservice)' + +-runproperties: \ + eclipse.log.enabled=false + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.logback;version='[1.0.3,1.0.4)',\ + org.apache.felix.logback.itests.standard.equinox.logservice;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-equinox-logservice/pom.xml b/logback/itests/standard-equinox-logservice/pom.xml new file mode 100644 index 0000000000..2b7c5b09f0 --- /dev/null +++ b/logback/itests/standard-equinox-logservice/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.equinox.logservice + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java new file mode 100644 index 0000000000..e6444af214 --- /dev/null +++ b/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -0,0 +1,77 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.felix.logback.test.helper.ls.LogServiceHelper; +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.log.LogService; +import org.osgi.service.log.Logger; + +public class LogServiceTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogServiceHelper.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + + @Test + public void service() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceRegistration registration = bundleContext.registerService(Integer.class, Integer.valueOf(25), null); + + ServiceReference reference = registration.getReference(); + String refString = reference.toString(); + + try { + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + } + finally { + registration.unregister(); + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + } + } + + @SuppressWarnings("deprecation") + @Test + public void logService() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(LogService.class); + LogService logService = bundleContext.getService(reference); + + logService.log(LogService.LOG_INFO, "Test log"); + + try { + assertLog("INFO|LogService." + getBSN() + "|Test log"); + } + finally { + bundleContext.ungetService(reference); + } + } + +} diff --git a/logback/itests/standard-felix-logservice/itest.bndrun b/logback/itests/standard-felix-logservice/itest.bndrun new file mode 100644 index 0000000000..1376f8f4db --- /dev/null +++ b/logback/itests/standard-felix-logservice/itest.bndrun @@ -0,0 +1,21 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runblacklist: osgi.identity;filter:='(osgi.identity=org.eclipse.osgi)' + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.felix.logservice)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ + org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.logback;version='[1.0.3,1.0.4)',\ + org.apache.felix.logback.itests.standard.felix.logservice;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-felix-logservice/pom.xml b/logback/itests/standard-felix-logservice/pom.xml new file mode 100644 index 0000000000..1f02717187 --- /dev/null +++ b/logback/itests/standard-felix-logservice/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.felix.logservice + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + org.apache.felix + org.apache.felix.log + ${felix.log.version} + runtime + + + diff --git a/logback/itests/standard-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/standard-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java new file mode 100644 index 0000000000..e6444af214 --- /dev/null +++ b/logback/itests/standard-felix-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -0,0 +1,77 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.felix.logback.test.helper.ls.LogServiceHelper; +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.log.LogService; +import org.osgi.service.log.Logger; + +public class LogServiceTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogServiceHelper.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + + @Test + public void service() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceRegistration registration = bundleContext.registerService(Integer.class, Integer.valueOf(25), null); + + ServiceReference reference = registration.getReference(); + String refString = reference.toString(); + + try { + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + } + finally { + registration.unregister(); + assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + } + } + + @SuppressWarnings("deprecation") + @Test + public void logService() { + BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(LogService.class); + LogService logService = bundleContext.getService(reference); + + logService.log(LogService.LOG_INFO, "Test log"); + + try { + assertLog("INFO|LogService." + getBSN() + "|Test log"); + } + finally { + bundleContext.ungetService(reference); + } + } + +} diff --git a/logback/itests/standard-jbl/itest.bndrun b/logback/itests/standard-jbl/itest.bndrun new file mode 100644 index 0000000000..387721aa0a --- /dev/null +++ b/logback/itests/standard-jbl/itest.bndrun @@ -0,0 +1,23 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jbl)' + +-runproperties: \ + org.jboss.logging.provider=slf4j + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ + org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.logback;version='[1.0.3,1.0.4)',\ + org.apache.felix.logback.itests.standard.jbl;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-jbl/pom.xml b/logback/itests/standard-jbl/pom.xml new file mode 100644 index 0000000000..591aa63288 --- /dev/null +++ b/logback/itests/standard-jbl/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.jbl + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java b/logback/itests/standard-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java new file mode 100644 index 0000000000..711bc55698 --- /dev/null +++ b/logback/itests/standard-jbl/src/main/java/org/apache/felix/logback/test/JBLTest.java @@ -0,0 +1,37 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.jboss.logging.Logger; +import org.junit.Test; + +public class JBLTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = Logger.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/standard-jcl/itest.bndrun b/logback/itests/standard-jcl/itest.bndrun new file mode 100644 index 0000000000..f9119cf289 --- /dev/null +++ b/logback/itests/standard-jcl/itest.bndrun @@ -0,0 +1,17 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=log4j.over.slf4j)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jcl)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + org.apache.commons.logging;version='[1.2.0,1.2.1)',\ + org.apache.felix.logback.itests.standard.jcl;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-jcl/pom.xml b/logback/itests/standard-jcl/pom.xml new file mode 100644 index 0000000000..7b7ad3a0a4 --- /dev/null +++ b/logback/itests/standard-jcl/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.jcl + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java b/logback/itests/standard-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java new file mode 100644 index 0000000000..e3b525fe36 --- /dev/null +++ b/logback/itests/standard-jcl/src/main/java/org/apache/felix/logback/test/JCLTest.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; + +public class JCLTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Log logger = LogFactory.getLog(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/standard-jul/itest.bndrun b/logback/itests/standard-jul/itest.bndrun new file mode 100644 index 0000000000..0a179230e0 --- /dev/null +++ b/logback/itests/standard-jul/itest.bndrun @@ -0,0 +1,19 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jul)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ + org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.logback;version='[1.0.3,1.0.4)',\ + org.apache.felix.logback.itests.standard.jul;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-jul/pom.xml b/logback/itests/standard-jul/pom.xml new file mode 100644 index 0000000000..ac8d2672a3 --- /dev/null +++ b/logback/itests/standard-jul/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.jul + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java new file mode 100644 index 0000000000..47791a0d31 --- /dev/null +++ b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java @@ -0,0 +1,39 @@ +/* + * 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.felix.logback.test; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; + +public class JULTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = Logger.getLogger(getClass().getName()); + if (logger.isLoggable(Level.INFO)) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/standard-log4j1/itest.bndrun b/logback/itests/standard-log4j1/itest.bndrun new file mode 100644 index 0000000000..c79f9771b5 --- /dev/null +++ b/logback/itests/standard-log4j1/itest.bndrun @@ -0,0 +1,15 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.log4j1)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + org.apache.felix.logback.itests.standard.log4j1;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-log4j1/pom.xml b/logback/itests/standard-log4j1/pom.xml new file mode 100644 index 0000000000..1f75d63b2f --- /dev/null +++ b/logback/itests/standard-log4j1/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.log4j1 + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java b/logback/itests/standard-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java new file mode 100644 index 0000000000..3ba9d53cac --- /dev/null +++ b/logback/itests/standard-log4j1/src/main/java/org/apache/felix/logback/test/Log4j1Test.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.junit.Test; + +public class Log4j1Test extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LogManager.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/standard-log4j2/itest.bndrun b/logback/itests/standard-log4j2/itest.bndrun new file mode 100644 index 0000000000..82e08b4b0b --- /dev/null +++ b/logback/itests/standard-log4j2/itest.bndrun @@ -0,0 +1,17 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.logging.log4j.to-slf4j)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.log4j2)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.logback.itests.standard.log4j2;version='[1.0.0,1.0.1)',\ + org.apache.logging.log4j.api;version='[2.14.1,2.14.2)',\ + org.apache.logging.log4j.to-slf4j;version='[2.14.1,2.14.2)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-log4j2/pom.xml b/logback/itests/standard-log4j2/pom.xml new file mode 100644 index 0000000000..3759f9023a --- /dev/null +++ b/logback/itests/standard-log4j2/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.log4j2 + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Activator.java b/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Activator.java new file mode 100644 index 0000000000..a24367735e --- /dev/null +++ b/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Activator.java @@ -0,0 +1,50 @@ +/* + * 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.felix.logback.test; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.slf4j.SLF4JProvider; +import org.osgi.annotation.bundle.Header; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; + +@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") +public class Activator implements BundleActivator { + + @Override + public void start(BundleContext bundleContext) throws Exception { + Provider slf4jProvider = new SLF4JProvider(); + Dictionary properties = new Hashtable<>(); + properties.put("APIVersion", slf4jProvider.getVersions()); + serviceRegistration = bundleContext.registerService(Provider.class, slf4jProvider, properties); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + serviceRegistration.unregister(); + } + + private volatile ServiceRegistration serviceRegistration; + +} diff --git a/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java b/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java new file mode 100644 index 0000000000..ced97fc35c --- /dev/null +++ b/logback/itests/standard-log4j2/src/main/java/org/apache/felix/logback/test/Log4j2Test.java @@ -0,0 +1,42 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Test; + +public class Log4j2Test extends LogTestHelper { + + @Test + public void test() { + + // You'll notice that this setup requires that we publish + // an slf4j Provider as a service. See the activator in this bundle. + + long time = System.nanoTime(); + Logger logger = LogManager.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/standard-slf4j/itest.bndrun b/logback/itests/standard-slf4j/itest.bndrun new file mode 100644 index 0000000000..c730edb9f3 --- /dev/null +++ b/logback/itests/standard-slf4j/itest.bndrun @@ -0,0 +1,14 @@ +-include: ../base.bndrun + +-runfw: org.apache.felix.framework + +-runrequires:\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.slf4j)' + +-runbundles: \ + ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ + ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + org.apache.felix.logback.itests.standard.slf4j;version='[1.0.0,1.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ + slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file diff --git a/logback/itests/standard-slf4j/pom.xml b/logback/itests/standard-slf4j/pom.xml new file mode 100644 index 0000000000..0ff9697389 --- /dev/null +++ b/logback/itests/standard-slf4j/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + org.apache.felix.logback.itests.standard.slf4j + + + + org.apache.felix + org.apache.felix.logback.itests.helper + ${project.version} + provided + + + diff --git a/logback/itests/standard-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java b/logback/itests/standard-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java new file mode 100644 index 0000000000..0c53343621 --- /dev/null +++ b/logback/itests/standard-slf4j/src/main/java/org/apache/felix/logback/test/SLF4JTest.java @@ -0,0 +1,38 @@ +/* + * 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.felix.logback.test; + +import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SLF4JTest extends LogTestHelper { + + @Test + public void test() { + long time = System.nanoTime(); + Logger logger = LoggerFactory.getLogger(getClass()); + if (logger.isInfoEnabled()) { + logger.info(time + ""); + } + assertLog("INFO", getClass().getName(), time); + } + +} diff --git a/logback/itests/test-helper/pom.xml b/logback/itests/test-helper/pom.xml new file mode 100644 index 0000000000..4634044c7d --- /dev/null +++ b/logback/itests/test-helper/pom.xml @@ -0,0 +1,64 @@ + + + + org.apache.felix + org.apache.felix.logback.itests.parent + 1.0.0-SNAPSHOT + ../itest-parent/pom.xml + + + 4.0.0 + jar + org.apache.felix.logback.itests.helper + + + true + true + true + true + true + true + true + + + + + biz.aQute.bnd + biz.aQute.junit + ${bnd.version} + + + ch.qos.logback + logback-classic + 1.2.3 + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.osgi + org.osgi.service.log + 1.4.0 + + + diff --git a/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java new file mode 100644 index 0000000000..aa6b9ec2d0 --- /dev/null +++ b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java @@ -0,0 +1,91 @@ +/* + * 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.felix.logback.test.helper; + +import java.util.Iterator; + +import org.apache.felix.logback.test.helper.ls.LogServiceHelper; +import org.junit.BeforeClass; +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.log.LogService; +import org.osgi.service.log.Logger; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.read.ListAppender; + + +public abstract class LogTestHelper { + + protected static ListAppender listAppender; + protected static PatternLayoutEncoder encoder; + + @BeforeClass + public static void before() throws Exception { + LoggerContext context = (LoggerContext)org.slf4j.LoggerFactory.getILoggerFactory(); + + for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) { + for (Iterator> index = logger.iteratorForAppenders(); index.hasNext();) { + Appender appender = index.next(); + + if (appender instanceof ListAppender) { + listAppender = (ListAppender)appender; + } + } + } + + encoder = new PatternLayoutEncoder(); + encoder.setPattern("%level|%logger{1000}|%msg"); + encoder.setContext(context); + encoder.start(); + } + + public String getBSN() { + return FrameworkUtil.getBundle(getClass()).getSymbolicName(); + } + + protected void assertLog(String level, String name, long time) { + assertLog(level + "|" + name + "|" + time); + } + + protected void assertLog(String record) { + try { + // we need to make sure we wait for async logging internals to cool down + Thread.sleep(10); + } + catch (InterruptedException e) { + } + + if ((listAppender.list == null) || listAppender.list.isEmpty() || + !listAppender.list.stream().anyMatch(r -> { + String lr = new String(encoder.encode(r)); + return lr.equals(record); + })) { + + throw new RuntimeException("Log record not found: " + record); + } + } + +} diff --git a/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/ls/LogServiceHelper.java b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/ls/LogServiceHelper.java new file mode 100644 index 0000000000..c64644d82a --- /dev/null +++ b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/ls/LogServiceHelper.java @@ -0,0 +1,35 @@ +/* + * 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.felix.logback.test.helper.ls; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +public class LogServiceHelper { + + public static org.osgi.service.log.Logger getLogger(Class clazz) { + BundleContext bundleContext = FrameworkUtil.getBundle(clazz).getBundleContext(); + ServiceReference serviceReference = + bundleContext.getServiceReference(org.osgi.service.log.LoggerFactory.class); + org.osgi.service.log.LoggerFactory loggerFactory = bundleContext.getService(serviceReference); + return loggerFactory.getLogger(clazz); + } + +} diff --git a/logback/logback/DEPENDENCIES b/logback/logback/DEPENDENCIES new file mode 100644 index 0000000000..ee8cd02b57 --- /dev/null +++ b/logback/logback/DEPENDENCIES @@ -0,0 +1,22 @@ +Apache Felix Logback +Copyright 2018 The Apache Software Foundation + +This software was developed at the Apache Software Foundation +(http://www.apache.org) and may have dependencies on other +Apache software licensed under Apache License 2.0. + +I. Included Third-Party Software + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). +Licensed under the Apache License 2.0. + +II. Used Third-Party Software + +This product used software developed at The Logback +Project (https://logback.qos.ch/). Licensed under the +Eclipse Public License 1.0. + +III. Overall License Summary +- Apache License 2.0 +- Eclipse Public License 1.0 \ No newline at end of file diff --git a/logback/logback/LICENSE b/logback/logback/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/logback/logback/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/logback/logback/NOTICE b/logback/logback/NOTICE new file mode 100644 index 0000000000..32d39ccfc7 --- /dev/null +++ b/logback/logback/NOTICE @@ -0,0 +1,6 @@ +Apache Felix Logback Backend Integration +Copyright 2018 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). +Licensed under the Apache License 2.0. diff --git a/logback/logback/README.md b/logback/logback/README.md new file mode 100644 index 0000000000..dbe890687d --- /dev/null +++ b/logback/logback/README.md @@ -0,0 +1,148 @@ +# Apache Felix Logback + +**Apache Felix Logback** is a small integration of the [Logback](https://logback.qos.ch/) backend with OSGi. + +With **OSGi R7** the **Log Service Specification 1.4** (Log 1.4) brings a slew of new features designed to improve the developer experience with logging, the details of which can be read about [here](https://osgi.org/specification/osgi.cmpn/7.0.0/service.log.html). + +This project is intended to help bridge the last frontier of OSGi logging by leveraging many capabilities of [Logback](https://logback.qos.ch/), the new **Log 1.4** features to provide a **unified backend** where: + +1. all logging *can be* configured in one place +2. all log records are appended together (or at least all appenders are setup in one place) +3. support as many logging APIs as possible + +## Depedencies + +To add Logback to an OSGi framework include the following dependencies + + :::xml + + ch.qos.logback + logback-classic + 1.2.x + + + ch.qos.logback + logback-core + 1.2.x + + + org.slf4j + slf4j-api + 1.7.x + + +These provide the `slf4j` logging API over the Logback backend. + +## Basic Configuration + +Configuring logback is most easily handled by setting the system property `logback.configurationFile` to point to a file on the file system. + +This is an example using a *bndrun* file: + + :::properties + -runproperties: logback.configurationFile=file:${.}/logback.xml + + +where in bnd `${.}` gives the path to the directory of the bndrun file. + +Logback offers many features from it's configuration file, so make sure to look through the [documentation](https://logback.qos.ch/documentation.html). + +## Deployment Options + +There are at least two possible deployment scenarios. + +### The Standard Approach + +The **standard approach** is to install all bundles into the OSGi runtime. + +**pros**: + +- this is the most understood and expected deployment scenario +- bundles are managed through any OSGi management agent + +**cons**: + +- due to the fact that logging APIs are most commonly static there is an inevitable race condition which is exacerbated by aspects such as: + - the order in which the bundles are started + - which bundle makes the first call the static API that most-likely has the side effect of binding an implementation + - buffering is required to collect the complete set of logs, but configuring the buffer to hold that many logs can cause memory issues, or if too small to lose records. Also, some logging frameworks simply don't provide such buffering, and some actually simply fail in this scenario. + In the case of the OSGi Log Service this is handled by implementations that support Log 1.4, or perhaps earlier through proprietary means. + +### The Immediate Approach + +The **immediate approach** overcomes the **cons** of *the standard approach* and initializes the log engine as early as possible in order to avoid buffering, binding issues, or losing records. + +**pros**: + +- there's very little chance to miss any log records +- there are no temporal issues for binding or bundle start order since the log engine is setup before most other application activities take place + +**cons**: + +- bundles on the framework class path are not installed into the framework so they cannot be managed through regular means. *On the other hand, logging is one of those infrastructural aspects that transcends the application and for which there are very few scenarios under which one would want to manage logging API or implementation bundles this way. Configuration? Sure!* + +## Unified Backend + +Of course adding Logback does not magically result in all logs being funneled into the same appenders, particularly the OSGi logs. However, it is quite common to assemble an OSGi application from a variety of bundles which use a variety of logging APIs. Many of these can already be mapped onto Logback. + +Many examples of setting up the various logging APIs can be found in the integration tests of the project. + +The following APIs are tested: + +- JBoss Logging 3.3.x +- Commons Logging 1.2 +- JUL (Java Util Logging) +- Log4j 1 +- Log4j 2 +- Slf4j + +## Mapping of OSGi Events + + +The OSGi Log specification 1.4 describes events resulting in log records. **Log 1.4** defines logger names mapping to these events. + +Event | Logger Name | Events types +------------------------- | ------------------ | ------------------------------------------------------------ +Bundle event | `Events.Bundle` | `INSTALLED` - BundleEvent INSTALLED
`STARTED` - BundleEvent STARTED
`STOPPED` - BundleEvent STOPPED
`UPDATED` - BundleEvent UPDATED
`UNINSTALLED` - BundleEvent UNINSTALLED
`RESOLVED` - BundleEvent RESOLVED
`UNRESOLVED` - BundleEvent UNRESOLVED +Service event | `Events.Service` | `REGISTERED` - ServiceEvent REGISTERED
`MODIFIED` - ServiceEvent MODIFIED
`UNREGISTERING` - ServiceEvent UNREGISTERING +Framework event | `Events.Framework` | `STARTED` - FrameworkEvent STARTED
`ERROR` - FrameworkEvent ERROR
`PACKAGES_REFRESHED` - FrameworkEvent PACKAGES REFRESHED
`STARTLEVEL_CHANGED` - FrameworkEvent STARTLEVEL CHANGED
`WARNING` - FrameworkEvent WARNING
`INFO` - FrameworkEvent INFO +Legacy Log Service events | `LogService` | any log events originating from bundles that used the original `LogService` logging API + +**Note:** In order to improve the granularity of the logging associated with these events, **Apache Felix Logback** allows appending a period (`.`) followed by any number of segments (separated by periods) of the `Bundle-SymbolicName` for the logger names used in configuring the levels. This greatly improves the configurability. + +Consider the following `logback.xml` example: + + :::xml + + + + + + %d{HH:mm:ss.SSS} [%.15thread] %-5level %logger{36}:%line - %msg%n + + + + + + + + + + + + + + + + + + + + + + + +## Notes + +- **Apache Felix Logback** supports Logback's [automatic reloading](https://logback.qos.ch/manual/configuration.html#autoScan) upon file modification +- When using **equinox** framework you may want to disable it's internal appenders using the system property `eclipse.log.enabled=false` diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml new file mode 100644 index 0000000000..283c62f192 --- /dev/null +++ b/logback/logback/pom.xml @@ -0,0 +1,150 @@ + + + + org.apache.felix + felix-parent + 6 + ../../pom/pom.xml + + + 4.0.0 + Apache Felix Logback Backend Integration + + A simple integration of the OSGi R7 Log (1.4) service to Logback backend. + + 1.0.7-SNAPSHOT + org.apache.felix.logback + + + scm:git:https://github.com/apache/felix-dev.git + scm:git:https://github.com/apache/felix-dev.git + https://gitbox.apache.org/repos/asf?p=felix-dev.git + HEAD + + + + 6.1.0 + + true + 8 + 1.8 + 1.8 + UTF-8 + + 1.7.32 + + 1.2.8 + + + + + + org.osgi + org.osgi.service.log + 1.4.0 + provided + + + org.slf4j + slf4j-api + ${slf4j.api.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.eclipse.platform + org.eclipse.osgi + 3.17.0 + provided + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.osgi + osgi.annotation + 8.0.1 + provided + + + org.slf4j + jul-to-slf4j + ${slf4j.api.version} + provided + + + + + + biz.aQute.bnd + bnd-maven-plugin + ${bnd.version} + + + + + + bnd-process + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.rat + apache-rat-plugin + + false + true + true + + doc/* + maven-eclipse.xml + .checkstyle + .externalToolBuilders/* + + + + + + diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java new file mode 100644 index 0000000000..d697ed331a --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -0,0 +1,139 @@ +/** + * Licensed 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.felix.logback.internal; + +import java.util.AbstractMap.SimpleEntry; +import java.util.function.Supplier; + +import org.osgi.annotation.bundle.Header; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogReaderService; +import org.osgi.service.log.admin.LoggerAdmin; +import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.bridge.SLF4JBridgeHandler; + +@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") +public class Activator implements BundleActivator { + + static { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } + + private volatile ServiceTracker lat; + + @Override + public void start(final BundleContext bundleContext) throws Exception { + lat = new ServiceTracker( + bundleContext, LoggerAdmin.class, null) { + + @Override + public LRST addingService( + ServiceReference reference) { + + return tccl(() -> { + LoggerAdmin loggerAdmin = bundleContext.getService(reference); + + LRST lrst = new LRST(bundleContext, loggerAdmin); + + lrst.open(); + + return lrst; + }); + } + + @Override + public void removedService( + ServiceReference reference, LRST lrst) { + + tccl(() -> { + lrst.close(); + return null; + }); + } + }; + + lat.open(); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + lat.close(); + } + + private static R tccl(Supplier action) { + Thread currentThread = Thread.currentThread(); + ClassLoader original = currentThread.getContextClassLoader(); + try { + currentThread.setContextClassLoader(Activator.class.getClassLoader()); + return action.get(); + } + finally { + currentThread.setContextClassLoader(original); + } + } + + class LRST extends ServiceTracker { + + public LRST(BundleContext context, LoggerAdmin loggerAdmin) { + super(context, LogReaderService.class, null); + + this.loggerAdmin = loggerAdmin; + } + + @Override + public Pair addingService( + ServiceReference reference) { + + return tccl(() -> { + LogReaderService logReaderService = context.getService(reference); + + LogbackLogListener logbackLogListener = new LogbackLogListener(loggerAdmin); + + logReaderService.addLogListener(logbackLogListener); + + return new Pair(logReaderService, logbackLogListener); + }); + } + + @Override + public void removedService( + ServiceReference reference, + Pair pair) { + + tccl(() -> { + pair.getKey().removeLogListener(pair.getValue()); + return null; + }); + } + + private final LoggerAdmin loggerAdmin; + + } + + class Pair extends SimpleEntry { + + private static final long serialVersionUID = 1L; + + public Pair(LogReaderService logReaderService, LogbackLogListener logbackLogListener) { + super(logReaderService, logbackLogListener); + } + + } + +} diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java new file mode 100644 index 0000000000..1b05bd8338 --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java @@ -0,0 +1,34 @@ +/** + * Licensed 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.felix.logback.internal; + +import org.eclipse.osgi.internal.hookregistry.ActivatorHookFactory; +import org.eclipse.osgi.internal.hookregistry.HookConfigurator; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; +import org.osgi.framework.BundleActivator; + +public class EquinoxHookSupport implements ActivatorHookFactory, HookConfigurator { + + @Override + public BundleActivator createActivator() { + return new Activator(); + } + + @Override + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addActivatorHookFactory(this); + } + +} diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java new file mode 100644 index 0000000000..6e55de1e13 --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -0,0 +1,311 @@ +/** + * Licensed 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.felix.logback.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogLevel; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.admin.LoggerAdmin; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.CallerData; +import ch.qos.logback.classic.spi.LoggerContextListener; +import ch.qos.logback.classic.spi.LoggerContextVO; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.classic.spi.ThrowableProxy; + +public class LogbackLogListener implements LogListener, LoggerContextListener { + + private static final String EVENTS_BUNDLE = "Events.Bundle"; + private static final String EVENTS_FRAMEWORK = "Events.Framework"; + private static final String EVENTS_SERVICE = "Events.Service"; + private static final String LOG_SERVICE = "LogService"; + + private static final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); + private static Optional setSequenceNumber = Optional.empty(); + + static { + MethodType mt = MethodType.methodType(void.class, long.class); + try { + MethodHandle methodHandle = publicLookup.findVirtual(LoggingEvent.class, "setSquenceNumber", mt); + + setSequenceNumber = Optional.of(methodHandle); + } catch (NoSuchMethodException | IllegalAccessException e) { + } + } + + volatile LoggerContext loggerContext; + volatile Logger rootLogger; + volatile LoggerContextVO loggerContextVO; + final Map initialLogLevels; + final org.osgi.service.log.admin.LoggerContext osgiLoggerContext; + + public LogbackLogListener(LoggerAdmin loggerAdmin) { + osgiLoggerContext = loggerAdmin.getLoggerContext(null); + initialLogLevels = osgiLoggerContext.getLogLevels(); + + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + + if (!(loggerFactory instanceof LoggerContext)) { + throw new IllegalStateException("This bundle only works with logback-classic"); + } + + onStart((LoggerContext)loggerFactory); + + loggerContext.addListener(this); + } + + @Override + public boolean isResetResistant() { + return true; + } + + @Override + public void logged(final LogEntry entry) { + String loggerName = entry.getLoggerName(); + String message = entry.getMessage(); + Object[] arguments = null; + Level level = from(entry.getLogLevel()); + final AtomicBoolean avoidCallerData = new AtomicBoolean(); + + if (EVENTS_BUNDLE.equals(loggerName) || + EVENTS_FRAMEWORK.equals(loggerName) || + LOG_SERVICE.equals(loggerName)) { + + loggerName = formatBundle(entry.getBundle(), loggerName); + avoidCallerData.set(true); + } + else if (loggerName.startsWith(EVENTS_BUNDLE) || + loggerName.startsWith(EVENTS_FRAMEWORK) || + loggerName.startsWith(LOG_SERVICE)) { + + avoidCallerData.set(true); + } + else if (EVENTS_SERVICE.equals(loggerName)) { + loggerName = formatBundle(entry.getBundle(), loggerName); + message = message + " {}"; + arguments = new Object[] {entry.getServiceReference()}; + avoidCallerData.set(true); + } + else if (loggerName.startsWith(EVENTS_SERVICE)) { + message = message + " {}"; + arguments = new Object[] {entry.getServiceReference()}; + avoidCallerData.set(true); + } + + Logger logger = loggerContext.getLogger(loggerName); + + // Check to see if there's a logger defined in our configuration and + // if there is, then make sure it's handled as an override for the + // effective level. + if (!logger.equals(rootLogger) && !logger.isEnabledFor(level)) { + return; + } + + LoggingEvent le = new LoggingEvent() { + + @Override + public StackTraceElement[] getCallerData() { + if (avoidCallerData.get() || callerData != null) + return callerData; + return callerData = getCallerData0(entry.getLocation()); + } + + private volatile StackTraceElement[] callerData; + + }; + + le.setArgumentArray(arguments); + le.setMessage(message); + le.setLevel(level); + le.setLoggerContextRemoteView(loggerContextVO); + le.setLoggerName(loggerName); + le.setThreadName(entry.getThreadInfo()); + le.setThrowableProxy(getThrowableProxy(entry.getException())); + le.setTimeStamp(entry.getTime()); + + setSequenceNumber.ifPresent(mh -> { + try { + mh.bindTo(le).invokeExact(entry.getSequence()); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + + rootLogger.callAppenders(le); + } + + @Override + public void onLevelChange(Logger logger, Level level) { + Map updatedLevels = osgiLoggerContext.getLogLevels(); + + if (Level.OFF.equals(level)) { + updatedLevels.remove(logger.getName()); + } + else { + updatedLevels.put(logger.getName(), from(level)); + } + + osgiLoggerContext.setLogLevels(updatedLevels); + } + + @Override + public void onStart(LoggerContext context) { + loggerContext = context; + rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + loggerContextVO = loggerContext.getLoggerContextRemoteView(); + + Map updatedLevels = updateLevels(loggerContext, initialLogLevels); + + osgiLoggerContext.setLogLevels(updatedLevels); + } + + @Override + public void onStop(LoggerContext context) { + osgiLoggerContext.setLogLevels(initialLogLevels); + } + + @Override + public void onReset(LoggerContext context) { + onStart(context); + } + + String formatBundle(Bundle bundle, String loggerName) { + return new StringBuilder().append( + loggerName + ).append( + "." + ).append( + bundle.getSymbolicName() + ).toString(); + } + + LogLevel from(Level level) { + if (Level.ALL.equals(level)) { + return LogLevel.TRACE; + } + else if (Level.DEBUG.equals(level)) { + return LogLevel.DEBUG; + } + else if (Level.ERROR.equals(level)) { + return LogLevel.ERROR; + } + else if (Level.INFO.equals(level)) { + return LogLevel.INFO; + } + else if (Level.TRACE.equals(level)) { + return LogLevel.TRACE; + } + else if (Level.WARN.equals(level)) { + return LogLevel.WARN; + } + + return LogLevel.WARN; + } + + Level from(LogLevel logLevel) { + switch (logLevel) { + case AUDIT: + return Level.TRACE; + case DEBUG: + return Level.DEBUG; + case ERROR: + return Level.ERROR; + case INFO: + return Level.INFO; + case TRACE: + return Level.TRACE; + case WARN: + default: + return Level.WARN; + } + } + + StackTraceElement[] getCallerData0(StackTraceElement stackTraceElement) { + StackTraceElement[] callerData = CallerData.extract( + new Throwable(), + org.osgi.service.log.Logger.class.getName(), + loggerContext.getMaxCallerDataDepth(), + loggerContext.getFrameworkPackages()); + + if (stackTraceElement != null) { + if (callerData.length == 0) { + callerData = new StackTraceElement[] {stackTraceElement}; + } + else { + StackTraceElement[] copy = new StackTraceElement[callerData.length + 1]; + copy[0] = stackTraceElement; + System.arraycopy(callerData, 0, copy, 1, callerData.length); + callerData = copy; + } + } + + return callerData; + } + + ThrowableProxy getThrowableProxy(Throwable t) { + if (t == null) + return null; + + ThrowableProxy throwableProxy = new ThrowableProxy(t); + + if (loggerContext.isPackagingDataEnabled()) { + throwableProxy.calculatePackagingData(); + } + + return throwableProxy; + } + + Map updateLevels(LoggerContext loggerContext, Map levels) { + Map copy = new HashMap(levels); + + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + LogLevel rootLevel = from(root.getLevel()); + copy.put(org.osgi.service.log.Logger.ROOT_LOGGER_NAME, rootLevel); + copy.put(EVENTS_BUNDLE, LogLevel.TRACE); + copy.put(EVENTS_FRAMEWORK, LogLevel.TRACE); + copy.put(EVENTS_SERVICE, LogLevel.TRACE); + copy.put(LOG_SERVICE, LogLevel.TRACE); + + for (Logger logger : loggerContext.getLoggerList()) { + String name = logger.getName(); + Level level = logger.getLevel(); + + if (level != null) { + copy.remove(name); + + if (level != Level.OFF) { + copy.put(name, from(level)); + } + } + } + + return copy; + } + +} diff --git a/logback/logback/src/main/resources/hookconfigurators.properties b/logback/logback/src/main/resources/hookconfigurators.properties new file mode 100644 index 0000000000..eaf5bb131d --- /dev/null +++ b/logback/logback/src/main/resources/hookconfigurators.properties @@ -0,0 +1,15 @@ +# Copyright 2018 Raymond Auge +# +# Licensed 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. + +hook.configurators=org.apache.felix.logback.internal.EquinoxHookSupport \ No newline at end of file diff --git a/logback/pom.xml b/logback/pom.xml new file mode 100644 index 0000000000..51dd877b80 --- /dev/null +++ b/logback/pom.xml @@ -0,0 +1,44 @@ + + + + 4.0.0 + + org.apache.felix + felix-parent + 5 + ../pom/pom.xml + + + Apache Felix Logback Reactor + org.apache.felix.logback.reactor + 1.0.0-SNAPSHOT + pom + + + scm:git:https://github.com/apache/felix-dev.git + scm:git:https://github.com/apache/felix-dev.git + https://gitbox.apache.org/repos/asf?p=felix-dev.git + + + + logback + itests + + From f25d1ed4085cfe616204c32f326839460ae9c261 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 19:07:52 +0200 Subject: [PATCH 02/24] Update to Felix parent 9 --- logback/logback/pom.xml | 2 +- logback/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 283c62f192..8cb673c220 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -20,7 +20,7 @@ org.apache.felix felix-parent - 6 + 9 ../../pom/pom.xml diff --git a/logback/pom.xml b/logback/pom.xml index 51dd877b80..371c5a865e 100644 --- a/logback/pom.xml +++ b/logback/pom.xml @@ -22,7 +22,7 @@ org.apache.felix felix-parent - 5 + 9 ../pom/pom.xml From eb261ed22d8a5567f21615cba918afde8020f487 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 19:46:34 +0200 Subject: [PATCH 03/24] Modernize Felix Logback --- .../immediate-equinox-logservice/itest.bndrun | 4 +-- .../immediate-equinox-logservice/pom.xml | 2 +- .../immediate-felix-logservice/itest.bndrun | 4 +-- .../itests/immediate-felix-logservice/pom.xml | 2 +- logback/itests/immediate-jbl/itest.bndrun | 4 +-- logback/itests/immediate-jbl/pom.xml | 2 +- logback/itests/immediate-jcl/itest.bndrun | 6 ++--- logback/itests/immediate-jcl/pom.xml | 2 +- .../itests/immediate-jul-felix/itest.bndrun | 4 +-- logback/itests/immediate-jul-felix/pom.xml | 2 +- logback/itests/immediate-jul/itest.bndrun | 4 +-- logback/itests/immediate-jul/pom.xml | 2 +- logback/itests/immediate-log4j1/itest.bndrun | 6 ++--- logback/itests/immediate-log4j1/pom.xml | 2 +- logback/itests/immediate-log4j2/itest.bndrun | 6 ++--- logback/itests/immediate-log4j2/pom.xml | 2 +- logback/itests/immediate-slf4j/itest.bndrun | 4 +-- logback/itests/immediate-slf4j/pom.xml | 2 +- logback/itests/itest-parent/pom.xml | 26 +++++++++++-------- logback/itests/pom.xml | 2 +- .../standard-equinox-logservice/itest.bndrun | 13 ++++++---- .../standard-equinox-logservice/pom.xml | 2 +- .../standard-felix-logservice/itest.bndrun | 13 ++++++---- .../itests/standard-felix-logservice/pom.xml | 2 +- logback/itests/standard-jbl/itest.bndrun | 13 ++++++---- logback/itests/standard-jbl/pom.xml | 2 +- logback/itests/standard-jcl/itest.bndrun | 13 ++++++---- logback/itests/standard-jcl/pom.xml | 2 +- logback/itests/standard-jul/itest.bndrun | 13 ++++++---- logback/itests/standard-jul/pom.xml | 2 +- logback/itests/standard-log4j1/itest.bndrun | 13 ++++++---- logback/itests/standard-log4j1/pom.xml | 2 +- logback/itests/standard-log4j2/itest.bndrun | 17 +++++++----- logback/itests/standard-log4j2/pom.xml | 2 +- logback/itests/standard-slf4j/itest.bndrun | 11 +++++--- logback/itests/standard-slf4j/pom.xml | 2 +- logback/itests/test-helper/pom.xml | 4 +-- logback/logback/README.md | 6 ++--- logback/logback/pom.xml | 18 ++++++------- .../felix/logback/internal/Activator.java | 16 ++++++------ logback/pom.xml | 2 +- 41 files changed, 141 insertions(+), 115 deletions(-) diff --git a/logback/itests/immediate-equinox-logservice/itest.bndrun b/logback/itests/immediate-equinox-logservice/itest.bndrun index 95424cb332..fd4ab0a40f 100644 --- a/logback/itests/immediate-equinox-logservice/itest.bndrun +++ b/logback/itests/immediate-equinox-logservice/itest.bndrun @@ -12,5 +12,5 @@ org.apache.felix.logback;version=latest -runbundles: \ - org.apache.felix.logback.itests.immediate.equinox.logservice;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.equinox.logservice;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-equinox-logservice/pom.xml b/logback/itests/immediate-equinox-logservice/pom.xml index aa05340fe0..eb34076bac 100644 --- a/logback/itests/immediate-equinox-logservice/pom.xml +++ b/logback/itests/immediate-equinox-logservice/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-felix-logservice/itest.bndrun b/logback/itests/immediate-felix-logservice/itest.bndrun index f0754326d1..5ba81c03b0 100644 --- a/logback/itests/immediate-felix-logservice/itest.bndrun +++ b/logback/itests/immediate-felix-logservice/itest.bndrun @@ -15,5 +15,5 @@ -runbundles: \ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.felix.logservice;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.felix.logservice;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-felix-logservice/pom.xml b/logback/itests/immediate-felix-logservice/pom.xml index e0efc8c56b..440eea7ac5 100644 --- a/logback/itests/immediate-felix-logservice/pom.xml +++ b/logback/itests/immediate-felix-logservice/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent diff --git a/logback/itests/immediate-jbl/itest.bndrun b/logback/itests/immediate-jbl/itest.bndrun index 5176812cd5..2f68e5757c 100644 --- a/logback/itests/immediate-jbl/itest.bndrun +++ b/logback/itests/immediate-jbl/itest.bndrun @@ -23,6 +23,6 @@ -runbundles: \ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.jbl;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.immediate.jbl;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)' \ No newline at end of file + org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)' diff --git a/logback/itests/immediate-jbl/pom.xml b/logback/itests/immediate-jbl/pom.xml index 60fbf05322..42641e1d94 100644 --- a/logback/itests/immediate-jbl/pom.xml +++ b/logback/itests/immediate-jbl/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-jcl/itest.bndrun b/logback/itests/immediate-jcl/itest.bndrun index 3e7aae2e4d..6fcf4845eb 100644 --- a/logback/itests/immediate-jcl/itest.bndrun +++ b/logback/itests/immediate-jcl/itest.bndrun @@ -20,8 +20,8 @@ org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} -runbundles: \ - log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + log4j.over.slf4j;version='[2.0.18,2.0.19)',\ org.apache.commons.logging;version='[1.2.0,1.2.1)',\ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.jcl;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.jcl;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-jcl/pom.xml b/logback/itests/immediate-jcl/pom.xml index e3cf061638..7ffc850ebf 100644 --- a/logback/itests/immediate-jcl/pom.xml +++ b/logback/itests/immediate-jcl/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-jul-felix/itest.bndrun b/logback/itests/immediate-jul-felix/itest.bndrun index 9641288c53..e26b602f8b 100644 --- a/logback/itests/immediate-jul-felix/itest.bndrun +++ b/logback/itests/immediate-jul-felix/itest.bndrun @@ -15,5 +15,5 @@ -runbundles: \ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.jul.felix;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.jul.felix;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-jul-felix/pom.xml b/logback/itests/immediate-jul-felix/pom.xml index 00dc1518b6..cc529ece81 100644 --- a/logback/itests/immediate-jul-felix/pom.xml +++ b/logback/itests/immediate-jul-felix/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-jul/itest.bndrun b/logback/itests/immediate-jul/itest.bndrun index d56c960790..72a2a80f47 100644 --- a/logback/itests/immediate-jul/itest.bndrun +++ b/logback/itests/immediate-jul/itest.bndrun @@ -17,5 +17,5 @@ org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} -runbundles: \ - org.apache.felix.logback.itests.immediate.jul;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.jul;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-jul/pom.xml b/logback/itests/immediate-jul/pom.xml index a341a69aca..5a48fc5833 100644 --- a/logback/itests/immediate-jul/pom.xml +++ b/logback/itests/immediate-jul/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-log4j1/itest.bndrun b/logback/itests/immediate-log4j1/itest.bndrun index 90429c8404..867c153c28 100644 --- a/logback/itests/immediate-log4j1/itest.bndrun +++ b/logback/itests/immediate-log4j1/itest.bndrun @@ -19,7 +19,7 @@ org.slf4j.spi;version=${version_cleanup;${slf4j.api.version}} -runbundles: \ - log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + log4j.over.slf4j;version='[2.0.18,2.0.19)',\ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.log4j1;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.log4j1;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-log4j1/pom.xml b/logback/itests/immediate-log4j1/pom.xml index bc7926c4fd..67b9e72b6e 100644 --- a/logback/itests/immediate-log4j1/pom.xml +++ b/logback/itests/immediate-log4j1/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-log4j2/itest.bndrun b/logback/itests/immediate-log4j2/itest.bndrun index 8ed22d94c2..70e116be72 100644 --- a/logback/itests/immediate-log4j2/itest.bndrun +++ b/logback/itests/immediate-log4j2/itest.bndrun @@ -10,7 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ org.apache.logging.log4j.api;version=latest,\ - org.apache.logging.log4j.to-slf4j;version=latest,\ + org.apache.logging.log4j.to.slf4j;version=latest,\ slf4j.api;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest @@ -23,5 +23,5 @@ -runbundles: \ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.log4j2;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.log4j2;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-log4j2/pom.xml b/logback/itests/immediate-log4j2/pom.xml index f79b92b7c2..9c468d4640 100644 --- a/logback/itests/immediate-log4j2/pom.xml +++ b/logback/itests/immediate-log4j2/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/immediate-slf4j/itest.bndrun b/logback/itests/immediate-slf4j/itest.bndrun index 39995e228a..36fd407819 100644 --- a/logback/itests/immediate-slf4j/itest.bndrun +++ b/logback/itests/immediate-slf4j/itest.bndrun @@ -20,5 +20,5 @@ -runbundles: \ org.apache.felix.log.extension;version='[1.0.0,1.0.1)',\ - org.apache.felix.logback.itests.immediate.slf4j;version='[1.0.0,1.0.1)',\ - org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' \ No newline at end of file + org.apache.felix.logback.itests.immediate.slf4j;version='[2.0.0,2.0.1)',\ + org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)' diff --git a/logback/itests/immediate-slf4j/pom.xml b/logback/itests/immediate-slf4j/pom.xml index eb6611b61d..f7fee89a09 100644 --- a/logback/itests/immediate-slf4j/pom.xml +++ b/logback/itests/immediate-slf4j/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/itest-parent/pom.xml b/logback/itests/itest-parent/pom.xml index 8820043b57..f6e44bb34a 100644 --- a/logback/itests/itest-parent/pom.xml +++ b/logback/itests/itest-parent/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.reactor - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../../pom.xml @@ -39,18 +39,16 @@ 6.1.0 - 8 - 1.8 - 1.8 + 11 + 11 + 11 UTF-8 - 2.14.1 + 2.26.1 1.2.5-SNAPSHOT - 1.7.32 - - 1.2.8 - + 2.0.18 + 1.5.38 true true @@ -70,6 +68,12 @@ logback-classic ${logback.version} + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + runtime + org.osgi osgi.annotation @@ -135,7 +139,7 @@ org.apache.felix org.apache.felix.logback - 1.0.3-SNAPSHOT + 2.0.0-SNAPSHOT runtime @@ -173,7 +177,7 @@ org.apache.logging.log4j log4j-to-slf4j - 2.14.1 + ${apache.logging.log4j.version} diff --git a/logback/itests/pom.xml b/logback/itests/pom.xml index 703d321d65..2f8ed6e4a3 100644 --- a/logback/itests/pom.xml +++ b/logback/itests/pom.xml @@ -23,7 +23,7 @@ Apache Felix Logback ITests Reactor org.apache.felix.logback.itests.reactor org.apache.felix - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT pom diff --git a/logback/itests/standard-equinox-logservice/itest.bndrun b/logback/itests/standard-equinox-logservice/itest.bndrun index dd8560d43d..6b2344a9d7 100644 --- a/logback/itests/standard-equinox-logservice/itest.bndrun +++ b/logback/itests/standard-equinox-logservice/itest.bndrun @@ -4,6 +4,7 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.equinox.logservice)' @@ -11,9 +12,11 @@ eclipse.log.enabled=false -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ - org.apache.felix.logback;version='[1.0.3,1.0.4)',\ - org.apache.felix.logback.itests.standard.equinox.logservice;version='[1.0.0,1.0.1)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ + org.apache.felix.logback;version='[2.0.0,2.0.1)',\ + org.apache.felix.logback.itests.standard.equinox.logservice;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-equinox-logservice/pom.xml b/logback/itests/standard-equinox-logservice/pom.xml index 2b7c5b09f0..e04e61eb1b 100644 --- a/logback/itests/standard-equinox-logservice/pom.xml +++ b/logback/itests/standard-equinox-logservice/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-felix-logservice/itest.bndrun b/logback/itests/standard-felix-logservice/itest.bndrun index 1376f8f4db..9e01874dab 100644 --- a/logback/itests/standard-felix-logservice/itest.bndrun +++ b/logback/itests/standard-felix-logservice/itest.bndrun @@ -6,16 +6,19 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.felix.logservice)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ - org.apache.felix.logback;version='[1.0.3,1.0.4)',\ - org.apache.felix.logback.itests.standard.felix.logservice;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback;version='[2.0.0,2.0.1)',\ + org.apache.felix.logback.itests.standard.felix.logservice;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-felix-logservice/pom.xml b/logback/itests/standard-felix-logservice/pom.xml index 1f02717187..e7ff20d8a1 100644 --- a/logback/itests/standard-felix-logservice/pom.xml +++ b/logback/itests/standard-felix-logservice/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-jbl/itest.bndrun b/logback/itests/standard-jbl/itest.bndrun index 387721aa0a..f604ca1b24 100644 --- a/logback/itests/standard-jbl/itest.bndrun +++ b/logback/itests/standard-jbl/itest.bndrun @@ -4,6 +4,7 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jbl)' @@ -12,12 +13,14 @@ org.jboss.logging.provider=slf4j -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ - org.apache.felix.logback;version='[1.0.3,1.0.4)',\ - org.apache.felix.logback.itests.standard.jbl;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback;version='[2.0.0,2.0.1)',\ + org.apache.felix.logback.itests.standard.jbl;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-jbl/pom.xml b/logback/itests/standard-jbl/pom.xml index 591aa63288..9981278844 100644 --- a/logback/itests/standard-jbl/pom.xml +++ b/logback/itests/standard-jbl/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-jcl/itest.bndrun b/logback/itests/standard-jcl/itest.bndrun index f9119cf289..537a3bedf5 100644 --- a/logback/itests/standard-jcl/itest.bndrun +++ b/logback/itests/standard-jcl/itest.bndrun @@ -4,14 +4,17 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=log4j.over.slf4j)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jcl)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ - log4j.over.slf4j;version='[1.7.32,1.7.33)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + log4j.over.slf4j;version='[2.0.18,2.0.19)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.commons.logging;version='[1.2.0,1.2.1)',\ - org.apache.felix.logback.itests.standard.jcl;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback.itests.standard.jcl;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-jcl/pom.xml b/logback/itests/standard-jcl/pom.xml index 7b7ad3a0a4..9eeea60aa3 100644 --- a/logback/itests/standard-jcl/pom.xml +++ b/logback/itests/standard-jcl/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-jul/itest.bndrun b/logback/itests/standard-jul/itest.bndrun index 0a179230e0..b44e88f98c 100644 --- a/logback/itests/standard-jul/itest.bndrun +++ b/logback/itests/standard-jul/itest.bndrun @@ -4,16 +4,19 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.log)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.jul)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ - org.apache.felix.logback;version='[1.0.3,1.0.4)',\ - org.apache.felix.logback.itests.standard.jul;version='[1.0.0,1.0.1)',\ + org.apache.felix.logback;version='[2.0.0,2.0.1)',\ + org.apache.felix.logback.itests.standard.jul;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-jul/pom.xml b/logback/itests/standard-jul/pom.xml index ac8d2672a3..b84de630f4 100644 --- a/logback/itests/standard-jul/pom.xml +++ b/logback/itests/standard-jul/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-log4j1/itest.bndrun b/logback/itests/standard-log4j1/itest.bndrun index c79f9771b5..4849270908 100644 --- a/logback/itests/standard-log4j1/itest.bndrun +++ b/logback/itests/standard-log4j1/itest.bndrun @@ -4,12 +4,15 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.log4j1)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ - log4j.over.slf4j;version='[1.7.32,1.7.33)',\ - org.apache.felix.logback.itests.standard.log4j1;version='[1.0.0,1.0.1)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + log4j.over.slf4j;version='[2.0.18,2.0.19)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ + org.apache.felix.logback.itests.standard.log4j1;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-log4j1/pom.xml b/logback/itests/standard-log4j1/pom.xml index 1f75d63b2f..4915284d5b 100644 --- a/logback/itests/standard-log4j1/pom.xml +++ b/logback/itests/standard-log4j1/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-log4j2/itest.bndrun b/logback/itests/standard-log4j2/itest.bndrun index 82e08b4b0b..1acea21001 100644 --- a/logback/itests/standard-log4j2/itest.bndrun +++ b/logback/itests/standard-log4j2/itest.bndrun @@ -4,14 +4,17 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ - osgi.identity;filter:='(osgi.identity=org.apache.logging.log4j.to-slf4j)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ + osgi.identity;filter:='(osgi.identity=org.apache.logging.log4j.to.slf4j)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.log4j2)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ - org.apache.felix.logback.itests.standard.log4j2;version='[1.0.0,1.0.1)',\ - org.apache.logging.log4j.api;version='[2.14.1,2.14.2)',\ - org.apache.logging.log4j.to-slf4j;version='[2.14.1,2.14.2)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ + org.apache.felix.logback.itests.standard.log4j2;version='[2.0.0,2.0.1)',\ + org.apache.logging.log4j.api;version='[2.26.1,2.26.2)',\ + org.apache.logging.log4j.to.slf4j;version='[2.26.1,2.26.2)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-log4j2/pom.xml b/logback/itests/standard-log4j2/pom.xml index 3759f9023a..09cfc1fe86 100644 --- a/logback/itests/standard-log4j2/pom.xml +++ b/logback/itests/standard-log4j2/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/standard-slf4j/itest.bndrun b/logback/itests/standard-slf4j/itest.bndrun index c730edb9f3..82fb247800 100644 --- a/logback/itests/standard-slf4j/itest.bndrun +++ b/logback/itests/standard-slf4j/itest.bndrun @@ -4,11 +4,14 @@ -runrequires:\ osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=jakarta.servlet-api)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.slf4j)' -runbundles: \ - ch.qos.logback.classic;version='[1.2.8,1.2.9)',\ - ch.qos.logback.core;version='[1.2.8,1.2.9)',\ - org.apache.felix.logback.itests.standard.slf4j;version='[1.0.0,1.0.1)',\ + ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ + ch.qos.logback.core;version='[1.5.38,1.5.39)',\ + jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ + org.apache.felix.logback.itests.standard.slf4j;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ - slf4j.api;version='[1.7.32,1.7.33)' \ No newline at end of file + slf4j.api;version='[2.0.18,2.0.19)' diff --git a/logback/itests/standard-slf4j/pom.xml b/logback/itests/standard-slf4j/pom.xml index 0ff9697389..bd905994a0 100644 --- a/logback/itests/standard-slf4j/pom.xml +++ b/logback/itests/standard-slf4j/pom.xml @@ -22,7 +22,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml diff --git a/logback/itests/test-helper/pom.xml b/logback/itests/test-helper/pom.xml index 4634044c7d..0a627b6901 100644 --- a/logback/itests/test-helper/pom.xml +++ b/logback/itests/test-helper/pom.xml @@ -20,7 +20,7 @@ org.apache.felix org.apache.felix.logback.itests.parent - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT ../itest-parent/pom.xml @@ -47,7 +47,7 @@ ch.qos.logback logback-classic - 1.2.3 + ${logback.version} org.osgi diff --git a/logback/logback/README.md b/logback/logback/README.md index dbe890687d..b513e3da91 100644 --- a/logback/logback/README.md +++ b/logback/logback/README.md @@ -18,17 +18,17 @@ To add Logback to an OSGi framework include the following dependencies ch.qos.logback logback-classic - 1.2.x + 1.5.x ch.qos.logback logback-core - 1.2.x + 1.5.x org.slf4j slf4j-api - 1.7.x + 2.0.x These provide the `slf4j` logging API over the Logback backend. diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 8cb673c220..1e23e18df2 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -29,7 +29,7 @@ A simple integration of the OSGi R7 Log (1.4) service to Logback backend. - 1.0.7-SNAPSHOT + 2.0.0-SNAPSHOT org.apache.felix.logback @@ -43,15 +43,13 @@ 6.1.0 true - 8 - 1.8 - 1.8 + 11 + 11 + 11 UTF-8 - 1.7.32 - - 1.2.8 - + 2.0.18 + 1.5.38 @@ -106,8 +104,8 @@ diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index d697ed331a..1128eb5b21 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -14,9 +14,6 @@ package org.apache.felix.logback.internal; -import java.util.AbstractMap.SimpleEntry; -import java.util.function.Supplier; - import org.osgi.annotation.bundle.Header; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -25,20 +22,23 @@ import org.osgi.service.log.LogReaderService; import org.osgi.service.log.admin.LoggerAdmin; import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; +import java.util.AbstractMap.SimpleEntry; +import java.util.function.Supplier; + @Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") public class Activator implements BundleActivator { - static { - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); - } - private volatile ServiceTracker lat; @Override public void start(final BundleContext bundleContext) throws Exception { + LoggerFactory.getILoggerFactory(); + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + lat = new ServiceTracker( bundleContext, LoggerAdmin.class, null) { diff --git a/logback/pom.xml b/logback/pom.xml index 371c5a865e..e3a1036011 100644 --- a/logback/pom.xml +++ b/logback/pom.xml @@ -28,7 +28,7 @@ Apache Felix Logback Reactor org.apache.felix.logback.reactor - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT pom From 29cf0a04fad1e35ad18cfa5cb6739270c9d1cedf Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 20:39:21 +0200 Subject: [PATCH 04/24] Restore JUL root handlers when stopping Felix Logback Save the existing root handlers before installing the SLF4J bridge and restore them when the bundle stops. Preserve handlers added by other components while the bridge is active. --- .../apache/felix/logback/test/JULTest.java | 85 ++++++++++++++++++ .../felix/logback/internal/Activator.java | 87 ++++++++++++------- .../felix/logback/internal/JULBridge.java | 85 ++++++++++++++++++ 3 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java diff --git a/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java index 47791a0d31..de6c5e6245 100644 --- a/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java +++ b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java @@ -18,11 +18,17 @@ */ package org.apache.felix.logback.test; +import java.util.logging.Handler; import java.util.logging.Level; +import java.util.logging.LogRecord; import java.util.logging.Logger; import org.apache.felix.logback.test.helper.LogTestHelper; +import org.junit.Assert; import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; public class JULTest extends LogTestHelper { @@ -36,4 +42,83 @@ public void test() { assertLog("INFO", getClass().getName(), time); } + @Test + public void testRootHandlersAreRestored() throws Exception { + Logger rootLogger = Logger.getLogger(""); + BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + Bundle logbackBundle = getBundle(context, "org.apache.felix.logback"); + boolean restart = logbackBundle.getState() == Bundle.ACTIVE; + Handler savedHandler = new NoOpHandler(); + Handler addedHandler = new NoOpHandler(); + + try { + if (restart) { + logbackBundle.stop(); + } + + rootLogger.addHandler(savedHandler); + logbackBundle.start(); + + Handler[] activeHandlers = rootLogger.getHandlers(); + Assert.assertEquals(1, activeHandlers.length); + Handler bridgeHandler = activeHandlers[0]; + Assert.assertFalse(contains(rootLogger.getHandlers(), savedHandler)); + + rootLogger.addHandler(addedHandler); + logbackBundle.stop(); + + Assert.assertFalse(contains(rootLogger.getHandlers(), bridgeHandler)); + Assert.assertTrue(contains(rootLogger.getHandlers(), savedHandler)); + Assert.assertTrue(contains(rootLogger.getHandlers(), addedHandler)); + } + finally { + if (logbackBundle.getState() == Bundle.ACTIVE) { + logbackBundle.stop(); + } + + rootLogger.removeHandler(savedHandler); + rootLogger.removeHandler(addedHandler); + + if (restart) { + logbackBundle.start(); + } + } + } + + private static Bundle getBundle(BundleContext context, String symbolicName) { + for (Bundle bundle : context.getBundles()) { + if (symbolicName.equals(bundle.getSymbolicName())) { + return bundle; + } + } + + throw new AssertionError("Bundle not found: " + symbolicName); + } + + private static boolean contains(Handler[] handlers, Handler target) { + for (Handler handler : handlers) { + if (handler == target) { + return true; + } + } + + return false; + } + + private static class NoOpHandler extends Handler { + + @Override + public void publish(LogRecord record) { + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + + } + } diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index 1128eb5b21..5f0fc756e6 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -14,6 +14,9 @@ package org.apache.felix.logback.internal; +import java.util.AbstractMap.SimpleEntry; +import java.util.function.Supplier; + import org.osgi.annotation.bundle.Header; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -23,57 +26,83 @@ import org.osgi.service.log.admin.LoggerAdmin; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.LoggerFactory; -import org.slf4j.bridge.SLF4JBridgeHandler; - -import java.util.AbstractMap.SimpleEntry; -import java.util.function.Supplier; @Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") public class Activator implements BundleActivator { + private final JULBridge julBridge = new JULBridge(); private volatile ServiceTracker lat; @Override public void start(final BundleContext bundleContext) throws Exception { LoggerFactory.getILoggerFactory(); - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); + julBridge.install(); - lat = new ServiceTracker( - bundleContext, LoggerAdmin.class, null) { + try { + lat = new ServiceTracker( + bundleContext, LoggerAdmin.class, null) { - @Override - public LRST addingService( - ServiceReference reference) { + @Override + public LRST addingService( + ServiceReference reference) { - return tccl(() -> { - LoggerAdmin loggerAdmin = bundleContext.getService(reference); + return tccl(() -> { + LoggerAdmin loggerAdmin = bundleContext.getService(reference); - LRST lrst = new LRST(bundleContext, loggerAdmin); + LRST lrst = new LRST(bundleContext, loggerAdmin); - lrst.open(); + lrst.open(); - return lrst; - }); - } + return lrst; + }); + } - @Override - public void removedService( - ServiceReference reference, LRST lrst) { + @Override + public void removedService( + ServiceReference reference, LRST lrst) { - tccl(() -> { - lrst.close(); - return null; - }); - } - }; + tccl(() -> { + lrst.close(); + return null; + }); + } + }; - lat.open(); + lat.open(); + } + catch (Exception | Error e) { + closeTracker(e); + julBridge.restore(e); + throw e; + } } @Override public void stop(BundleContext bundleContext) throws Exception { - lat.close(); + try { + closeTracker(); + } + finally { + julBridge.restore(); + } + } + + private void closeTracker(Throwable failure) { + try { + closeTracker(); + } + catch (RuntimeException | Error e) { + failure.addSuppressed(e); + } + } + + private void closeTracker() { + ServiceTracker tracker = lat; + lat = null; + + if (tracker != null) { + tracker.close(); + } } private static R tccl(Supplier action) { diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java new file mode 100644 index 0000000000..97fd485f83 --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java @@ -0,0 +1,85 @@ +/** + * Licensed 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.felix.logback.internal; + +import java.util.logging.Handler; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.slf4j.bridge.SLF4JBridgeHandler; + +final class JULBridge { + + private Logger rootLogger; + private Handler[] rootHandlers; + private SLF4JBridgeHandler bridgeHandler; + + void install() { + rootLogger = LogManager.getLogManager().getLogger(""); + rootHandlers = rootLogger.getHandlers(); + bridgeHandler = new SLF4JBridgeHandler(); + + try { + for (Handler handler : rootHandlers) { + rootLogger.removeHandler(handler); + } + rootLogger.addHandler(bridgeHandler); + } + catch (RuntimeException | Error e) { + restore(e); + throw e; + } + } + + void restore(Throwable failure) { + try { + restore(); + } + catch (RuntimeException | Error e) { + failure.addSuppressed(e); + } + } + + void restore() { + if (rootLogger == null) { + return; + } + + if (bridgeHandler != null) { + rootLogger.removeHandler(bridgeHandler); + } + + for (Handler handler : rootHandlers) { + if (!contains(rootLogger.getHandlers(), handler)) { + rootLogger.addHandler(handler); + } + } + + bridgeHandler = null; + rootHandlers = null; + rootLogger = null; + } + + private static boolean contains(Handler[] handlers, Handler target) { + for (Handler handler : handlers) { + if (handler == target) { + return true; + } + } + + return false; + } + +} From d271332d6fc03318a6c2a51cea4e8d43af968350 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:04:47 +0200 Subject: [PATCH 05/24] Make Logback tracker helpers static --- .../java/org/apache/felix/logback/internal/Activator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index 5f0fc756e6..bb28d757e3 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -117,7 +117,7 @@ private static R tccl(Supplier action) { } } - class LRST extends ServiceTracker { + static class LRST extends ServiceTracker { public LRST(BundleContext context, LoggerAdmin loggerAdmin) { super(context, LogReaderService.class, null); @@ -155,7 +155,7 @@ public void removedService( } - class Pair extends SimpleEntry { + static class Pair extends SimpleEntry { private static final long serialVersionUID = 1L; From 78dda1620c27a190a75cbd09ba5dded3e328a752 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:07:27 +0200 Subject: [PATCH 06/24] Release tracked OSGi Log services --- logback/logback/pom.xml | 12 ++ .../felix/logback/internal/Activator.java | 138 +++++++++++---- .../felix/logback/internal/ActivatorTest.java | 162 ++++++++++++++++++ 3 files changed, 275 insertions(+), 37 deletions(-) create mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 1e23e18df2..d68f39ded6 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -93,6 +93,18 @@ ${slf4j.api.version} provided + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.8.1 + test + diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index bb28d757e3..277fb81da6 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -15,6 +15,8 @@ package org.apache.felix.logback.internal; import java.util.AbstractMap.SimpleEntry; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; import org.osgi.annotation.bundle.Header; @@ -31,7 +33,7 @@ public class Activator implements BundleActivator { private final JULBridge julBridge = new JULBridge(); - private volatile ServiceTracker lat; + private volatile LoggerAdminServiceTracker lat; @Override public void start(final BundleContext bundleContext) throws Exception { @@ -39,34 +41,7 @@ public void start(final BundleContext bundleContext) throws Exception { julBridge.install(); try { - lat = new ServiceTracker( - bundleContext, LoggerAdmin.class, null) { - - @Override - public LRST addingService( - ServiceReference reference) { - - return tccl(() -> { - LoggerAdmin loggerAdmin = bundleContext.getService(reference); - - LRST lrst = new LRST(bundleContext, loggerAdmin); - - lrst.open(); - - return lrst; - }); - } - - @Override - public void removedService( - ServiceReference reference, LRST lrst) { - - tccl(() -> { - lrst.close(); - return null; - }); - } - }; + lat = new LoggerAdminServiceTracker(bundleContext); lat.open(); } @@ -97,7 +72,7 @@ private void closeTracker(Throwable failure) { } private void closeTracker() { - ServiceTracker tracker = lat; + LoggerAdminServiceTracker tracker = lat; lat = null; if (tracker != null) { @@ -117,12 +92,75 @@ private static R tccl(Supplier action) { } } + static class LoggerAdminServiceTracker extends ServiceTracker { + + LoggerAdminServiceTracker(BundleContext context) { + this(context, LRST::new); + } + + LoggerAdminServiceTracker( + BundleContext context, + BiFunction trackerFactory) { + + super(context, LoggerAdmin.class, null); + this.trackerFactory = trackerFactory; + } + + @Override + public LRST addingService(ServiceReference reference) { + return tccl(() -> { + LoggerAdmin loggerAdmin = context.getService(reference); + + if (loggerAdmin == null) { + return null; + } + + try { + LRST lrst = trackerFactory.apply(context, loggerAdmin); + lrst.open(); + return lrst; + } + catch (RuntimeException | Error e) { + context.ungetService(reference); + throw e; + } + }); + } + + @Override + public void removedService( + ServiceReference reference, LRST lrst) { + + try { + tccl(() -> { + lrst.close(); + return null; + }); + } + finally { + context.ungetService(reference); + } + } + + private final BiFunction trackerFactory; + + } + static class LRST extends ServiceTracker { public LRST(BundleContext context, LoggerAdmin loggerAdmin) { + this(context, loggerAdmin, LogbackLogListener::new); + } + + LRST( + BundleContext context, + LoggerAdmin loggerAdmin, + Function listenerFactory) { + super(context, LogReaderService.class, null); this.loggerAdmin = loggerAdmin; + this.listenerFactory = listenerFactory; } @Override @@ -132,11 +170,31 @@ public Pair addingService( return tccl(() -> { LogReaderService logReaderService = context.getService(reference); - LogbackLogListener logbackLogListener = new LogbackLogListener(loggerAdmin); + if (logReaderService == null) { + return null; + } + + LogbackLogListener logbackLogListener = null; - logReaderService.addLogListener(logbackLogListener); + try { + logbackLogListener = listenerFactory.apply(loggerAdmin); + logReaderService.addLogListener(logbackLogListener); - return new Pair(logReaderService, logbackLogListener); + return new Pair(logReaderService, logbackLogListener); + } + catch (RuntimeException | Error e) { + if (logbackLogListener != null) { + try { + logReaderService.removeLogListener(logbackLogListener); + } + catch (RuntimeException | Error cleanup) { + e.addSuppressed(cleanup); + } + } + + context.ungetService(reference); + throw e; + } }); } @@ -145,13 +203,19 @@ public void removedService( ServiceReference reference, Pair pair) { - tccl(() -> { - pair.getKey().removeLogListener(pair.getValue()); - return null; - }); + try { + tccl(() -> { + pair.getKey().removeLogListener(pair.getValue()); + return null; + }); + } + finally { + context.ungetService(reference); + } } private final LoggerAdmin loggerAdmin; + private final Function listenerFactory; } diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java new file mode 100644 index 0000000000..25c9b54c6a --- /dev/null +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java @@ -0,0 +1,162 @@ +/** + * Licensed 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.felix.logback.internal; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogReaderService; +import org.osgi.service.log.admin.LoggerAdmin; + +@SuppressWarnings("unchecked") +public class ActivatorTest { + + @Test + public void loggerAdminIsReleased() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + Activator.LRST lrst = mock(Activator.LRST.class); + when(context.getService(reference)).thenReturn(loggerAdmin); + + Activator.LoggerAdminServiceTracker tracker = + new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); + + assertSame(lrst, tracker.addingService(reference)); + tracker.removedService(reference, lrst); + + verify(lrst).open(); + verify(lrst).close(); + verify(context).ungetService(reference); + } + + @Test + public void loggerAdminIsReleasedWhenOpeningItsTrackerFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + Activator.LRST lrst = mock(Activator.LRST.class); + when(context.getService(reference)).thenReturn(loggerAdmin); + doThrow(new IllegalStateException()).when(lrst).open(); + + Activator.LoggerAdminServiceTracker tracker = + new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); + + assertThrows(IllegalStateException.class, () -> tracker.addingService(reference)); + verify(context).ungetService(reference); + } + + @Test + public void loggerAdminIsReleasedWhenClosingItsTrackerFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + Activator.LRST lrst = mock(Activator.LRST.class); + doThrow(new IllegalStateException()).when(lrst).close(); + Activator.LoggerAdminServiceTracker tracker = + new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); + + assertThrows( + IllegalStateException.class, + () -> tracker.removedService(reference, lrst)); + verify(context).ungetService(reference); + } + + @Test + public void unavailableLoggerAdminIsNotTrackedOrReleased() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + Activator.LoggerAdminServiceTracker tracker = + new Activator.LoggerAdminServiceTracker(context, (c, a) -> mock(Activator.LRST.class)); + + assertNull(tracker.addingService(reference)); + verify(context, never()).ungetService(reference); + } + + @Test + public void logReaderServiceIsReleased() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + LogReaderService logReaderService = mock(LogReaderService.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + when(context.getService(reference)).thenReturn(logReaderService); + + Activator.LRST tracker = new Activator.LRST(context, loggerAdmin, a -> listener); + + Activator.Pair pair = tracker.addingService(reference); + assertSame(logReaderService, pair.getKey()); + assertSame(listener, pair.getValue()); + tracker.removedService(reference, pair); + + verify(logReaderService).addLogListener(listener); + verify(logReaderService).removeLogListener(listener); + verify(context).ungetService(reference); + } + + @Test + public void logReaderServiceIsReleasedWhenListenerRegistrationFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + LogReaderService logReaderService = mock(LogReaderService.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + when(context.getService(reference)).thenReturn(logReaderService); + doThrow(new IllegalStateException()).when(logReaderService).addLogListener(listener); + + Activator.LRST tracker = new Activator.LRST(context, loggerAdmin, a -> listener); + + assertThrows(IllegalStateException.class, () -> tracker.addingService(reference)); + verify(logReaderService).removeLogListener(listener); + verify(context).ungetService(reference); + } + + @Test + public void logReaderServiceIsReleasedWhenListenerRemovalFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + LogReaderService logReaderService = mock(LogReaderService.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + doThrow(new IllegalStateException()).when(logReaderService).removeLogListener(listener); + Activator.LRST tracker = new Activator.LRST( + context, mock(LoggerAdmin.class), a -> listener); + Activator.Pair pair = new Activator.Pair(logReaderService, listener); + + assertThrows( + IllegalStateException.class, + () -> tracker.removedService(reference, pair)); + verify(context).ungetService(reference); + } + + @Test + public void unavailableLogReaderServiceIsNotTrackedOrReleased() { + BundleContext context = mock(BundleContext.class); + ServiceReference reference = mock(ServiceReference.class); + Activator.LRST tracker = new Activator.LRST( + context, mock(LoggerAdmin.class), a -> mock(LogbackLogListener.class)); + + assertNull(tracker.addingService(reference)); + verify(context, never()).ungetService(reference); + } + +} From 057e3bef6e41b4f240b5985406f361f172f799be Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:09:03 +0200 Subject: [PATCH 07/24] Dispose Logback listeners when services unbind --- .../apache/felix/logback/test/JULTest.java | 16 +++++ .../felix/logback/internal/Activator.java | 28 +++++++-- .../logback/internal/LogbackLogListener.java | 56 +++++++++++++++--- .../felix/logback/internal/ActivatorTest.java | 3 + .../internal/LogbackLogListenerTest.java | 59 +++++++++++++++++++ 5 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java diff --git a/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java index de6c5e6245..e7f9224e70 100644 --- a/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java +++ b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java @@ -30,8 +30,13 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; +import ch.qos.logback.classic.LoggerContext; + public class JULTest extends LogTestHelper { + private static final String LOG_LISTENER = + "org.apache.felix.logback.internal.LogbackLogListener"; + @Test public void test() { long time = System.nanoTime(); @@ -54,10 +59,12 @@ public void testRootHandlersAreRestored() throws Exception { try { if (restart) { logbackBundle.stop(); + Assert.assertEquals(0, getLogListenerCount()); } rootLogger.addHandler(savedHandler); logbackBundle.start(); + Assert.assertEquals(1, getLogListenerCount()); Handler[] activeHandlers = rootLogger.getHandlers(); Assert.assertEquals(1, activeHandlers.length); @@ -66,6 +73,7 @@ public void testRootHandlersAreRestored() throws Exception { rootLogger.addHandler(addedHandler); logbackBundle.stop(); + Assert.assertEquals(0, getLogListenerCount()); Assert.assertFalse(contains(rootLogger.getHandlers(), bridgeHandler)); Assert.assertTrue(contains(rootLogger.getHandlers(), savedHandler)); @@ -81,10 +89,18 @@ public void testRootHandlersAreRestored() throws Exception { if (restart) { logbackBundle.start(); + Assert.assertEquals(1, getLogListenerCount()); } } } + private static long getLogListenerCount() { + LoggerContext context = (LoggerContext)org.slf4j.LoggerFactory.getILoggerFactory(); + return context.getCopyOfListenerList().stream() + .filter(listener -> LOG_LISTENER.equals(listener.getClass().getName())) + .count(); + } + private static Bundle getBundle(BundleContext context, String symbolicName) { for (Bundle bundle : context.getBundles()) { if (symbolicName.equals(bundle.getSymbolicName())) { diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index 277fb81da6..6b7c0525a6 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -190,6 +190,7 @@ public Pair addingService( catch (RuntimeException | Error cleanup) { e.addSuppressed(cleanup); } + close(logbackLogListener, e); } context.ungetService(reference); @@ -204,16 +205,35 @@ public void removedService( Pair pair) { try { - tccl(() -> { - pair.getKey().removeLogListener(pair.getValue()); - return null; - }); + try { + tccl(() -> { + pair.getKey().removeLogListener(pair.getValue()); + return null; + }); + } + catch (RuntimeException | Error e) { + close(pair.getValue(), e); + throw e; + } + + pair.getValue().close(); } finally { context.ungetService(reference); } } + private static void close( + LogbackLogListener logbackLogListener, Throwable failure) { + + try { + logbackLogListener.close(); + } + catch (RuntimeException | Error cleanup) { + failure.addSuppressed(cleanup); + } + } + private final LoggerAdmin loggerAdmin; private final Function listenerFactory; diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 6e55de1e13..48d1ad1f44 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -39,7 +39,7 @@ import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; -public class LogbackLogListener implements LogListener, LoggerContextListener { +public class LogbackLogListener implements AutoCloseable, LogListener, LoggerContextListener { private static final String EVENTS_BUNDLE = "Events.Bundle"; private static final String EVENTS_FRAMEWORK = "Events.Framework"; @@ -64,20 +64,40 @@ public class LogbackLogListener implements LogListener, LoggerContextListener { volatile LoggerContextVO loggerContextVO; final Map initialLogLevels; final org.osgi.service.log.admin.LoggerContext osgiLoggerContext; + final AtomicBoolean closed = new AtomicBoolean(); public LogbackLogListener(LoggerAdmin loggerAdmin) { + this(loggerAdmin, getLoggerContext()); + } + + LogbackLogListener(LoggerAdmin loggerAdmin, LoggerContext loggerContext) { osgiLoggerContext = loggerAdmin.getLoggerContext(null); initialLogLevels = osgiLoggerContext.getLogLevels(); + this.loggerContext = loggerContext; + + try { + onStart(loggerContext); + loggerContext.addListener(this); + } + catch (RuntimeException | Error e) { + try { + osgiLoggerContext.setLogLevels(initialLogLevels); + } + catch (RuntimeException | Error cleanup) { + e.addSuppressed(cleanup); + } + throw e; + } + } + private static LoggerContext getLoggerContext() { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - if (!(loggerFactory instanceof LoggerContext)) { - throw new IllegalStateException("This bundle only works with logback-classic"); + if (loggerFactory instanceof LoggerContext) { + return (LoggerContext)loggerFactory; } - onStart((LoggerContext)loggerFactory); - - loggerContext.addListener(this); + throw new IllegalStateException("This bundle only works with logback-classic"); } @Override @@ -85,6 +105,14 @@ public boolean isResetResistant() { return true; } + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + loggerContext.removeListener(this); + osgiLoggerContext.setLogLevels(initialLogLevels); + } + } + @Override public void logged(final LogEntry entry) { String loggerName = entry.getLoggerName(); @@ -162,6 +190,10 @@ public StackTraceElement[] getCallerData() { @Override public void onLevelChange(Logger logger, Level level) { + if (closed.get()) { + return; + } + Map updatedLevels = osgiLoggerContext.getLogLevels(); if (Level.OFF.equals(level)) { @@ -176,6 +208,10 @@ public void onLevelChange(Logger logger, Level level) { @Override public void onStart(LoggerContext context) { + if (closed.get()) { + return; + } + loggerContext = context; rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); loggerContextVO = loggerContext.getLoggerContextRemoteView(); @@ -187,12 +223,16 @@ public void onStart(LoggerContext context) { @Override public void onStop(LoggerContext context) { - osgiLoggerContext.setLogLevels(initialLogLevels); + if (!closed.get()) { + osgiLoggerContext.setLogLevels(initialLogLevels); + } } @Override public void onReset(LoggerContext context) { - onStart(context); + if (!closed.get()) { + onStart(context); + } } String formatBundle(Bundle bundle, String loggerName) { diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java index 25c9b54c6a..1ce6a44bdd 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java @@ -111,6 +111,7 @@ public void logReaderServiceIsReleased() { verify(logReaderService).addLogListener(listener); verify(logReaderService).removeLogListener(listener); + verify(listener).close(); verify(context).ungetService(reference); } @@ -128,6 +129,7 @@ public void logReaderServiceIsReleasedWhenListenerRegistrationFails() { assertThrows(IllegalStateException.class, () -> tracker.addingService(reference)); verify(logReaderService).removeLogListener(listener); + verify(listener).close(); verify(context).ungetService(reference); } @@ -145,6 +147,7 @@ public void logReaderServiceIsReleasedWhenListenerRemovalFails() { assertThrows( IllegalStateException.class, () -> tracker.removedService(reference, pair)); + verify(listener).close(); verify(context).ungetService(reference); } diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java new file mode 100644 index 0000000000..f83d8a4d7c --- /dev/null +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -0,0 +1,59 @@ +/** + * Licensed 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.felix.logback.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.osgi.service.log.LogLevel; +import org.osgi.service.log.admin.LoggerAdmin; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; + +public class LogbackLogListenerTest { + + @Test + public void closeRemovesContextListenerAndRestoresLogLevels() { + LoggerContext loggerContext = new LoggerContext(); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.INFO); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + Map initialLevels = new HashMap<>(); + initialLevels.put("existing", LogLevel.ERROR); + when(loggerAdmin.getLoggerContext(null)).thenReturn(osgiLoggerContext); + when(osgiLoggerContext.getLogLevels()).thenReturn(initialLevels); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + + assertTrue(loggerContext.getCopyOfListenerList().contains(listener)); + listener.close(); + listener.close(); + + assertFalse(loggerContext.getCopyOfListenerList().contains(listener)); + verify(osgiLoggerContext, times(1)).setLogLevels(initialLevels); + } + +} From 23825ca44a9670518b381d6ef8752a3f15815869 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:14:15 +0200 Subject: [PATCH 08/24] Preserve externally changed OSGi log levels --- .../logback/internal/LogbackLogListener.java | 140 +++++++++++++++--- .../internal/LogbackLogListenerTest.java | 57 ++++++- 2 files changed, 176 insertions(+), 21 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 48d1ad1f44..0c6f4b1348 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -18,8 +18,10 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.osgi.framework.Bundle; @@ -62,9 +64,10 @@ public class LogbackLogListener implements AutoCloseable, LogListener, LoggerCon volatile LoggerContext loggerContext; volatile Logger rootLogger; volatile LoggerContextVO loggerContextVO; - final Map initialLogLevels; final org.osgi.service.log.admin.LoggerContext osgiLoggerContext; final AtomicBoolean closed = new AtomicBoolean(); + final Map> originalLogLevels = new HashMap<>(); + final Map> appliedLogLevels = new HashMap<>(); public LogbackLogListener(LoggerAdmin loggerAdmin) { this(loggerAdmin, getLoggerContext()); @@ -72,7 +75,6 @@ public LogbackLogListener(LoggerAdmin loggerAdmin) { LogbackLogListener(LoggerAdmin loggerAdmin, LoggerContext loggerContext) { osgiLoggerContext = loggerAdmin.getLoggerContext(null); - initialLogLevels = osgiLoggerContext.getLogLevels(); this.loggerContext = loggerContext; try { @@ -81,7 +83,7 @@ public LogbackLogListener(LoggerAdmin loggerAdmin) { } catch (RuntimeException | Error e) { try { - osgiLoggerContext.setLogLevels(initialLogLevels); + restoreLogLevels(); } catch (RuntimeException | Error cleanup) { e.addSuppressed(cleanup); @@ -109,7 +111,7 @@ public boolean isResetResistant() { public void close() { if (closed.compareAndSet(false, true)) { loggerContext.removeListener(this); - osgiLoggerContext.setLogLevels(initialLogLevels); + restoreLogLevels(); } } @@ -194,16 +196,8 @@ public void onLevelChange(Logger logger, Level level) { return; } - Map updatedLevels = osgiLoggerContext.getLogLevels(); - - if (Level.OFF.equals(level)) { - updatedLevels.remove(logger.getName()); - } - else { - updatedLevels.put(logger.getName(), from(level)); - } - - osgiLoggerContext.setLogLevels(updatedLevels); + updateLogLevel(logger.getName(), + Level.OFF.equals(level) ? Optional.empty() : Optional.of(from(level))); } @Override @@ -216,15 +210,13 @@ public void onStart(LoggerContext context) { rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); loggerContextVO = loggerContext.getLoggerContextRemoteView(); - Map updatedLevels = updateLevels(loggerContext, initialLogLevels); - - osgiLoggerContext.setLogLevels(updatedLevels); + configureLogLevels(loggerContext); } @Override public void onStop(LoggerContext context) { if (!closed.get()) { - osgiLoggerContext.setLogLevels(initialLogLevels); + restoreLogLevels(); } } @@ -348,4 +340,116 @@ Map updateLevels(LoggerContext loggerContext, Map level) { + + Map currentLevels = osgiLoggerContext.getLogLevels(); + Map updatedLevels = new HashMap<>(currentLevels); + setLevel(updatedLevels, name, level); + Map> nextOriginalLogLevels = + new HashMap<>(originalLogLevels); + Map> nextAppliedLogLevels = + new HashMap<>(appliedLogLevels); + + updateOwnership(currentLevels, updatedLevels, + nextOriginalLogLevels, nextAppliedLogLevels); + osgiLoggerContext.setLogLevels(updatedLevels); + replaceOwnership(nextOriginalLogLevels, nextAppliedLogLevels); + } + + private synchronized void configureLogLevels(LoggerContext context) { + Map currentLevels = osgiLoggerContext.getLogLevels(); + Map releasedLevels = getReleasedLogLevels(currentLevels); + Map updatedLevels = updateLevels(context, releasedLevels); + Map> nextOriginalLogLevels = new HashMap<>(); + Map> nextAppliedLogLevels = new HashMap<>(); + + updateOwnership(releasedLevels, updatedLevels, + nextOriginalLogLevels, nextAppliedLogLevels); + osgiLoggerContext.setLogLevels(updatedLevels); + replaceOwnership(nextOriginalLogLevels, nextAppliedLogLevels); + } + + private static void updateOwnership( + Map currentLevels, + Map updatedLevels, + Map> originalLevels, + Map> appliedLevels) { + + Set names = new HashSet<>(currentLevels.keySet()); + names.addAll(updatedLevels.keySet()); + names.addAll(appliedLevels.keySet()); + + for (String name : names) { + Optional current = getLevel(currentLevels, name); + Optional updated = getLevel(updatedLevels, name); + Optional applied = appliedLevels.get(name); + + if (applied != null && !current.equals(applied)) { + originalLevels.remove(name); + appliedLevels.remove(name); + } + + if (!current.equals(updated)) { + originalLevels.putIfAbsent(name, current); + appliedLevels.put(name, updated); + } + } + } + + private Map getReleasedLogLevels( + Map currentLevels) { + + Map releasedLevels = new HashMap<>(currentLevels); + + for (Map.Entry> entry : appliedLogLevels.entrySet()) { + String name = entry.getKey(); + + if (getLevel(currentLevels, name).equals(entry.getValue())) { + setLevel(releasedLevels, name, originalLogLevels.get(name)); + } + } + + return releasedLevels; + } + + private synchronized void restoreLogLevels() { + Map currentLevels = osgiLoggerContext.getLogLevels(); + Map restoredLevels = getReleasedLogLevels(currentLevels); + + if (!currentLevels.equals(restoredLevels)) { + osgiLoggerContext.setLogLevels(restoredLevels); + } + + originalLogLevels.clear(); + appliedLogLevels.clear(); + } + + private void replaceOwnership( + Map> originalLevels, + Map> appliedLevels) { + + originalLogLevels.clear(); + originalLogLevels.putAll(originalLevels); + appliedLogLevels.clear(); + appliedLogLevels.putAll(appliedLevels); + } + + private static Optional getLevel( + Map levels, String name) { + + return Optional.ofNullable(levels.get(name)); + } + + private static void setLevel( + Map levels, String name, Optional level) { + + if (level.isPresent()) { + levels.put(name, level.get()); + } + else { + levels.remove(name); + } + } + } diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java index f83d8a4d7c..394c2665a1 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -14,8 +14,11 @@ package org.apache.felix.logback.internal; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -23,6 +26,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.osgi.service.log.LogLevel; @@ -35,7 +39,7 @@ public class LogbackLogListenerTest { @Test - public void closeRemovesContextListenerAndRestoresLogLevels() { + public void closeRemovesContextListenerAndRestoresLogLevelsAfterReset() { LoggerContext loggerContext = new LoggerContext(); loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.INFO); LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); @@ -43,17 +47,64 @@ public void closeRemovesContextListenerAndRestoresLogLevels() { mock(org.osgi.service.log.admin.LoggerContext.class); Map initialLevels = new HashMap<>(); initialLevels.put("existing", LogLevel.ERROR); - when(loggerAdmin.getLoggerContext(null)).thenReturn(osgiLoggerContext); - when(osgiLoggerContext.getLogLevels()).thenReturn(initialLevels); + AtomicReference> levels = mockLogLevels( + loggerAdmin, osgiLoggerContext, initialLevels); LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); assertTrue(loggerContext.getCopyOfListenerList().contains(listener)); + listener.onReset(loggerContext); listener.close(); listener.close(); assertFalse(loggerContext.getCopyOfListenerList().contains(listener)); + assertEquals(initialLevels, levels.get()); verify(osgiLoggerContext, times(1)).setLogLevels(initialLevels); } + @Test + public void closePreservesLogLevelsChangedByAnotherOwner() { + LoggerContext loggerContext = new LoggerContext(); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.INFO); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + Map initialLevels = new HashMap<>(); + initialLevels.put(Logger.ROOT_LOGGER_NAME, LogLevel.ERROR); + initialLevels.put("existing", LogLevel.WARN); + AtomicReference> levels = mockLogLevels( + loggerAdmin, osgiLoggerContext, initialLevels); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + Map externalLevels = new HashMap<>(levels.get()); + externalLevels.put("Events.Bundle", LogLevel.DEBUG); + externalLevels.put("external", LogLevel.TRACE); + levels.set(externalLevels); + + listener.close(); + + Map expected = new HashMap<>(initialLevels); + expected.put("Events.Bundle", LogLevel.DEBUG); + expected.put("external", LogLevel.TRACE); + assertEquals(expected, levels.get()); + } + + private static AtomicReference> mockLogLevels( + LoggerAdmin loggerAdmin, + org.osgi.service.log.admin.LoggerContext osgiLoggerContext, + Map initialLevels) { + + AtomicReference> levels = + new AtomicReference<>(new HashMap<>(initialLevels)); + when(loggerAdmin.getLoggerContext(null)).thenReturn(osgiLoggerContext); + when(osgiLoggerContext.getLogLevels()).thenAnswer( + invocation -> new HashMap<>(levels.get())); + doAnswer(invocation -> { + levels.set(new HashMap<>(invocation.getArgument(0))); + return null; + }).when(osgiLoggerContext).setLogLevels(anyMap()); + + return levels; + } + } From 7c75a67bd8199e1ed72e1b64364d78ba4f7feb29 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:20:17 +0200 Subject: [PATCH 09/24] Bind only highest-ranked OSGi Log services --- .../felix/logback/internal/Activator.java | 192 +------- .../felix/logback/internal/OSGiLogBridge.java | 410 ++++++++++++++++++ .../felix/logback/internal/ActivatorTest.java | 165 ------- .../logback/internal/OSGiLogBridgeTest.java | 190 ++++++++ 4 files changed, 607 insertions(+), 350 deletions(-) create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java delete mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java create mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index 6b7c0525a6..d931d69c7f 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -14,26 +14,17 @@ package org.apache.felix.logback.internal; -import java.util.AbstractMap.SimpleEntry; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; - import org.osgi.annotation.bundle.Header; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceReference; -import org.osgi.service.log.LogReaderService; -import org.osgi.service.log.admin.LoggerAdmin; -import org.osgi.util.tracker.ServiceTracker; import org.slf4j.LoggerFactory; @Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") public class Activator implements BundleActivator { private final JULBridge julBridge = new JULBridge(); - private volatile LoggerAdminServiceTracker lat; + private volatile OSGiLogBridge logBridge; @Override public void start(final BundleContext bundleContext) throws Exception { @@ -41,9 +32,9 @@ public void start(final BundleContext bundleContext) throws Exception { julBridge.install(); try { - lat = new LoggerAdminServiceTracker(bundleContext); + logBridge = new OSGiLogBridge(bundleContext); - lat.open(); + logBridge.open(); } catch (Exception | Error e) { closeTracker(e); @@ -72,181 +63,12 @@ private void closeTracker(Throwable failure) { } private void closeTracker() { - LoggerAdminServiceTracker tracker = lat; - lat = null; - - if (tracker != null) { - tracker.close(); - } - } - - private static R tccl(Supplier action) { - Thread currentThread = Thread.currentThread(); - ClassLoader original = currentThread.getContextClassLoader(); - try { - currentThread.setContextClassLoader(Activator.class.getClassLoader()); - return action.get(); - } - finally { - currentThread.setContextClassLoader(original); - } - } - - static class LoggerAdminServiceTracker extends ServiceTracker { - - LoggerAdminServiceTracker(BundleContext context) { - this(context, LRST::new); - } - - LoggerAdminServiceTracker( - BundleContext context, - BiFunction trackerFactory) { - - super(context, LoggerAdmin.class, null); - this.trackerFactory = trackerFactory; - } - - @Override - public LRST addingService(ServiceReference reference) { - return tccl(() -> { - LoggerAdmin loggerAdmin = context.getService(reference); - - if (loggerAdmin == null) { - return null; - } - - try { - LRST lrst = trackerFactory.apply(context, loggerAdmin); - lrst.open(); - return lrst; - } - catch (RuntimeException | Error e) { - context.ungetService(reference); - throw e; - } - }); - } - - @Override - public void removedService( - ServiceReference reference, LRST lrst) { - - try { - tccl(() -> { - lrst.close(); - return null; - }); - } - finally { - context.ungetService(reference); - } - } - - private final BiFunction trackerFactory; - - } - - static class LRST extends ServiceTracker { + OSGiLogBridge bridge = logBridge; + logBridge = null; - public LRST(BundleContext context, LoggerAdmin loggerAdmin) { - this(context, loggerAdmin, LogbackLogListener::new); + if (bridge != null) { + bridge.close(); } - - LRST( - BundleContext context, - LoggerAdmin loggerAdmin, - Function listenerFactory) { - - super(context, LogReaderService.class, null); - - this.loggerAdmin = loggerAdmin; - this.listenerFactory = listenerFactory; - } - - @Override - public Pair addingService( - ServiceReference reference) { - - return tccl(() -> { - LogReaderService logReaderService = context.getService(reference); - - if (logReaderService == null) { - return null; - } - - LogbackLogListener logbackLogListener = null; - - try { - logbackLogListener = listenerFactory.apply(loggerAdmin); - logReaderService.addLogListener(logbackLogListener); - - return new Pair(logReaderService, logbackLogListener); - } - catch (RuntimeException | Error e) { - if (logbackLogListener != null) { - try { - logReaderService.removeLogListener(logbackLogListener); - } - catch (RuntimeException | Error cleanup) { - e.addSuppressed(cleanup); - } - close(logbackLogListener, e); - } - - context.ungetService(reference); - throw e; - } - }); - } - - @Override - public void removedService( - ServiceReference reference, - Pair pair) { - - try { - try { - tccl(() -> { - pair.getKey().removeLogListener(pair.getValue()); - return null; - }); - } - catch (RuntimeException | Error e) { - close(pair.getValue(), e); - throw e; - } - - pair.getValue().close(); - } - finally { - context.ungetService(reference); - } - } - - private static void close( - LogbackLogListener logbackLogListener, Throwable failure) { - - try { - logbackLogListener.close(); - } - catch (RuntimeException | Error cleanup) { - failure.addSuppressed(cleanup); - } - } - - private final LoggerAdmin loggerAdmin; - private final Function listenerFactory; - - } - - static class Pair extends SimpleEntry { - - private static final long serialVersionUID = 1L; - - public Pair(LogReaderService logReaderService, LogbackLogListener logbackLogListener) { - super(logReaderService, logbackLogListener); - } - } } diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java new file mode 100644 index 0000000000..ffc18277ec --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java @@ -0,0 +1,410 @@ +/** + * Licensed 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.felix.logback.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogReaderService; +import org.osgi.service.log.admin.LoggerAdmin; +import org.osgi.util.tracker.ServiceTracker; + +final class OSGiLogBridge implements AutoCloseable { + + OSGiLogBridge(BundleContext context) { + this(context, LogbackLogListener::new); + } + + OSGiLogBridge( + BundleContext context, + Function listenerFactory) { + + this.listenerFactory = listenerFactory; + loggerAdminTracker = new RankedServiceTracker<>( + context, LoggerAdmin.class, new ServiceChangeHandler() { + + @Override + public void added( + ServiceReference reference, + LoggerAdmin service) { + + addLoggerAdmin(reference, service); + } + + @Override + public void modified( + ServiceReference reference, + LoggerAdmin service) { + + rebind(); + } + + @Override + public void removed( + ServiceReference reference, + LoggerAdmin service) { + + removeLoggerAdmin(reference); + } + }); + logReaderTracker = new RankedServiceTracker<>( + context, LogReaderService.class, + new ServiceChangeHandler() { + + @Override + public void added( + ServiceReference reference, + LogReaderService service) { + + addLogReader(reference, service); + } + + @Override + public void modified( + ServiceReference reference, + LogReaderService service) { + + rebind(); + } + + @Override + public void removed( + ServiceReference reference, + LogReaderService service) { + + removeLogReader(reference); + } + }); + } + + void open() { + try { + loggerAdminTracker.open(); + logReaderTracker.open(); + } + catch (RuntimeException | Error e) { + try { + close(); + } + catch (RuntimeException | Error cleanup) { + e.addSuppressed(cleanup); + } + throw e; + } + } + + @Override + public void close() { + Binding current; + + synchronized (this) { + closing = true; + current = binding; + binding = null; + selectedLoggerAdmin = null; + selectedLogReader = null; + } + + Runnable closeCurrent = current == null ? null : () -> tccl(() -> { + current.close(); + return null; + }); + Throwable failure = runClose(closeCurrent, null); + failure = runClose(logReaderTracker::close, failure); + failure = runClose(loggerAdminTracker::close, failure); + + synchronized (this) { + loggerAdmins.clear(); + logReaders.clear(); + } + + if (failure instanceof RuntimeException) { + throw (RuntimeException)failure; + } + if (failure instanceof Error) { + throw (Error)failure; + } + } + + private synchronized void addLoggerAdmin( + ServiceReference reference, LoggerAdmin service) { + + loggerAdmins.put(reference, service); + try { + rebind(); + } + catch (RuntimeException | Error e) { + loggerAdmins.remove(reference); + rebindAfterFailure(e); + throw e; + } + } + + private synchronized void removeLoggerAdmin( + ServiceReference reference) { + + loggerAdmins.remove(reference); + rebind(); + } + + private synchronized void addLogReader( + ServiceReference reference, LogReaderService service) { + + logReaders.put(reference, service); + try { + rebind(); + } + catch (RuntimeException | Error e) { + logReaders.remove(reference); + rebindAfterFailure(e); + throw e; + } + } + + private synchronized void removeLogReader( + ServiceReference reference) { + + logReaders.remove(reference); + rebind(); + } + + private void rebindAfterFailure(Throwable failure) { + try { + rebind(); + } + catch (RuntimeException | Error cleanup) { + failure.addSuppressed(cleanup); + } + } + + private synchronized void rebind() { + if (closing) { + return; + } + + ServiceReference loggerAdminReference = + highestRanked(loggerAdmins); + ServiceReference logReaderReference = + highestRanked(logReaders); + + if (Objects.equals(selectedLoggerAdmin, loggerAdminReference) && + Objects.equals(selectedLogReader, logReaderReference)) { + + return; + } + + Binding current = binding; + binding = null; + selectedLoggerAdmin = null; + selectedLogReader = null; + + if (current != null) { + current.close(); + } + + if (loggerAdminReference != null && logReaderReference != null) { + binding = createBinding( + loggerAdmins.get(loggerAdminReference), + logReaders.get(logReaderReference)); + selectedLoggerAdmin = loggerAdminReference; + selectedLogReader = logReaderReference; + } + } + + private Binding createBinding( + LoggerAdmin loggerAdmin, LogReaderService logReaderService) { + + LogbackLogListener listener = null; + + try { + listener = Objects.requireNonNull(listenerFactory.apply(loggerAdmin)); + logReaderService.addLogListener(listener); + return new Binding(logReaderService, listener); + } + catch (RuntimeException | Error e) { + if (listener != null) { + try { + logReaderService.removeLogListener(listener); + } + catch (RuntimeException | Error cleanup) { + e.addSuppressed(cleanup); + } + addSuppressedClose(listener, e); + } + throw e; + } + } + + private static ServiceReference highestRanked( + Map, S> services) { + + return services.keySet().stream().max(ServiceReference::compareTo).orElse(null); + } + + private static Throwable runClose(Runnable action, Throwable failure) { + if (action == null) { + return failure; + } + + try { + action.run(); + } + catch (RuntimeException | Error e) { + if (failure == null) { + return e; + } + failure.addSuppressed(e); + } + + return failure; + } + + private static void addSuppressedClose( + AutoCloseable closeable, Throwable failure) { + try { + closeable.close(); + } + catch (RuntimeException | Error cleanup) { + failure.addSuppressed(cleanup); + } + catch (Exception cleanup) { + failure.addSuppressed(cleanup); + } + } + + private static R tccl(Supplier action) { + Thread currentThread = Thread.currentThread(); + ClassLoader original = currentThread.getContextClassLoader(); + try { + currentThread.setContextClassLoader(Activator.class.getClassLoader()); + return action.get(); + } + finally { + currentThread.setContextClassLoader(original); + } + } + + private final class Binding implements AutoCloseable { + + private Binding( + LogReaderService logReaderService, LogbackLogListener listener) { + + this.logReaderService = logReaderService; + this.listener = listener; + } + + @Override + public void close() { + try { + logReaderService.removeLogListener(listener); + } + catch (RuntimeException | Error e) { + OSGiLogBridge.addSuppressedClose(listener, e); + throw e; + } + + listener.close(); + } + + private final LogReaderService logReaderService; + private final LogbackLogListener listener; + + } + + static final class RankedServiceTracker + extends ServiceTracker { + + private RankedServiceTracker( + BundleContext context, + Class serviceClass, + ServiceChangeHandler changeHandler) { + + super(context, serviceClass, null); + this.changeHandler = changeHandler; + } + + @Override + public S addingService(ServiceReference reference) { + return tccl(() -> { + S service = super.addingService(reference); + + if (service == null) { + return null; + } + + try { + changeHandler.added(reference, service); + return service; + } + catch (RuntimeException | Error e) { + super.removedService(reference, service); + throw e; + } + }); + } + + @Override + public void modifiedService(ServiceReference reference, S service) { + tccl(() -> { + changeHandler.modified(reference, service); + return null; + }); + } + + @Override + public void removedService(ServiceReference reference, S service) { + try { + tccl(() -> { + changeHandler.removed(reference, service); + return null; + }); + } + finally { + super.removedService(reference, service); + } + } + + private final ServiceChangeHandler changeHandler; + + } + + private interface ServiceChangeHandler { + + void added(ServiceReference reference, S service); + + void modified(ServiceReference reference, S service); + + void removed(ServiceReference reference, S service); + + } + + final RankedServiceTracker loggerAdminTracker; + final RankedServiceTracker logReaderTracker; + private final Function listenerFactory; + private final Map, LoggerAdmin> loggerAdmins = + new HashMap<>(); + private final Map, LogReaderService> logReaders = + new HashMap<>(); + private ServiceReference selectedLoggerAdmin; + private ServiceReference selectedLogReader; + private Binding binding; + private boolean closing; + +} diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java deleted file mode 100644 index 1ce6a44bdd..0000000000 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ActivatorTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Licensed 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.felix.logback.internal; - -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.junit.Test; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.service.log.LogReaderService; -import org.osgi.service.log.admin.LoggerAdmin; - -@SuppressWarnings("unchecked") -public class ActivatorTest { - - @Test - public void loggerAdminIsReleased() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); - Activator.LRST lrst = mock(Activator.LRST.class); - when(context.getService(reference)).thenReturn(loggerAdmin); - - Activator.LoggerAdminServiceTracker tracker = - new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); - - assertSame(lrst, tracker.addingService(reference)); - tracker.removedService(reference, lrst); - - verify(lrst).open(); - verify(lrst).close(); - verify(context).ungetService(reference); - } - - @Test - public void loggerAdminIsReleasedWhenOpeningItsTrackerFails() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); - Activator.LRST lrst = mock(Activator.LRST.class); - when(context.getService(reference)).thenReturn(loggerAdmin); - doThrow(new IllegalStateException()).when(lrst).open(); - - Activator.LoggerAdminServiceTracker tracker = - new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); - - assertThrows(IllegalStateException.class, () -> tracker.addingService(reference)); - verify(context).ungetService(reference); - } - - @Test - public void loggerAdminIsReleasedWhenClosingItsTrackerFails() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - Activator.LRST lrst = mock(Activator.LRST.class); - doThrow(new IllegalStateException()).when(lrst).close(); - Activator.LoggerAdminServiceTracker tracker = - new Activator.LoggerAdminServiceTracker(context, (c, a) -> lrst); - - assertThrows( - IllegalStateException.class, - () -> tracker.removedService(reference, lrst)); - verify(context).ungetService(reference); - } - - @Test - public void unavailableLoggerAdminIsNotTrackedOrReleased() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - Activator.LoggerAdminServiceTracker tracker = - new Activator.LoggerAdminServiceTracker(context, (c, a) -> mock(Activator.LRST.class)); - - assertNull(tracker.addingService(reference)); - verify(context, never()).ungetService(reference); - } - - @Test - public void logReaderServiceIsReleased() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - LogReaderService logReaderService = mock(LogReaderService.class); - LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); - LogbackLogListener listener = mock(LogbackLogListener.class); - when(context.getService(reference)).thenReturn(logReaderService); - - Activator.LRST tracker = new Activator.LRST(context, loggerAdmin, a -> listener); - - Activator.Pair pair = tracker.addingService(reference); - assertSame(logReaderService, pair.getKey()); - assertSame(listener, pair.getValue()); - tracker.removedService(reference, pair); - - verify(logReaderService).addLogListener(listener); - verify(logReaderService).removeLogListener(listener); - verify(listener).close(); - verify(context).ungetService(reference); - } - - @Test - public void logReaderServiceIsReleasedWhenListenerRegistrationFails() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - LogReaderService logReaderService = mock(LogReaderService.class); - LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); - LogbackLogListener listener = mock(LogbackLogListener.class); - when(context.getService(reference)).thenReturn(logReaderService); - doThrow(new IllegalStateException()).when(logReaderService).addLogListener(listener); - - Activator.LRST tracker = new Activator.LRST(context, loggerAdmin, a -> listener); - - assertThrows(IllegalStateException.class, () -> tracker.addingService(reference)); - verify(logReaderService).removeLogListener(listener); - verify(listener).close(); - verify(context).ungetService(reference); - } - - @Test - public void logReaderServiceIsReleasedWhenListenerRemovalFails() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - LogReaderService logReaderService = mock(LogReaderService.class); - LogbackLogListener listener = mock(LogbackLogListener.class); - doThrow(new IllegalStateException()).when(logReaderService).removeLogListener(listener); - Activator.LRST tracker = new Activator.LRST( - context, mock(LoggerAdmin.class), a -> listener); - Activator.Pair pair = new Activator.Pair(logReaderService, listener); - - assertThrows( - IllegalStateException.class, - () -> tracker.removedService(reference, pair)); - verify(listener).close(); - verify(context).ungetService(reference); - } - - @Test - public void unavailableLogReaderServiceIsNotTrackedOrReleased() { - BundleContext context = mock(BundleContext.class); - ServiceReference reference = mock(ServiceReference.class); - Activator.LRST tracker = new Activator.LRST( - context, mock(LoggerAdmin.class), a -> mock(LogbackLogListener.class)); - - assertNull(tracker.addingService(reference)); - verify(context, never()).ungetService(reference); - } - -} diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java new file mode 100644 index 0000000000..a2b04d1bc9 --- /dev/null +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java @@ -0,0 +1,190 @@ +/** + * Licensed 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.felix.logback.internal; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.mockito.InOrder; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogReaderService; +import org.osgi.service.log.admin.LoggerAdmin; + +@SuppressWarnings("unchecked") +public class OSGiLogBridgeTest { + + @Test + public void servicesAreReleasedWhenBindingIsRemoved() { + BundleContext context = mock(BundleContext.class); + ServiceReference adminReference = mock(ServiceReference.class); + ServiceReference readerReference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogReaderService logReader = mock(LogReaderService.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + when(context.getService(adminReference)).thenReturn(loggerAdmin); + when(context.getService(readerReference)).thenReturn(logReader); + OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); + + bridge.loggerAdminTracker.addingService(adminReference); + bridge.logReaderTracker.addingService(readerReference); + bridge.logReaderTracker.removedService(readerReference, logReader); + bridge.loggerAdminTracker.removedService(adminReference, loggerAdmin); + + verify(logReader).addLogListener(listener); + verify(logReader).removeLogListener(listener); + verify(listener).close(); + verify(context).ungetService(readerReference); + verify(context).ungetService(adminReference); + } + + @Test + public void highestRankedServicesAreBoundWithFallbackOnRemoval() { + BundleContext context = mock(BundleContext.class); + ServiceReference lowAdminReference = mock(ServiceReference.class); + ServiceReference highAdminReference = mock(ServiceReference.class); + ServiceReference lowReaderReference = mock(ServiceReference.class); + ServiceReference highReaderReference = mock(ServiceReference.class); + LoggerAdmin lowAdmin = mock(LoggerAdmin.class); + LoggerAdmin highAdmin = mock(LoggerAdmin.class); + LogReaderService lowReader = mock(LogReaderService.class); + LogReaderService highReader = mock(LogReaderService.class); + List selectedAdmins = new ArrayList<>(); + List listeners = new ArrayList<>(); + when(context.getService(lowAdminReference)).thenReturn(lowAdmin); + when(context.getService(highAdminReference)).thenReturn(highAdmin); + when(context.getService(lowReaderReference)).thenReturn(lowReader); + when(context.getService(highReaderReference)).thenReturn(highReader); + makeHigher(highAdminReference, lowAdminReference); + makeHigher(highReaderReference, lowReaderReference); + OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> { + LogbackLogListener listener = mock(LogbackLogListener.class); + selectedAdmins.add(admin); + listeners.add(listener); + return listener; + }); + + bridge.loggerAdminTracker.addingService(lowAdminReference); + bridge.logReaderTracker.addingService(lowReaderReference); + bridge.loggerAdminTracker.addingService(highAdminReference); + bridge.logReaderTracker.addingService(highReaderReference); + bridge.logReaderTracker.removedService(highReaderReference, highReader); + bridge.loggerAdminTracker.removedService(highAdminReference, highAdmin); + + InOrder order = inOrder(lowReader, highReader, listeners.get(0), + listeners.get(1), listeners.get(2), listeners.get(3), listeners.get(4)); + order.verify(lowReader).addLogListener(listeners.get(0)); + order.verify(lowReader).removeLogListener(listeners.get(0)); + order.verify(listeners.get(0)).close(); + order.verify(lowReader).addLogListener(listeners.get(1)); + order.verify(lowReader).removeLogListener(listeners.get(1)); + order.verify(listeners.get(1)).close(); + order.verify(highReader).addLogListener(listeners.get(2)); + order.verify(highReader).removeLogListener(listeners.get(2)); + order.verify(listeners.get(2)).close(); + order.verify(lowReader).addLogListener(listeners.get(3)); + order.verify(lowReader).removeLogListener(listeners.get(3)); + order.verify(listeners.get(3)).close(); + order.verify(lowReader).addLogListener(listeners.get(4)); + assertSame(lowAdmin, selectedAdmins.get(0)); + assertSame(highAdmin, selectedAdmins.get(1)); + assertSame(highAdmin, selectedAdmins.get(2)); + assertSame(highAdmin, selectedAdmins.get(3)); + assertSame(lowAdmin, selectedAdmins.get(4)); + verify(lowReader, never()).addLogListener(listeners.get(2)); + verify(highReader, never()).addLogListener(listeners.get(0)); + verify(highReader, never()).addLogListener(listeners.get(1)); + verify(context).ungetService(highReaderReference); + verify(context).ungetService(highAdminReference); + } + + @Test + public void unavailableServicesAreNotTrackedOrReleased() { + BundleContext context = mock(BundleContext.class); + ServiceReference adminReference = mock(ServiceReference.class); + ServiceReference readerReference = mock(ServiceReference.class); + OSGiLogBridge bridge = new OSGiLogBridge( + context, admin -> mock(LogbackLogListener.class)); + + assertNull(bridge.loggerAdminTracker.addingService(adminReference)); + assertNull(bridge.logReaderTracker.addingService(readerReference)); + + verify(context, never()).ungetService(adminReference); + verify(context, never()).ungetService(readerReference); + } + + @Test + public void logReaderIsReleasedWhenListenerRegistrationFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference adminReference = mock(ServiceReference.class); + ServiceReference readerReference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogReaderService logReader = mock(LogReaderService.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + when(context.getService(adminReference)).thenReturn(loggerAdmin); + when(context.getService(readerReference)).thenReturn(logReader); + doThrow(new IllegalStateException()).when(logReader).addLogListener(listener); + OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); + bridge.loggerAdminTracker.addingService(adminReference); + + assertThrows(IllegalStateException.class, + () -> bridge.logReaderTracker.addingService(readerReference)); + + verify(logReader).removeLogListener(listener); + verify(listener).close(); + verify(context).ungetService(readerReference); + } + + @Test + public void logReaderIsReleasedWhenListenerRemovalFails() { + BundleContext context = mock(BundleContext.class); + ServiceReference adminReference = mock(ServiceReference.class); + ServiceReference readerReference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogReaderService logReader = mock(LogReaderService.class); + LogbackLogListener listener = mock(LogbackLogListener.class); + when(context.getService(adminReference)).thenReturn(loggerAdmin); + when(context.getService(readerReference)).thenReturn(logReader); + doThrow(new IllegalStateException()).when(logReader).removeLogListener(listener); + OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); + bridge.loggerAdminTracker.addingService(adminReference); + bridge.logReaderTracker.addingService(readerReference); + + assertThrows(IllegalStateException.class, + () -> bridge.logReaderTracker.removedService(readerReference, logReader)); + + verify(listener).close(); + verify(context).ungetService(readerReference); + } + + private static void makeHigher( + ServiceReference high, ServiceReference low) { + + when(high.compareTo(low)).thenReturn(1); + when(low.compareTo(high)).thenReturn(-1); + } + +} From 0c8da52cce1ab2524ed80775001e628be1068b88 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:21:20 +0200 Subject: [PATCH 10/24] Honor named Logback appender routing --- .../logback/internal/LogbackLogListener.java | 2 +- .../internal/LogbackLogListenerTest.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 0c6f4b1348..1464cf78a7 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -187,7 +187,7 @@ public StackTraceElement[] getCallerData() { } }); - rootLogger.callAppenders(le); + logger.callAppenders(le); } @Override diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java index 394c2665a1..ace9c4292f 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -29,12 +29,15 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import org.osgi.service.log.LogEntry; import org.osgi.service.log.LogLevel; import org.osgi.service.log.admin.LoggerAdmin; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; public class LogbackLogListenerTest { @@ -89,6 +92,39 @@ public void closePreservesLogLevelsChangedByAnotherOwner() { assertEquals(expected, levels.get()); } + @Test + public void loggedUsesNamedLoggerAppenderRouting() { + LoggerContext loggerContext = new LoggerContext(); + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.TRACE); + Logger named = loggerContext.getLogger("test.named"); + named.setLevel(Level.INFO); + named.setAdditive(false); + ListAppender rootAppender = new ListAppender<>(); + ListAppender namedAppender = new ListAppender<>(); + rootAppender.setContext(loggerContext); + rootAppender.start(); + namedAppender.setContext(loggerContext); + namedAppender.start(); + root.addAppender(rootAppender); + named.addAppender(namedAppender); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + mockLogLevels(loggerAdmin, osgiLoggerContext, new HashMap<>()); + LogEntry entry = mock(LogEntry.class); + when(entry.getLoggerName()).thenReturn(named.getName()); + when(entry.getMessage()).thenReturn("message"); + when(entry.getLogLevel()).thenReturn(LogLevel.INFO); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + listener.logged(entry); + + assertEquals(1, namedAppender.list.size()); + assertTrue(rootAppender.list.isEmpty()); + listener.close(); + } + private static AtomicReference> mockLogLevels( LoggerAdmin loggerAdmin, org.osgi.service.log.admin.LoggerContext osgiLoggerContext, From 1deb8e0f2704943c315160bd5ecfa813a0f87600 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:21:59 +0200 Subject: [PATCH 11/24] Propagate OSGi log entry sequence numbers --- .../logback/internal/LogbackLogListener.java | 25 +------------------ .../internal/LogbackLogListenerTest.java | 2 ++ 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 1464cf78a7..4e116f220b 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -14,9 +14,6 @@ package org.apache.felix.logback.internal; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -48,19 +45,6 @@ public class LogbackLogListener implements AutoCloseable, LogListener, LoggerCon private static final String EVENTS_SERVICE = "Events.Service"; private static final String LOG_SERVICE = "LogService"; - private static final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); - private static Optional setSequenceNumber = Optional.empty(); - - static { - MethodType mt = MethodType.methodType(void.class, long.class); - try { - MethodHandle methodHandle = publicLookup.findVirtual(LoggingEvent.class, "setSquenceNumber", mt); - - setSequenceNumber = Optional.of(methodHandle); - } catch (NoSuchMethodException | IllegalAccessException e) { - } - } - volatile LoggerContext loggerContext; volatile Logger rootLogger; volatile LoggerContextVO loggerContextVO; @@ -178,14 +162,7 @@ public StackTraceElement[] getCallerData() { le.setThreadName(entry.getThreadInfo()); le.setThrowableProxy(getThrowableProxy(entry.getException())); le.setTimeStamp(entry.getTime()); - - setSequenceNumber.ifPresent(mh -> { - try { - mh.bindTo(le).invokeExact(entry.getSequence()); - } catch (Throwable e) { - e.printStackTrace(); - } - }); + le.setSequenceNumber(entry.getSequence()); logger.callAppenders(le); } diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java index ace9c4292f..43a36eb779 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -116,11 +116,13 @@ public void loggedUsesNamedLoggerAppenderRouting() { when(entry.getLoggerName()).thenReturn(named.getName()); when(entry.getMessage()).thenReturn("message"); when(entry.getLogLevel()).thenReturn(LogLevel.INFO); + when(entry.getSequence()).thenReturn(42L); LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); listener.logged(entry); assertEquals(1, namedAppender.list.size()); + assertEquals(42L, namedAppender.list.get(0).getSequenceNumber()); assertTrue(rootAppender.list.isEmpty()); listener.close(); } From 75c30f27cff3e046343ed061e67dfeab9a5dc8f4 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:23:43 +0200 Subject: [PATCH 12/24] Honor Logback root and OFF levels --- .../logback/internal/LogbackLogListener.java | 12 ++-- .../internal/LogbackLogListenerTest.java | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 4e116f220b..0cb3d87ec0 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -137,7 +137,7 @@ else if (loggerName.startsWith(EVENTS_SERVICE)) { // Check to see if there's a logger defined in our configuration and // if there is, then make sure it's handled as an override for the // effective level. - if (!logger.equals(rootLogger) && !logger.isEnabledFor(level)) { + if (!logger.isEnabledFor(level)) { return; } @@ -173,8 +173,7 @@ public void onLevelChange(Logger logger, Level level) { return; } - updateLogLevel(logger.getName(), - Level.OFF.equals(level) ? Optional.empty() : Optional.of(from(level))); + updateLogLevel(logger.getName(), Optional.of(from(level))); } @Override @@ -215,7 +214,7 @@ String formatBundle(Bundle bundle, String loggerName) { } LogLevel from(Level level) { - if (Level.ALL.equals(level)) { + if (Level.ALL.equals(level) || Level.OFF.equals(level)) { return LogLevel.TRACE; } else if (Level.DEBUG.equals(level)) { @@ -307,10 +306,7 @@ Map updateLevels(LoggerContext loggerContext, Map appender = new ListAppender<>(); + appender.setContext(loggerContext); + appender.start(); + root.addAppender(appender); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + mockLogLevels(loggerAdmin, osgiLoggerContext, new HashMap<>()); + LogEntry entry = mock(LogEntry.class); + when(entry.getLoggerName()).thenReturn(Logger.ROOT_LOGGER_NAME); + when(entry.getMessage()).thenReturn("message"); + when(entry.getLogLevel()).thenReturn(LogLevel.INFO); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + listener.logged(entry); + + assertTrue(appender.list.isEmpty()); + listener.close(); + } + + @Test + public void offLevelIsFilteredByLogback() { + LoggerContext loggerContext = new LoggerContext(); + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + Logger named = loggerContext.getLogger("test.off"); + named.setLevel(Level.OFF); + ListAppender appender = new ListAppender<>(); + appender.setContext(loggerContext); + appender.start(); + root.addAppender(appender); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + AtomicReference> levels = mockLogLevels( + loggerAdmin, osgiLoggerContext, new HashMap<>()); + LogEntry entry = mock(LogEntry.class); + when(entry.getLoggerName()).thenReturn(named.getName()); + when(entry.getMessage()).thenReturn("message"); + when(entry.getLogLevel()).thenReturn(LogLevel.ERROR); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + listener.onLevelChange(named, Level.OFF); + listener.logged(entry); + + assertEquals(LogLevel.TRACE, levels.get().get(named.getName())); + assertTrue(appender.list.isEmpty()); + listener.close(); + } + private static AtomicReference> mockLogLevels( LoggerAdmin loggerAdmin, org.osgi.service.log.admin.LoggerContext osgiLoggerContext, From 4d0e4193f240c1d12af776fd424441279680a7cf Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 21:57:01 +0200 Subject: [PATCH 13/24] Remove stale inherited OSGi log levels --- .../logback/internal/LogbackLogListener.java | 7 ++++- .../internal/LogbackLogListenerTest.java | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 0cb3d87ec0..1f92978fac 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -173,7 +173,12 @@ public void onLevelChange(Logger logger, Level level) { return; } - updateLogLevel(logger.getName(), Optional.of(from(level))); + Level configuredLevel = logger.getLevel(); + + updateLogLevel( + logger.getName(), + configuredLevel == null ? + Optional.empty() : Optional.of(from(configuredLevel))); } @Override diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java index 519b0fed50..4e62733777 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -92,6 +92,35 @@ public void closePreservesLogLevelsChangedByAnotherOwner() { assertEquals(expected, levels.get()); } + @Test + public void clearedNamedLevelContinuesToInheritChangedRootLevel() { + LoggerContext loggerContext = new LoggerContext(); + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + Logger named = loggerContext.getLogger("test.inherited"); + named.setLevel(Level.ERROR); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + Map initialLevels = new HashMap<>(); + initialLevels.put(named.getName(), LogLevel.WARN); + AtomicReference> levels = mockLogLevels( + loggerAdmin, osgiLoggerContext, initialLevels); + + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + assertEquals(LogLevel.ERROR, levels.get().get(named.getName())); + + named.setLevel(null); + assertFalse(levels.get().containsKey(named.getName())); + + root.setLevel(Level.TRACE); + assertEquals(LogLevel.TRACE, levels.get().get(Logger.ROOT_LOGGER_NAME)); + assertFalse(levels.get().containsKey(named.getName())); + + listener.close(); + assertEquals(initialLevels, levels.get()); + } + @Test public void loggedUsesNamedLoggerAppenderRouting() { LoggerContext loggerContext = new LoggerContext(); From fef051852cc02a744ac3d625db2020c02c90312d Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:00:30 +0200 Subject: [PATCH 14/24] Restore OSGi R7 framework compatibility --- logback/itests/itest-parent/pom.xml | 2 +- logback/logback/pom.xml | 13 +++--- .../felix/logback/internal/ManifestTest.java | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java diff --git a/logback/itests/itest-parent/pom.xml b/logback/itests/itest-parent/pom.xml index f6e44bb34a..a3bb0f8c75 100644 --- a/logback/itests/itest-parent/pom.xml +++ b/logback/itests/itest-parent/pom.xml @@ -83,7 +83,7 @@ org.osgi osgi.core - 6.0.0 + 7.0.0 provided diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index d68f39ded6..408b3b5d98 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -70,15 +70,15 @@ ${logback.version} - org.eclipse.platform - org.eclipse.osgi - 3.17.0 + org.osgi + osgi.core + 7.0.0 provided - org.osgi - osgi.core - 6.0.0 + org.eclipse.platform + org.eclipse.osgi + 3.17.0 provided @@ -118,6 +118,7 @@ !org.eclipse.osgi.internal.hookregistry,\ ch.qos.logback.classic.*;version="[1.5,2)",\ org.slf4j.*;version="[2.0,3)",\ + org.osgi.framework;version="[1.9,2)",\ * -conditionalpackage: org.slf4j.bridge ]]> diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java new file mode 100644 index 0000000000..b86cb816e6 --- /dev/null +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java @@ -0,0 +1,45 @@ +/** + * Licensed 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.felix.logback.internal; + +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.Manifest; + +import org.junit.Test; + +public class ManifestTest { + + @Test + public void frameworkImportSupportsOSGiR7() throws Exception { + Path classes = Paths.get(Activator.class.getProtectionDomain( + ).getCodeSource().getLocation().toURI()); + + try (InputStream input = Files.newInputStream( + classes.resolve("META-INF/MANIFEST.MF"))) { + + String imports = new Manifest(input).getMainAttributes().getValue( + "Import-Package"); + + assertTrue(imports, imports.contains( + "org.osgi.framework;version=\"[1.9,2)\"")); + } + } + +} From 6c76d79988b4be7e6add790fc4c6afc1fb9c849d Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:03:38 +0200 Subject: [PATCH 15/24] Bind coherent OSGi Log service providers --- .../felix/logback/internal/OSGiLogBridge.java | 42 +++++- .../logback/internal/OSGiLogBridgeTest.java | 126 +++++++++++------- 2 files changed, 118 insertions(+), 50 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java index ffc18277ec..1bd268c987 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java @@ -18,8 +18,10 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.log.LogReaderService; @@ -199,10 +201,10 @@ private synchronized void rebind() { return; } - ServiceReference loggerAdminReference = - highestRanked(loggerAdmins); ServiceReference logReaderReference = - highestRanked(logReaders); + selectLogReader(); + ServiceReference loggerAdminReference = + selectLoggerAdmin(logReaderReference); if (Objects.equals(selectedLoggerAdmin, loggerAdminReference) && Objects.equals(selectedLogReader, logReaderReference)) { @@ -252,10 +254,40 @@ private Binding createBinding( } } + private ServiceReference selectLogReader() { + return highestRanked(logReaders, reference -> { + Bundle provider = reference.getBundle(); + + return provider != null && highestRanked( + loggerAdmins, + adminReference -> provider.equals(adminReference.getBundle())) != null; + }); + } + + private ServiceReference selectLoggerAdmin( + ServiceReference logReaderReference) { + + if (logReaderReference == null) { + return null; + } + + Bundle provider = logReaderReference.getBundle(); + + if (provider == null) { + return null; + } + + return highestRanked( + loggerAdmins, + reference -> provider.equals(reference.getBundle())); + } + private static ServiceReference highestRanked( - Map, S> services) { + Map, S> services, + Predicate> selector) { - return services.keySet().stream().max(ServiceReference::compareTo).orElse(null); + return services.keySet().stream().filter(selector).max( + ServiceReference::compareTo).orElse(null); } private static Throwable runClose(Runnable action, Throwable failure) { diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java index a2b04d1bc9..585216613e 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/OSGiLogBridgeTest.java @@ -26,9 +26,12 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import org.junit.Test; import org.mockito.InOrder; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.log.LogReaderService; @@ -45,8 +48,11 @@ public void servicesAreReleasedWhenBindingIsRemoved() { LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); LogReaderService logReader = mock(LogReaderService.class); LogbackLogListener listener = mock(LogbackLogListener.class); + Bundle provider = mock(Bundle.class); when(context.getService(adminReference)).thenReturn(loggerAdmin); when(context.getService(readerReference)).thenReturn(logReader); + when(adminReference.getBundle()).thenReturn(provider); + when(readerReference.getBundle()).thenReturn(provider); OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); bridge.loggerAdminTracker.addingService(adminReference); @@ -62,24 +68,34 @@ public void servicesAreReleasedWhenBindingIsRemoved() { } @Test - public void highestRankedServicesAreBoundWithFallbackOnRemoval() { + public void readerRankingSelectsCoherentProviderPairs() { BundleContext context = mock(BundleContext.class); - ServiceReference lowAdminReference = mock(ServiceReference.class); - ServiceReference highAdminReference = mock(ServiceReference.class); - ServiceReference lowReaderReference = mock(ServiceReference.class); - ServiceReference highReaderReference = mock(ServiceReference.class); - LoggerAdmin lowAdmin = mock(LoggerAdmin.class); - LoggerAdmin highAdmin = mock(LoggerAdmin.class); - LogReaderService lowReader = mock(LogReaderService.class); - LogReaderService highReader = mock(LogReaderService.class); + ServiceReference adminAReference = mock(ServiceReference.class); + ServiceReference adminBReference = mock(ServiceReference.class); + ServiceReference readerAReference = mock(ServiceReference.class); + ServiceReference readerBReference = mock(ServiceReference.class); + LoggerAdmin adminA = mock(LoggerAdmin.class); + LoggerAdmin adminB = mock(LoggerAdmin.class); + LogReaderService readerA = mock(LogReaderService.class); + LogReaderService readerB = mock(LogReaderService.class); + Bundle providerA = mock(Bundle.class); + Bundle providerB = mock(Bundle.class); + AtomicBoolean readerBIsHigher = new AtomicBoolean(); List selectedAdmins = new ArrayList<>(); List listeners = new ArrayList<>(); - when(context.getService(lowAdminReference)).thenReturn(lowAdmin); - when(context.getService(highAdminReference)).thenReturn(highAdmin); - when(context.getService(lowReaderReference)).thenReturn(lowReader); - when(context.getService(highReaderReference)).thenReturn(highReader); - makeHigher(highAdminReference, lowAdminReference); - makeHigher(highReaderReference, lowReaderReference); + when(context.getService(adminAReference)).thenReturn(adminA); + when(context.getService(adminBReference)).thenReturn(adminB); + when(context.getService(readerAReference)).thenReturn(readerA); + when(context.getService(readerBReference)).thenReturn(readerB); + when(adminAReference.getBundle()).thenReturn(providerA); + when(readerAReference.getBundle()).thenReturn(providerA); + when(adminBReference.getBundle()).thenReturn(providerB); + when(readerBReference.getBundle()).thenReturn(providerB); + makeHigher(adminBReference, adminAReference); + when(readerAReference.compareTo(readerBReference)).thenAnswer( + invocation -> readerBIsHigher.get() ? -1 : 1); + when(readerBReference.compareTo(readerAReference)).thenAnswer( + invocation -> readerBIsHigher.get() ? 1 : -1); OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> { LogbackLogListener listener = mock(LogbackLogListener.class); selectedAdmins.add(admin); @@ -87,38 +103,52 @@ public void highestRankedServicesAreBoundWithFallbackOnRemoval() { return listener; }); - bridge.loggerAdminTracker.addingService(lowAdminReference); - bridge.logReaderTracker.addingService(lowReaderReference); - bridge.loggerAdminTracker.addingService(highAdminReference); - bridge.logReaderTracker.addingService(highReaderReference); - bridge.logReaderTracker.removedService(highReaderReference, highReader); - bridge.loggerAdminTracker.removedService(highAdminReference, highAdmin); - - InOrder order = inOrder(lowReader, highReader, listeners.get(0), - listeners.get(1), listeners.get(2), listeners.get(3), listeners.get(4)); - order.verify(lowReader).addLogListener(listeners.get(0)); - order.verify(lowReader).removeLogListener(listeners.get(0)); + bridge.loggerAdminTracker.addingService(adminAReference); + bridge.logReaderTracker.addingService(readerAReference); + bridge.loggerAdminTracker.addingService(adminBReference); + bridge.logReaderTracker.addingService(readerBReference); + readerBIsHigher.set(true); + bridge.logReaderTracker.modifiedService(readerBReference, readerB); + bridge.logReaderTracker.removedService(readerBReference, readerB); + + InOrder order = inOrder(readerA, readerB, listeners.get(0), + listeners.get(1), listeners.get(2)); + order.verify(readerA).addLogListener(listeners.get(0)); + order.verify(readerA).removeLogListener(listeners.get(0)); order.verify(listeners.get(0)).close(); - order.verify(lowReader).addLogListener(listeners.get(1)); - order.verify(lowReader).removeLogListener(listeners.get(1)); + order.verify(readerB).addLogListener(listeners.get(1)); + order.verify(readerB).removeLogListener(listeners.get(1)); order.verify(listeners.get(1)).close(); - order.verify(highReader).addLogListener(listeners.get(2)); - order.verify(highReader).removeLogListener(listeners.get(2)); - order.verify(listeners.get(2)).close(); - order.verify(lowReader).addLogListener(listeners.get(3)); - order.verify(lowReader).removeLogListener(listeners.get(3)); - order.verify(listeners.get(3)).close(); - order.verify(lowReader).addLogListener(listeners.get(4)); - assertSame(lowAdmin, selectedAdmins.get(0)); - assertSame(highAdmin, selectedAdmins.get(1)); - assertSame(highAdmin, selectedAdmins.get(2)); - assertSame(highAdmin, selectedAdmins.get(3)); - assertSame(lowAdmin, selectedAdmins.get(4)); - verify(lowReader, never()).addLogListener(listeners.get(2)); - verify(highReader, never()).addLogListener(listeners.get(0)); - verify(highReader, never()).addLogListener(listeners.get(1)); - verify(context).ungetService(highReaderReference); - verify(context).ungetService(highAdminReference); + order.verify(readerA).addLogListener(listeners.get(2)); + assertSame(adminA, selectedAdmins.get(0)); + assertSame(adminB, selectedAdmins.get(1)); + assertSame(adminA, selectedAdmins.get(2)); + verify(readerA, never()).addLogListener(listeners.get(1)); + verify(readerB, never()).addLogListener(listeners.get(0)); + verify(readerB, never()).addLogListener(listeners.get(2)); + verify(context).ungetService(readerBReference); + } + + @Test + public void servicesFromDifferentProvidersAreNotBound() { + BundleContext context = mock(BundleContext.class); + ServiceReference adminReference = mock(ServiceReference.class); + ServiceReference readerReference = mock(ServiceReference.class); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + LogReaderService logReader = mock(LogReaderService.class); + Function listenerFactory = mock(Function.class); + when(context.getService(adminReference)).thenReturn(loggerAdmin); + when(context.getService(readerReference)).thenReturn(logReader); + when(adminReference.getBundle()).thenReturn(mock(Bundle.class)); + when(readerReference.getBundle()).thenReturn(mock(Bundle.class)); + OSGiLogBridge bridge = new OSGiLogBridge(context, listenerFactory); + + bridge.loggerAdminTracker.addingService(adminReference); + bridge.logReaderTracker.addingService(readerReference); + + verify(listenerFactory, never()).apply(loggerAdmin); + verify(logReader, never()).addLogListener( + org.mockito.ArgumentMatchers.any(LogbackLogListener.class)); } @Test @@ -144,8 +174,11 @@ public void logReaderIsReleasedWhenListenerRegistrationFails() { LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); LogReaderService logReader = mock(LogReaderService.class); LogbackLogListener listener = mock(LogbackLogListener.class); + Bundle provider = mock(Bundle.class); when(context.getService(adminReference)).thenReturn(loggerAdmin); when(context.getService(readerReference)).thenReturn(logReader); + when(adminReference.getBundle()).thenReturn(provider); + when(readerReference.getBundle()).thenReturn(provider); doThrow(new IllegalStateException()).when(logReader).addLogListener(listener); OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); bridge.loggerAdminTracker.addingService(adminReference); @@ -166,8 +199,11 @@ public void logReaderIsReleasedWhenListenerRemovalFails() { LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); LogReaderService logReader = mock(LogReaderService.class); LogbackLogListener listener = mock(LogbackLogListener.class); + Bundle provider = mock(Bundle.class); when(context.getService(adminReference)).thenReturn(loggerAdmin); when(context.getService(readerReference)).thenReturn(logReader); + when(adminReference.getBundle()).thenReturn(provider); + when(readerReference.getBundle()).thenReturn(provider); doThrow(new IllegalStateException()).when(logReader).removeLogListener(listener); OSGiLogBridge bridge = new OSGiLogBridge(context, admin -> listener); bridge.loggerAdminTracker.addingService(adminReference); From 33ad25698cb742b0a3e2c23e9bab65e88403ab81 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:04:39 +0200 Subject: [PATCH 16/24] Handle bundle-less OSGi log entries --- .../logback/internal/LogbackLogListener.java | 8 ++- .../internal/LogbackLogListenerTest.java | 50 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 1f92978fac..8ade57b27b 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -209,12 +209,18 @@ public void onReset(LoggerContext context) { } String formatBundle(Bundle bundle, String loggerName) { + String symbolicName = bundle == null ? null : bundle.getSymbolicName(); + + if (symbolicName == null) { + return loggerName; + } + return new StringBuilder().append( loggerName ).append( "." ).append( - bundle.getSymbolicName() + symbolicName ).toString(); } diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java index 4e62733777..93a66514c8 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import org.osgi.framework.Bundle; import org.osgi.service.log.LogEntry; import org.osgi.service.log.LogLevel; import org.osgi.service.log.admin.LoggerAdmin; @@ -211,6 +212,55 @@ public void offLevelIsFilteredByLogback() { listener.close(); } + @Test + public void bundleLessEventEntriesKeepBaseLoggerNames() { + LoggerContext loggerContext = new LoggerContext(); + Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.TRACE); + ListAppender appender = new ListAppender<>(); + appender.setContext(loggerContext); + appender.start(); + root.addAppender(appender); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + mockLogLevels(loggerAdmin, osgiLoggerContext, new HashMap<>()); + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + String[] loggerNames = { + "Events.Bundle", "Events.Framework", "Events.Service", "LogService" + }; + + for (String loggerName : loggerNames) { + LogEntry entry = mock(LogEntry.class); + when(entry.getLoggerName()).thenReturn(loggerName); + when(entry.getMessage()).thenReturn("message"); + when(entry.getLogLevel()).thenReturn(LogLevel.INFO); + + listener.logged(entry); + } + + assertEquals(loggerNames.length, appender.list.size()); + for (int i = 0; i < loggerNames.length; i++) { + assertEquals(loggerNames[i], appender.list.get(i).getLoggerName()); + } + listener.close(); + } + + @Test + public void bundleWithoutSymbolicNameKeepsBaseLoggerName() { + LoggerContext loggerContext = new LoggerContext(); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.TRACE); + LoggerAdmin loggerAdmin = mock(LoggerAdmin.class); + org.osgi.service.log.admin.LoggerContext osgiLoggerContext = + mock(org.osgi.service.log.admin.LoggerContext.class); + mockLogLevels(loggerAdmin, osgiLoggerContext, new HashMap<>()); + LogbackLogListener listener = new LogbackLogListener(loggerAdmin, loggerContext); + + assertEquals("Events.Bundle", listener.formatBundle( + mock(Bundle.class), "Events.Bundle")); + listener.close(); + } + private static AtomicReference> mockLogLevels( LoggerAdmin loggerAdmin, org.osgi.service.log.admin.LoggerContext osgiLoggerContext, From 26203cd797e1235f1f94bf6aed779b6ebd821611 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:16:56 +0200 Subject: [PATCH 17/24] Add Logback class Javadocs --- .../felix/logback/internal/Activator.java | 4 ++++ .../logback/internal/EquinoxHookSupport.java | 4 ++++ .../felix/logback/internal/JULBridge.java | 3 +++ .../logback/internal/LogbackLogListener.java | 4 ++++ .../felix/logback/internal/OSGiLogBridge.java | 20 +++++++++++++++++++ 5 files changed, 35 insertions(+) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java index d931d69c7f..f99be70eb8 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java @@ -20,6 +20,10 @@ import org.osgi.framework.Constants; import org.slf4j.LoggerFactory; +/** + * Starts and stops the services that make Logback the common backend for JUL + * and the OSGi Log Service. + */ @Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") public class Activator implements BundleActivator { diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java index 1b05bd8338..a0ce3fcaa9 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java @@ -19,6 +19,10 @@ import org.eclipse.osgi.internal.hookregistry.HookRegistry; import org.osgi.framework.BundleActivator; +/** + * Makes Felix Logback available during Equinox framework startup before + * ordinary bundles are activated. + */ public class EquinoxHookSupport implements ActivatorHookFactory, HookConfigurator { @Override diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java index 97fd485f83..ee512ea1a1 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java @@ -20,6 +20,9 @@ import org.slf4j.bridge.SLF4JBridgeHandler; +/** + * Allows JUL records to use the Logback backend shared by the application. + */ final class JULBridge { private Logger rootLogger; diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 8ade57b27b..cc84af77b1 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -38,6 +38,10 @@ import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; +/** + * Presents OSGi Log Service entries to Logback and keeps OSGi log levels + * aligned with the Logback configuration. + */ public class LogbackLogListener implements AutoCloseable, LogListener, LoggerContextListener { private static final String EVENTS_BUNDLE = "Events.Bundle"; diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java index 1bd268c987..0a41ed38cc 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java @@ -28,6 +28,10 @@ import org.osgi.service.log.admin.LoggerAdmin; import org.osgi.util.tracker.ServiceTracker; +/** + * Maintains the active connection between Felix Logback and an available OSGi + * Log Service provider. + */ final class OSGiLogBridge implements AutoCloseable { OSGiLogBridge(BundleContext context) { @@ -333,6 +337,10 @@ private static R tccl(Supplier action) { } } + /** + * Represents an active OSGi-to-Logback connection whose resources share a + * common lifetime. + */ private final class Binding implements AutoCloseable { private Binding( @@ -360,6 +368,12 @@ public void close() { } + /** + * Keeps the bridge's candidate set synchronized with the services + * available from OSGi. + * + * @param the tracked service type + */ static final class RankedServiceTracker extends ServiceTracker { @@ -417,6 +431,12 @@ public void removedService(ServiceReference reference, S service) { } + /** + * Receives service lifecycle changes that may require the bridge to select + * a new provider. + * + * @param the changing service type + */ private interface ServiceChangeHandler { void added(ServiceReference reference, S service); From fbae46b53082011ba5cd84f86b07b9eec8914b73 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:36:30 +0200 Subject: [PATCH 18/24] Externalize JUL bridge bundle --- .../immediate-equinox-logservice/itest.bndrun | 1 + .../immediate-felix-logservice/itest.bndrun | 1 + logback/itests/immediate-jbl/itest.bndrun | 1 + logback/itests/immediate-jcl/itest.bndrun | 1 + .../itests/immediate-jul-felix/itest.bndrun | 1 + logback/itests/immediate-jul/itest.bndrun | 1 + logback/itests/immediate-log4j1/itest.bndrun | 1 + logback/itests/immediate-log4j2/itest.bndrun | 1 + logback/itests/immediate-slf4j/itest.bndrun | 1 + .../standard-equinox-logservice/itest.bndrun | 1 + .../standard-felix-logservice/itest.bndrun | 1 + logback/itests/standard-jbl/itest.bndrun | 1 + logback/itests/standard-jul/itest.bndrun | 1 + logback/logback/pom.xml | 2 -- .../felix/logback/internal/ManifestTest.java | 25 +++++++++++++++---- 15 files changed, 33 insertions(+), 7 deletions(-) diff --git a/logback/itests/immediate-equinox-logservice/itest.bndrun b/logback/itests/immediate-equinox-logservice/itest.bndrun index fd4ab0a40f..7639e00e52 100644 --- a/logback/itests/immediate-equinox-logservice/itest.bndrun +++ b/logback/itests/immediate-equinox-logservice/itest.bndrun @@ -9,6 +9,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.logback;version=latest -runbundles: \ diff --git a/logback/itests/immediate-felix-logservice/itest.bndrun b/logback/itests/immediate-felix-logservice/itest.bndrun index 5ba81c03b0..3e96a854a4 100644 --- a/logback/itests/immediate-felix-logservice/itest.bndrun +++ b/logback/itests/immediate-felix-logservice/itest.bndrun @@ -10,6 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-jbl/itest.bndrun b/logback/itests/immediate-jbl/itest.bndrun index 2f68e5757c..8de52d2e27 100644 --- a/logback/itests/immediate-jbl/itest.bndrun +++ b/logback/itests/immediate-jbl/itest.bndrun @@ -10,6 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-jcl/itest.bndrun b/logback/itests/immediate-jcl/itest.bndrun index 6fcf4845eb..08be049fde 100644 --- a/logback/itests/immediate-jcl/itest.bndrun +++ b/logback/itests/immediate-jcl/itest.bndrun @@ -11,6 +11,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-jul-felix/itest.bndrun b/logback/itests/immediate-jul-felix/itest.bndrun index e26b602f8b..c7c0bb62b5 100644 --- a/logback/itests/immediate-jul-felix/itest.bndrun +++ b/logback/itests/immediate-jul-felix/itest.bndrun @@ -10,6 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-jul/itest.bndrun b/logback/itests/immediate-jul/itest.bndrun index 72a2a80f47..c4d4f05f21 100644 --- a/logback/itests/immediate-jul/itest.bndrun +++ b/logback/itests/immediate-jul/itest.bndrun @@ -9,6 +9,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.logback;version=latest -runsystempackages: \ diff --git a/logback/itests/immediate-log4j1/itest.bndrun b/logback/itests/immediate-log4j1/itest.bndrun index 867c153c28..29d744d243 100644 --- a/logback/itests/immediate-log4j1/itest.bndrun +++ b/logback/itests/immediate-log4j1/itest.bndrun @@ -10,6 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-log4j2/itest.bndrun b/logback/itests/immediate-log4j2/itest.bndrun index 70e116be72..7fdfde2d00 100644 --- a/logback/itests/immediate-log4j2/itest.bndrun +++ b/logback/itests/immediate-log4j2/itest.bndrun @@ -12,6 +12,7 @@ org.apache.logging.log4j.api;version=latest,\ org.apache.logging.log4j.to.slf4j;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/immediate-slf4j/itest.bndrun b/logback/itests/immediate-slf4j/itest.bndrun index 36fd407819..794b7d9493 100644 --- a/logback/itests/immediate-slf4j/itest.bndrun +++ b/logback/itests/immediate-slf4j/itest.bndrun @@ -10,6 +10,7 @@ ch.qos.logback.classic;version=latest,\ ch.qos.logback.core;version=latest,\ slf4j.api;version=latest,\ + jul.to.slf4j;version=latest,\ org.apache.felix.log;version=latest,\ org.apache.felix.logback;version=latest diff --git a/logback/itests/standard-equinox-logservice/itest.bndrun b/logback/itests/standard-equinox-logservice/itest.bndrun index 6b2344a9d7..eb90f8ec0a 100644 --- a/logback/itests/standard-equinox-logservice/itest.bndrun +++ b/logback/itests/standard-equinox-logservice/itest.bndrun @@ -15,6 +15,7 @@ ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ ch.qos.logback.core;version='[1.5.38,1.5.39)',\ jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.logback;version='[2.0.0,2.0.1)',\ org.apache.felix.logback.itests.standard.equinox.logservice;version='[2.0.0,2.0.1)',\ diff --git a/logback/itests/standard-felix-logservice/itest.bndrun b/logback/itests/standard-felix-logservice/itest.bndrun index 9e01874dab..b1bab2153e 100644 --- a/logback/itests/standard-felix-logservice/itest.bndrun +++ b/logback/itests/standard-felix-logservice/itest.bndrun @@ -15,6 +15,7 @@ ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ ch.qos.logback.core;version='[1.5.38,1.5.39)',\ jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ diff --git a/logback/itests/standard-jbl/itest.bndrun b/logback/itests/standard-jbl/itest.bndrun index f604ca1b24..e1cf25ff88 100644 --- a/logback/itests/standard-jbl/itest.bndrun +++ b/logback/itests/standard-jbl/itest.bndrun @@ -16,6 +16,7 @@ ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ ch.qos.logback.core;version='[1.5.38,1.5.39)',\ jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ diff --git a/logback/itests/standard-jul/itest.bndrun b/logback/itests/standard-jul/itest.bndrun index b44e88f98c..7f9820cfe0 100644 --- a/logback/itests/standard-jul/itest.bndrun +++ b/logback/itests/standard-jul/itest.bndrun @@ -13,6 +13,7 @@ ch.qos.logback.classic;version='[1.5.38,1.5.39)',\ ch.qos.logback.core;version='[1.5.38,1.5.39)',\ jakarta.servlet-api;version='[5.0.0,5.0.1)',\ + jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.apache.felix.log;version='[1.2.5,1.2.6)',\ diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 408b3b5d98..f82ffa522b 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -91,7 +91,6 @@ org.slf4j jul-to-slf4j ${slf4j.api.version} - provided junit @@ -120,7 +119,6 @@ org.slf4j.*;version="[2.0,3)",\ org.osgi.framework;version="[1.9,2)",\ * - -conditionalpackage: org.slf4j.bridge ]]> diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java index b86cb816e6..95b904e544 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java @@ -14,6 +14,7 @@ package org.apache.felix.logback.internal; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.InputStream; @@ -28,17 +29,31 @@ public class ManifestTest { @Test public void frameworkImportSupportsOSGiR7() throws Exception { + String imports = getManifestHeader("Import-Package"); + + assertTrue(imports, imports.contains( + "org.osgi.framework;version=\"[1.9,2)\"")); + } + + @Test + public void importsJULBridgeBundle() throws Exception { + String imports = getManifestHeader("Import-Package"); + String privatePackages = getManifestHeader("Private-Package"); + + assertTrue(imports, imports.contains( + "org.slf4j.bridge;version=\"[2.0,3)\"")); + assertFalse(privatePackages, privatePackages.contains( + "org.slf4j.bridge")); + } + + private static String getManifestHeader(String name) throws Exception { Path classes = Paths.get(Activator.class.getProtectionDomain( ).getCodeSource().getLocation().toURI()); try (InputStream input = Files.newInputStream( classes.resolve("META-INF/MANIFEST.MF"))) { - String imports = new Manifest(input).getMainAttributes().getValue( - "Import-Package"); - - assertTrue(imports, imports.contains( - "org.osgi.framework;version=\"[1.9,2)\"")); + return new Manifest(input).getMainAttributes().getValue(name); } } From e1402210a4136fb07142277c2bb3f48dfb11db85 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:39:08 +0200 Subject: [PATCH 19/24] Update Logback SCM URL --- logback/itests/itest-parent/pom.xml | 2 +- logback/itests/pom.xml | 2 +- logback/logback/pom.xml | 6 +++--- .../org/apache/felix/logback/internal/ManifestTest.java | 8 ++++++++ logback/pom.xml | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/logback/itests/itest-parent/pom.xml b/logback/itests/itest-parent/pom.xml index a3bb0f8c75..84726dfb56 100644 --- a/logback/itests/itest-parent/pom.xml +++ b/logback/itests/itest-parent/pom.xml @@ -33,7 +33,7 @@ scm:git:https://github.com/apache/felix-dev.git scm:git:https://github.com/apache/felix-dev.git - https://gitbox.apache.org/repos/asf?p=felix-dev.git + https://github.com/apache/felix-dev diff --git a/logback/itests/pom.xml b/logback/itests/pom.xml index 2f8ed6e4a3..201df31543 100644 --- a/logback/itests/pom.xml +++ b/logback/itests/pom.xml @@ -29,7 +29,7 @@ scm:git:https://github.com/apache/felix-dev.git scm:git:https://github.com/apache/felix-dev.git - https://gitbox.apache.org/repos/asf?p=felix-dev.git + https://github.com/apache/felix-dev diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index f82ffa522b..217258f490 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -35,9 +35,9 @@ scm:git:https://github.com/apache/felix-dev.git scm:git:https://github.com/apache/felix-dev.git - https://gitbox.apache.org/repos/asf?p=felix-dev.git - HEAD - + https://github.com/apache/felix-dev + HEAD + 6.1.0 diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java index 95b904e544..dd89bae1cc 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java @@ -46,6 +46,14 @@ public void importsJULBridgeBundle() throws Exception { "org.slf4j.bridge")); } + @Test + public void scmPointsToGitHubRepository() throws Exception { + String scm = getManifestHeader("Bundle-SCM"); + + assertTrue(scm, scm.contains( + "url=\"https://github.com/apache/felix-dev\"")); + } + private static String getManifestHeader(String name) throws Exception { Path classes = Paths.get(Activator.class.getProtectionDomain( ).getCodeSource().getLocation().toURI()); diff --git a/logback/pom.xml b/logback/pom.xml index e3a1036011..e02771c66b 100644 --- a/logback/pom.xml +++ b/logback/pom.xml @@ -34,7 +34,7 @@ scm:git:https://github.com/apache/felix-dev.git scm:git:https://github.com/apache/felix-dev.git - https://gitbox.apache.org/repos/asf?p=felix-dev.git + https://github.com/apache/felix-dev From 094e58089b367c3df18269b8eb434a58bd4bbafa Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 22:41:40 +0200 Subject: [PATCH 20/24] Generate OSGi service requirements --- logback/logback/pom.xml | 10 ++++++-- .../felix/logback/internal/OSGiLogBridge.java | 11 ++++++++ .../felix/logback/internal/ManifestTest.java | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 217258f490..61a9f77943 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -83,8 +83,14 @@ org.osgi - osgi.annotation - 8.0.1 + org.osgi.annotation.bundle + 1.0.0 + provided + + + org.osgi + org.osgi.namespace.service + 1.0.0 provided diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java index 0a41ed38cc..a4871ec194 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java @@ -21,9 +21,12 @@ import java.util.function.Predicate; import java.util.function.Supplier; +import org.osgi.annotation.bundle.Requirement; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; +import org.osgi.namespace.service.ServiceNamespace; +import org.osgi.resource.Namespace; import org.osgi.service.log.LogReaderService; import org.osgi.service.log.admin.LoggerAdmin; import org.osgi.util.tracker.ServiceTracker; @@ -32,6 +35,14 @@ * Maintains the active connection between Felix Logback and an available OSGi * Log Service provider. */ +@Requirement( + namespace = ServiceNamespace.SERVICE_NAMESPACE, + filter = "(objectClass=org.osgi.service.log.LogReaderService)", + effective = Namespace.EFFECTIVE_ACTIVE) +@Requirement( + namespace = ServiceNamespace.SERVICE_NAMESPACE, + filter = "(objectClass=org.osgi.service.log.admin.LoggerAdmin)", + effective = Namespace.EFFECTIVE_ACTIVE) final class OSGiLogBridge implements AutoCloseable { OSGiLogBridge(BundleContext context) { diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java index dd89bae1cc..2c1b565268 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java @@ -46,6 +46,16 @@ public void importsJULBridgeBundle() throws Exception { "org.slf4j.bridge")); } + @Test + public void declaresRequiredOSGiLogServices() throws Exception { + String requirements = getManifestHeader("Require-Capability"); + + assertActiveServiceRequirement( + requirements, "org.osgi.service.log.LogReaderService"); + assertActiveServiceRequirement( + requirements, "org.osgi.service.log.admin.LoggerAdmin"); + } + @Test public void scmPointsToGitHubRepository() throws Exception { String scm = getManifestHeader("Bundle-SCM"); @@ -65,4 +75,19 @@ private static String getManifestHeader(String name) throws Exception { } } + private static void assertActiveServiceRequirement( + String requirements, String serviceClass) { + + String filter = "filter:=\"(objectClass=" + serviceClass + ")\""; + + for (String requirement : requirements.split(",")) { + if (requirement.contains(filter)) { + assertTrue(requirement, requirement.contains("effective:=active")); + return; + } + } + + throw new AssertionError(requirements); + } + } From d56e378c7ec40c3b771da33331a007306d5cb73a Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 23:11:08 +0200 Subject: [PATCH 21/24] Enrich Equinox log entries --- .../felix/logback/test/LogServiceTest.java | 11 ++- .../felix/logback/test/LogServiceTest.java | 11 ++- .../logback/test/helper/LogTestHelper.java | 52 +++++++++-- logback/logback/pom.xml | 1 + .../internal/EquinoxLogEntryEnricher.java | 48 ++++++++++ .../logback/internal/LogEntryEnricher.java | 43 +++++++++ .../logback/internal/LogbackLogListener.java | 19 +++- .../internal/NoOpLogEntryEnricher.java | 36 +++++++ .../felix/logback/internal/OSGiLogBridge.java | 11 ++- .../internal/LogEntryEnricherTest.java | 93 +++++++++++++++++++ .../felix/logback/internal/ManifestTest.java | 8 ++ 11 files changed, 321 insertions(+), 12 deletions(-) create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxLogEntryEnricher.java create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/LogEntryEnricher.java create mode 100644 logback/logback/src/main/java/org/apache/felix/logback/internal/NoOpLogEntryEnricher.java create mode 100644 logback/logback/src/test/java/org/apache/felix/logback/internal/LogEntryEnricherTest.java diff --git a/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java index e6444af214..041fe4a70d 100644 --- a/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java +++ b/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.log.LogService; @@ -49,11 +50,17 @@ public void service() { String refString = reference.toString(); try { - assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + assertExtendedServiceLog( + "INFO|Events.Service." + getBSN() + + "|ServiceEvent REGISTERED " + refString, + ServiceEvent.REGISTERED, reference); } finally { registration.unregister(); - assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + assertExtendedServiceLog( + "INFO|Events.Service." + getBSN() + + "|ServiceEvent UNREGISTERING " + refString, + ServiceEvent.UNREGISTERING, reference); } } diff --git a/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java b/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java index e6444af214..041fe4a70d 100644 --- a/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java +++ b/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.log.LogService; @@ -49,11 +50,17 @@ public void service() { String refString = reference.toString(); try { - assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent REGISTERED " + refString); + assertExtendedServiceLog( + "INFO|Events.Service." + getBSN() + + "|ServiceEvent REGISTERED " + refString, + ServiceEvent.REGISTERED, reference); } finally { registration.unregister(); - assertLog("INFO|Events.Service." + getBSN() + "|ServiceEvent UNREGISTERING " + refString); + assertExtendedServiceLog( + "INFO|Events.Service." + getBSN() + + "|ServiceEvent UNREGISTERING " + refString, + ServiceEvent.UNREGISTERING, reference); } } diff --git a/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java index aa6b9ec2d0..2797b3409c 100644 --- a/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java +++ b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java @@ -18,17 +18,23 @@ */ package org.apache.felix.logback.test.helper; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.Iterator; +import java.util.List; import org.apache.felix.logback.test.helper.ls.LogServiceHelper; import org.junit.BeforeClass; import org.junit.Test; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.log.LogService; import org.osgi.service.log.Logger; +import org.slf4j.event.KeyValuePair; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; @@ -71,6 +77,10 @@ protected void assertLog(String level, String name, long time) { } protected void assertLog(String record) { + findLog(record); + } + + protected ILoggingEvent findLog(String record) { try { // we need to make sure we wait for async logging internals to cool down Thread.sleep(10); @@ -78,14 +88,44 @@ protected void assertLog(String record) { catch (InterruptedException e) { } - if ((listAppender.list == null) || listAppender.list.isEmpty() || - !listAppender.list.stream().anyMatch(r -> { - String lr = new String(encoder.encode(r)); - return lr.equals(record); - })) { + if (listAppender.list != null) { + for (ILoggingEvent event : listAppender.list) { + if (record.equals(new String(encoder.encode(event)))) { + return event; + } + } + } + + throw new RuntimeException("Log record not found: " + record); + } + + protected void assertExtendedServiceLog( + String record, int type, ServiceReference reference) { + + ILoggingEvent event = findLog(record); + Object context = getKeyValue(event, "osgi.log.context"); + Object threadId = getKeyValue(event, "osgi.log.thread.id"); + + assertTrue(context instanceof ServiceEvent); + ServiceEvent serviceEvent = (ServiceEvent)context; + assertEquals(type, serviceEvent.getType()); + assertEquals(reference, serviceEvent.getServiceReference()); + assertTrue(threadId instanceof Long); + assertTrue(((Long)threadId).longValue() > 0); + } + + private static Object getKeyValue(ILoggingEvent event, String key) { + List values = event.getKeyValuePairs(); - throw new RuntimeException("Log record not found: " + record); + if (values != null) { + for (KeyValuePair value : values) { + if (key.equals(value.key)) { + return value.value; + } + } } + + return null; } } diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 61a9f77943..40496e84e0 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -122,6 +122,7 @@ Import-Package: \ !org.eclipse.osgi.internal.hookregistry,\ ch.qos.logback.classic.*;version="[1.5,2)",\ + org.eclipse.equinox.log;version="[1.1,2)";resolution:=optional,\ org.slf4j.*;version="[2.0,3)",\ org.osgi.framework;version="[1.9,2)",\ * diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxLogEntryEnricher.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxLogEntryEnricher.java new file mode 100644 index 0000000000..6cc6d1c2d6 --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxLogEntryEnricher.java @@ -0,0 +1,48 @@ +/** + * Licensed 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.felix.logback.internal; + +import ch.qos.logback.classic.spi.LoggingEvent; +import org.eclipse.equinox.log.ExtendedLogEntry; +import org.osgi.service.log.LogEntry; +import org.slf4j.event.KeyValuePair; + +/** + * Makes Equinox log context and thread identity available to Logback + * appenders as structured data. + */ +final class EquinoxLogEntryEnricher implements LogEntryEnricher { + + static final String CONTEXT_KEY = "osgi.log.context"; + static final String THREAD_ID_KEY = "osgi.log.thread.id"; + + @Override + public void enrich(LogEntry entry, LoggingEvent event) { + if (!(entry instanceof ExtendedLogEntry)) { + return; + } + + ExtendedLogEntry extendedEntry = (ExtendedLogEntry)entry; + Object context = extendedEntry.getContext(); + + if (context != null) { + event.addKeyValuePair(new KeyValuePair(CONTEXT_KEY, context)); + } + + event.addKeyValuePair(new KeyValuePair( + THREAD_ID_KEY, extendedEntry.getThreadId())); + } + +} diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogEntryEnricher.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogEntryEnricher.java new file mode 100644 index 0000000000..686c170a26 --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogEntryEnricher.java @@ -0,0 +1,43 @@ +/** + * Licensed 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.felix.logback.internal; + +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogEntry; + +import ch.qos.logback.classic.spi.LoggingEvent; + +/** + * Preserves provider-specific OSGi log details when the provider API is + * available without making that API mandatory. + */ +interface LogEntryEnricher { + + String EQUINOX_EXTENDED_LOG_ENTRY = + "org.eclipse.equinox.log.ExtendedLogEntry"; + + static LogEntryEnricher create(Bundle bundle) { + try { + bundle.loadClass(EQUINOX_EXTENDED_LOG_ENTRY); + return new EquinoxLogEntryEnricher(); + } + catch (ClassNotFoundException e) { + return NoOpLogEntryEnricher.INSTANCE; + } + } + + void enrich(LogEntry entry, LoggingEvent event); + +} diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index cc84af77b1..68ec3d7e66 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -53,16 +53,32 @@ public class LogbackLogListener implements AutoCloseable, LogListener, LoggerCon volatile Logger rootLogger; volatile LoggerContextVO loggerContextVO; final org.osgi.service.log.admin.LoggerContext osgiLoggerContext; + final LogEntryEnricher logEntryEnricher; final AtomicBoolean closed = new AtomicBoolean(); final Map> originalLogLevels = new HashMap<>(); final Map> appliedLogLevels = new HashMap<>(); public LogbackLogListener(LoggerAdmin loggerAdmin) { - this(loggerAdmin, getLoggerContext()); + this(loggerAdmin, getLoggerContext(), NoOpLogEntryEnricher.INSTANCE); + } + + LogbackLogListener( + LoggerAdmin loggerAdmin, LogEntryEnricher logEntryEnricher) { + + this(loggerAdmin, getLoggerContext(), logEntryEnricher); } LogbackLogListener(LoggerAdmin loggerAdmin, LoggerContext loggerContext) { + this(loggerAdmin, loggerContext, NoOpLogEntryEnricher.INSTANCE); + } + + LogbackLogListener( + LoggerAdmin loggerAdmin, + LoggerContext loggerContext, + LogEntryEnricher logEntryEnricher) { + osgiLoggerContext = loggerAdmin.getLoggerContext(null); + this.logEntryEnricher = logEntryEnricher; this.loggerContext = loggerContext; try { @@ -167,6 +183,7 @@ public StackTraceElement[] getCallerData() { le.setThrowableProxy(getThrowableProxy(entry.getException())); le.setTimeStamp(entry.getTime()); le.setSequenceNumber(entry.getSequence()); + logEntryEnricher.enrich(entry, le); logger.callAppenders(le); } diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/NoOpLogEntryEnricher.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/NoOpLogEntryEnricher.java new file mode 100644 index 0000000000..b2f610c0bb --- /dev/null +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/NoOpLogEntryEnricher.java @@ -0,0 +1,36 @@ +/** + * Licensed 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.felix.logback.internal; + +import org.osgi.service.log.LogEntry; + +import ch.qos.logback.classic.spi.LoggingEvent; + +/** + * Keeps the bridge usable with providers that expose only the standard OSGi + * Log Service API. + */ +final class NoOpLogEntryEnricher implements LogEntryEnricher { + + static final NoOpLogEntryEnricher INSTANCE = new NoOpLogEntryEnricher(); + + private NoOpLogEntryEnricher() { + } + + @Override + public void enrich(LogEntry entry, LoggingEvent event) { + } + +} diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java index a4871ec194..3f8948100c 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java @@ -46,7 +46,16 @@ final class OSGiLogBridge implements AutoCloseable { OSGiLogBridge(BundleContext context) { - this(context, LogbackLogListener::new); + this(context, createListenerFactory(context.getBundle())); + } + + private static Function createListenerFactory( + Bundle bundle) { + + LogEntryEnricher logEntryEnricher = LogEntryEnricher.create(bundle); + + return loggerAdmin -> new LogbackLogListener( + loggerAdmin, logEntryEnricher); } OSGiLogBridge( diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/LogEntryEnricherTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogEntryEnricherTest.java new file mode 100644 index 0000000000..d2624dc684 --- /dev/null +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogEntryEnricherTest.java @@ -0,0 +1,93 @@ +/** + * Licensed 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.felix.logback.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.eclipse.equinox.log.ExtendedLogEntry; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogEntry; +import org.slf4j.event.KeyValuePair; + +import ch.qos.logback.classic.spi.LoggingEvent; + +public class LogEntryEnricherTest { + + @Test + public void equinoxEntriesExposeRawContextAndThreadId() { + Object context = new Object(); + ExtendedLogEntry entry = mock(ExtendedLogEntry.class); + when(entry.getContext()).thenReturn(context); + when(entry.getThreadId()).thenReturn(47L); + LoggingEvent event = new LoggingEvent(); + + new EquinoxLogEntryEnricher().enrich(entry, event); + + List values = event.getKeyValuePairs(); + assertEquals(2, values.size()); + assertEquals(EquinoxLogEntryEnricher.CONTEXT_KEY, values.get(0).key); + assertSame(context, values.get(0).value); + assertEquals(EquinoxLogEntryEnricher.THREAD_ID_KEY, values.get(1).key); + assertEquals(Long.valueOf(47L), values.get(1).value); + } + + @Test + public void standardEntriesAreNotEnriched() { + LoggingEvent event = new LoggingEvent(); + + new EquinoxLogEntryEnricher().enrich(mock(LogEntry.class), event); + + assertNull(event.getKeyValuePairs()); + } + + @Test + public void unavailableEquinoxApiSelectsNoOpEnricher() throws Exception { + Bundle bundle = mock(Bundle.class); + when(bundle.loadClass(LogEntryEnricher.EQUINOX_EXTENDED_LOG_ENTRY)) + .thenThrow(new ClassNotFoundException()); + + assertSame(NoOpLogEntryEnricher.INSTANCE, + LogEntryEnricher.create(bundle)); + } + + @Test + public void availableEquinoxApiSelectsEquinoxEnricher() throws Exception { + Bundle bundle = mock(Bundle.class); + when(bundle.loadClass(LogEntryEnricher.EQUINOX_EXTENDED_LOG_ENTRY)) + .thenAnswer(invocation -> ExtendedLogEntry.class); + + assertEquals(EquinoxLogEntryEnricher.class, + LogEntryEnricher.create(bundle).getClass()); + } + + @Test + public void linkageFailuresRemainVisible() throws Exception { + Bundle bundle = mock(Bundle.class); + when(bundle.loadClass(LogEntryEnricher.EQUINOX_EXTENDED_LOG_ENTRY)) + .thenThrow(new NoClassDefFoundError("incompatible Equinox API")); + + assertThrows(NoClassDefFoundError.class, + () -> LogEntryEnricher.create(bundle)); + } + +} diff --git a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java index 2c1b565268..59aaa236fe 100644 --- a/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java +++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/ManifestTest.java @@ -46,6 +46,14 @@ public void importsJULBridgeBundle() throws Exception { "org.slf4j.bridge")); } + @Test + public void equinoxLogApiIsOptional() throws Exception { + String imports = getManifestHeader("Import-Package"); + + assertTrue(imports, imports.contains( + "org.eclipse.equinox.log;version=\"[1.1,2)\";resolution:=optional")); + } + @Test public void declaresRequiredOSGiLogServices() throws Exception { String requirements = getManifestHeader("Require-Capability"); From 8f558a167bd62daa44faf535db96aacd93727285 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 23:23:07 +0200 Subject: [PATCH 22/24] Attach sources JAR --- logback/logback/pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/logback/logback/pom.xml b/logback/logback/pom.xml index 40496e84e0..c170c2e3ad 100644 --- a/logback/logback/pom.xml +++ b/logback/logback/pom.xml @@ -146,6 +146,18 @@ + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + org.apache.rat apache-rat-plugin From 611acf0fc4f82fd1b5d82d80996e7a6eb44ac7c9 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Tue, 11 Aug 2026 09:45:24 +0200 Subject: [PATCH 23/24] Use felix.log 1.3.2 in tests. --- logback/itests/itest-parent/pom.xml | 2 +- logback/itests/standard-felix-logservice/itest.bndrun | 2 +- logback/itests/standard-jbl/itest.bndrun | 2 +- logback/itests/standard-jul/itest.bndrun | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/logback/itests/itest-parent/pom.xml b/logback/itests/itest-parent/pom.xml index 84726dfb56..1cea813c19 100644 --- a/logback/itests/itest-parent/pom.xml +++ b/logback/itests/itest-parent/pom.xml @@ -45,7 +45,7 @@ UTF-8 2.26.1 - 1.2.5-SNAPSHOT + 1.3.2 2.0.18 1.5.38 diff --git a/logback/itests/standard-felix-logservice/itest.bndrun b/logback/itests/standard-felix-logservice/itest.bndrun index b1bab2153e..4101ef02d5 100644 --- a/logback/itests/standard-felix-logservice/itest.bndrun +++ b/logback/itests/standard-felix-logservice/itest.bndrun @@ -18,7 +18,7 @@ jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ - org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.log;version='[1.3.2,1.3.3)',\ org.apache.felix.logback;version='[2.0.0,2.0.1)',\ org.apache.felix.logback.itests.standard.felix.logservice;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ diff --git a/logback/itests/standard-jbl/itest.bndrun b/logback/itests/standard-jbl/itest.bndrun index e1cf25ff88..67947fa2e1 100644 --- a/logback/itests/standard-jbl/itest.bndrun +++ b/logback/itests/standard-jbl/itest.bndrun @@ -19,7 +19,7 @@ jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ - org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.log;version='[1.3.2,1.3.3)',\ org.apache.felix.logback;version='[2.0.0,2.0.1)',\ org.apache.felix.logback.itests.standard.jbl;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ diff --git a/logback/itests/standard-jul/itest.bndrun b/logback/itests/standard-jul/itest.bndrun index 7f9820cfe0..707f2f4b29 100644 --- a/logback/itests/standard-jul/itest.bndrun +++ b/logback/itests/standard-jul/itest.bndrun @@ -16,7 +16,7 @@ jul.to.slf4j;version='[2.0.18,2.0.19)',\ org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.4,1.3.5)',\ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ - org.apache.felix.log;version='[1.2.5,1.2.6)',\ + org.apache.felix.log;version='[1.3.2,1.3.3)',\ org.apache.felix.logback;version='[2.0.0,2.0.1)',\ org.apache.felix.logback.itests.standard.jul;version='[2.0.0,2.0.1)',\ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\ From c4dc95bea499af91522c7d58fb8fb5e715f8944d Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Tue, 11 Aug 2026 20:12:04 +0200 Subject: [PATCH 24/24] Set LoggerContext on bridged events --- .../logback/internal/LogbackLogListener.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java index 68ec3d7e66..53cec8a010 100644 --- a/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java +++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java @@ -14,21 +14,6 @@ package org.apache.felix.logback.internal; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.osgi.framework.Bundle; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogLevel; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.admin.LoggerAdmin; -import org.slf4j.ILoggerFactory; -import org.slf4j.LoggerFactory; - import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; @@ -37,6 +22,20 @@ import ch.qos.logback.classic.spi.LoggerContextVO; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogLevel; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.admin.LoggerAdmin; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Presents OSGi Log Service entries to Logback and keeps OSGi log levels @@ -177,6 +176,7 @@ public StackTraceElement[] getCallerData() { le.setArgumentArray(arguments); le.setMessage(message); le.setLevel(level); + le.setLoggerContext(loggerContext); le.setLoggerContextRemoteView(loggerContextVO); le.setLoggerName(loggerName); le.setThreadName(entry.getThreadInfo());