Showing posts with label logging. Show all posts
Showing posts with label logging. Show all posts

Thursday, December 1, 2011

Assorted facts about JBoss. Fact 4: per-application logging is broken

You came here to read about setting up the per-application logging in JBoss 6? Congratulations, this is the right place to be. I have posted some info on this subject before. But if you have not read those posts no need to hurry. And if you have read them and followed the advice then forget it and undo the changes.

The per-application logging in JBoss 6 is broken beyond any possibility of repair. Even more, activating per-application logging makes the situation much worse.

Why the hell?! Need to scratch some itch? Look at the bug database, there is so much to do. "No, it is not for us, let's invent something new." And release this piece of ... software, half baked, and forget about any documentation.

So many words unspoken! Or rather unwritten; spoken they were, and a lot.

Back to the topic: why does it not work? To understand it you need first to understand how it really works.

The per-application logging configuration is bound to a classloader that loads jboss-logging.xml file with <define-context> element. When some component (your application code, some jar that come with JBoss, does not matter) invokes getLogger() the execution ends up in JBoss LogManager class. If the requested logger does not exist it has to be created. If the per-application logging is activated some helper class (ClassLoaderLogContextSelector) traverses the invocation stack starting from the most recent entry. For each entry (class) in the invocation stack the check is done whether the entry's classloader has a bound logging configuration. If such a configuration exists it is used and the traversal is stopped. If no classloader-bound logging configuration is found after the traversal the default one (coming from (deploy/jboss-logging.xml) is used.

For example, if your application with a custom jboss-logging.xml calls getLogger() then your class is very close to the top of the invocation stack:
  Some JBoss logging code
Some more JBoss logging code
<your class>.getLogger()
Some other code from the application
Some JBoss EJB/servlet/you name it invocation code, a lot of it actually
Thread.run()
The traversal algorithm starts with Some JBoss logging codeand goes down until it finds <your class>.getLogger(). The application has a per-application logging which is bound to the application's classloader, the very same classloader that has loaded <your class>. Bingo, the logger the application code gets back from JBoss will use the application's custom configuration.

It works even when getLogger() is used by code in some jar packaged with JBoss when your application uses that code. The invocation stack is longer, the traversal takes more time, but this is the only difference:
  Some JBoss logging code
Some more JBoss logging code
<library class>.getLogger()
Some other library code, can be a lot
Some code from the application calling the library
Some other code from the application
Some JBoss EJB/servlet/you name it invocation code, a lot of it actually
Thread.run()
So what is wrong with this?

A lot actually:
  1. What a performance hog this is! Worst of all the code that does not use the per-application logging suffers the most because all the entries in the invocation stack must be checked before JBoss decides to use the default logging configuration. And stack traces under JBoss are known to be looooong. Like hundreds of entries long. To be fair JBoss warns about this problem in deployers/jboss-logging.deployer/META-INF/logmanager-jboss-beans.xml:
    <!--
    ~ These two beans define the per-classloader log context selector which allows per-deployment logging. Since
    ~ enabling this feature may have a performance impact in certain cases, it's started up lazily (on demand)
    ~ when a separate log context is defined in a user deployment.
    -->
    Of course being JBoss code it does not actually works this way. Bug JBAS-9407 strikes back: the proposed workaround activates this stack traversal algorithm even if no component is using the per-application logging. My patch attached to this bug fixes this and makes sure the stack scanning is activated only if a component with the per-application logging configuration is deployed. But being activated the traversal code is always executed when a logger is created, regardless what component/module asks for a logger.

  2. One might say this is not a big problem. After all the traversal is performed only if a new logger is created and not every time it is used to output something. And because loggers are cached it does not matter how many times getLogger() is invoked, creation and hence the traversal happens only once.

    But exactly this fact (traversal at creation) is one of the reasons why the per-application logging is broken. Let's look at the example above, the one with the application using some library. Here is the stack trace again:
      Some JBoss logging code
    Some more JBoss logging code
    <library class>.getLogger()
    Some other library code, can be a lot
    Some code from the application calling the library
    Some other code from the application
    Some JBoss EJB/servlet/you name it invocation code, a lot of it actually
    Thread.run()
    Your application calls the library, for example, JPA. JPA provider classes, Hibernate under JBoss, call getLogger(). JBoss finds the application classes in the invocation stack, so the application logging configuration is used also for Hibernate classes. Logging output from Hibernate goes to your logging file. Sun is shining, life is good. You go ahead and deploy a second application that uses JPA and per-application logging. The log file for the second application is created. Logging from the application classes goes there. But not the logging from Hibernate: it goes to the log file of the first application. Surprise!

    The reason is clear: when the Hibernate logger was created the classes from the first application were on the stack, so the logger became "bound" to the configuration loaded from the first application.

    In reality it gets much more interesting: some Hibernate logging output goes to the default JBoss log (Hibernate code is executed during the application deployment/startup), other might go to this application's log, yet another might go to some other application's log. It all depends on the invocation stack at the moment of creation of the logger. Кто первый встал, того и тапки, which roughly translates to "The first man gets the oyster, the second man gets the shell".

    And what happens if the first application is undeployed? Where Hibernate log goes then? I did not bother to check this: nothing good anyway.

  3. As it were enough. You see, when I am talking about application-specific logging I really want the logging from the shared classes still go to the application log when the shared classes execute on behalf of the application. Even if there is yet no class from the application on the stack. For example, JBoss code that runs before invoking a servlet or an EJB business method from the application. Or a web service: there is a lot going on before the application code is invoked: XML parsing, unmarshaling, you name it. Where the logging, including error stack traces, goes? Most likely to the default logging: there is after all no classes from the application on the stack yet. Or to some other log file: the first man gets the oyster, remember?

  4. It can get even stranger. What I have seen in my tests is that the app-specific logging is activated pretty late in the deployed process. If any of the application code calls getLogger() before the logging configuration is hooked up the logging from the application classes goes to the wrong place: to the default logging. Most often there is a static Logger field in a class. This results in getLogger() being invoked during the loading of the class. For example, JPA provider loads classes looking for annotations. Or CXF code looks for JAX-WS annotations. All this loading is happening before the logging configuration is bound to the application classloader.

    This problem can be somewhat compensated by not using a static Logger field, but it is applicable only to the code you control and not to 3rd party code. There can also be situations where static initializer does something worth logging.

