Loggifier.unkrig.de: Difference between revisions

From unkrig.de
Jump to navigation Jump to search
mNo edit summary
Line 46: Line 46:
   '''$'''
   '''$'''


So now let's loggify the 'Main' class! Get 'de.unkrig.loggifier.jar' and 'de.unkrig.loggifier-runtime.jar' from [http://loggifier.unkrig.de/download/] and run:
So now let's loggify the <code>Main</code> class! Get <code>de.unkrig.loggifier.jar</code> and <code>de.unkrig.loggifier-runtime.jar</code> from [http://loggifier.unkrig.de/download/] and run:


   '''$ ls -l'''
   '''$ ls -l'''
Line 55: Line 55:
   '''$'''
   '''$'''


Not only can you pass class files to LOGGIFER, but also directories, files in ZIP format that contain '.class' files
Not only can you pass class files to LOGGIFER, but also directories, files in ZIP format that contain <code>.class</code> files (e.g. JAR and WAR files), and even ZIP files that contain nested ZIP files with <code>.class</code> files (e.g. EAR files). In
(e.g. JAR and WAR files), and even ZIP files that contain nested ZIP files with '.class' files (e.g. EAR files). In
any case, LOGGIFIER will modify the contained <code>.class</code> files "in-place".
any case, LOGGIFIER will modify the contained '.class' "in-place".


Now what logging do we get now?
Now what logging do we get now?
Line 70: Line 69:
   '''$'''
   '''$'''


Notice that you now need to have the (very small) 'de.unkrig.loggifier-runtime.jar' on the class path, because the
Notice that you now need to have the (very small) <code>de.unkrig.loggifier-runtime.jar</code> on the class path, because the injected logging code needs that.
injected logging code needs that.


By default, LOGGIFIER injects a 'reasonable' amount of logging: You can see how the class initializer of the 'Main' class is executed, then the 'main()' method. Notice how the hand-written logging seamlessly mixes with the automatically injected logging.
By default, LOGGIFIER injects a 'reasonable' amount of logging: You can see how the class initializer of the <code>Main</code> class is executed, then the <code>main()</code> method. Notice how the hand-written logging seamlessly mixes with the automatically injected logging.


Effectively, we can now remove the hand-written logging code from our class, because the automatically injected logging
Effectively, we can now remove the hand-written logging code from our class, because the automatically injected logging
Line 95: Line 93:
   '''$'''
   '''$'''


