diff --git a/logbackend/LICENSE b/logbackend/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/logbackend/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/logbackend/NOTICE b/logbackend/NOTICE new file mode 100644 index 00000000000..32d39ccfc7b --- /dev/null +++ b/logbackend/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/logbackend/pom.xml b/logbackend/pom.xml new file mode 100644 index 00000000000..33314a5a64d --- /dev/null +++ b/logbackend/pom.xml @@ -0,0 +1,112 @@ + + + + org.apache.felix + felix-parent + 4 + ../pom/pom.xml + + + 4.0.0 + bundle + Apache Felix Logback Backend Integration + + A simple integration of the OSGi R7 Log (1.4) service to Logback backend. + + 1.0.0-SNAPSHOT + org.apache.felix.logbackend + + + + org.osgi + org.osgi.service.log + 1.4.0 + provided + + + ch.qos.logback + logback-classic + 1.2.0 + + + org.eclipse.platform + org.eclipse.osgi + 3.12.100 + true + provided + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.osgi + org.osgi.annotation.bundle + 1.0.0 + provided + + + org.osgi + org.osgi.annotation.versioning + 1.1.0 + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.felix + maven-bundle-plugin + 3.5.0 + true + + + !org.eclipse.osgi.internal.hookregistry, * + + + + + org.codehaus.mojo + rat-maven-plugin + + false + true + true + + doc/* + maven-eclipse.xml + .checkstyle + .externalToolBuilders/* + + + + + + diff --git a/logbackend/src/main/java/org/apache/felix/logbackend/internal/Activator.java b/logbackend/src/main/java/org/apache/felix/logbackend/internal/Activator.java new file mode 100644 index 00000000000..a7f8aebabbd --- /dev/null +++ b/logbackend/src/main/java/org/apache/felix/logbackend/internal/Activator.java @@ -0,0 +1,111 @@ +/** + * 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.logbackend.internal; + +import java.util.AbstractMap.SimpleEntry; + +import org.osgi.annotation.bundle.Header; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogReaderService; +import org.osgi.service.log.admin.LoggerAdmin; +import org.osgi.util.tracker.ServiceTracker; + +@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") +@Header(name = Constants.BUNDLE_VENDOR, value = "The Apache Software Foundation") +public class Activator implements BundleActivator { + + private volatile ServiceTracker lat; + + @Override + public void start(final BundleContext bundleContext) throws Exception { + lat = new ServiceTracker( + bundleContext, LoggerAdmin.class, null) { + + @Override + public LRST addingService( + ServiceReference reference) { + + LoggerAdmin loggerAdmin = bundleContext.getService(reference); + + LRST lrst = new LRST(bundleContext, loggerAdmin); + + lrst.open(); + + return lrst; + } + + @Override + public void removedService( + ServiceReference reference, LRST lrst) { + + lrst.close(); + } + }; + + lat.open(); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + lat.close(); + } + + class LRST extends ServiceTracker { + + public LRST(BundleContext context, LoggerAdmin loggerAdmin) { + super(context, LogReaderService.class, null); + + this.loggerAdmin = loggerAdmin; + } + + @Override + public Pair addingService( + ServiceReference reference) { + + LogReaderService logReaderService = context.getService(reference); + + LogbackLogListener logbackLogListener = new LogbackLogListener(loggerAdmin); + + logReaderService.addLogListener(logbackLogListener); + + return new Pair(logReaderService, logbackLogListener); + } + + @Override + public void removedService( + ServiceReference reference, + Pair pair) { + + pair.getKey().removeLogListener(pair.getValue()); + } + + private final LoggerAdmin loggerAdmin; + + } + + class Pair extends SimpleEntry { + + private static final long serialVersionUID = 1L; + + public Pair(LogReaderService logReaderService, LogbackLogListener logbackLogListener) { + super(logReaderService, logbackLogListener); + } + + } + +} diff --git a/logbackend/src/main/java/org/apache/felix/logbackend/internal/EquinoxHookSupport.java b/logbackend/src/main/java/org/apache/felix/logbackend/internal/EquinoxHookSupport.java new file mode 100644 index 00000000000..1298ddd4fde --- /dev/null +++ b/logbackend/src/main/java/org/apache/felix/logbackend/internal/EquinoxHookSupport.java @@ -0,0 +1,34 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.felix.logbackend.internal; + +import org.eclipse.osgi.internal.hookregistry.ActivatorHookFactory; +import org.eclipse.osgi.internal.hookregistry.HookConfigurator; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; +import org.osgi.framework.BundleActivator; + +public class EquinoxHookSupport implements ActivatorHookFactory, HookConfigurator { + + @Override + public BundleActivator createActivator() { + return new Activator(); + } + + @Override + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addActivatorHookFactory(this); + } + +} diff --git a/logbackend/src/main/java/org/apache/felix/logbackend/internal/LogbackLogListener.java b/logbackend/src/main/java/org/apache/felix/logbackend/internal/LogbackLogListener.java new file mode 100644 index 00000000000..b4b6293a140 --- /dev/null +++ b/logbackend/src/main/java/org/apache/felix/logbackend/internal/LogbackLogListener.java @@ -0,0 +1,279 @@ +/** + * 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.logbackend.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogLevel; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.admin.LoggerAdmin; +import org.slf4j.ILoggerFactory; +import org.slf4j.impl.StaticLoggerBinder; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.CallerData; +import ch.qos.logback.classic.spi.LoggerContextListener; +import ch.qos.logback.classic.spi.LoggerContextVO; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.classic.spi.ThrowableProxy; + +public class LogbackLogListener implements LogListener, LoggerContextListener { + + volatile LoggerContext loggerContext; + volatile Logger rootLogger; + volatile LoggerContextVO loggerContextVO; + final Map initialLogLevels; + final org.osgi.service.log.admin.LoggerContext osgiLoggerContext; + + public LogbackLogListener(LoggerAdmin loggerAdmin) { + osgiLoggerContext = loggerAdmin.getLoggerContext(null); + initialLogLevels = osgiLoggerContext.getLogLevels(); + + ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + + if (!(loggerFactory instanceof LoggerContext)) { + throw new IllegalStateException("This bundle only works with logback-classic"); + } + + loggerContext = (LoggerContext)loggerFactory; + rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + loggerContextVO = loggerContext.getLoggerContextRemoteView(); + + Map updatedLevels = updateLevels(loggerContext, initialLogLevels); + + osgiLoggerContext.setLogLevels(updatedLevels); + + loggerContext.addListener(this); + } + + @Override + public boolean isResetResistant() { + return true; + } + + @Override + public void logged(final LogEntry entry) { + String loggerName = entry.getLoggerName(); + String message = entry.getMessage(); + Object[] arguments = null; + Level level = from(entry.getLogLevel()); + final AtomicBoolean avoidCallerData = new AtomicBoolean(); + + if ("Events.Bundle".equals(loggerName) || + "Events.Framework".equals(loggerName) || + "LogService".equals(loggerName)) { + + loggerName = formatBundle(entry.getBundle(), loggerName); + avoidCallerData.set(true); + } + else if ("Events.Service".equals(loggerName)) { + loggerName = formatBundle(entry.getBundle(), loggerName); + message = message + " {}"; + arguments = new Object[] {entry.getServiceReference()}; + avoidCallerData.set(true); + } + + Logger logger = loggerContext.getLogger(loggerName); + + // Check to see if there's a logger defined in our configuration and + // if there is, then make sure it's handled as an override for the + // effective level. + if (!logger.equals(rootLogger) && !logger.isEnabledFor(level)) { + return; + } + + LoggingEvent le = new LoggingEvent() { + + @Override + public StackTraceElement[] getCallerData() { + if (avoidCallerData.get() || callerData != null) + return callerData; + return callerData = getCallerData0(entry.getLocation()); + } + + private volatile StackTraceElement[] callerData; + + }; + + le.setArgumentArray(arguments); + le.setMessage(message); + le.setLevel(level); + le.setLoggerContextRemoteView(loggerContextVO); + le.setLoggerName(loggerName); + le.setThreadName(entry.getThreadInfo()); + le.setThrowableProxy(getThrowableProxy(entry.getException())); + le.setTimeStamp(entry.getTime()); + + rootLogger.callAppenders(le); + } + + @Override + public void onLevelChange(Logger logger, Level level) { + Map updatedLevels = osgiLoggerContext.getLogLevels(); + + if (Level.OFF.equals(level)) { + updatedLevels.remove(logger.getName()); + } + else { + updatedLevels.put(logger.getName(), from(level)); + } + + osgiLoggerContext.setLogLevels(updatedLevels); + } + + @Override + public void onStart(LoggerContext context) { + loggerContext = context; + rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + loggerContextVO = loggerContext.getLoggerContextRemoteView(); + + Map updatedLevels = updateLevels(loggerContext, initialLogLevels); + + osgiLoggerContext.setLogLevels(updatedLevels); + } + + @Override + public void onStop(LoggerContext context) { + osgiLoggerContext.setLogLevels(initialLogLevels); + } + + @Override + public void onReset(LoggerContext context) { + loggerContext = context; + rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + loggerContextVO = loggerContext.getLoggerContextRemoteView(); + + Map updatedLevels = updateLevels(loggerContext, initialLogLevels); + + osgiLoggerContext.setLogLevels(updatedLevels); + } + + String formatBundle(Bundle bundle, String loggerName) { + return new StringBuilder().append( + loggerName + ).append( + "." + ).append( + bundle.getSymbolicName() + ).toString(); + } + + LogLevel from(Level level) { + if (Level.ALL.equals(level)) { + return LogLevel.TRACE; + } + else if (Level.DEBUG.equals(level)) { + return LogLevel.DEBUG; + } + else if (Level.ERROR.equals(level)) { + return LogLevel.ERROR; + } + else if (Level.INFO.equals(level)) { + return LogLevel.INFO; + } + else if (Level.TRACE.equals(level)) { + return LogLevel.TRACE; + } + else if (Level.WARN.equals(level)) { + return LogLevel.WARN; + } + + return LogLevel.WARN; + } + + Level from(LogLevel logLevel) { + switch (logLevel) { + case AUDIT: + return Level.TRACE; + case DEBUG: + return Level.DEBUG; + case ERROR: + return Level.ERROR; + case INFO: + return Level.INFO; + case TRACE: + return Level.TRACE; + case WARN: + default: + return Level.WARN; + } + } + + /* + * TODO This method should be tuned with the correct packages and + * class names. + */ + 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); + copy.put(org.osgi.service.log.Logger.ROOT_LOGGER_NAME, from(root.getLevel())); + + for (Logger logger : loggerContext.getLoggerList()) { + String name = logger.getName(); + Level level = logger.getLevel(); + + copy.remove(name); + + if (level != Level.OFF) { + copy.put(name, from(level)); + } + } + + return copy; + } + +} diff --git a/logbackend/src/main/resources/hookconfigurators.properties b/logbackend/src/main/resources/hookconfigurators.properties new file mode 100644 index 00000000000..5e56cb493ee --- /dev/null +++ b/logbackend/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=com.github.rotty3000.osgi.logback.EquinoxHookSupport \ No newline at end of file