Nicely implemented feature, isn't it?

You might be tempted to package more 3rd party jars in your application and play some classloading tricks to make sure that the classes are loaded from the application-packaged jars and not from JBoss-provided jars. Don't!
  1. This won't buy you anything except maybe the server upgrade. The memory consumption will skyrocket, especially if there are multiple applications doing this.

  2. This might help you with problems 2 and 3 above. Problems 1 and 4 remain.

  3. You need to understand Java classloading pretty good. Then you have to forget it and learn the mess that JBoss made of it. And even then most likely it will not work. JBoss will not even deploy your application no matter how hard you try.

    For example just packaging Hibernate jars in a simplest EJB application that has otherwise no Hibernate or any other persistency dependencies is enough. If the application has JBoss classloading config that instruct JBoss to look for classes first in jars packaged with the application and only then in the parent (JBoss) clasloader the application will not deploy. While I have quite good understanding of JBoss classloading I would not even try to make this application deployable because I also understand the reason why it fails.

    Feel lucky? Give it a try. At best you can make this scheme work with some 3rd party jars but not with the most important and interesting, like Hibernate or CXF.

The question remains: is it actually possible to have the application-specific logging in JBoss 6?

Of course! Scrum solved that problem long time ago: a team decides what they want to have done and as they progress they can redefine what "done" means. Pretty neat and useful. You can do the same here: you can define what "application-specific" means in your case and then implement it.

Jokes aside there is not much you can actually do. The simplest and most sane solution is to forget about all that JBoss woodoo with the application-specific logging. Rollback the workaround for bug JBAS-9407. Use the default logging configuration in deploy/jboss-logging.xml. You can direct the output from your classes to a separate file. All the logging from the shared classes still goes to server.log by default. Directing the output from the shared classes to your application's log file is a bad thing to do: imagine if every application tries to do this. Including thread names in the logging output helps matching the records from the default JBoss log (server.log) back to your application if necessary.

JBoss, you never stops to amuse me!

Friday, October 7, 2011

Dynamically changing app-specific logging under JBoss AS: parallel deployment

01.12.2011. Update: Go read first this post. Then come back here if you want.

Last time I described one way of changing the app-specific logging configuration at runtime. The proposed solution is limited to changing log level only. This post describes what can be done to be able to change the complete logging configuration without redeploying the application.

There are several ways to do it but they share the same basic idea: instead of deploying one artifact (the application with the logging configuration) deploy the application and the logging configuration separately. Later, if some logging detail must be changed, only the separately deployed logging configuration has to be changed. JBoss detects the change and redeploys the configuration. The application keeps running. Important note: if the application has to be redeployed then both deployments must be redeployed.

