Details
-
Type:
New Feature
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: None
-
Fix Version/s: None
-
Component/s: logback-classic
-
Labels:None
-
Environment:
Eclipse Virgo
Description
This enhancement request, although is geared towards a use case in Eclipse Virgo, should be useful in other circumstances.
Eclipse Virgo has a capability, called medic, that allows OSGi bundles to define their own logback.xml. To configure loggers and appenders.
Appenders, though, need to be fragments. Fragments to logback, though, need to be applied within the Eclipse Virgo "kernel region" whereas the application bundles are in the "user region" and this presents a configuration challenge as one now needs to muck with the Virgo configuration files.
A more dynamic method of registering appender implementations could be supported if logback provided a mechanism to define an appender factory implementation. Given a class name the factory (which itself could be a generic fragment attached to the logback bundle in the kernel region) could look up any registered appender implementations, instantiate and return the appender.
Happy to discuss the motivation and ideas further...
Activity
Yea, I was just looking at the OptionHelper implementation and that seemed like a reasonable place to have a hook. Motivated to lend a helping hand too. Thanks much for the feedback.
Great!
Since the context is the registry point, we must ensure that the existing code which uses OptionHelper to instantiate objects has access to the context. My little investigation shows this to be the case. So we are good.
There is also the Loader.loadClass(String classname, Context context) used to load classes. It is used by NestedComplexPropertyIA which is a rather important class used to load most logback components other than appenders.
This begs the following question. Is the requirement satisfied by just a pluggable class loading mechanism or is object creation also are requirement?
I am biased to a factory implementation. I offer the following as use cases (they may not be good use cases, but use cases nonetheless):
1) The factory might return singletons for some classes
2) The factory might want to wrap a proxy (java.lang.reflect.Proxy) around the real implementation
3) The factory may deal with multiple bundles, each with its own classloader. As such there isn't just one classloader but several and the factory could deal with a dynamic list of classloaders.
In terms of configuration, would the approach be to have something like:
<property name="logback.onDemandObjectFactory" value="com.acme.MyLogbackObjectFactory"/>
(or classloader broker if we don't go with an object factory).
Reasons 1 and 2 make sense. I don't understand reason 3. If the pluggable class loader mechanism returns the appropriate class, it does not matter which classloader or which bundle it came from.
Anyway, let's say that we go for an object factory with the capability to load classes as well. Here is a tentative interface:
interface ComponentFactory {
Object getInstance(String className);
}
The are two alternatives for registering a ComponentFactory.
1) Register a ComponentFactory instance directly with the context. Example:
MyComponentFactory myComponentFactory = new MyComponentFactory();
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setComponentFactory(myComponentFactory);
2) Register a ComponentFactory class in the configuration file:
<configuration>
<componentFactory class="com.acme.MyComponentFactory "/>
....
</configuration>
The second approach suffers from the same problem that it tries to solve. MyComponentFactory may need to be loaded by a class loader unreachable by logback's own class loader.
NOTE: I think that the matter of class loading by invoking Loader.loadClass in NestedComplexPropertyIA can be ignored.
Ironically, the third use case is the one I'd really like to solve but may not have conveyed well.
In an OSGi environment I would have multiple bundles offering ComponentFactory services. These bundles may come and go as bundles are installed, started, stopped, etc.
Another bundle can listen for ComponentFactory service registrations and act as a broker whenever there is a need to instantiate a component. This broker would itself implement ComponentFactory and it is that implementation that is registered with logback. When logback needs to instantiate an object it could go to the broker, which knows about all implementations of ComponentFactory and could find the one that is willing to instantiate an object. This separates the notion of OSGi services from logback.
What I'd like to do is make it easy to dynamically add appender implementations (and other components too I suppose) without having to revisit the OSGi framework's configuration for each fragment. Instead, the configuration could specify the broker, which implements the OSGi service listener, one time and then everything is dynamic from that point on. The broker in this case would be reachable by logback's classloader.
In any event, hopefully that explained my intent. The interface you've proposed and configuration options sound great.
Here is a proposed implementation:
https://github.com/richmayfield/logback/commit/e0f4c095f188190be5d7adaa1f47310630b6e190
With this in place I can register ComponentFactory implementations as OSGi services, which avoids many configuration issues associated with adding fragments to logback in Eclipse Virgo.
The main change is in OptionHelper, which is where we get the component factory, if defined, use it, and then use the default/current mechanism as a fallback.
The ContextComponentFactory is an implementation of ComponentFactory that has the current behavior based upon the logback context.
The ClassLoaderComponentFactory can be used by any OSGi bundle to register it's own class loader.
At your convenience, please let me know what you think.
Great stuff. I am amazed at how well you navigated the logback jungle.
I reiterate my earlier question. When the class name of the component factory is passed as a string, logback needs to instantiate the component factory itself. This approach seems to suffer from the same problem that it tries to solve. The custom ComponentFactory may need to be loaded by a class loader unreachable by logback's own class loader. In any case, the setComponentFactory method in Context should take a ComponentFactory instance, not a string of the factory name. It's just more efficient and is a small change with respect to what you developed already.
Note to self: another interesting chicken and egg problem: http://tinyurl.com/ctmo2go
Yes, logback would instantiate the the component factory itself and it would need to be visible to logback's classloader. The idea I'm advocating is to keep logback from having to know anything about the various OSGi bundles that implement Appender (and other) interfaces. Logback, in this case, would delegate to an OSGi bundle that is listening for other ComponentFactory implementations.
The deployment model looks something like the following:
1) Bundle A has an implementation of ComponentFactory - com.acme.logging.ComponentFactoryImpl. This bundle also registers an OSGi service listener that keeps track of any implementations of ComponentFactory that application bundles register. ComponentFactoryImpl will delegate to the implementations of ComponentFactory to create logback objects.
2) Bundle B is a fragment bundle with a host of ch.qos.logback-classic. It includes an Export-Package of com.acme.logging. This allows logback's classloader to see com.acme.logging.ComponentFactoryImpl.
3) Bundle C is an application bundle that includes an implementation of an appender - com.acme.appenders.MyAppender. It includes an implementation of ComponentFactory and registers this as an OSGi service.
4) Bundle D is another application bundle that has a logback.xml that includes:
<configuration>
<componentFactory class="com.acme.logging.ComponentFactoryImpl"/>
<appender name="MyAppender" class="com.acme.appenders.MyAppender">
...
</appender>
...
</configuration>
Bundles A & B live in Eclipse Virgo's kernel region. In fact, I would propose that these become a part of Virgo's medic rather than introducing anything new.
Bundles C & D would be an example of how to provide an appender without having to change Virgo's configuration. This would allow one to dynamically add appenders and other logback implementations using a whiteboard pattern.
There may be any number of these application bundles like C & D. The benefit to this whole thing is that there only need be A & B configured in the Virgo kernel region once. Additional application bundles like C & D do not need one to muck with the configuration.
By the way, this first came up and was proposed through the Eclipse Virgo forum. For reference only: http://www.eclipse.org/forums/index.php/mv/msg/357043/879420/#msg_879420
I actually have a working implementation of all of the above bundles as examples. It's actually pretty straightforward, as was the enhancement to logback. I'll try to carve out some time to include those in my github repository. Hopefully that will make the intent more clear.
Thank you for the detailed explanation. Nice to hear that your proof of concept is working.
As mentioned in my previous comment, the setComponentFactory method in Context should take a ComponentFactory instance, not a string of the factory name. It's just more efficient and is a small change with respect to what you developed already. You would simply move the code that instantiates a ComponentFactory given a clas name (a string), to ComponentFactoryAction which would also inject the instance into Context. Easy as pie.
Updated my repository with the suggested change. Context now refers to an instantiated ComponentFactory rather than a class name.
https://github.com/richmayfield/logback/commit/115d1b57048b44162366d636041726ff49384ad6
Submitted pull request. Any thoughts on when this might be formally pulled into the project?
I'd like to approach the Eclipse Virgo team with enhancements that would build upon this new capability but am holding off until I have an idea when this might be released.
Thanks much,
rich
I suppose this factory must be able to construct objects other than appenders. Indeed, most appenders require sub-components of various types.
Currently, on demand object building is done by the instantiateByClassName() method (and variants) located in the ch.qos.logback.core.util OptionHelper class. We could refactor the code so that the on demand object building is performed by an instance of an interface, say OnDemandObjectFactory. Logback would ship with a default implementation of this interface. However, it would be possible to override this implementation by registering a different implementation.
All logback components get injected a context. So the most obvious registry point for a custom OnDemandObjectFactory would be a new method in the ch.qos.logback.core.Context interface.