You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ro...@apache.org on 2018/07/02 21:27:41 UTC

svn commit: r1834903 - in /felix/trunk/log: ./ src/main/java/org/apache/felix/log/

Author: rotty3000
Date: Mon Jul  2 21:27:40 2018
New Revision: 1834903

URL: http://svn.apache.org/viewvc?rev=1834903&view=rev
Log:
FELIX-5876 Update to Log Service Specification 1.4

Doesn't yet implement LogStreamProvider but otherwise passes the org.osgi.test.cases.log and org.osgi.test.cases.log.launch CTs

Signed-off-by: Raymond Auge <ro...@apache.org>

Added:
    felix/trunk/log/src/main/java/org/apache/felix/log/FormatterLoggerImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LoggerAdminImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LoggerContextImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LoggerImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/RootLoggerContextImpl.java
Modified:
    felix/trunk/log/pom.xml
    felix/trunk/log/src/main/java/org/apache/felix/log/Activator.java
    felix/trunk/log/src/main/java/org/apache/felix/log/Log.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogEntryImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogException.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogListenerThread.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogNodeEnumeration.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceFactory.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceImpl.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceFactory.java
    felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceImpl.java

Modified: felix/trunk/log/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/log/pom.xml?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/pom.xml (original)
+++ felix/trunk/log/pom.xml Mon Jul  2 21:27:40 2018
@@ -20,7 +20,7 @@
   <parent>
     <groupId>org.apache.felix</groupId>
     <artifactId>felix-parent</artifactId>
-    <version>1.2.1</version>
+    <version>5</version>
     <relativePath>../pom/pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
@@ -31,52 +31,68 @@
   </description>
   <version>1.1.0-SNAPSHOT</version>
   <artifactId>org.apache.felix.log</artifactId>
+
+  <properties>
+    <maven.compiler.source>1.7</maven.compiler.source>
+    <maven.compiler.target>1.7</maven.compiler.target>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-      <version>4.1.0</version>
+      <artifactId>osgi.core</artifactId>
+      <version>6.0.0</version>
     </dependency>
     <dependency>
       <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.compendium</artifactId>
-      <version>4.1.0</version>
+      <artifactId>org.osgi.service.cm</artifactId>
+      <version>1.6.0</version>
     </dependency>
-  </dependencies>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.service.log</artifactId>
+      <version>1.4.0</version>
+    </dependency>
+   </dependencies>
   <build>
     <plugins>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>2.3.4</version>
+        <version>3.5.0</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
-            <Export-Package>org.osgi.service.log</Export-Package>
+            <Export-Package>org.osgi.service.log,org.osgi.service.log.admin</Export-Package>
             <Private-Package>org.apache.felix.log</Private-Package>
             <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
             <Bundle-Activator>${pom.artifactId}.Activator</Bundle-Activator>
             <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
             <Export-Service>org.osgi.service.log.LogService,org.osgi.service.log.LogReaderService</Export-Service>
             <Include-Resource>META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+            <Provide-Capability><![CDATA[
+                osgi.service;objectClass:List<String>="org.osgi.service.log.admin.LoggerAdmin";uses:="org.osgi.service.log,org.osgi.service.log.admin",
+                osgi.service;objectClass:List<String>="org.osgi.service.log.LogReaderService";uses:="org.osgi.service.log,org.osgi.service.log.admin",
+                osgi.service;objectClass:List<String>="org.osgi.service.log.LogService,org.osgi.service.log.LoggerFactory";uses:="org.osgi.service.log,org.osgi.service.log.admin"
+            ]]></Provide-Capability>
           </instructions>
         </configuration>
       </plugin>
        <plugin>
-		<groupId>org.codehaus.mojo</groupId>
-		<artifactId>rat-maven-plugin</artifactId>
-		<configuration>
-			<excludeSubProjects>false</excludeSubProjects>
-			<useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
-			<useMavenDefaultExcludes>true</useMavenDefaultExcludes>
-			<excludes>
-				<param>doc/*</param>
-				<param>maven-eclipse.xml</param>
-				<param>.checkstyle</param>
-				<param>.externalToolBuilders/*</param>
-			</excludes>
-		</configuration>
-	   </plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+        <configuration>
+            <excludeSubProjects>false</excludeSubProjects>
+            <useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
+            <useMavenDefaultExcludes>true</useMavenDefaultExcludes>
+            <excludes>
+                <param>doc/*</param>
+                <param>maven-eclipse.xml</param>
+                <param>.checkstyle</param>
+                <param>.externalToolBuilders/*</param>
+            </excludes>
+        </configuration>
+    </plugin>
     </plugins>
   </build>
 </project>

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/Activator.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/Activator.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/Activator.java Mon Jul  2 21:27:40 2018
@@ -18,10 +18,28 @@
  */
 package org.apache.felix.log;
 
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.log.LogLevel;
 import org.osgi.service.log.LogReaderService;
 import org.osgi.service.log.LogService;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.service.log.admin.LoggerAdmin;
+import org.osgi.service.log.admin.LoggerContext;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 /**
  * The bundle activator for the OSGi log service (see section 101 of the service
@@ -57,6 +75,10 @@ public final class Activator implements
     private static final boolean DEFAULT_STORE_DEBUG = false;
     /** The log. */
     private Log m_log;
