Skip to main content

Log4j : Debugging Concepts - Part-4

Appenders
Having all of your log requests scrolling on your console is not what you need all of the time. In fact limiting your log requests to your screen only violates all what we mentioned in the previous tutorial about how flexible logging is if compared to the use of traditional debuggers. One sometimes wants to save his log requests in the form of disk files for later analysis. These files can be used for the purpose of auditing as well. In some cases, you may want to see your log requests on a machine different from the one over which your application is running. As you can see, all these scenarios, and of course many other scenarios that are not mentioned here, are all about where the output of your log requests will go. When we speak about the destination to which your log requests will be delivered then we are essentially talking about an important log4j concept: Appenders.





Here's a list of appenders that log4j currently supports:
  • Console
  • Disk files
  • Java Message Service
  • Microsoft Windows Event Loggers
  • Remote Socket Servers
  • Remote UNIX Syslog daemons
  • Swing components
Greedy? Want to have many of them?
As you may expect, any logger can be assigned an appender. In other words, you can configure to which destination the log requests issued by a given logger will go. As you may recall, we mentioned before that if there is no specific level assigned to a logger, then it will set itself to the level of it's parent and so on. We are recalling this because we need to point your attention to an important fact: At a any specific time, a single logger cannot posses more than one level. This is simply because it will get it's level from itself or, if not set, from it's parent. Appenders are another story in this regard. A single logger can send it's log requests to more than one destination (i.e., can have more than one appender) at the same time. These appenders can be tied to a logger either directly by adding a new appender to that logger or indirectly by adding a new appender to the parent of that logger. This way you can have your log requests logged to more than on destination in the same time without duplicating your log requests code lines.
Example 2: Loggers with more than one appender
In the following example we will reveal a set of important facts about how appenders and levels propagate inside the loggers’ hierarchy as well as how you have to think about loggers having more than one appender in the same time.
Type and compile the following code:
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.FileAppender;
import org.apache.log4j.SimpleLayout;

