ops4j / org.ops4j.pax.logging

The OSGi Logging framework implementation. Supports SLF4J,LOG4J,JCL etc.
https://ops4j1.jira.com/wiki/spaces/paxlogging/overview
Apache License 2.0
47 stars 79 forks source link

Reconfigure log4j2 programmatically #419

Open raubel opened 2 years ago

raubel commented 2 years ago

Hi,

I would like to reconfigure log4j2 after initialisation using an Xml Configuration provided by a UI (i.e. not persisted to a file). However, it looks like pax-logging with log4j2 backend only supports configuration by a set of log4j2 properties (but we do not want that) or by the path to a log4j2 configuration file (but we do not want to persist the UI configuration to a temporary file).

Is there a way to pass a log4j2 XmlConfiguration to log4j2 through pax-logging? Or would a direct reconfiguration of log4j2 (using log4j2 Configurator) in the context of pax-logging and OSGI work?

Any help would be greatly appreciated. Rémi

rmischke-dlr commented 2 years ago

We have faced a similar situation, and I think I have found a setup that works. It is not pretty, as it triggers the reconfiguration of the log4j2 backend via reflection, which ignores the implementation's encapsulation. It seems to work fine so far, though, and I'm fairly optimistic that even if it breaks on a future pax-logging upgrade, it should be easy to adapt.

I have not actively looked for a way to completely configure the backend without any configuration file. What we do, however, is loading an XML configuration file that is parameterized with several system properties ("${sys:...}"). These can be then changed at runtime, and after triggering a reconfiguration (as below), the new settings are applied.

To trigger the reconfiguration, the core part is getting hold of the single runtime instance of org.ops4j.pax.logging.log4j2.internal.PaxLoggingServiceImpl, and then calling update(null) on it -- just like the bundle activator does on configuration changes. To get that instance, I first fetched the registered OSGi service for "org.ops4j.pax.logging.PaxLoggingService". One might expect that this is the needed service right away, but it is slightly more complicated: The registered OSGi service is an OSGi service factory, and returns a different object. Luckily, that object is an inner class of org.ops4j.pax.logging.log4j2.internal.PaxLoggingServiceImpl, so the outer instance can be fetched by accessing the hidden "this$0" field via reflection.

We are currently preparing the related code for testing, and if it holds up, we will probably release this in about 1-2 weeks. Then I can add a pointer to actual code here. (We only mirror our code to Github on releases at this time.)

A cleaner solution, of course, would be the pax developers providing an API method that does the same as the internal update() method. Any feedback on whether this makes sense in general is welcome -- if yes, then I'd open a separate issue for it.

grgrzybek commented 2 years ago

Thanks for describing this clever solution! I'd have to think about it, but for now I think we should avoid reflective access to this$0 ;)

But do not lose hope - there's #336 issue which aims to implement OSGi CMPN R8 101.4 Logger Configuration

maybe this is the way to get "into" the internals?

rmischke-dlr commented 2 years ago

The this$0 step is obviously not nice in general -- but do you have any particular reason to avoid it? From what I've read, it seemed to be fairly standardized, even across JVMs. I have only tested it with AdoptOpenJDK so far, though. If that step is unreliable, I'd really like to know, for obvious reasons :)

The LoggerAdmin API looks like a good step in general, but I'm not sure if it would help with this use case. In particular, there is no "reconfigure()" method or similar, only clearing the configuration and setting log levels, unless I'm missing something. These operations are, however, too limited for our use case -- we need to be able to fundamentally reconfigure appenders at runtime.

grgrzybek commented 2 years ago

Indeed - the best thing would be to get full access to underlying Log4j manager... After I survive this log4j CVE apocalypse I'll provide more comments, ok?