First things first: in order for this scheme to work the application and the logging configuration must be specially prepared. The steps are:
  1. Configure your application to use the app-specific logging and make sure it really works.

  2. Split your logging configuration in two files. One file remains packaged in the application as jboss-logging.xml. It should look like this:
    <logging xmlns="urn:jboss:logging:6.0" context="MyWonderfulLogContext"> 
    <define-context name="MyWonderfulLogContext"/>
    </logging>
    Only context attribute and define-context element stay in the file. While it is possible to include some other logging configuration in the file I would not recommend it because these definitions can't be changed without redeploying the application itself.

  3. Deploy your application. You will notice that it generates no logging. It is logical: there is after all no logging definitions in jboss-logging.xml of the application.

  4. Prepare the second deployment artifact. The main part of it is the remaining logging configuration. It is the original jboss-logging.xml without define-context element.:
    <logging xmlns="urn:jboss:logging:6.0" context="MyWonderfulLogContext"> 
    <!-- Here come definitions of handlers, loggers, etc -->
    </logging>

  5. Deploy it. There are 3 possibilities:

    • Package it into something JBoss recognizes as a valid deployment (a jar or an ear). The packaging does not have to match the packaging of your application. For example create a file named MyWonderfulLogContext.ear with a single file META-INF/jboss-logging.xml. Deploy this file next to your application. As of this moment you should see some logging being generated. If you need to change the configuration you have to change create jboss-logging.xml, repackage MyWonderfulLogContext.ear and redeploy it.

    • You can also deploy the prepared package exploded. For example, create directory MyWonderfulLogContext.ear in the deploy directory of JBoss, create META-INF subdirectory and copy jboss-logging.xml into it. Later you can just edit this file if you need to change the configuration.

    • Rename the prepared jboss-logging.xml to something like MyWonderfulLogContext-jboss-logging.xml (the name must end with jboss-logging.xml) and copy it into the deploy directory of JBoss. Later you can just edit this file if you need to change the configuration.

      This does not work with out of the box JBoss: it recognizes only the file named 'jboss-logging.xml'. Fortunately it is not hardcoded in some java class but specified as part of the configuration. To enable JBoss to recognize more than just jboss-logging.xml you need to change file JBOSS_HOME/server/<servername>/deployers/jboss-logging.deployer/META-INF/logmanager-jboss-beans.xml. Locate bean named "JBossLoggingMetaDataSchemaResolverDeployer" and change
      <property name="name">jboss-logging.xml</property>
      into
      <property name="suffix">jboss-logging.xml</property>

What makes the parallel deployment scheme work is define-context element in jboss-logging.xml of the main application. As soon as the application is deployed JBoss creates the context object. The loggers used by the application are retrieved from this context object. As long as the application is deployed the context stays around. (Re-)Deploying the second deployment artifact just adds or removes the logging configuration to or from the context.

You can combine this scheme with the JMX bean solution. The JMX bean belongs to the context so it is also available as soon as the context is created (at deployment/startup) and remains valid until the log context is valid.

Friday, September 23, 2011

Dynamically changing app-specific logging under JBoss AS: JMX

01.12.2011. Update: Go read first this post. Then come back here if you want.

I have already blogged on JBoss 6 application specific logging, see here.

The setup works as expected. But I still miss a very important piece of functionality. The logging is not the main (you'd be surprised what some creative people can do!) reason why an application is developed. The logging helps identifying and finding problems in the application. One of the requirements to the logging framework is the ability to change at least some setting without a new deployment of the application:
  1. Changing log levels is a must. If I have a problem with a running application I want to be able to change log levels of some loggers and see the changes being applied as soon as possible.

  2. Changing the output formatting might come handy in some cases. I have been in a situation when I really wanted to change the formatting only once: the output did not include thread names.

  3. Changing where the log is written is not that important. Well, of course, it the log is currently written to nowhere then it is important to be able to change it. But normally the log is written somewhere and it is good enough to investigate a problem.
None of this is possible by default with the application-specific logging in JBoss AS 6. It looks like a crude joke: the standard logging configuration in JBoss is hot-deployable. Any change to <jboss>/server/<server_name>/deploy/jboss-logging.xml is applied almost immediately. There is a JMX MBean (java.util.logging:type=Logging) which can be used to query and change log levels. And if I want to change some configuration in the application-specific logging configuration? Only by redeploying the application.

I must say I find it more and more annoying. It looks suspiciously close to early versions of red hat linux :-) The things seem to work but if I need just a bit more functionality, which I know is there, I need a hammer. Or a debugger this time.

Anyway after some quality swearing, mainly at jboss, but maven and eclipse got their share of attention, and countless attempts to deploy an application and change some configuration files I have found several ways to (partially) change the per-application logging without redeploying the application.

Most of the solutions are just variations of the same approach. They allow changing every detail of the application logging with various degrees of cumbersomeness.

