tjwatson commented on a change in pull request #36:
URL: https://github.com/apache/felix-dev/pull/36#discussion_r468120717



##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
##########
@@ -0,0 +1,293 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.Logger;
+
+/**
+ * Implements a SCR based log manager. This class was needed to not change the
+ * whole codebase. It looks very similar to the old logging but leverages the
+ * {@link LogManager}. This class implements the existing behavior and the
+ * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
+ * method makes this distinction based on the configuration.
+ */
+public class ScrLogManager extends LogManager {
+
+       private final Bundle                    bundle;
+       private final ScrConfiguration  config;
+
+       /**
+        * Get a new log manager based on the configuration.
+        * 
+        * @param context
+        *                    the bundle context of the SCR bundle
+        * @param config
+        *                    the SCR configuration
+        * @return a proper ScrLogManager
+        */
+
+       public static ScrLogger scr(BundleContext context, ScrConfiguration 
config) {
+               if (config.isLogExtension())
+                       return new ExtLogManager(context, config).scr();
+               else
+                       return new ScrLogManager(context, config).scr();
+       }
+
+       ScrLogManager(BundleContext context, ScrConfiguration config) {
+               super(context);
+               this.config = config;
+               this.bundle = context.getBundle();
+       }
+
+       /**
+        * This logger is used for the main code of SCR. This will use the SCR
+        * bundle & the {@link Logger#ROOT_LOGGER_NAME}
+        * 
+        * @return an Scr Logger.
+        */
+       public ScrLogger scr() {
+               ScrLoggerFacade scrl = super.getLogger(bundle, 
Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+               scrl.setPrefix(getBundleIdentifier(bundle));
+               return scrl;
+       }
+
+       /**
+        * This logger is used for the logging on a per bundle basis. This will 
use
+        * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
+        * 
+        * @param bundle
+        *                   the target bundle
+        * @return a logger suitable to log bundle entries
+        */
+       public BundleLogger bundle(Bundle bundle) {
+               ScrLoggerFacade logger = getLogger(bundle, 
Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+               logger.setPrefix(getBundleIdentifier(bundle));
+               return logger;
+       }
+
+       /**
+        * This logger is used for the logging on a per bundle basis. This will 
use
+        * the target bundle & the implementation class as logger name.
+        * 
+        * @param bundle
+        *                   the target bundle
+        * @return a logger suitable to log bundle entries
+        */
+       public ComponentLogger component(Bundle bundle, String 
implementationClass, String name) {
+
+               // assert bundle != null;
+               // assert bundle.getSymbolicName() != null : "scr requires 
recent bundles";
+               // assert implementationClass != null;
+               // assert name != null;
+
+               ScrLoggerFacade facade = getLogger(bundle, implementationClass, 
ScrLoggerFacade.class);
+               facade.setComponentId(-1);
+               return (ComponentLogger) facade;
+       }
+
+       class ScrLoggerFacade extends LoggerFacade implements InternalLogger, 
ScrLogger, BundleLogger, ComponentLogger {
+               ScrLoggerFacade(LogDomain logDomain, String name) {
+                       super(logDomain, name);
+               }
+
+               @Override
+               public void setComponentId(long id) {
+                       setPrefix(componentPrefix(this, id));
+               }
+
+               public boolean isLogEnabled(Level level) {
+
+                       // assert !closed.get();
+
+                       Logger logger = getLogger();
+                       if (logger != null) {
+                               switch (level) {
+                               case AUDIT:
+                                       return true;
+                               case ERROR:
+                                       return logger.isErrorEnabled();
+                               case WARN:
+                                       return logger.isWarnEnabled();
+                               case INFO:
+                                       return logger.isInfoEnabled();
+                               case TRACE:
+                                       return logger.isTraceEnabled();
+                               case DEBUG:
+                               default:
+                                       return logger.isDebugEnabled();
+                               }
+                       } else {
+                               return getLogLevel().implies(level);
+                       }
+               }
+
+               @Override
+               public void log(Level level, String format, Throwable ex, 
Object... arguments) {
+                       log(level, format(format, arguments), ex);

Review comment:
       The `format` work should not be done here when `isLogEnabled(level)` is 
false.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
##########
@@ -0,0 +1,293 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.Logger;
+
+/**
+ * Implements a SCR based log manager. This class was needed to not change the
+ * whole codebase. It looks very similar to the old logging but leverages the
+ * {@link LogManager}. This class implements the existing behavior and the
+ * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
+ * method makes this distinction based on the configuration.
+ */
+public class ScrLogManager extends LogManager {
+
+       private final Bundle                    bundle;
+       private final ScrConfiguration  config;
+
+       /**
+        * Get a new log manager based on the configuration.
+        * 
+        * @param context
+        *                    the bundle context of the SCR bundle
+        * @param config
+        *                    the SCR configuration
+        * @return a proper ScrLogManager
+        */
+
+       public static ScrLogger scr(BundleContext context, ScrConfiguration 
config) {
+               if (config.isLogExtension())
+                       return new ExtLogManager(context, config).scr();
+               else
+                       return new ScrLogManager(context, config).scr();
+       }
+
+       ScrLogManager(BundleContext context, ScrConfiguration config) {
+               super(context);
+               this.config = config;
+               this.bundle = context.getBundle();
+       }
+
+       /**
+        * This logger is used for the main code of SCR. This will use the SCR
+        * bundle & the {@link Logger#ROOT_LOGGER_NAME}
+        * 
+        * @return an Scr Logger.
+        */
+       public ScrLogger scr() {
+               ScrLoggerFacade scrl = super.getLogger(bundle, 
Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+               scrl.setPrefix(getBundleIdentifier(bundle));
+               return scrl;
+       }
+
+       /**
+        * This logger is used for the logging on a per bundle basis. This will 
use
+        * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
+        * 
+        * @param bundle
+        *                   the target bundle
+        * @return a logger suitable to log bundle entries
+        */
+       public BundleLogger bundle(Bundle bundle) {
+               ScrLoggerFacade logger = getLogger(bundle, 
Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+               logger.setPrefix(getBundleIdentifier(bundle));
+               return logger;
+       }
+
+       /**
+        * This logger is used for the logging on a per bundle basis. This will 
use
+        * the target bundle & the implementation class as logger name.
+        * 
+        * @param bundle
+        *                   the target bundle
+        * @return a logger suitable to log bundle entries
+        */
+       public ComponentLogger component(Bundle bundle, String 
implementationClass, String name) {
+
+               // assert bundle != null;
+               // assert bundle.getSymbolicName() != null : "scr requires 
recent bundles";
+               // assert implementationClass != null;
+               // assert name != null;
+
+               ScrLoggerFacade facade = getLogger(bundle, implementationClass, 
ScrLoggerFacade.class);
+               facade.setComponentId(-1);
+               return (ComponentLogger) facade;
+       }
+
+       class ScrLoggerFacade extends LoggerFacade implements InternalLogger, 
ScrLogger, BundleLogger, ComponentLogger {
+               ScrLoggerFacade(LogDomain logDomain, String name) {
+                       super(logDomain, name);
+               }
+
+               @Override
+               public void setComponentId(long id) {
+                       setPrefix(componentPrefix(this, id));
+               }
+
+               public boolean isLogEnabled(Level level) {
+
+                       // assert !closed.get();
+
+                       Logger logger = getLogger();
+                       if (logger != null) {
+                               switch (level) {
+                               case AUDIT:
+                                       return true;
+                               case ERROR:
+                                       return logger.isErrorEnabled();
+                               case WARN:
+                                       return logger.isWarnEnabled();
+                               case INFO:
+                                       return logger.isInfoEnabled();
+                               case TRACE:
+                                       return logger.isTraceEnabled();
+                               case DEBUG:
+                               default:
+                                       return logger.isDebugEnabled();
+                               }
+                       } else {
+                               return getLogLevel().implies(level);
+                       }
+               }
+
+               @Override
+               public void log(Level level, String format, Throwable ex, 
Object... arguments) {
+                       log(level, format(format, arguments), ex);
+               }
+
+               @Override
+               public void log(Level level, String message, Throwable ex) {
+                       if (prefix != null && prefix.length() > 0) {

Review comment:
       Can move this into the if check for `isLogEnabled(level)` to avoid 
unnecessary work.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {

Review comment:
       Can this class be folded into the default `ScrLogManager`?  It doesn't 
seem that anything else directly extends this class except `ScrLogManager` and 
then `ExtLogManager` extends `ScrLogManager`.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {
+                       reset();
+               }
+       }
+
+       private void reset() {
+               for (LogDomain domain : domains.values()) {

Review comment:
       Need `synchronized (domains)` around the for loop to avoid concurrent 
modification exception.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {
+                       reset();
+               }
+       }
+
+       private void reset() {
+               for (LogDomain domain : domains.values()) {
+                       domain.reset();
+               }
+       }
+
+       <T> T getLogger(Bundle bundle, String name, Class<T> type) {
+               return type.cast(getLogDomain(bundle).getLogger(name));
+       }
+
+       LoggerFactory getLoggerFactory() {
+               int trackingCount = getTrackingCount();
+               if (this.trackingCount < trackingCount) {
+                       this.trackingCount = trackingCount;
+                       factory = getService();
+               }
+               return factory;
+       }
+
+       /*
+        * Tracks a bundle's LoggerFactory service
+        */
+       class LogDomain
+                       implements Closeable, BundleListener {
+
+               private final Bundle                    bundle;
+               private final Set<LoggerFacade> facades = new HashSet<>();
+
+               LogDomain(Bundle bundle) {
+                       this.bundle = bundle;
+                       scrContext.addBundleListener(this);
+                       open();
+               }
+
+               void reset() {
+                       for (LoggerFacade facade : facades) {
+                               facade.reset();
+                       }
+               }
+
+               @SuppressWarnings("resource")
+               @Override
+               public void bundleChanged(BundleEvent event) {
+                       if (event.getBundle() == bundle && event.getType() == 
BundleEvent.STOPPED && !closed.get()) {
+                               scrContext.removeBundleListener(this);
+                               LogDomain remove;
+                               synchronized (domains) {
+                                       remove = domains.remove(bundle);
+                               }
+                               if (remove != null)
+                                       remove.close();
+                       }
+               }
+
+               LoggerFacade getLogger(String name) {
+                       LoggerFacade facade = createLoggerFacade(this, name);
+                       facades.add(facade);
+                       return facade;
+               }
+
+               @Override
+               public void close() {
+                       reset();
+               }
+
+       }
+
+       class LoggerFacade {
+               private final String    name;
+               private final LogDomain domain;
+               volatile Logger                 logger;
+               volatile String                 prefix;
+
+               LoggerFacade(LogDomain logDomain, String name) {
+                       this.domain = logDomain;
+                       this.name = name;
+               }
+
+               void reset() {
+                       logger = null;
+               }
+
+               Logger getLogger() {
+                       Logger l = this.logger;
+                       if (l == null) {
+                               LoggerFactory lf = getLoggerFactory();
+                               if (lf == null)
+                                       return null;
+
+                               l = this.logger = lf.getLogger(domain.bundle, 
name, Logger.class);
+                       }
+                       return l;
+               }
+
+               Bundle getBundle() {
+                       return domain.bundle;
+               }
+
+               String getName() {
+                       return name;
+               }
+
+       }
+
+       public void close() {

Review comment:
       This does not call `super.close` from ServiceTracker?

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {
+                       reset();
+               }
+       }
+
+       private void reset() {
+               for (LogDomain domain : domains.values()) {
+                       domain.reset();
+               }
+       }
+
+       <T> T getLogger(Bundle bundle, String name, Class<T> type) {
+               return type.cast(getLogDomain(bundle).getLogger(name));
+       }
+
+       LoggerFactory getLoggerFactory() {
+               int trackingCount = getTrackingCount();
+               if (this.trackingCount < trackingCount) {
+                       this.trackingCount = trackingCount;
+                       factory = getService();
+               }
+               return factory;
+       }
+
+       /*
+        * Tracks a bundle's LoggerFactory service
+        */
+       class LogDomain
+                       implements Closeable, BundleListener {
+
+               private final Bundle                    bundle;
+               private final Set<LoggerFacade> facades = new HashSet<>();
+
+               LogDomain(Bundle bundle) {
+                       this.bundle = bundle;
+                       scrContext.addBundleListener(this);
+                       open();
+               }
+
+               void reset() {
+                       for (LoggerFacade facade : facades) {

Review comment:
       Access to facades over this for loop needs to be thread safe with the 
`getLogger(String)` method to avoid co-modification errors.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {
+                       reset();
+               }
+       }
+
+       private void reset() {
+               for (LogDomain domain : domains.values()) {
+                       domain.reset();
+               }
+       }
+
+       <T> T getLogger(Bundle bundle, String name, Class<T> type) {
+               return type.cast(getLogDomain(bundle).getLogger(name));
+       }
+
+       LoggerFactory getLoggerFactory() {
+               int trackingCount = getTrackingCount();
+               if (this.trackingCount < trackingCount) {
+                       this.trackingCount = trackingCount;
+                       factory = getService();
+               }
+               return factory;
+       }
+
+       /*
+        * Tracks a bundle's LoggerFactory service
+        */
+       class LogDomain
+                       implements Closeable, BundleListener {
+
+               private final Bundle                    bundle;
+               private final Set<LoggerFacade> facades = new HashSet<>();
+
+               LogDomain(Bundle bundle) {
+                       this.bundle = bundle;
+                       scrContext.addBundleListener(this);
+                       open();
+               }
+
+               void reset() {
+                       for (LoggerFacade facade : facades) {
+                               facade.reset();
+                       }
+               }
+
+               @SuppressWarnings("resource")
+               @Override
+               public void bundleChanged(BundleEvent event) {
+                       if (event.getBundle() == bundle && event.getType() == 
BundleEvent.STOPPED && !closed.get()) {

Review comment:
       We can do this with a single `BundleListener`.  I suggest `LogManager` 
implement `BundleListener` because this method is managing the map from 
`LogManager` 

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {

Review comment:
       This does not call `super.removedService`.  I don't see where the 
LoggerFactory service would be ungotten without this.  This also does not 
`null` out the `LogManager.factory` field.

##########
File path: scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger 
objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger 
objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, 
we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, 
we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. 
If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<LoggerFactory, LoggerFactory> {
+
+       // Guard is itself. Don't replace it with a ConcurrentHashMap since the
+       // LogDomain value is not stateless.
+
+       final Map<Bundle, LogDomain>    domains = new HashMap<>();
+       final BundleContext                             scrContext;
+       final AtomicBoolean                             closed  = new 
AtomicBoolean(false);
+       volatile int                                    trackingCount;
+       volatile LoggerFactory                  factory;
+
+       LogManager(BundleContext context) {
+               super(context, LoggerFactory.class, null);
+               this.scrContext = context;
+       }
+
+       private LogDomain getLogDomain(Bundle bundle) {
+               synchronized (domains) {
+                       LogDomain domain = domains.get(bundle);
+                       if (domain == null) {
+                               domain = new LogDomain(bundle);
+                               domains.put(bundle, domain);
+                       }
+                       return domain;
+               }
+       }
+
+       @Override
+       public void removedService(ServiceReference<LoggerFactory> reference, 
LoggerFactory service) {
+               if (!closed.get()) {
+                       reset();
+               }
+       }
+
+       private void reset() {
+               for (LogDomain domain : domains.values()) {
+                       domain.reset();
+               }
+       }
+
+       <T> T getLogger(Bundle bundle, String name, Class<T> type) {
+               return type.cast(getLogDomain(bundle).getLogger(name));
+       }
+
+       LoggerFactory getLoggerFactory() {
+               int trackingCount = getTrackingCount();
+               if (this.trackingCount < trackingCount) {
+                       this.trackingCount = trackingCount;
+                       factory = getService();

Review comment:
       In the case where a new higher ranked `LoggerFactory` has been 
registered don't we need to reset all the loggers from the previous 
`LoggerFactory`?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to