+    /** The LoggerAdmin. */
+    private LoggerAdminImpl m_loggerAdmin;
+    /** ConfigurationAdmin tracker */
+    private ServiceTracker<ConfigurationAdmin, ConfigurationAdmin> m_cmtracker;
 
     /**
      * Returns the maximum size for the log.
@@ -102,6 +124,15 @@ public final class Activator implements
     }
 
     /**
+     * Return the default log level.
+     * @param context
+     * @return the default log level
+     */
+    private static String getDefaultLogLevel(final BundleContext context) {
+        return context.getProperty(LoggerContext.LOGGER_CONTEXT_DEFAULT_LOGLEVEL);
+    }
+
+    /**
      * Called by the OSGi framework when the bundle is started.
      * Used to register the service implementations with the framework.
      * @param context the bundle context
@@ -111,6 +142,8 @@ public final class Activator implements
     {
         // create the log instance
         m_log = new Log(getMaxSize(context), getStoreDebug(context));
+        // create the LoggerAdmin instance
+        m_loggerAdmin = new LoggerAdminImpl(getDefaultLogLevel(context), m_log);
 
         // register the listeners
         context.addBundleListener(m_log);
@@ -118,11 +151,54 @@ public final class Activator implements
         context.addServiceListener(m_log);
 
         // register the services with the framework
-        context.registerService(LogService.class.getName(),
-            new LogServiceFactory(m_log), null);
+        ServiceRegistration<?> serviceRegistration = context.registerService(
+            new String[] {LogService.class.getName(), LoggerFactory.class.getName()},
+            new LogServiceFactory(m_loggerAdmin), null);
 
         context.registerService(LogReaderService.class.getName(),
             new LogReaderServiceFactory(m_log), null);
+
+        Dictionary<String, Object> properties = new Hashtable<>();
+        properties.put(
+            LoggerAdmin.LOG_SERVICE_ID,
+            serviceRegistration.getReference().getProperty(Constants.SERVICE_ID));
+        context.registerService(LoggerAdmin.class.getName(), m_loggerAdmin, properties);
+
+        // create the cm tracker
+        m_cmtracker = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(
+            context, ConfigurationAdmin.class, new CMLogCustomizer(context));
+
+        m_cmtracker.open();
+
+        context.registerService(
+            ConfigurationListener.class.getName(),
+            new ConfigurationListener() {
+
+                @Override
+                public void configurationEvent(ConfigurationEvent event) {
+                    String pid = event.getPid();
+                    String configName = null;
+                    if (pid.startsWith("org.osgi.service.log.admin|")) {
+                        configName = pid.substring("org.osgi.service.log.admin|".length());
+                    }
+
+                    switch (event.getType()) {
+                        case ConfigurationEvent.CM_DELETED:
+                            m_loggerAdmin.updateConfiguration(configName, null);
+                            break;
+                        case ConfigurationEvent.CM_LOCATION_CHANGED:
+                        case ConfigurationEvent.CM_UPDATED:
+                            ConfigurationAdmin cm = m_cmtracker.getService(event.getReference());
+                            try {
+                                Configuration configuration = cm.getConfiguration(pid);
+                                m_loggerAdmin.updateConfiguration(configName, configuration.getProperties());
+                            } catch (IOException e) {
+                                m_log.log(Activator.class.getName(), context.getBundle(), null, LogLevel.ERROR, "An error occured while LogService was getting configuration from cm.", e);
+                            }
+                    }
+                }
+
+            }, null);
     }
 
     /**
@@ -132,7 +208,61 @@ public final class Activator implements
      */
     public void stop(final BundleContext context) throws Exception
     {
+        // close the cm tracker
+        m_cmtracker.close();
         // close the log
         m_log.close();
     }
+
+    class CMLogCustomizer implements ServiceTrackerCustomizer<ConfigurationAdmin, ConfigurationAdmin> {
+
+        private final BundleContext m_context;
+
+        public CMLogCustomizer(BundleContext context) {
+            m_context = context;
+        }
+
+        @Override
+        public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
+            ConfigurationAdmin cm = m_context.getService(reference);
+            // configure ROOT context
+            updateContext(null, "org.osgi.service.log.admin", cm, false);
+            // configure bundle contexts
+            for (String name : m_loggerAdmin.getLoggerContextNames()) {
+                String pid = "org.osgi.service.log.admin|" + name;
+                updateContext(name, pid, cm, false);
+            }
+            return cm;
+        }
+
+        @Override
+        public void modifiedService(ServiceReference<ConfigurationAdmin> reference, ConfigurationAdmin service) {
+        }
+
+        @Override
+        public void removedService(ServiceReference<ConfigurationAdmin> reference, ConfigurationAdmin cm) {
+            // un-configure bundle contexts
+            for (String name : m_loggerAdmin.getLoggerContextNames()) {
+                String pid = "org.osgi.service.log.admin|" + name;
+                updateContext(name, pid, cm, true);
+            }
+            // un-configure ROOT context
+            updateContext(null, "org.osgi.service.log.admin", cm, true);
+        }
+
+        private void updateContext(String name, String pid, ConfigurationAdmin cm, boolean delete) {
+            try {
+                Configuration[] configurations = cm.listConfigurations(String.format("(%s=%s)", Constants.SERVICE_PID, pid));
+
+                if (configurations != null && configurations.length > 0) {
+                    m_loggerAdmin.updateConfiguration(name, delete ? null : configurations[0].getProperties());
+                }
+            }
+            catch (IOException | InvalidSyntaxException e) {
+                m_log.log(Activator.class.getName(), m_context.getBundle(), null, LogLevel.ERROR, "An error occured while LogService was getting configuration from cm.", e);
+            }
+        }
+
+    }
+
 }
\ No newline at end of file

Added: felix/trunk/log/src/main/java/org/apache/felix/log/FormatterLoggerImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/FormatterLoggerImpl.java?rev=1834903&view=auto
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/FormatterLoggerImpl.java (added)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/FormatterLoggerImpl.java Mon Jul  2 21:27:40 2018
@@ -0,0 +1,45 @@
+/*
+ * 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.log;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.log.FormatterLogger;
+
+public class FormatterLoggerImpl extends LoggerImpl implements FormatterLogger {
+
+    public FormatterLoggerImpl(
+        final String name, final Bundle bundle, final Log log, final LoggerAdminImpl loggerAdmin) {
+
+        super(name, bundle, log, loggerAdmin);
+    }
+
+    String format(String format, LogParameters logParameters) {
+        StringBuilder sb = new StringBuilder();
+
+        try (Formatter formatter = new Formatter(sb, Locale.getDefault())) {
+            formatter.format(format, logParameters.args);
+
+            return sb.toString();
+        }
+    }
+
+}

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/Log.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/Log.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/Log.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/Log.java Mon Jul  2 21:27:40 2018
@@ -20,15 +20,17 @@ package org.apache.felix.log;
 
 import java.util.Enumeration;
 
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleEvent;
 import org.osgi.framework.BundleListener;
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.FrameworkListener;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogLevel;
 import org.osgi.service.log.LogListener;
-import org.osgi.service.log.LogService;
 
 /**
  * Class used to represent the log.  This class is used by the implementations
@@ -79,6 +81,17 @@ final class Log implements BundleListene
         m_size = 0;
     }
 
+    void log(
+        final String name,
+        final Bundle bundle,
+        final ServiceReference<?> sr,
+        final LogLevel level,
+        final String message,
+        final Throwable exception) {
+
+        addEntry(new LogEntryImpl(name, bundle, sr, level, message, exception, getStackTraceElement()));
+    }
+
     /**
      * Adds the entry to the log.
      * @param entry the entry to add to the log
@@ -88,7 +101,7 @@ final class Log implements BundleListene
         if (m_maxSize != 0)
         {
             // add the entry to the historic log
-            if (m_storeDebug || entry.getLevel() != LogService.LOG_DEBUG)
+            if (m_storeDebug || entry.getLogLevel() != LogLevel.DEBUG)
             {
                 // create a new node for the entry
                 LogNode node = new LogNode(entry);
@@ -172,7 +185,7 @@ final class Log implements BundleListene
      * Returns an enumeration of all the entries in the log most recent first.
      * @return an enumeration of all the entries in the log most recent first
      */
