Reputation: 19225
I'm working with a third-party log4j v2 Appender which gives me the opportunity to plug-in some custom classes of my own (the Appender will create those via Reflection).
In other words: My class gets instantiated from the Appender which again gets instantiated as part of log4j's normal bootstrap. I do not have control over the Appender which is from another library.
I would like a few properties for my own custom class to be read from the Log4j configuration. It makes sense that it lives there since it is indeed related to logging.
However, I cannot figure out how to do this. At the moment I do something like this from the constructor of my custom class:
LoggerContext loggerContext = LoggerContext.getContext(true);
if (loggerContext != null) {
Configuration config = loggerContext.getConfiguration();
if (config != null) {
StrSubstitutor strSubstitutor = config.getStrSubstitutor();
if (strSubstitutor != null) {
StrLookup variableResolver = strSubstitutor.getVariableResolver();
if (variableResolver != null) {
String myVar = variableResolver.lookup("myPropertyName");
}
}
}
}
but the problem seems to be that at this point in time (during bootstrap) the Log4j configuration is indeed not yet initialized so
LoggerContext.getContext(true).getConfiguration()
returns an instance of DefaultConfiguration
(which obviously doesn't have my property) where
I would have expected an instance of XmlConfiguration
. The latter I get when I call LoggerContext.getContext(true).getConfiguration()
at any later point in time and I can indeed read my custom properties from that one.
Upvotes: 3
Views: 1057
Reputation: 16045
Since you do have access to the third-party appender, I would modify it to take advantage of Log4j2 injection capabilities. For example you can:
Inject a Node
into the appender's @PluginFactory
:
@PluginFactory
public static HttpEventCollectorLog4jAppender createAppender(
...
@PluginAttribute("middleware") final String middlewareClassName,
@PluginAttribute("eventBodySerializer") final String eventBodySerializerClassName,
@PluginAttribute("eventHeaderSerializer") final String eventHeaderSerializerClassName,
...
@PluginNode Node node)
Use the Node
to retrieve plugins of the correct type:
HttpSenderMiddleware middleware = null;
EventBodySerializer eventBodySerializer = null;
EventHeaderSerializer eventHeaderSerializer = null;
for (final Node child : node.getChildren()) {
final PluginType< ? > pluginType = child.getType();
final Object component = child.getObject();
switch (pluginType.getElementName()) {
case "middleware" :
middleware = (HttpSenderMiddleware) component;
break;
case "eventBodySerializer" :
eventBodySerializer = (EventBodySerializer) component;
break;
case "eventHeaderSerializer" :
eventHeaderSerializer = (EventHeaderSerializer) component;
}
}
if (middlewareClass != null && !middlewareClass.isEmpty()) {
try {
middleware = (HttpEventCollectorMiddleware.HttpSenderMiddleware) (Class.forName(middlewareClass).newInstance());
} catch (Exception ignored) {
}
}
...
Adapt the HttpEventCollectorLog4jAppender
's constructor to take class instances instead of class names as parameters.
Annotate your classes as Log4j plugins:
@Plugin(name = "MyMiddleware", elementType = "middleware", category = "Core")
public class Middleware extends HttpSenderMiddleware {
@PluginFactory
public static Middleware createMiddleware(
@PluginAttribute("one") String one,
@PluginAttribute("two") String two
) {
...
}
<SplunkHttp>
<MyMiddleware one="1" two="2" />
</SplunkHttp>
Upvotes: 1
Reputation: 53381
As you guessed, and as stated in the Log4j architecture documentation when they describe LoggerContext
Configuration
:
Every
LoggerContext
has an activeConfiguration
. TheConfiguration
contains all theAppender
s, context-wideFilter
s,LoggerConfig
s and contains the reference to theStrSubstitutor
. During reconfiguration twoConfiguration
objects will exist. Once allLogger
s have been redirected to the newConfiguration
, the oldConfiguration
will be stopped and discarded.
This reconfiguration process is performed in the LoggerContext
start
method, which in turn calls reconfigure
, and finally setConfiguration
.
Pay attention to the following line in setConfiguration
:
firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
As you can see, LoggerContext
will propagate the change in the configuration to the configured java.beans.PropertyChangeListener
s.
This approach is followed in the own library in the Log4jBridgeHandler
class. In its init
method you can see:
if (propagateLevels) {
@SuppressWarnings("resource") // no need to close the AutoCloseable ctx here
LoggerContext context = LoggerContext.getContext(false);
context.addPropertyChangeListener(this);
propagateLogLevels(context.getConfiguration());
// note: java.util.logging.LogManager.addPropertyChangeListener() could also
// be set here, but a call of JUL.readConfiguration() will be done on purpose
}
And they define the appropriate propertyChange
event handler:
@Override
// impl. for PropertyChangeListener
public void propertyChange(PropertyChangeEvent evt) {
SLOGGER.debug("Log4jBridgeHandler.propertyChange(): {}", evt);
if (LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName()) && evt.getNewValue() instanceof Configuration) {
propagateLogLevels((Configuration) evt.getNewValue());
}
}
I think you can follow a similar approach in your component. Please:
java.beans.PropertyChangeListener
.propertyChange
event handler method, reconfigure your component as required:@Override
public void propertyChange(PropertyChangeEvent evt) {
if (LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName()) && evt.getNewValue() instanceof Configuration) {
Configuration config = (Configuration) evt.getNewValue();
if (config != null) {
StrSubstitutor strSubstitutor = config.getStrSubstitutor();
if (strSubstitutor != null) {
StrLookup variableResolver = strSubstitutor.getVariableResolver();
if (variableResolver != null) {
String myVar = variableResolver.lookup("myPropertyName");
}
}
}
}
}
LoggerContext context = LoggerContext.getContext(false);
context.addPropertyChangeListener(this);
Upvotes: 4