0

To summarize the problem, i have a common class in a bundle that is being used all around the project. This class is an Aspect class which puts values into ThreadContext(log4j2) in order for our logging to have corresponding session details into the logs.

I have found out that ThreadContext works like a ThreadLocal variable. And since OSGi have different classloaders for each bundle, callers/users of the ThreadContext values are not able to see these values at all.

References:

Effect of ThreadLocals and side-by-side classloading https://stackoverflow.com/a/34738856/8136561

What i expect is to still have a bundle with the common code in it but the other bundles would still be able to see the values put into the ThreadContext. I'm not sure if this is even possible.

Edit: Added Sample Code

Bundle1 (Common Code)

@Configurable
@Aspect
public class AspectLogger {

    @Before("within(@org.springframework.stereotype.Controller *)")
    public void beforeControllerAdvice(JoinPoint joinPoint) {
        Object[] paramValues = null;
        paramValues = joinPoint.getArgs();
        Object request = null;
        for (Object arg : paramValues) {
            if (arg instanceof RenderRequest) {
                request = arg;
            } else if (arg instanceof ResourceRequest) {
                request = arg;
            } else if (arg instanceof ActionRequest) {
                request = arg;
            }
        }

        if (request != null) {
            String transactionID = UUID.randomUUID().toString();
            ThreadContext.put("transactionID", transactionID);
        }
    }
}

Bundle 2,3...n (Using this Aspect as a Bean)

<bean id="aspectLogger" class="shared.bundle.common.AspectLogger" />

Log4j2 configuration using this value for logger appenders (Problem values are empty in the logs)

<PatternLayout>
    <Pattern>%-5p | %d{yyyy-MM-dd HH:mm:ss} | [%t] %c{2} (%F:%L) [transactionID: %X{transactionID}] - %m%n</Pattern>
</PatternLayout>

From Debugger point of view, i can see that the controller's thread and aspect's thread is basically the same. But i am still not getting the values, i even tried ThreadContext.get("transactionID") in the @Controller level but it is empty.

Karl Alexander
  • 351
  • 4
  • 16
  • Can you provide some sample code? – Christian Schneider Oct 01 '19 at 13:33
  • Note that the situation described in your 1st reference (2 class loaders for a class) is actually very different from the OSGi class loading explained in the 2nd reference (same class ('Aspect class') -> same classloader (through delegation)). This should just work in OSGi. If it really doesn't, some sample code would help. – user3653004 Oct 01 '19 at 14:24
  • Provided sample code for your reference. – Karl Alexander Oct 02 '19 at 04:41
  • Are you embedding log4j in the bundles? If yes then it would explain why you have more than one class of ThreadContext. The example code also shows that you are using spring. This is not recommended in OSGi as spring has a strange use of class loading. – Christian Schneider Oct 02 '19 at 06:59
  • Yes we are embedding the log4j2 JARs in each WAR. It is making more sense to me now. So basically we have to make the log4j2 work within the OSGi container to have this ThreadContext working as we have expected? Is it possible? – Karl Alexander Oct 02 '19 at 07:36

2 Answers2

2

So the reason for the problem is that log4j is embedded into each war file that is then deployed to OSGi. The embedded classes are loaded by the respective bundle. So they live in different classloaders.

Embedding logging is a bad idea in OSGi. One of the reasons is that you then can not have a central logging config. Another reason is the problem above.

You can look into pax-logging or the felix logback support as a proper OSGi logging solutions. Both provide the log4j API.

In your war files you will then need to define the imports for the logging api packages. Then all wars should use the ThreadContext from the same delegated classloader and the issue should be solved.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
0

ThreadLocals and class loaders are orthogonal. Using different class loaders for each bundles says nothing about what threads may be calling classes loaded from those bundles. So as long as you set the ThreadLocal before calling code in another bundle, that code will be able to see the thread's ThreadLocals.

BJ Hargrave
  • 9,324
  • 1
  • 19
  • 27
  • Yes but from what I infer in the references. If bundle A is setting values into the thread using ThreadLocal. Bundle B will not be able to see these because they basically live in different class loaders. Added sample code for your reference. – Karl Alexander Oct 02 '19 at 04:43
  • The ThreadLocal class should normally come from a common bundle providing log4j api. In OSGi this means that all bundles using this class will delegate the loading to the common bundle. So all using bundles should see the same class from the same classloader. – Christian Schneider Oct 02 '19 at 07:01
  • As I stated, Class Loaders are orthogonal to Threads. Threads execute code in classes, classes are loaded by Class Loaders. So any Thread can execute code loaded by any Class Loader. So a ThreadLocal set in a Thread can be viewed by any class executed by the Thread regardless of the Class Loader which loaded the class. – BJ Hargrave Oct 02 '19 at 14:35