-    synchronized Enumeration getEntries()
+    synchronized Enumeration<LogEntry> getEntries()
     {
         return new LogNodeEnumeration(m_head, m_tail);
     }
@@ -205,13 +218,13 @@ final class Log implements BundleListene
             }
         }
 
-        LogEntry entry = new LogEntryImpl(event.getBundle(),
+        log(
+            "Events.Framework",
+            event.getBundle(),
             null,
-            (eventType == FrameworkEvent.ERROR) ? LogService.LOG_ERROR : LogService.LOG_INFO,
+            (eventType == FrameworkEvent.ERROR) ? LogLevel.ERROR : LogLevel.INFO,
             message,
             event.getThrowable());
-
-        addEntry(entry);
     }
 
     /** The messages returned for the bundle events. */
@@ -245,14 +258,27 @@ final class Log implements BundleListene
 
         if (message != null)
         {
-            LogEntry entry = new LogEntryImpl(event.getBundle(),
+            log(
+                "Events.Bundle",
+                event.getBundle(),
                 null,
-                LogService.LOG_INFO,
+                LogLevel.INFO,
                 message,
                 null);
+        }
+    }
 
-            addEntry(entry);
+    public static StackTraceElement getStackTraceElement() {
+        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
+        if (elements.length == 0) {
+            return null;
+        }
+        for (int i = 1; i < elements.length; i++) {
+            if (!elements[i].getClassName().startsWith("org.apache.felix.log")) {
+                return elements[i];
+            }
         }
+        return elements[1];
     }
 
     /** The messages returned for the service events. */
@@ -280,12 +306,12 @@ final class Log implements BundleListene
             }
         }
 
-        LogEntry entry = new LogEntryImpl(event.getServiceReference().getBundle(),
+        log(
+            "Events.Service",
+            event.getServiceReference().getBundle(),
             event.getServiceReference(),
-            (eventType == ServiceEvent.MODIFIED) ? LogService.LOG_DEBUG : LogService.LOG_INFO,
+            (eventType == ServiceEvent.MODIFIED) ? LogLevel.DEBUG : LogLevel.INFO,
             message,
             null);
-
-        addEntry(entry);
     }
 }
\ No newline at end of file

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogEntryImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogEntryImpl.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogEntryImpl.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogEntryImpl.java Mon Jul  2 21:27:40 2018
@@ -18,9 +18,15 @@
  */
 package org.apache.felix.log;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicLong;