public class Class2
{
  //----------
  static Logger lgr1 = Logger.getLogger("Class1");
  static Logger lgr2 = Logger.getLogger("Class1.x");
  static Logger lgr3 = Logger.getLogger("Class1.x.a1");
  static Logger lgr4 = Logger.getLogger("Class1.x.a2");
  static Logger lgr5 = Logger.getLogger("Class1.x.a2.b1");
  static Logger lgr6 = Logger.getLogger("Class1.x.a2.b2");
  //----------
  static public void main(String[] arsthrows Exception
  
    BasicConfigurator.configure();
    //----------
    //Prepare our appenders
/*A*/    SimpleLayout sl1=new SimpleLayout();
/*B*/    SimpleLayout sl2=new SimpleLayout();
/*C*/    FileAppender ap1=new FileAppender(sl1,"ap1.txt");
/*D*/    FileAppender ap2=new FileAppender(sl2,"ap2.txt");
    //----------
    //Set log requests importance levels
/*E*/    lgr1.setLevel(Level.FATAL);
/*F*/    lgr1.setLevel(Level.DEBUG);
    //----------
    //Add our appenders
/*G*/    lgr2.addAppender(ap1);
/*H*/    lgr4.addAppender(ap2);
    //----------
    lgr1.debug("This is from logger lgr1");
    lgr2.debug("This is from logger lgr2");
    lgr3.debug("This is from logger lgr3");
    lgr4.debug("This is from logger lgr4");
    lgr5.debug("This is from logger lgr5");
    lgr6.debug("This is from logger lgr6");
    //----------
  
}
Run the above code and you will see the following output (Please note that the output will not be shown on the console only but will be directed to disk files as well. For your convince and to keep things clear, we depicted the contents of these disk files as well).


Image
    Figure 3 - Console output




Image
    Figure 4 - Disk file: ap1.txt




Image
    Figure 5 - Disk file: ap2.txt


We have many important issues to tell you about this example:
First of all let's examine how appenders was created and used:
First you have to create a SimpleLayout object as we did in line A.
Next you create your appender passing the layout object and the name of the disk file you want your output to be forwarded to (as we did in line C).

To add an appender to a logger so that all log requests coming from this logger are directed to this appender (i.e., this disk file in our example), use the addAppender() method (as we did in line G).

Second, we need you to examine the two lines E and F. As you can see we are setting the filtering level for all of our loggers structure to Level.FATAL then to Level.DEBUG. Do you know which of these levels will actually apply? Of course the one that was set finally (i.e., Level.DEBUG from line F). But why the question from the first place if it's that obvious? This is because we wanted to make sure that you grasped this important (yet simple) fact: Any logger is set to only one level at any time. Setting it to another level will simply change the level and the older level will no longer apply. This already mentioned before fact is obvious as well, we are re-stating it here with the sole purpose to contrast the levels / loggers behavior with the appenders / loggers behavior. The fact that a logger has only one level does not apply for appenders: A logger can have more than one appender in the same time --- Curious to see how? Read on...

As you can see in lines G, and H, we are adding appenders to loggers lgr2, and lgr4. Adding an appender to lgr2 will automatically add the same appender to all of our loggers (except for lgr1). Adding an appender to lgr4 will automatically add the same appender to loggers lgr4, lgr5, and lgr6. The interesting fact here is that some loggers are assigned more than one appender this way! (specifically: They are loggers lgr4, lgr5, and lgr6). Contrast this with when we assign a level which automatically removed the previously assigned levels. This behavior of appenders is typically refereed to as appender additively in log4j contexts.

Let's now analyze our output in the light of the above facts:
All log requests will be issued and none of them will be blocked. Our filtering level (established because of line F) is Level.DEBUG and all of the log requests we issue are of higher or equal levels. This applies for all of the three output screens above.

On the other hand, not all log requests will go to all destinations. Here's how:
Initially all of our loggers have their default appender set to the console (This is the default behavior in log4j) and you need to do nothing to achieve this. So it's normal to see the entire log requesting appearing in figure 3.
When we add the appender ap1 to logger lgr2 we are affecting all loggers except for lgr1. This is why all these loggers issued their output to the file ap1.txt shown in figure 4. Note that this changes nothing at all about that they still have to show their output on the console in figure 3.

When we add the appender ap2 to logger lgr4 we are affecting loggers lgr4, lgr5, and lgr6. This is why all these loggers issued their output to the file ap2.txt shown in figure 5. Again, note that this changes nothing at all about that they still have to show their output on the console (figure 3), and on appender ap1 (i.e., file ap1.txt).

But, we sometimes do not like that!
In case the behavior of logger additively is not exactly what you want or in other words if you want the addition of a new appender to replace any previously added appenders, then you can use the setAdditivity(false); method which disables this behavior.
Let’s recompile and run our example after using this method:
 import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.FileAppender;
import org.apache.log4j.SimpleLayout;

public class Class2
{
  //----------
  static Logger lgr1 = Logger.getLogger("Class1");
  static Logger lgr2 = Logger.getLogger("Class1.x");
  static Logger lgr3 = Logger.getLogger("Class1.x.a1");
  static Logger lgr4 = Logger.getLogger("Class1.x.a2");
  static Logger lgr5 = Logger.getLogger("Class1.x.a2.b1");
  static Logger lgr6 = Logger.getLogger("Class1.x.a2.b2");
  //----------
  static public void main(String[] arsthrows Exception
  
    BasicConfigurator.configure();
    //----------
    //Prepare our appenders
/*A*/    SimpleLayout sl1=new SimpleLayout();
/*B*/    SimpleLayout sl2=new SimpleLayout();
/*C*/    FileAppender ap1=new FileAppender(sl1,"ap1.txt");
/*D*/    FileAppender ap2=new FileAppender(sl2,"ap2.txt");
    //----------
    //Set log requests importance levels
/*E*/    lgr1.setLevel(Level.FATAL);
/*F*/    lgr1.setLevel(Level.DEBUG);
    //----------
    //Add our appenders
/*F.1*/  lgr2.setAdditivity(false);
/*F.2*/  lgr4.setAdditivity(false);    
/*G*/    lgr2.addAppender(ap1);
/*H*/    lgr4.addAppender(ap2);
    //----------
    lgr1.debug("This is from logger lgr1");
    lgr2.debug("This is from logger lgr2");
    lgr3.debug("This is from logger lgr3");
    lgr4.debug("This is from logger lgr4");
    lgr5.debug("This is from logger lgr5");
    lgr6.debug("This is from logger lgr6");
    //----------
  
}
Run the above code and you will see the following output (Please note that the output will not be shown on the console only but will be directed to disk files as well. For your convince and to keep things clear, we included the contents of these disk files as well).


Image
    Figure 6 - Console output




Image
    Figure 7 - Disk file: ap1.txt




Image
    Figure 8 - Disk file: ap2.txt


As you can see, we added the lines F.1 and F.2 which specify that any appenders to be added to loggers lgr2, and lgr4 are to replace (and not to coexist with) the previous appenders.

If we analyzed our output in the light of this fact we will find the following:
Initially all of our loggers has their default appender set to the console (This is the default behavior in log4j).
Wait ... do not look at the output at figure 6 for now!

Due to line F.1, all loggers (except for logger lgr1) are now directed to the appender ap1 (ap1.txt). Now you can review the output of figure 6 and see that only lgr1 can show it's log request on the console. Nothing else is tied to the console appender now.

Due to line F.2, loggers lgr4, lgr5, and lgr6 are now tied to appender ap2 and no longer tied to ap1. The only loggers tied to appender ap1 now are lgr2, and lgr3. This way we can see why only lgr2 and lgr3 appear in figure 7, as well as why only lgr4, lgr5, and lgr6 appear in figure 8.

Relevant Links and References
  1. Preparing for log4j version 1.3
  2. Log4j official website
  3. Log4j documentation
  4. To buy 'The complete log4j manual'
  5. To download log4j
  6. For more about NLOG4J
About the Author:
ThinkAndCare was first established in 1989 as one of the very first pioneers in the IT industry in Egypt. Along its 17 years of age, ThinkAndCare played a major (if not an exclusive) rule in the production and delivery of several Software Development related titles to the Egyptian market. Being deeply involved in the field of Software Development and Software Engineering, ThinkAndCare was able to produce outstanding training products if compared to their competitors who are specialized solely in the training field.

Comments

Popular posts from this blog

Asynchronous Vs. Synchronous Communications

Synchronous (One thread):   1 thread -> |<---A---->||<----B---------->||<------C----->| Synchronous (multi-threaded):   thread A -> |<---A---->| \ thread B ------------> ->|<----B---------->| \ thread C ----------------------------------> ->|<------C----->|

WebSphere MQ Interview Questions

What is MQ and what does it do? Ans. MQ stands for MESSAGE QUEUEING. WebSphere MQ allows application programs to use message queuing to participate in message-driven processing. Application programs can communicate across different platforms by using the appropriate message queuing software products. What is Message driven process? Ans . When messages arrive on a queue, they can automatically start an application using triggering. If necessary, the applications can be stopped when the message (or messages) have been processed. What are advantages of the MQ? Ans. 1. Integration. 2. Asynchrony 3. Assured Delivery 4. Scalability. How does it support the Integration? Ans. Because the MQ is independent of the Operating System you use i.e. it may be Windows, Solaris,AIX.It is independent of the protocol (i.e. TCP/IP, LU6.2, SNA, NetBIOS, UDP).It is not required that both the sender and receiver should be running on the same platform What is Asynchrony? Ans. With messag...

Advantages & Disadvantages of Synchronous / Asynchronous Communications?

  Asynchronous Communication Advantages: Requests need not be targeted to specific server. Service need not be available when request is made. No blocking, so resources could be freed.  Could use connectionless protocol Disadvantages: Response times are unpredictable. Error handling usually more complex.  Usually requires connection-oriented protocol.  Harder to design apps Synchronous Communication Advantages: Easy to program Outcome is known immediately  Error recovery easier (usually)  Better real-time response (usually) Disadvantages: Service must be up and ready. Requestor blocks, held resources are “tied up”.  Usually requires connection-oriented protocol