For demonstration, we now set the amount of logging to the highest possible level. There are several ways to configure
For demonstration, we now set the amount of logging to the highest possible level. There are several ways to configure the injection of logging code: We can pass a 'loggification rule' to the LOGGIFER like '-rule ALL=FINE', or through a <code>@Loggify</code> annotation to any class or method declaration:
the injection of logging code: We can pass a 'loggification rule' to the LOGGIFER like '-rule ALL=FINE', or through a '@Loggify'
annotation to any class or method declaration:


   import de.unkrig.loggifier.runtime.annotation.Loggify;
   '''import''' de.unkrig.loggifier.runtime.annotation.Loggify;
      
      
   @Loggify("ALL=FINE")
   @Loggify("ALL=FINE")
   public class Main {
   '''public class''' Main {
       // ...
       // ...


Line 141: Line 137:
   '''$ java -cp de.unkrig.loggifier.jar de.unkrig.loggifier.Main -help'''
   '''$ java -cp de.unkrig.loggifier.jar de.unkrig.loggifier.Main -help'''
   Implants logging code into Java class files 'in-place', also in ZIP format
   Implants logging code into Java class files 'in-place', also in ZIP format
   files (zip, jar, war, ear, ...) files and nested ZIP format files.
   files (zip, jar, war, ear, ...) and nested ZIP format files.
   Usage:
   Usage:
     java de.unkrig.loggifier.Main [ &lt;option> ] ... &lt;dir-or-file>
     java de.unkrig.loggifier.Main [ &lt;option> ] ... &lt;dir-or-file>
Line 149: Line 145:
   Valid options:
   Valid options:
     -keep          Keep copies of original files
     -keep          Keep copies of original files
     -selector &lt;pattern>
     -selector &lt;glob>
                     Loggify only classes matching the &lt;pattern>
                     Loggify only files matching the &lt;glob>, e.g. '**.jar!**Test*.class'
     -rule &lt;rule>    Add the given rule
     -rule &lt;rule>    Add the given rule
   The rule format is as follows
   The rule format is as follows
Line 167: Line 163:
=== ECLIPSE Plugin ===
=== ECLIPSE Plugin ===


When the plug-in is installed (see below), you will find a new page in the 'Project Properties...' dialog:
When the plug-in is installed (see below), you will find a new page in the 'Project Properties' dialog:


[[File:Loggifer_project_properties_page.png]]
[[File:Loggifer_project_properties_page.png]]
Line 175: Line 171:
=== ANT task ===
=== ANT task ===


LOGGIFIER defines a new task.
LOGGIFIER defines a new task that loggifies your code just like the command line tool.


==== Attributes ====
==== Attributes ====
Line 181: Line 177:
;selector      :Which class files to instrument, e.g. "~**Test*.class". Default is "all files".
;selector      :Which class files to instrument, e.g. "~**Test*.class". Default is "all files".
;out          :Where to write the output file or directory. Default is to perform an "in-place" loggification
;out          :Where to write the output file or directory. Default is to perform an "in-place" loggification
;keepOriginals :Keep copies of the original files (e.g. '.MyClass.class.orig' and '.MyJar.jar.orig').
;keepOriginals :Keep copies of the original files (e.g. <code>.MyClass.class.orig</code> and <code>.MyJar.jar.orig</code>).


==== Subelements ====
==== Subelements ====

Revision as of 12:42, 23 August 2013

Summary

Have you ever wondered how much cleaner your code would look, if it weren't littered with all that logging code? And, when you're hunting bugs in production environments, do you produce tons of logging output, but often find that the critical bit of information that you need is missing?

If the answers are "no" and "no", then stop reading. Otherwise, LOGGIFIER may be the tool you need.

Getting started

I presume that you know what java.util.logging is and why you want to use logging at all (e.g. in favor of an interactive debugger).

So let's start with a very simple program:

 import java.util.logging.Logger;
   
 public class Main {
   
     private static final Logger LOGGER = Logger.getLogger("Main.USER");
   
     public static void main(String[] args) {
         System.out.println("HELLO WORLD");
         LOGGER.fine("About to terminate");
     }
 }

By default, java.util.logging is disabled, so we don't get any logging output:

 $ javac Main.java
 $ java Main
 HELLO WORLD
 $

To enable logging, we need to create one more file

 # File 'my_logging.properties'.
 .level = FINEST
 handlers = java.util.logging.ConsoleHandler
 java.util.logging.ConsoleHandler.level     = FINEST
 java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
 java.util.logging.SimpleFormatter.format   = [%2$20s] %4$s: %5$s%n

, and run our program with the java.util.logging.config.file system property set:

 $ java -Djava.util.logging.config.file=my_logging.properties Main
 HELLO WORLD
 [           Main main] FINE: About to terminate
 $

So now let's loggify the Main class! Get de.unkrig.loggifier.jar and de.unkrig.loggifier-runtime.jar from [1] and run:

 $ ls -l
 -rwx------+ 1 aunkrig users 969 Aug 26 11:23 Main.class
 $ java -cp de.unkrig.loggifier.jar de.unkrig.loggifier.Main Main.class
 $ ls -l
 -rwx------+ 1 aunkrig users 2314 Aug 26 11:23 Main.class
 $

Not only can you pass class files to LOGGIFER, but also directories, files in ZIP format that contain .class files (e.g. JAR and WAR files), and even ZIP files that contain nested ZIP files with .class files (e.g. EAR files). In any case, LOGGIFIER will modify the contained .class files "in-place".

Now what logging do we get now?

 $ java -Djava.util.logging.config.file=my_logging.properties -cp de.unkrig.loggifier-runtime.jar\;. Main
 [     Main <clinit>()] FINER: (Main.java:6) ENTRY  args=()
 [     Main <clinit>()] FINER: (Main.java:6) RETURN
 [ Main main(String[])] FINER: (Main.java:9) ENTRY  args=(args=String[0])
 HELLO WORLD
 [           Main main] FINE: About to terminate
 [ Main main(String[])] FINER: (Main.java:12) RETURN
 $

Notice that you now need to have the (very small) de.unkrig.loggifier-runtime.jar on the class path, because the injected logging code needs that.

By default, LOGGIFIER injects a 'reasonable' amount of logging: You can see how the class initializer of the Main class is executed, then the main() method. Notice how the hand-written logging seamlessly mixes with the automatically injected logging.

Effectively, we can now remove the hand-written logging code from our class, because the automatically injected logging code now does the job:

 public class Main {
   
     public static void main(String[] args) {
         System.out.println("HELLO WORLD");
     }
 }

Now let's compile, loggify and run again:

 $ java -Djava.util.logging.config.file=my_logging.properties -cp de.unkrig.loggifier-runtime.jar\;. Main
 [     Main <clinit>()] FINER: (Main.java:6) ENTRY  args=()
 [     Main <clinit>()] FINER: (Main.java:6) RETURN
 [ Main main(String[])] FINER: (Main.java:9) ENTRY  args=(args=String[0])
 HELLO WORLD
 [ Main main(String[])] FINER: (Main.java:12) RETURN
 $

For demonstration, we now set the amount of logging to the highest possible level. There are several ways to configure the injection of logging code: We can pass a 'loggification rule' to the LOGGIFER like '-rule ALL=FINE', or through a @Loggify annotation to any class or method declaration:

 import de.unkrig.loggifier.runtime.annotation.Loggify;
   
 @Loggify("ALL=FINE")
 public class Main {
     // ...

Then compile, loggify and run again:

 $ javac Main.java -cp de.unkrig.loggifier-runtime.jar
 $ java -cp de.unkrig.loggifier.jar de.unkrig.loggifier.Main Main.class
 $ java -Djava.util.logging.config.file=my_logging.properties -cp de.unkrig.loggifier-runtime.jar\;. Main
 [     Main <clinit>()] FINE: (Main.java:1) CLINIT Main
 [     Main <clinit>()] FINE: (Main.java:9) ENTRY  args=()
 [     Main <clinit>()] FINE: (Main.java:9) CONST  "Main.USER"
 [     Main <clinit>()] FINE: (Main.java:9) INVOKE Logger.getLogger(String): args=("Main.USER")
 [     Main <clinit>()] FINE: (Main.java:9) RESULT Logger.getLogger(String) => java.util.logging.Logger@53c60f74
 [     Main <clinit>()] FINE: (Main.java:9) PUT    java.util.logging.Logger@53c60f74 => Main.LOGGER
 [     Main <clinit>()] FINE: (Main.java:9) RETURN
 [ Main main(String[])] FINE: (Main.java:12) ENTRY  args=(args=String[0])
 [ Main main(String[])] FINE: (Main.java:12) GET    System.out => java.io.PrintStream@7020b3a3
 [ Main main(String[])] FINE: (Main.java:12) CONST  "HELLO WORLD"
 [ Main main(String[])] FINE: (Main.java:12) INVOKE PrintStream.println(String): target=java.io.PrintStream@7020b3a3, args=("HELLO WORLD")
 HELLO WORLD
 [ Main main(String[])] FINE: (Main.java:12) RESULT PrintStream.println(String)
 [ Main main(String[])] FINE: (Main.java:14) GET    Main.LOGGER => java.util.logging.Logger@53c60f74
 [ Main main(String[])] FINE: (Main.java:14) CONST  "About to terminate"
 [ Main main(String[])] FINE: (Main.java:14) INVOKE Logger.fine(String): target=java.util.logging.Logger@53c60f74, args=("About to terminate")
 [           Main main] FINE: About to terminate
 [ Main main(String[])] FINE: (Main.java:14) RESULT Logger.fine(String)
 [ Main main(String[])] FINE: (Main.java:15) RETURN
 $

(Notice that JAVAC now needs "de.unkrig.loggifier-runtime.jar" on the class path, because "@de.unkrig.loggifier.runtime.annotation.Loggify" lives there.)

Now you can watch how the System.out field is read and the string constant "HELLO WORLD" is used - if you're interested.

Usage

Command line tool

 $ java -cp de.unkrig.loggifier.jar de.unkrig.loggifier.Main -help
 Implants logging code into Java class files 'in-place', also in ZIP format
 files (zip, jar, war, ear, ...) and nested ZIP format files.
 Usage:
   java de.unkrig.loggifier.Main [ <option> ] ... <dir-or-file>
   java de.unkrig.loggifier.Main [ <option> ] ... <dir-or-file> <dir-or-file>
   java de.unkrig.loggifier.Main [ <option> ] ... <dir-or-file> ... <dir>
 
 Valid options:
   -keep           Keep copies of original files
   -selector <glob>
                   Loggify only files matching the <glob>, e.g. '**.jar!**Test*.class'
   -rule <rule>    Add the given rule
 The rule format is as follows
   <action>[,<action>]...=<level>[:<expression>]

 <action>     is one of [ALOAD, ARITH, ASTORE, CAST, CATCH, CLINIT, CONST, CONVERT, ENTRY, GET, INVOKE, INSTANCEOF,
              LENGTH, LOAD, NEW, PUT, RESULT, RETURN, STORE, SWITCH, THROW]
 <level>      is one of [ OFF SEVERE WARNING INFO CONFIG FINE FINER FINEST ]
 <expression> is like '(<eq> || <eq>) && !<eq>', where
 <eq>         is '<var> == <glob>', where
 <var>        is one of [ class source line ] (for action 'STORE' also 'name').
 <glob>       is a 'glob pattern' in single quotes like '*main*'.
 
 The exit status is 1 iff errors eccurred during loggification.

ECLIPSE Plugin

When the plug-in is installed (see below), you will find a new page in the 'Project Properties' dialog:

Loggifer project properties page.png

Here you can enable and configure loggification for the project.

ANT task

LOGGIFIER defines a new task that loggifies your code just like the command line tool.

Attributes

selector
Which class files to instrument, e.g. "~**Test*.class". Default is "all files".
out
Where to write the output file or directory. Default is to perform an "in-place" loggification
keepOriginals
Keep copies of the original files (e.g. .MyClass.class.orig and .MyJar.jar.orig).

Subelements

rule
A loggification rule, e.g. 'STORE=FINE'
Resource Collection (<fileset> etc.)
The files to loggify (.class, .jar, .war, .ear, ...)

Example

 <project>
     <taskdef name="loggifier" classname="de.unkrig.loggifier.AntTask" classpath="lib/de.unkrig.loggifier.jar" />
 
     <target name="default">
         <loggifier>
             <rule>ALL=FINE</rule>
             <fileset file="bin/Main.class" />
         </loggifier>
     </target>
 </project>

Download

To get the LOGGIFIER ECLIPSE plugins, define an update site

   http://loggifier.unkrig.de/update/

in "Help / Install new software...".

To get the JAR files mentioned above, download them from

   http://loggifier.unkrig.de/download/

The source code is available through SVN:

   https://svn.code.sf.net/p/loggifier/code

License

LOGGIFIER is published under the "New BSD License".