+
 import org.osgi.framework.Bundle;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.LogService;
 
 /**
  * Implementation of the OSGi {@link LogEntry} interface.  See section 101
@@ -37,39 +43,99 @@ import org.osgi.service.log.LogEntry;
  */
 final class LogEntryImpl implements LogEntry
 {
+    /** The sequence number generator. */
+    private static final AtomicLong m_sequenceGenerator = new AtomicLong();
+
+    /** The name of the logger used to create the LogEntry. */
+    private final String m_name;
     /** The bundle that created the LogEntry object. */
     private final Bundle m_bundle;
     /** The exception associated with this LogEntry object. */
     private final Throwable m_exception;
     /** The severity level of this LogEntry object. */
-    private final int m_level;
+    private final LogLevel m_level;
+    private final int m_legacyLevel;
     /** The message associated with this LogEntry object. */
     private final String m_message;
     /** The service reference associated with this LogEntry object. */
-    private final ServiceReference m_serviceReference;
+    private final ServiceReference<?> m_serviceReference;
     /** The system time in milliseconds when this LogEntry object was created. */
     private final long m_time;
+    /** The sequence of the LogEntry. */
+    private final long m_sequence;
+    /** The information about the Thread which logged the message. */
+    private final String m_threadInfo;
+    /** The StackTraceElement where the message was originally logged. */
+    private final StackTraceElement m_stackTraceElement;
+
+    private volatile String _toString;
 
     /**
      * Create a new instance.
+     * @param name the name of the logger used to create the LogEntry
      * @param bundle the bundle that created the LogEntry object
      * @param sr the service reference to associate with this LogEntry object
      * @param level the severity level for this LogEntry object
      * @param message the message to associate with this LogEntry object
      * @param exception the exception to associate with this LogEntry object
      */
-    LogEntryImpl(final Bundle bundle,
-        final ServiceReference sr,
-        final int level,
+    LogEntryImpl(
+        final String name,
+        final Bundle bundle,
+        final ServiceReference<?> sr,
+        final LogLevel level,
+        final String message,
+        final Throwable exception,
+        final StackTraceElement stackTraceElement)
+    {
+        this.m_name = name;
+        this.m_bundle = bundle;
+        this.m_exception = LogException.getException(exception);
+        this.m_level = level;
+        this.m_legacyLevel = level.ordinal();
+        this.m_message = message;
+        this.m_serviceReference = sr;
+        this.m_time = System.currentTimeMillis();
+        this.m_sequence = m_sequenceGenerator.getAndIncrement();
+        this.m_threadInfo = Thread.currentThread().getName();
+        this.m_stackTraceElement = stackTraceElement;
+    }
+
+    @SuppressWarnings("deprecation")
+    LogEntryImpl(
+        final String name,
+        final Bundle bundle,
+        final ServiceReference<?> sr,
+        final int legacyLevel,
         final String message,
-        final Throwable exception)
+        final Throwable exception,
+        final StackTraceElement stackTraceElement)
     {
+        this.m_name = name;
         this.m_bundle = bundle;
         this.m_exception = LogException.getException(exception);
+        LogLevel level = LogLevel.TRACE;
+        switch (legacyLevel) {
+            case LogService.LOG_ERROR:
+                level = LogLevel.ERROR;
+                break;
+            case LogService.LOG_WARNING:
+                level = LogLevel.WARN;
+                break;
+            case LogService.LOG_INFO:
+                level = LogLevel.INFO;
+                break;
+            case LogService.LOG_DEBUG:
+                level = LogLevel.DEBUG;
+        }
         this.m_level = level;
+        this.m_legacyLevel = ((level == LogLevel.TRACE) ? legacyLevel : level.ordinal());
         this.m_message = message;
         this.m_serviceReference = sr;
         this.m_time = System.currentTimeMillis();
+        this.m_sequence = m_sequenceGenerator.getAndIncrement();
+        this.m_threadInfo = Thread.currentThread().getName();
+        this.m_stackTraceElement = stackTraceElement;
     }
 
     /**
@@ -89,7 +155,7 @@ final class LogEntryImpl implements LogE
      * this LogEntry object; <code>null</code> if no {@link ServiceReference} object
      * was provided
      */
-    public ServiceReference getServiceReference()
+    public ServiceReference<?> getServiceReference()
     {
         return m_serviceReference;
     }
@@ -107,7 +173,11 @@ final class LogEntryImpl implements LogE
      */
     public int getLevel()
     {
-        return m_level;
+        if (m_legacyLevel != m_level.ordinal()) {
+            return m_legacyLevel;
+        }
+
+        return m_level.ordinal();
     }
 
     /**
@@ -147,4 +217,49 @@ final class LogEntryImpl implements LogE
     {
         return m_time;
     }
+
+    @Override
+    public LogLevel getLogLevel() {
+        return m_level;
+    }
+
+    @Override
+    public String getLoggerName() {
+        return m_name;
+    }
+
+    @Override
+    public long getSequence() {
+        return m_sequence;
+    }
+
+    @Override
+    public String getThreadInfo() {
+        return m_threadInfo;
+    }
+
+    @Override
+    public StackTraceElement getLocation() {
+        return m_stackTraceElement;
+    }
+
+    @Override
+    public String toString() {
+        if (_toString == null) {
+            _toString = m_time + "#" + m_sequence + " [" + m_threadInfo + "] " + m_level +
+                " (" + m_legacyLevel + ") [" + m_bundle.getBundleId() + ":" + m_name + "] " +
+                    m_stackTraceElement.getClassName() + ":" +
+                    m_stackTraceElement.getLineNumber() + " > " + m_message +
+                        (m_exception != null ? "\n" + exceptionString(m_exception) : "");
+        }
+        return _toString;
+    }
+
+    String exceptionString(Throwable t) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        t.printStackTrace(pw);
+        return sw.toString();
+    }
+
 }
\ No newline at end of file

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogException.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogException.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogException.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogException.java Mon Jul  2 21:27:40 2018
@@ -28,6 +28,9 @@ package org.apache.felix.log;
  */
 final class LogException extends Exception
 {
+
+    private static final long serialVersionUID = 1296580652284691039L;
+
     /** The class name of the original exception. */
     private final String m_className;
     /** The message from the original exception. */

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogListenerThread.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogListenerThread.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogListenerThread.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogListenerThread.java Mon Jul  2 21:27:40 2018
@@ -34,9 +34,9 @@ import org.osgi.service.log.LogListener;
 final class LogListenerThread extends Thread
 {
     // The list of entries waiting to be delivered to the log listeners.
-    private final List m_entriesToDeliver = new ArrayList();
+    private final List<LogEntry> m_entriesToDeliver = new ArrayList<>();
     // The list of listeners.
-    private final List m_listeners = new ArrayList();
+    private final List<LogListener> m_listeners = new ArrayList<>();
 
     LogListenerThread() {
         super("FelixLogListener");
@@ -110,7 +110,7 @@ final class LogListenerThread extends Th
     {
         while (!isInterrupted())
         {
-            List entriesToDeliver = new ArrayList();
+            List<LogEntry> entriesToDeliver = new ArrayList<>();
             synchronized (m_entriesToDeliver)
             {
                 if (m_entriesToDeliver.isEmpty())
@@ -121,46 +121,47 @@ final class LogListenerThread extends Th
                     }
                     catch (InterruptedException e)
                     {
-                        // the interrupt-flag is cleared; so, let's play nice and 
+                        // the interrupt-flag is cleared; so, let's play nice and
                         // interrupt this thread again to stop it...
                         interrupt();
                     }
                 }
-                else 
+                else
                 {
                     // Copy all current entries and deliver them in a single go...
                     entriesToDeliver.addAll(m_entriesToDeliver);
                     m_entriesToDeliver.clear();
                 }
             }
-            
+
             if (!entriesToDeliver.isEmpty())
             {
                 // Take a snapshot of all current listeners and deliver all
                 // pending messages to them...
-                List listeners = new ArrayList();
-                synchronized (m_listeners) 
+                List<LogListener> listeners = new ArrayList<>();
+                synchronized (m_listeners)
                 {
                     listeners.addAll(m_listeners);
                 }
 
-                Iterator entriesIt = entriesToDeliver.iterator();
-                while (entriesIt.hasNext()) 
+                Iterator<LogEntry> entriesIt = entriesToDeliver.iterator();
+                while (entriesIt.hasNext())
                 {
-                    LogEntry entry = (LogEntry) entriesIt.next();
-                    
-                    Iterator listenerIt = listeners.iterator();
+                    LogEntry entry = entriesIt.next();
+
+                    Iterator<LogListener> listenerIt = listeners.iterator();
                     while (listenerIt.hasNext())
                     {
-                        LogListener listener = (LogListener) listenerIt.next();
-                        
+                        LogListener listener = listenerIt.next();
+
                         try
                         {
                             listener.logged(entry);
                         }
                         catch (Throwable t)
                         {
-                            // catch and discard any exceptions thrown by the listener
+                            System.err.println("Logger failed to log with " + t.getMessage());
+                            t.printStackTrace(System.err);
                         }
                     }
                 }

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogNodeEnumeration.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogNodeEnumeration.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogNodeEnumeration.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogNodeEnumeration.java Mon Jul  2 21:27:40 2018
@@ -26,7 +26,7 @@ import org.osgi.service.log.LogEntry;
  * Implementation of the {@link Enumeration} interface for a linked list of
  * {@link LogNode} entries.
  */
-final class LogNodeEnumeration implements Enumeration
+final class LogNodeEnumeration implements Enumeration<LogEntry>
 {
     /** The next node. */
     private LogNode m_next;
@@ -57,7 +57,7 @@ final class LogNodeEnumeration implement
      * Returns the current element and moves onto the next element.
      * @return the current element
      */
-    public Object nextElement()
+    public LogEntry nextElement()
     {
         LogEntry result = null;
 

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceFactory.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceFactory.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceFactory.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceFactory.java Mon Jul  2 21:27:40 2018
@@ -21,12 +21,13 @@ package org.apache.felix.log;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogReaderService;
 
 /**
  * {@link ServiceFactory} implementation for {@link LogReaderService}.  Associates
  * an individual {@link LogReaderService} with a {@link Bundle}.
  */
-final class LogReaderServiceFactory implements ServiceFactory
+final class LogReaderServiceFactory implements ServiceFactory<LogReaderService>
 {
     /** The log to associate the service implementations with. */
     private final Log m_log;
@@ -46,8 +47,8 @@ final class LogReaderServiceFactory impl
      * @param registration the service registration
      * @return the log reader service implementation for the specified bundle
      */
-    public Object getService(final Bundle bundle,
-        final ServiceRegistration registration)
+    public LogReaderService getService(final Bundle bundle,
+        final ServiceRegistration<LogReaderService> registration)
     {
         return new LogReaderServiceImpl(m_log);
     }
@@ -60,8 +61,8 @@ final class LogReaderServiceFactory impl
      * @param service the service to release
      */
     public void ungetService(final Bundle bundle,
-        final ServiceRegistration registration,
-        final Object service)
+        final ServiceRegistration<LogReaderService> registration,
+        final LogReaderService service)
     {
         ((LogReaderServiceImpl) service).removeAllLogListeners();
     }

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceImpl.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceImpl.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogReaderServiceImpl.java Mon Jul  2 21:27:40 2018
@@ -23,6 +23,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
 
+import org.osgi.service.log.LogEntry;
 import org.osgi.service.log.LogListener;
 import org.osgi.service.log.LogReaderService;
 
@@ -41,7 +42,7 @@ final class LogReaderServiceImpl impleme
     /** The log implementation. */
     private final Log m_log;
     /** The listeners associated with this service. */
-    private final List m_listeners = new Vector();
+    private final List<LogListener> m_listeners = new Vector<>();
 
     /**
      * Create a new instance.
@@ -87,7 +88,7 @@ final class LogReaderServiceImpl impleme
      * entry first.
      * @return an enumeration of the {@link LogEntry} objects that have been stored
      */
-    public Enumeration getLog()
+    public Enumeration<LogEntry> getLog()
     {
         return m_log.getEntries();
     }
@@ -97,10 +98,10 @@ final class LogReaderServiceImpl impleme
      */
     synchronized void removeAllLogListeners()
     {
-        Iterator listenerIt = m_listeners.iterator();
+        Iterator<LogListener> listenerIt = m_listeners.iterator();
         while (listenerIt.hasNext())
         {
-            LogListener listener = (LogListener) listenerIt.next();
+            LogListener listener = listenerIt.next();
             m_log.removeListener(listener);
         }
     }

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceFactory.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceFactory.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceFactory.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceFactory.java Mon Jul  2 21:27:40 2018
@@ -21,23 +21,24 @@ package org.apache.felix.log;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
 
 /**
  * {@link ServiceFactory} implementation for {@link LogService}.  Associates
  * an individual {@link LogService} with a {@link Bundle}.
  */
-final class LogServiceFactory implements ServiceFactory
+final class LogServiceFactory implements ServiceFactory<LogService>
 {
-    /** The log to associate the service implementations with. */
-    private final Log m_log;
+    /** The logger admin impl. */
+    private final LoggerAdminImpl m_loggerAdminImpl;
 
     /**
      * Create a new instance.
      * @param log the log to associate the service implementations with.,
      */
-    LogServiceFactory(final Log log)
+    LogServiceFactory(final LoggerAdminImpl loggerAdminImpl)
     {
-        m_log = log;
+        m_loggerAdminImpl = loggerAdminImpl;
     }
 
     /**
@@ -46,10 +47,10 @@ final class LogServiceFactory implements
      * @param registration the service registration
      * @return the log service implementation for the specified bundle
      */
-    public Object getService(final Bundle bundle,
-        final ServiceRegistration registration)
+    public LogService getService(final Bundle bundle,
+        final ServiceRegistration<LogService> registration)
     {
-        return new LogServiceImpl(m_log, bundle);
+        return new LogServiceImpl(bundle, m_loggerAdminImpl);
     }
 
     /**
@@ -60,8 +61,8 @@ final class LogServiceFactory implements
      * @param service the service to release
      */
     public void ungetService(final Bundle bundle,
-        final ServiceRegistration registration,
-        final Object service)
+        final ServiceRegistration<LogService> registration,
+        final LogService service)
     {
         // do nothing
     }

Modified: felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceImpl.java?rev=1834903&r1=1834902&r2=1834903&view=diff
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceImpl.java (original)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LogServiceImpl.java Mon Jul  2 21:27:40 2018
@@ -19,28 +19,32 @@
 package org.apache.felix.log;
 
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.log.LogService;
+import org.osgi.service.log.Logger;
 
 /**
  * Implementation of the OSGi {@link LogService}.
  */
 final class LogServiceImpl implements LogService
 {
-    /** The log implementation. */
-    private final Log m_log;
     /** The bundle associated with this implementation. */
     private final Bundle m_bundle;
+    /** The logger admin impl. */
+    private final LoggerAdminImpl m_loggerAdminImpl;
 
     /**
      * Create a new instance.
      * @param log the log implementation
      * @param bundle the bundle associated with this implementation
+     * @param serviceReference
      */
-    LogServiceImpl(final Log log, final Bundle bundle)
+    LogServiceImpl(final Bundle bundle, final LoggerAdminImpl loggerAdminImpl)
     {
-        this.m_log = log;
         this.m_bundle = bundle;
+        this.m_loggerAdminImpl = loggerAdminImpl;
     }
 
     /**
@@ -74,7 +78,7 @@ final class LogServiceImpl implements Lo
      * @param level the level to log the message at
      * @param message the message to log
      */
-    public void log(final ServiceReference sr,
+    public void log(final ServiceReference<?> sr,
         final int level,
         final String message)
     {
@@ -89,15 +93,73 @@ final class LogServiceImpl implements Lo
      * @param message the message to log
      * @param exception the exception to log
      */
-    public void log(final ServiceReference sr,
+    @SuppressWarnings("deprecation")
+    public void log(final ServiceReference<?> sr,
         final int level,
         final String message,
         final Throwable exception)
     {
-        m_log.addEntry(new LogEntryImpl((sr != null) ? sr.getBundle() : m_bundle,
-            sr,
-            level,
-            message,
-            exception));
+        LoggerImpl logger = (LoggerImpl)m_loggerAdminImpl.getLogger(m_bundle, "LogService", Logger.class);
+
+        switch (level) {
+            case LogService.LOG_DEBUG:
+                logger.debug(message, sr, exception);
+                break;
+            case LogService.LOG_ERROR:
+                logger.error(message, sr, exception);
+                break;
+            case LogService.LOG_INFO:
+                logger.info(message, sr, exception);
+                break;
+            case LogService.LOG_WARNING:
+                logger.warn(message, sr, exception);
+                break;
+            default:
+                logger.log(level, message, sr, exception);
+        }
+    }
+
+    @Override
+    public Logger getLogger(String name) {
+        return m_loggerAdminImpl.getLogger(m_bundle, name, Logger.class);
+    }
+
+    @Override
+    public Logger getLogger(Class<?> clazz) {
+        LogService logService = getLogService(clazz);
+        return logService.getLogger(clazz.getName());
+    }
+
+    @Override
+    public <L extends Logger> L getLogger(String name, Class<L> loggerType) {
+        return m_loggerAdminImpl.getLogger(m_bundle, name, loggerType);
+    }
+
+    @Override
+    public <L extends Logger> L getLogger(Class<?> clazz, Class<L> loggerType) {
+        LogService logService = getLogService(clazz);
+        return logService.getLogger(clazz.getName(), loggerType);
+    }
+
+    @Override
+    public <L extends Logger> L getLogger(Bundle bundle, String name, Class<L> loggerType) {
+        LogService logService = getLogService(bundle);
+        return logService.getLogger(name, loggerType);
+    }
+
+    private LogService getLogService(Bundle bundle) {
+        if (((bundle.getState() & Bundle.ACTIVE) != Bundle.ACTIVE) &&
+            ((bundle.getState() & Bundle.RESOLVED) != Bundle.RESOLVED)) {
+            throw new IllegalArgumentException("Bundle " + bundle + " is not resolved.");
+        }
+        BundleContext bundleContext = bundle.getBundleContext();
+        ServiceReference<LogService> serviceReference = bundleContext.getServiceReference(LogService.class);
+        return bundleContext.getService(serviceReference);
+    }
+
+    private LogService getLogService(Class<?> clazz) {
+        Bundle bundle = FrameworkUtil.getBundle(clazz);
+        return getLogService(bundle);
     }
+
 }
\ No newline at end of file

Added: felix/trunk/log/src/main/java/org/apache/felix/log/LoggerAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LoggerAdminImpl.java?rev=1834903&view=auto
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LoggerAdminImpl.java (added)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LoggerAdminImpl.java Mon Jul  2 21:27:40 2018
@@ -0,0 +1,186 @@
+/*
+ * 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.log;
+
+import java.util.Dictionary;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.log.FormatterLogger;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.admin.LoggerAdmin;
+import org.osgi.service.log.admin.LoggerContext;
+
+public class LoggerAdminImpl implements LoggerAdmin {
+
+    private final Log m_log;
+    private final LoggerContext m_rootContext;
+    private final ConcurrentMap<String, LoggerContext> m_contexts = new ConcurrentHashMap<>();
+    private final ConcurrentMap<LoggerKey, Logger> _loggers = new ConcurrentHashMap<>();
+
+    public LoggerAdminImpl(final String defaultLogLevelString, final Log log) {
+        m_rootContext = new RootLoggerContextImpl(defaultLogLevelString, this);
+        m_log = log;
+    }
+
+    @Override
+    public LoggerContext getLoggerContext(String name) {
+        return getOrCreateLoggerContext(name);
+    }
+
+    public Set<String> getLoggerContextNames() {
+        return m_contexts.keySet();
+    }
+
+    protected void updateConfiguration(String name, Dictionary<String, Object> properties) {
+        LoggerContext loggerContext = getOrCreateLoggerContext(name);
+
+        LoggerContextImpl contextImpl = (LoggerContextImpl)loggerContext;
+
+        contextImpl.updateLoggerContext(properties);
+    }
+
+    protected LoggerContext getOrCreateLoggerContext(String name) {
+        if (name == null) {
+            return m_rootContext;
+        }
+
+        LoggerContext loggerContext = m_contexts.get(name);
+
+        if (loggerContext == null) {
+            loggerContext = new LoggerContextImpl(name, this, m_rootContext);
+        }
+
+        return loggerContext;
+    }
+
+    public void keepLoggerContext(String name, LoggerContextImpl loggerContext) {
+        if (loggerContext instanceof RootLoggerContextImpl) return;
+        m_contexts.put(name, loggerContext);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <L extends Logger> L getLogger(
+        final Bundle bundle, final String name, final Class<L> loggerType) {
+
+        LoggerKey key = new LoggerKey(bundle, name, loggerType);
+
+        L logger = (L)_loggers.get(key);
+
+        if (logger == null) {
+            if (loggerType.equals(FormatterLogger.class)) {
+                logger = (L)new FormatterLoggerImpl(name, bundle, m_log, this);
+            }
+            else {
+                logger = (L)new LoggerImpl(name, bundle, m_log, this);
+            }
+
+            L previous = (L)_loggers.putIfAbsent(key, logger);
+
+            if (previous != null) {
+                logger = previous;
+            }
+        }
+
+        return logger;
+    }
+
+    LoggerContext getLoggerContext(Bundle bundle, String name) {
+        String loggerContextName = String.format(
+            "%s|%s|%s", bundle.getSymbolicName(), bundle.getVersion(), bundle.getLocation());
+
+        LoggerContext loggerContext = getLoggerContext(loggerContextName);
+
+        if (loggerContext.isEmpty()) {
+            loggerContextName = String.format(
+                "%s|%s", bundle.getSymbolicName(), bundle.getVersion());
+
+            loggerContext = getLoggerContext(loggerContextName);
+        }
+
+        if (loggerContext.isEmpty()) {
+            loggerContext = getLoggerContext(bundle.getSymbolicName());
+        }
+
+        return loggerContext;
+    }
+
+    static class LoggerKey {
+
+        public LoggerKey(Bundle bundle, String name, Class<? extends Logger> loggerType) {
+            this.m_bundle = bundle;
+            this.m_name = name;
+            this.m_loggerType = loggerType;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((m_bundle == null) ? 0 : m_bundle.hashCode());
+            result = prime * result + ((m_loggerType == null) ? 0 : m_loggerType.hashCode());
+            result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+            return result;
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            LoggerKey other = (LoggerKey) obj;
+            if (m_bundle == null) {
+                if (other.m_bundle != null)
+                    return false;
+            } else if (m_bundle.getBundleId() != other.m_bundle.getBundleId())
+                return false;
+            if (m_loggerType == null) {
+                if (other.m_loggerType != null)
+                    return false;
+            } else if (!m_loggerType.equals(other.m_loggerType))
+                return false;
+            if (m_name == null) {
+                if (other.m_name != null)
+                    return false;
+            } else if (!m_name.equals(other.m_name))
+                return false;
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            if (m_string == null) {
+                m_string = getClass().getSimpleName() + "[" + m_bundle + "#" + m_name + "!" + m_loggerType.getSimpleName() + "]";
+            }
+            return m_string;
+        }
+
+        private final Bundle m_bundle;
+        private final String m_name;
+        private final Class<? extends Logger> m_loggerType;
+        private volatile String m_string;
+
+    }
+
+}

Added: felix/trunk/log/src/main/java/org/apache/felix/log/LoggerContextImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LoggerContextImpl.java?rev=1834903&view=auto
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LoggerContextImpl.java (added)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LoggerContextImpl.java Mon Jul  2 21:27:40 2018
@@ -0,0 +1,163 @@
+/*
+ * 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.log;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.admin.LoggerContext;
+
+public class LoggerContextImpl implements LoggerContext {
+
+    static final String ROOT = "ROOT";
+
+    private final String _name;
+    protected volatile Map<String, LogLevel> _levels;
+    protected final Lock _lock = new ReentrantLock();
+    protected final LoggerAdminImpl _loggerAdminImpl;
+    protected final LoggerContext _rootContext;
+
+    private volatile String _toString;
+
+    public LoggerContextImpl(String name, LoggerAdminImpl loggerAdminImpl, LoggerContext rootLoggerContext) {
+        _name = name;
+        _loggerAdminImpl = loggerAdminImpl;
+        _rootContext = rootLoggerContext;
+    }
+
+    public String getName() {
+        return _name;
+    }
+
+    public LogLevel getEffectiveLogLevel(String name) {
+        _lock.lock();
+        try {
+            if (_levels != null && !_levels.isEmpty()) {
+                String copy = name;
+                LogLevel level;
+                while (copy.length() > 0) {
+                    level = _levels.get(copy);
+                    if (level != null) {
+                        return level;
+                    }
+                    if (ROOT.equals(copy))
+                        break;
+                    copy = ancestor(copy);
+                }
+            }
+            return _rootContext.getEffectiveLogLevel(name);
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    public Map<String, LogLevel> getLogLevels() {
+        _lock.lock();
+        try {
+            if (_levels == null) {
+                return new HashMap<>();
+            }
+            return new HashMap<>(_levels);
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    public void setLogLevels(Map<String, LogLevel> logLevels) {
+        _lock.lock();
+        try {
+            _levels = new HashMap<>(logLevels);
+            _loggerAdminImpl.keepLoggerContext(_name, this);
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    public void clear() {
+        _lock.lock();
+        try {
+            _levels = null;
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    public boolean isEmpty() {
+        _lock.lock();
+        try {
+            return _levels == null || _levels.isEmpty();
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    void updateLoggerContext(Dictionary<String, Object> properties) {
+        _lock.lock();
+        try {
+            _levels = new HashMap<>();
+            if (properties != null) {
+                for (Enumeration<String> enu = properties.keys(); enu.hasMoreElements();) {
+                    String key = enu.nextElement();
+                    Object object = properties.get(key);
+                    if (object instanceof String) {
+                        String value = (String)object;
+                        for (LogLevel level : LogLevel.values()) {
+                            if (level.name().equalsIgnoreCase(value)) {
+                                _levels.put(key, level);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            _loggerAdminImpl.keepLoggerContext(_name, this);
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (_toString == null) {
+            _toString = getClass().getSimpleName() + "[" + _name + "]";
+        }
+        return _toString;
+    }
+
+    protected String ancestor(String name) {
+        int position = name.lastIndexOf('.');
+        if (position == -1) {
+            return ROOT;
+        }
+        return name.substring(0, position);
+    }
+
+}

Added: felix/trunk/log/src/main/java/org/apache/felix/log/LoggerImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/LoggerImpl.java?rev=1834903&view=auto
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/LoggerImpl.java (added)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/LoggerImpl.java Mon Jul  2 21:27:40 2018
@@ -0,0 +1,377 @@
+/*
+ * 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.log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerConsumer;
+
+public class LoggerImpl implements Logger {
+
+    private static final String ESCAPE = "\\";
+    private static final String MAX_CHAR = "\uFFFF";
+    private static final String BRACE_CLOSE = "}";
+    private static final String BRACE_OPEN = "{";
+
+    protected final String m_name;
+    protected final Bundle m_bundle;
+    protected final Log m_log;
+    protected final LoggerAdminImpl m_loggerAdmin;
+
+    public LoggerImpl(final String name, final Bundle bundle, final Log log, final LoggerAdminImpl loggerAdmin) {
+        m_name = name;
+        m_bundle = bundle;
+        m_log = log;
+        m_loggerAdmin = loggerAdmin;
+    }
+
+    @Override
+    public String getName() {
+        return m_name;
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return m_loggerAdmin.getLoggerContext(m_bundle, m_name).getEffectiveLogLevel(m_name).implies(LogLevel.TRACE);
+    }
+
+    void trace(String message, ServiceReference<?> serviceReference, Throwable t) {
+        if (!isTraceEnabled()) return;
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.TRACE, message, t);
+    }
+
+    @Override
+    public void trace(String message) {
+        trace(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void trace(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        trace(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void trace(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        trace(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void trace(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        trace(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public <E extends Exception> void trace(LoggerConsumer<E> consumer) throws E {
+        if (isTraceEnabled()) {
+            consumer.accept(this);
+        }
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return m_loggerAdmin.getLoggerContext(m_bundle, m_name).getEffectiveLogLevel(m_name).implies(LogLevel.DEBUG);
+    }
+
+    void debug(String message, ServiceReference<?> serviceReference, Throwable t) {
+        if (!isDebugEnabled()) return;
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.DEBUG, message, t);
+    }
+
+    @Override
+    public void debug(String message) {
+        debug(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void debug(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        debug(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void debug(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        debug(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void debug(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        debug(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public <E extends Exception> void debug(LoggerConsumer<E> consumer) throws E {
+        if (isDebugEnabled()) {
+            consumer.accept(this);
+        }
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return m_loggerAdmin.getLoggerContext(m_bundle, m_name).getEffectiveLogLevel(m_name).implies(LogLevel.INFO);
+    }
+
+    void info(String message, ServiceReference<?> serviceReference, Throwable t) {
+        if (!isInfoEnabled()) return;
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.INFO, message, t);
+    }
+
+    @Override
+    public void info(String message) {
+        info(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void info(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        info(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void info(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        info(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void info(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        info(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public <E extends Exception> void info(LoggerConsumer<E> consumer) throws E {
+        if (isInfoEnabled()) {
+            consumer.accept(this);
+        }
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return m_loggerAdmin.getLoggerContext(m_bundle, m_name).getEffectiveLogLevel(m_name).implies(LogLevel.WARN);
+    }
+
+    void warn(String message, ServiceReference<?> serviceReference, Throwable t) {
+        if (!isWarnEnabled()) return;
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.WARN, message, t);
+    }
+
+    @Override
+    public void warn(String message) {
+        warn(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void warn(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        warn(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void warn(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        warn(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void warn(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        warn(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public <E extends Exception> void warn(LoggerConsumer<E> consumer) throws E {
+        if (isWarnEnabled()) {
+            consumer.accept(this);
+        }
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return m_loggerAdmin.getLoggerContext(m_bundle, m_name).getEffectiveLogLevel(m_name).implies(LogLevel.ERROR);
+    }
+
+    void error(String message, ServiceReference<?> serviceReference, Throwable t) {
+        if (!isErrorEnabled()) return;
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.ERROR, message, t);
+    }
+
+    @Override
+    public void error(String message) {
+        error(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void error(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        error(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void error(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        error(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void error(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        error(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public <E extends Exception> void error(LoggerConsumer<E> consumer) throws E {
+        if (isErrorEnabled()) {
+            consumer.accept(this);
+        }
+    }
+
+    public void audit(String message, ServiceReference<?> serviceReference, Throwable t) {
+        m_log.log(m_name, m_bundle, serviceReference, LogLevel.AUDIT, message, t);
+    }
+
+    @Override
+    public void audit(String message) {
+        audit(message, (ServiceReference<?>)null, null);
+    }
+
+    @Override
+    public void audit(String format, Object arg) {
+        LogParameters logParameters = getLogParameters(arg);
+        audit(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void audit(String format, Object arg1, Object arg2) {
+        LogParameters logParameters = getLogParameters(arg1, arg2);
+        audit(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    @Override
+    public void audit(String format, Object... arguments) {
+        LogParameters logParameters = getLogParameters(arguments);
+        audit(format(format, logParameters), logParameters.sr, logParameters.t);
+    }
+
+    public void log(
+        final int level,
+        final String message,
+        final ServiceReference<?> sr,
+        final Throwable exception) {
+
+        m_log.addEntry(new LogEntryImpl(m_name, m_bundle, sr, level, message, exception, Log.getStackTraceElement()));
+    }
+
+    LogParameters getLogParameters(Object arg) {
+        return getLogParameters0(arg);
+    }
+
+    LogParameters getLogParameters(Object arg1, Object arg2) {
+        return getLogParameters0(arg1, arg2);
+    }
+
+    LogParameters getLogParameters(Object... arguments) {
+        return getLogParameters0(arguments);
+    }
+
+    LogParameters getLogParameters0(Object... arguments) {
+        if (arguments == null || arguments.length == 0) {
+            return new LogParameters(null, null, null);
+        }
+        ServiceReference<?> sr = null;
+        Throwable t = null;
+        List<Object> args = new ArrayList<>();
+        for (Object arg : arguments) {
+            if (t == null && arg instanceof Throwable) {
+                t = (Throwable)arg;
+            }
+            else if (sr == null && arg instanceof ServiceReference) {
+                sr = (ServiceReference<?>)arg;
+            }
+            else if (arg != null) {
+                args.add(arg);
+            }
+        }
+        return new LogParameters(args.toArray(), sr, t);
+    }
+
+    String format(String format, LogParameters logParameters) {
+        StringBuilder sb = new StringBuilder();
+        int offset = 0;
+        int length = format.length();
+        String previous = MAX_CHAR;
+        boolean escape = false;
+        int argIndex = 0;
+        while (offset < length) {
+            int curChar = format.codePointAt(offset);
+            offset += Character.charCount(curChar);
+            String cur = new String(Character.toChars(curChar));
+
+            if (argIndex == logParameters.args.length) {
+                sb.append(cur);
+            }
+            else if (escape) {
+                escape = false;
+                sb.append(cur);
+                previous = MAX_CHAR;
+            }
+            else if (ESCAPE.equals(cur)) {
+                escape = true;
+                previous = ESCAPE;
+            }
+            else if (BRACE_OPEN.equals(cur)) {
+                if (BRACE_OPEN.equals(previous)) {
+                    sb.append(previous);
+                }
+                previous = BRACE_OPEN;
+            }
+            else if (BRACE_CLOSE.equals(cur) && BRACE_OPEN.equals(previous)) {
+                sb.append(logParameters.args[argIndex++]);
+                previous = MAX_CHAR;
+            }
+            else {
+                sb.append(cur);
+                previous = MAX_CHAR;
+            }
+        }
+
+        return sb.toString();
+    }
+
+    static class LogParameters {
+        public LogParameters(Object[] args, ServiceReference<?> sr, Throwable t) {
+            this.args = args;
+            this.sr = sr;
+            this.t = t;
+        }
+        final Object[] args;
+        final ServiceReference<?> sr;
+        final Throwable t;
+    }
+
+}

Added: felix/trunk/log/src/main/java/org/apache/felix/log/RootLoggerContextImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/log/src/main/java/org/apache/felix/log/RootLoggerContextImpl.java?rev=1834903&view=auto
==============================================================================
--- felix/trunk/log/src/main/java/org/apache/felix/log/RootLoggerContextImpl.java (added)
+++ felix/trunk/log/src/main/java/org/apache/felix/log/RootLoggerContextImpl.java Mon Jul  2 21:27:40 2018
@@ -0,0 +1,86 @@
+/*
+ * 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.log;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+
+public class RootLoggerContextImpl extends LoggerContextImpl {
+
+    private final LogLevel _defaultLevel;
+
+    public RootLoggerContextImpl(String defaultLogLevelString, LoggerAdminImpl loggerAdminImpl) {
+        super(null, loggerAdminImpl, null);
+
+        LogLevel defaultLogLevel = LogLevel.WARN;
+        if (defaultLogLevelString != null) {
+            for (LogLevel level : LogLevel.values()) {
+                if (level.name().equalsIgnoreCase(defaultLogLevelString)) {
+                    defaultLogLevel = level;
+                    break;
+                }
+            }
+        }
+
+        _defaultLevel = defaultLogLevel;
+    }
+
+    public LogLevel getEffectiveLogLevel(String name) {
+        _lock.lock();
+        try {
+            if (_levels != null && !_levels.isEmpty()) {
+                LogLevel level;
+                while (name.length() > 0) {
+                    level = _levels.get(name);
+                    if (level != null) {
+                        return level;
+                    }
+                    if (ROOT.equals(name))
+                        break;
+                    name = ancestor(name);
+                }
+            }
+            return getEffectiveRootLogLevel();
+        }
+        finally {
+            _lock.unlock();
+        }
+    }
+
+    @Override
+    public void setLogLevels(Map<String, LogLevel> logLevels) {
+        super.setLogLevels(logLevels);
+    }
+
+    @Override
+    void updateLoggerContext(Dictionary<String, Object> properties) {
+        super.updateLoggerContext(properties);
+    }
+
+    private LogLevel getEffectiveRootLogLevel() {
+        if (_levels == null) return _defaultLevel;
+        LogLevel logLevel = _levels.get(Logger.ROOT_LOGGER_NAME);
+        return (logLevel == null)? _defaultLevel : logLevel;
+    }
+
+}