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..7639e00e52
--- /dev/null
+++ b/logback/itests/immediate-equinox-logservice/itest.bndrun
@@ -0,0 +1,17 @@
+-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,\
+ jul.to.slf4j;version=latest,\
+ org.apache.felix.logback;version=latest
+
+-runbundles: \
+ 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
new file mode 100644
index 0000000000..eb34076bac
--- /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
+ 2.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..041fe4a70d
--- /dev/null
+++ b/logback/itests/immediate-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.ServiceEvent;
+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 {
+ assertExtendedServiceLog(
+ "INFO|Events.Service." + getBSN() +
+ "|ServiceEvent REGISTERED " + refString,
+ ServiceEvent.REGISTERED, reference);
+ }
+ finally {
+ registration.unregister();
+ assertExtendedServiceLog(
+ "INFO|Events.Service." + getBSN() +
+ "|ServiceEvent UNREGISTERING " + refString,
+ ServiceEvent.UNREGISTERING, reference);
+ }
+ }
+
+ @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..3e96a854a4
--- /dev/null
+++ b/logback/itests/immediate-felix-logservice/itest.bndrun
@@ -0,0 +1,20 @@
+-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,\
+ jul.to.slf4j;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='[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
new file mode 100644
index 0000000000..440eea7ac5
--- /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
+ 2.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..8de52d2e27
--- /dev/null
+++ b/logback/itests/immediate-jbl/itest.bndrun
@@ -0,0 +1,29 @@
+-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,\
+ jul.to.slf4j;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='[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)'
diff --git a/logback/itests/immediate-jbl/pom.xml b/logback/itests/immediate-jbl/pom.xml
new file mode 100644
index 0000000000..42641e1d94
--- /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
+ 2.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..08be049fde
--- /dev/null
+++ b/logback/itests/immediate-jcl/itest.bndrun
@@ -0,0 +1,28 @@
+-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,\
+ jul.to.slf4j;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='[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='[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
new file mode 100644
index 0000000000..7ffc850ebf
--- /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
+ 2.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..c7c0bb62b5
--- /dev/null
+++ b/logback/itests/immediate-jul-felix/itest.bndrun
@@ -0,0 +1,20 @@
+-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,\
+ jul.to.slf4j;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='[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
new file mode 100644
index 0000000000..cc529ece81
--- /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
+ 2.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..c4d4f05f21
--- /dev/null
+++ b/logback/itests/immediate-jul/itest.bndrun
@@ -0,0 +1,22 @@
+-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,\
+ jul.to.slf4j;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='[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
new file mode 100644
index 0000000000..5a48fc5833
--- /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
+ 2.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..29d744d243
--- /dev/null
+++ b/logback/itests/immediate-log4j1/itest.bndrun
@@ -0,0 +1,26 @@
+-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,\
+ jul.to.slf4j;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='[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='[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
new file mode 100644
index 0000000000..67b9e72b6e
--- /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
+ 2.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..7fdfde2d00
--- /dev/null
+++ b/logback/itests/immediate-log4j2/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.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,\
+ jul.to.slf4j;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='[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
new file mode 100644
index 0000000000..9c468d4640
--- /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
+ 2.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..794b7d9493
--- /dev/null
+++ b/logback/itests/immediate-slf4j/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.slf4j)'
+
+-runpath: \
+ 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
+
+-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='[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
new file mode 100644
index 0000000000..f7fee89a09
--- /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
+ 2.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..1cea813c19
--- /dev/null
+++ b/logback/itests/itest-parent/pom.xml
@@ -0,0 +1,309 @@
+
+
+
+ 4.0.0
+
+ org.apache.felix
+ org.apache.felix.logback.reactor
+ 2.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://github.com/apache/felix-dev
+
+
+
+ 6.1.0
+
+ 11
+ 11
+ 11
+ UTF-8
+
+ 2.26.1
+ 1.3.2
+
+ 2.0.18
+ 1.5.38
+
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.api.version}
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 5.0.0
+ runtime
+
+
+ org.osgi
+ osgi.annotation
+ 8.0.1
+ provided
+
+
+ org.osgi
+ osgi.core
+ 7.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
+ 2.0.0-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
+ ${apache.logging.log4j.version}
+
+
+
+
+
+
+ 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..201df31543
--- /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
+ 2.0.0-SNAPSHOT
+ pom
+
+
+ scm:git:https://github.com/apache/felix-dev.git
+ scm:git:https://github.com/apache/felix-dev.git
+ https://github.com/apache/felix-dev
+
+
+
+ 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..eb90f8ec0a
--- /dev/null
+++ b/logback/itests/standard-equinox-logservice/itest.bndrun
@@ -0,0 +1,23 @@
+-include: ../base.bndrun
+
+-runfw: org.eclipse.osgi
+
+-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)'
+
+-runproperties: \
+ eclipse.log.enabled=false
+
+-runbundles: \
+ 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)',\
+ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\
+ 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
new file mode 100644
index 0000000000..e04e61eb1b
--- /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
+ 2.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..041fe4a70d
--- /dev/null
+++ b/logback/itests/standard-equinox-logservice/src/main/java/org/apache/felix/logback/test/LogServiceTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.ServiceEvent;
+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 {
+ assertExtendedServiceLog(
+ "INFO|Events.Service." + getBSN() +
+ "|ServiceEvent REGISTERED " + refString,
+ ServiceEvent.REGISTERED, reference);
+ }
+ finally {
+ registration.unregister();
+ assertExtendedServiceLog(
+ "INFO|Events.Service." + getBSN() +
+ "|ServiceEvent UNREGISTERING " + refString,
+ ServiceEvent.UNREGISTERING, reference);
+ }
+ }
+
+ @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..4101ef02d5
--- /dev/null
+++ b/logback/itests/standard-felix-logservice/itest.bndrun
@@ -0,0 +1,25 @@
+-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=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.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.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)',\
+ 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
new file mode 100644
index 0000000000..e7ff20d8a1
--- /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
+ 2.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..67947fa2e1
--- /dev/null
+++ b/logback/itests/standard-jbl/itest.bndrun
@@ -0,0 +1,27 @@
+-include: ../base.bndrun
+
+-runfw: org.apache.felix.framework
+
+-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)'
+
+-runproperties: \
+ org.jboss.logging.provider=slf4j
+
+-runbundles: \
+ 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.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)',\
+ org.jboss.logging.jboss-logging;version='[3.4.2,3.4.3)',\
+ 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
new file mode 100644
index 0000000000..9981278844
--- /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
+ 2.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..537a3bedf5
--- /dev/null
+++ b/logback/itests/standard-jcl/itest.bndrun
@@ -0,0 +1,20 @@
+-include: ../base.bndrun
+
+-runfw: org.apache.felix.framework
+
+-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.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='[2.0.0,2.0.1)',\
+ org.apache.servicemix.bundles.junit;version='[4.13.2,4.13.3)',\
+ 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
new file mode 100644
index 0000000000..9eeea60aa3
--- /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
+ 2.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..707f2f4b29
--- /dev/null
+++ b/logback/itests/standard-jul/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=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.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.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)',\
+ 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
new file mode 100644
index 0000000000..b84de630f4
--- /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
+ 2.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..e7f9224e70
--- /dev/null
+++ b/logback/itests/standard-jul/src/main/java/org/apache/felix/logback/test/JULTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.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;
+
+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();
+ Logger logger = Logger.getLogger(getClass().getName());
+ if (logger.isLoggable(Level.INFO)) {
+ logger.info(time + "");
+ }
+ 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();
+ Assert.assertEquals(0, getLogListenerCount());
+ }
+
+ rootLogger.addHandler(savedHandler);
+ logbackBundle.start();
+ Assert.assertEquals(1, getLogListenerCount());
+
+ 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.assertEquals(0, getLogListenerCount());
+
+ 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();
+ 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())) {
+ 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/itests/standard-log4j1/itest.bndrun b/logback/itests/standard-log4j1/itest.bndrun
new file mode 100644
index 0000000000..4849270908
--- /dev/null
+++ b/logback/itests/standard-log4j1/itest.bndrun
@@ -0,0 +1,18 @@
+-include: ../base.bndrun
+
+-runfw: org.apache.felix.framework
+
+-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.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='[2.0.18,2.0.19)'
diff --git a/logback/itests/standard-log4j1/pom.xml b/logback/itests/standard-log4j1/pom.xml
new file mode 100644
index 0000000000..4915284d5b
--- /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
+ 2.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..1acea21001
--- /dev/null
+++ b/logback/itests/standard-log4j2/itest.bndrun
@@ -0,0 +1,20 @@
+-include: ../base.bndrun
+
+-runfw: org.apache.felix.framework
+
+-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.logging.log4j.to.slf4j)',\
+ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.log4j2)'
+
+-runbundles: \
+ 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='[2.0.18,2.0.19)'
diff --git a/logback/itests/standard-log4j2/pom.xml b/logback/itests/standard-log4j2/pom.xml
new file mode 100644
index 0000000000..09cfc1fe86
--- /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
+ 2.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..82fb247800
--- /dev/null
+++ b/logback/itests/standard-slf4j/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=jakarta.servlet-api)',\
+ osgi.identity;filter:='(osgi.identity=org.apache.felix.logback.itests.standard.slf4j)'
+
+-runbundles: \
+ 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='[2.0.18,2.0.19)'
diff --git a/logback/itests/standard-slf4j/pom.xml b/logback/itests/standard-slf4j/pom.xml
new file mode 100644
index 0000000000..bd905994a0
--- /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
+ 2.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..0a627b6901
--- /dev/null
+++ b/logback/itests/test-helper/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ org.apache.felix
+ org.apache.felix.logback.itests.parent
+ 2.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
+ ${logback.version}
+
+
+ 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..2797b3409c
--- /dev/null
+++ b/logback/itests/test-helper/src/main/java/org/apache/felix/logback/test/helper/LogTestHelper.java
@@ -0,0 +1,131 @@
+/*
+ * 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 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;
+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) {
+ 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);
+ }
+ catch (InterruptedException e) {
+ }
+
+ 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();
+
+ if (values != null) {
+ for (KeyValuePair value : values) {
+ if (key.equals(value.key)) {
+ return value.value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
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..b513e3da91
--- /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.5.x
+
+
+ ch.qos.logback
+ logback-core
+ 1.5.x
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.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..c170c2e3ad
--- /dev/null
+++ b/logback/logback/pom.xml
@@ -0,0 +1,178 @@
+
+
+
+ org.apache.felix
+ felix-parent
+ 9
+ ../../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.
+
+ 2.0.0-SNAPSHOT
+ org.apache.felix.logback
+
+
+ scm:git:https://github.com/apache/felix-dev.git
+ scm:git:https://github.com/apache/felix-dev.git
+ https://github.com/apache/felix-dev
+ HEAD
+
+
+
+ 6.1.0
+
+ true
+ 11
+ 11
+ 11
+ UTF-8
+
+ 2.0.18
+ 1.5.38
+
+
+
+
+ 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.osgi
+ osgi.core
+ 7.0.0
+ provided
+
+
+ org.eclipse.platform
+ org.eclipse.osgi
+ 3.17.0
+ provided
+
+
+ org.osgi
+ org.osgi.annotation.bundle
+ 1.0.0
+ provided
+
+
+ org.osgi
+ org.osgi.namespace.service
+ 1.0.0
+ provided
+
+
+ org.slf4j
+ jul-to-slf4j
+ ${slf4j.api.version}
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.8.1
+ test
+
+
+
+
+
+ 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.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ 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..f99be70eb8
--- /dev/null
+++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/Activator.java
@@ -0,0 +1,78 @@
+/**
+ * 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.annotation.bundle.Header;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+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 {
+
+ private final JULBridge julBridge = new JULBridge();
+ private volatile OSGiLogBridge logBridge;
+
+ @Override
+ public void start(final BundleContext bundleContext) throws Exception {
+ LoggerFactory.getILoggerFactory();
+ julBridge.install();
+
+ try {
+ logBridge = new OSGiLogBridge(bundleContext);
+
+ logBridge.open();
+ }
+ catch (Exception | Error e) {
+ closeTracker(e);
+ julBridge.restore(e);
+ throw e;
+ }
+ }
+
+ @Override
+ public void stop(BundleContext bundleContext) throws Exception {
+ try {
+ closeTracker();
+ }
+ finally {
+ julBridge.restore();
+ }
+ }
+
+ private void closeTracker(Throwable failure) {
+ try {
+ closeTracker();
+ }
+ catch (RuntimeException | Error e) {
+ failure.addSuppressed(e);
+ }
+ }
+
+ private void closeTracker() {
+ OSGiLogBridge bridge = logBridge;
+ logBridge = null;
+
+ if (bridge != null) {
+ bridge.close();
+ }
+ }
+
+}
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..a0ce3fcaa9
--- /dev/null
+++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/EquinoxHookSupport.java
@@ -0,0 +1,38 @@
+/**
+ * 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;
+
+/**
+ * Makes Felix Logback available during Equinox framework startup before
+ * ordinary bundles are activated.
+ */
+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/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/JULBridge.java b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java
new file mode 100644
index 0000000000..ee512ea1a1
--- /dev/null
+++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/JULBridge.java
@@ -0,0 +1,88 @@
+/**
+ * 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;
+
+/**
+ * Allows JUL records to use the Logback backend shared by the application.
+ */
+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;
+ }
+
+}
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
new file mode 100644
index 0000000000..53cec8a010
--- /dev/null
+++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/LogbackLogListener.java
@@ -0,0 +1,460 @@
+/**
+ * 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.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;
+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
+ * aligned with the Logback configuration.
+ */
+public class LogbackLogListener implements AutoCloseable, 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";
+
+ volatile LoggerContext loggerContext;
+ 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(), 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 {
+ onStart(loggerContext);
+ loggerContext.addListener(this);
+ }
+ catch (RuntimeException | Error e) {
+ try {
+ restoreLogLevels();
+ }
+ catch (RuntimeException | Error cleanup) {
+ e.addSuppressed(cleanup);
+ }
+ throw e;
+ }
+ }
+
+ private static LoggerContext getLoggerContext() {
+ ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
+
+ if (loggerFactory instanceof LoggerContext) {
+ return (LoggerContext)loggerFactory;
+ }
+
+ throw new IllegalStateException("This bundle only works with logback-classic");
+ }
+
+ @Override
+ public boolean isResetResistant() {
+ return true;
+ }
+
+ @Override
+ public void close() {
+ if (closed.compareAndSet(false, true)) {
+ loggerContext.removeListener(this);
+ restoreLogLevels();
+ }
+ }
+
+ @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.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.setLoggerContext(loggerContext);
+ le.setLoggerContextRemoteView(loggerContextVO);
+ le.setLoggerName(loggerName);
+ le.setThreadName(entry.getThreadInfo());
+ le.setThrowableProxy(getThrowableProxy(entry.getException()));
+ le.setTimeStamp(entry.getTime());
+ le.setSequenceNumber(entry.getSequence());
+ logEntryEnricher.enrich(entry, le);
+
+ logger.callAppenders(le);
+ }
+
+ @Override
+ public void onLevelChange(Logger logger, Level level) {
+ if (closed.get()) {
+ return;
+ }
+
+ Level configuredLevel = logger.getLevel();
+
+ updateLogLevel(
+ logger.getName(),
+ configuredLevel == null ?
+ Optional.empty() : Optional.of(from(configuredLevel)));
+ }
+
+ @Override
+ public void onStart(LoggerContext context) {
+ if (closed.get()) {
+ return;
+ }
+
+ loggerContext = context;
+ rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
+ loggerContextVO = loggerContext.getLoggerContextRemoteView();
+
+ configureLogLevels(loggerContext);
+ }
+
+ @Override
+ public void onStop(LoggerContext context) {
+ if (!closed.get()) {
+ restoreLogLevels();
+ }
+ }
+
+ @Override
+ public void onReset(LoggerContext context) {
+ if (!closed.get()) {
+ onStart(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(
+ symbolicName
+ ).toString();
+ }
+
+ LogLevel from(Level level) {
+ if (Level.ALL.equals(level) || Level.OFF.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);
+ copy.put(name, from(level));
+ }
+ }
+
+ return copy;
+ }
+
+ private synchronized void updateLogLevel(
+ String name, Optional 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/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
new file mode 100644
index 0000000000..3f8948100c
--- /dev/null
+++ b/logback/logback/src/main/java/org/apache/felix/logback/internal/OSGiLogBridge.java
@@ -0,0 +1,482 @@
+/**
+ * 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.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;
+
+/**
+ * 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) {
+ this(context, createListenerFactory(context.getBundle()));
+ }
+
+ private static Function createListenerFactory(
+ Bundle bundle) {
+
+ LogEntryEnricher logEntryEnricher = LogEntryEnricher.create(bundle);
+
+ return loggerAdmin -> new LogbackLogListener(
+ loggerAdmin, logEntryEnricher);
+ }
+
+ 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 logReaderReference =
+ selectLogReader();
+ ServiceReference loggerAdminReference =
+ selectLoggerAdmin(logReaderReference);
+
+ 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 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,
+ Predicate> selector) {
+
+ return services.keySet().stream().filter(selector).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);
+ }
+ }
+
+ /**
+ * Represents an active OSGi-to-Logback connection whose resources share a
+ * common lifetime.
+ */
+ 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;
+
+ }
+
+ /**
+ * Keeps the bridge's candidate set synchronized with the services
+ * available from OSGi.
+ *
+ * @param the tracked service type
+ */
+ 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;
+
+ }
+
+ /**
+ * 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);
+
+ 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/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/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/LogbackLogListenerTest.java b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java
new file mode 100644
index 0000000000..93a66514c8
--- /dev/null
+++ b/logback/logback/src/test/java/org/apache/felix/logback/internal/LogbackLogListenerTest.java
@@ -0,0 +1,282 @@
+/**
+ * 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.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;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+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;
+
+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 {
+
+ @Test
+ public void closeRemovesContextListenerAndRestoresLogLevelsAfterReset() {
+ 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);
+ AtomicReference