One solution stands really apart. It is quite easy to setup and use, with a single limitation: only log levels can be changed, no other change is possible. But this is exactly what is needed most of the time. The solution offers the possibility to perform changes locally and remotely. Another advantage: if you have already an application with the app-specific logging configured and are just dying to change the log level "right now" you can do it without redeploying the application. The solution is described below:

Changing log levels via JMX.


Changing logging details looks like a perfect candidate for JMX. When I was experimenting with the logging I was a bit surprised by the lack of any logging JMX. JBoss is full with JMX beans, yet there is no JMX related to the logging except the one which comes from JDK: java.util.logging:type=Logging. (JBoss replaces it with its own at startup, but this is beyond the point.)

After inspecting JBoss logging source code I came across some mentioning of JMX. The most promising were classes org.jboss.logmanager.LogContext with its getLoggingMXBean() method and org.jboss.logmanager.LoggingMXBeanImpl implementing java.util.logging.LoggingMXBean interface. So I concentrated on them.

I must say JBoss guys have really interesting sence of humor. It walks like a duck, it swims like a duck, it quacks like a duck, yet it is not a duck. Now I have an example of such a beast. You see, the class is named LoggingMXBeanImpl, it implements LoggingMXBean, the method is getLoggingMXBean, and yet an instance of class org.jboss.logmanager.LoggingMXBeanImpl does not pass the validation rules of the JBoss JMX server. You can't do better than that even on purpose.

Anyway, here is it:
  1. Configure your application to use the app-specific logging. You end up creating a file named jboss-logging.xml that looks like this:
    <logging xmlns="urn:jboss:logging:6.0" context="MyWonderfulLogContext">
    <define-context name="MyWonderfulLogContext"/>

    <!-- Here come definitions of handlers, loggers, etc -->
    ...
    </logging>

  2. Deploy your application; make sure the app-specific logging actually works and the logging goes where it supposed to go.

  3. Create somewhere a file named for example MyWonderfulLog-jboss-beans.xml with the following content:
    <?xml version="1.0" encoding="UTF-8"?>
    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    <bean name="MyWonderfulLogContextMBean" class="java.util.logging.LoggingMXBean">
    <constructor factoryMethod="getLoggingMXBean">
    <factory bean="Logging:CONTEXT:MyWonderfulLogContext"/>
    </constructor>
    <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(
    exposedInterface=java.util.logging.LoggingMXBean.class,
    name="boom.tralala:context=MyWonderfulLogContext",
    registerDirectly=false)</annotation>
    </bean>
    </deployment>
    Attention: the annotation element must be on a single line!

  4. Deploy this file by copying it to <jboss>/server/<server_name>/deploy. If everything went OK and there are no errors in console or in <jboss>/server/<server_name>/log/server.log, then navigate to http://<jboss-host>:<jboss-port>/jmx-console. You should see something like this:




  5. Click on context=MyWonderfulLogContext link and you get to the page that allows you to query some information and, more importantly, change log levels with operation setLoggerLevel. The first parameter is the logger name like 'org.jboss' or 'com.mycompany.app.package'. The second parameter is the logging level like 'INFO' or 'DEBUG'. Go ahead and play with it.

  6. If everything is working you can repackage your application so that there is no need to deploy 2 files, your application and MyWonderfulLog-jboss-beans.xml. Rename the file to jboss-beans.xml and place it next to your jboss-logging.xml, for example, in EAR/META-INF. If you already have jboss-beans.xml just add MyWonderfulLogContextMBean bean definition to your file. Next time you deploy your application you get the logging JMX bean automatically.

That is it.

Oh, yeah. Some technical details:
  1. When JBoss deploys your logging configuration it creates some objects (beans) and registers them internally. These objects are not JMX beans. One of the objects is LogContext and it is registered under the name Logging:CONTEXT:<name attribute of define-context element from jboss-logging.xml>. Thus the bean name <factory bean="Logging:CONTEXT:MyWonderfulLogContext"/> in *-jboss-beans.xml must match the name JBoss uses internally.

  2. Ignoring 'annotation' element the definition can be read as "look up JBoss bean named Logging:CONTEXT:MyWonderfulLogContext, invoke its method getLoggingMXBean and place the result of invocation in the JBoss bean registry under the name MyWonderfulLogContextMBean". There is yet no JMX in sight.

  3. Now comes the annotation element. JBoss allows you to annotate bean instances dynamically. The result looks as if java class of the instance had this annotation. In this case: as if class org.jboss.logmanager.LoggingMXBeanImpl had annotation @org.jboss.aop.microcontainer.aspects.jmx.JMX. The only difference is that the annotation is not there if you have any instance of this class itself but it is there if you have MyWonderfulLogContextMBean bean. Important: the annotation element must be on a single line, and must not start (and probably end) with whitespaces. JBoss annotation parser is very picky.

  4. The annotation itself says that JBoss bean named MyWonderfulLogContextMBean has to be registered as a JMX bean. Attribute 'name' specifies the ObjectName under which the bean has to be registered. It is a good idea to include the name of your LogContext there. And try not to use 'jboss' anywhere in the name to avoid future naming conflicts.

    I have created a JBoss issue and if its fixed JBoss would have to come up with a naming scheme. I doubt 'boom.tralala' would be their first choice but I would not be surprised if they stick 'jboss' somewhere in the name.

    If attribute 'registerDirectly' is true JBoss expects that the bean is already a JMX MBean. Remember I said above that the instance of org.jboss.logmanager.LoggingMXBeanImpl does not pass the validation rules of the JBoss JMX server? This is the reason 'registerDirectly' is set to false. This makes JBoss wrap our instance in an instance of class javax.management.StandardMBean, which is recognized as a valid JMX MBean by the JBoss JMX server.

    And StandardMBean needs to know what interface it is going to represent. This explains attribute 'exposedInterface'.

Next time I explain how to change every aspect of the logging configuration at runtime.

Thursday, September 8, 2011

Assorted facts about JBoss. Fact 3: how to make per-application logging work

01.12.2011. Update: Go read first this post. Then come back here if you want.

I am now busy with some JBoss AS6 related work. One of the things I wanted to be able to do is to have a separate application specific log file. Very easy, very understandable requirement.

Except not under JBoss. It looks like there is a completely new logging subsystem in JBoss AS 6. No advice on how to do it in previous versions would help. And there is not much documentation on how to do it except of some examples here and there and complains that this functionality is broken. There is also a corresponding JBoss issue.

The issue page describes the problem and offers a workaround. The issue is marked as resolved in JBoss AS 6.1.0 Final. The "fix" is actually a proposed workaround; the fact that I immediately did not like.

I have created jboss-logging.xml so that all the logging from my application would go not to ${jboss.server.log.dir}/server.log but to ${jboss.server.log.dir}/my-app.log. After deploying my application I immediately noticed that the fix worked: there was no error. I also got my-app.log next to server.log. All nice and dice. Brand new empty my-app.log no matter what I did in my application. All the logging still went to server.log. So much for the fix.

And it looks like I am not alone. This post mentions the same problem: "My new log file is empty."

But the new wonderful logging subsystem does more for me! When I undeploy my application I get the following exception:
ERROR [org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer]
Error during undeploy: Logging:REGISTRATION:MyAppLogContext:Anonymous-5: java.lang.NullPointerException
at org.jboss.logging.metadata.GetClassLoaderBeanMetaData.setClassLoader(GetClassLoaderBeanMetaData.java:52)
at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.undeploy(BeanMetaDataDeployer.java:237)
at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.undeploy(BeanMetaDataDeployer.java:58)
Bummer!

After some poking around jboss configuration and the source code I have found a solution. Actually a workaround and a fix.

Workaround:
  1. If you are still working on JBoss AS 6.0.0.Final then you have to apply the workaround described in this issue: change file JBOSS_HOME/server/<servername>/deployers/jboss-logging.deployer/META-INF/logmanager-jboss-beans.xml. Now you have the same configuration as JBoss AS 6.1.0.Final.
  2. Change the logging deployer configuration (file JBOSS_HOME/server/<servername&g;/deployers/jboss-logging.deployer/META-INF/logmanager-jboss-beans.xml) by removing that mode="On Demand" from
    OnDemandJBossLogManagerContextSelector bean:
    <bean name="OnDemandJBossLogManagerContextSelector"
    class="org.jboss.logmanager.LogContextSelectorService"
    mode="On Demand">
    <property name="selector">
    <inject bean="JBossLogManagerContextSelectorService"/>
    </property>
    </bean>
    You can also remove that comment above about lazy loading. It is not true anyway.

Congratulations, you have now your application specific log file and your logging really goes to this file. Unfortunately you will get NPE (see above) when your application is undeployed. This can't be worked around.

The fix is actually a code patch. I have submitted it to JBoss (see here). Unfortunately it means we have to wait until it is accepted and incorporated into some release. For those who are not afraid of javac: you can download the patch from the issue page and build it yourself. You will have to update the logging deployer configuration (file JBOSS_HOME/server/<servername&g;/deployers/jboss-logging.deployer/META-INF/logmanager-jboss-beans.xml). It is also included in the patch.