DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Why and How To Integrate Elastic APM in Apache JMeter
  • Transitioning From Groovy to Kotlin for Gradle Android Projects
  • Streamlining Your Workflow With the Jenkins HTTP Request Plugin: A Guide to Replacing CURL in Scripts
  • Surviving the Incident

Trending

  • Supervised Fine-Tuning (SFT) on VLMs: From Pre-trained Checkpoints To Tuned Models
  • SaaS in an Enterprise - An Implementation Roadmap
  • Go 1.24+ Native FIPS Support for Easier Compliance
  • Creating a Web Project: Caching for Performance Optimization
  1. DZone
  2. Coding
  3. Java
  4. Log4J the Groovy Way

Log4J the Groovy Way

While developing around, now or then one wants something printed out at the console of his/her IDE.

By 
Gerhard Balthasar user avatar
Gerhard Balthasar
·
Mar. 04, 08 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
61.6K Views

Join the DZone community and get the full member experience.

Join For Free

While developing around, now or then one wants something printed out at the console of his/her IDE. Well, with Groovy, it's easy: println "Hello World!". But when it comes to some serious development and logging you must meet some requirements:

  • keep log statements in code for faster switching them on/off
  • timestamps for profiling
  • log log... who's logging?
  • a log file for tracing down errors
  • well, and so on...

One of many good log-solutions: log4j

At work our company is using log4j as part of JBoss Application Server, so I'm used to it. Sometime ago before I was developing with Groovy I stumbled upon an article at grovvy zone: Groovy and Log4j

In fact, this article made me finally getting started with Groovy, after following the community silently for months. Well I developed a bit around, every day with new cool and 'Groovy' features, but shortly after that I started another groovy project and so this went to background since yesterday, when I was about to implement logging for my project. So I digged out my code and polished it up a bit and voilá, here is my solution with log4j.

Why I'm telling you to use log4j? I don't. I don't want to show up any pros and cons. I just want to provide a simple and groovy solution to address logging requirements, for this example with log4j. So get your project equipped with log4j and start your engines, ladies and gentlemen...

The groovy way

From the mentioned article I first came up with this solution to test log4j:


package log4jimport org.apache.log4j.*class HelloWorldNoLog {  public static String PATTERN = "%d{ABSOLUTE} %-5p [%c{1}] %m%n"  static void main(args) {    def simple = new PatternLayout(PATTERN)    BasicConfigurator.configure(new ConsoleAppender(simple))    LogManager.rootLogger.level = Level.INFO    Logger log = Logger.getInstance(HelloWorldNoLog.class)    def name = "World"    log.info "Hello $name!"    log.warn "Groovy " + "logging " + "ahead..."    def x = 42    log.setLevel Level.DEBUG    if (log.isDebugEnabled()) {      log.debug "debug statement for var x: " + x    }  }}

gives you:

23:01:40,062 INFO  [HelloWorldNoLog] Hello World!23:01:40,078 WARN  [HelloWorldNoLog] Groovy logging ahead...23:01:40,078 DEBUG [HelloWorldNoLog] debug statement for var x: 42 

Nice, simple but not groovy enough IMHO, in fact there are some more or less ugly things here:

  • You have bootstrap code, but that is normally not a real problem
  • You need an instance of a Logger object and you must provide your class
  • You better check if a level is enabled for trace, debug (and info) before logging
  • You have + joined Strings instead of GString like in the first code example for multiple parameters

The real groovy way

Well, wouldn't it be easier to just have:

package log4jclass HelloWorldLog {   static void main(args) {      def name = "World"      Log.info "Hello $name!"      Log.warn "Groovy ", "logging ", "arrived!"      def x = 42      Log.setLevel "debug"      Log.debug "debug statement for var x: ", x   }}

 

to give you:

23:10:46,359 INFO  [HelloWorldLog] Hello World!23:10:46,359 WARN  [HelloWorldLog] Groovy logging arrived!23:10:46,375 DEBUG [HelloWorldLog] debug statement for var x: 42

Pretty groovy, hm? But what happened here? Where is all the code? Let's clear some things up: as 'Log.info...' starts with a capital letter, it must be a Class in the same package. By having this class in your packages, you eliminate the need for:

  • The boilerplate code
  • Getting an instance of Logger
  • Providing a class ;-)
  • Checking if a level is enabled
  • An enum for level changing and thus no import of log4j needed

What is really needed for this are only three things: log4j library in the classpath, Log helper class and an import when you log something (can also be a static import...). Okay okay, on next page comes the magic class...


Log.groovy

package log4jimport org.apache.log4j.*class Log {  public static String PATTERN = "%d{ABSOLUTE} %-5p [%c{1}] %m%n"  public static Level LEVEL = Level.INFO  private static boolean initialized = false  private static Logger logger() {    def caller = Thread.currentThread().stackTrace.getAt(42)    if (!initialized) basic()    return Logger.getInstance(caller.className)  }  static basic() {    def simple = new PatternLayout(PATTERN)    BasicConfigurator.configure(new ConsoleAppender(simple))    LogManager.rootLogger.level = LEVEL    initialized = true  }  static setLevel(level) {    def Level l = null    if (level instanceof Level) {      l = level    } else if (level instanceof String) {      l = (Level."${level.toUpperCase()}")?: null    }    if (l) LogManager.rootLogger.level = l  }  static trace(Object... messages) { log("Trace", null, messages) }  static trace(Throwable t, Object... messages) { log("Trace", t, messages) }    static debug(Object... messages) { log("Debug", null, messages) }  static debug(Throwable t, Object... messages) { log("Debug", t, messages) }  static info(Object... messages) { log("Info", null, messages) }  static info(Throwable t, Object... messages) { log("Info", t, messages) }  static warn(Object... messages) { log("Warn", null, messages) }  static warn(Throwable t, Object... messages) { log("Warn", t, messages) }  static error(Object... messages) { log("Error", null, messages) }  static error(Throwable t, Object... messages) { log("Error", t, messages) }  static fatal(Object... messages) { log("Fatal", null, messages) }  static fatal(Throwable t, Object... messages) { log("Fatal", t, messages) }  private static log(String level, Throwable t, Object... messages) {    if (messages) {      def log = logger()      if (level.equals("Warn") || level.equals("Error") || level.equals("Fatal") || log."is${level}Enabled" ()) {        log."${level.toLowerCase()}" (messages.join(), t)      }    }  }}

When you call any of the methods trace, debug, and so on (with or without a Throwable) a common method log() is called with the level as first letter uppercase String (note the usage of varargs too):

private static log(String level, Throwable t, Object... messages) {  if (messages) {    def log = logger()    if (level.equals("Warn") || level.equals("Error") || level.equals("Fatal") || log."is${level}Enabled" ()) {      log."${level.toLowerCase()}" (messages.join(), t)    }  }}
  • The method gets itself a logger first (still no need for a Class ;-)
  • It then checks if the level is warn, error, or fatal to log directly
  • Otherwise the method isTraceEnabled, isDebugEnable or isInfoEnabled is called (that's why the first letter is uppercase)
  • Logging itself takes place with the level lowercased as a dynamic method invocation
  • The messages are joined automatically (you may use comma here to support multiple ways of logging)

Now for the Logger object, this method does all the magic:

  private static Logger logger() {    def caller = Thread.currentThread().stackTrace.getAt(42)    if (!initialized) basic()    return Logger.getInstance(caller.className)  }

Notes:

  • To get the Class for the log4j logger use the stacktrace of the current Thread
  • As the stackpath to this method does not vary internally the caller can be found at a fixed position
  • So the Logger instance is been created by using the plain classname

The rest is only setLevel here. So long so groovy.

What's next

Well, I think this is a pretty slim solution for easy logging, but after all it's only a basic example. From this point one could extend this in many (groovy) ways, for example:

  • Support log4j.xml for configuration (from classpath or user dir)
  • Allow Appenders, Categories and the rest
  • Or create a DSL for configuration like this one (just wrote it up, untested!):
def log4jDSL = {  configuration(debug: false) {    appender(name: "file", type: "DailyRolling") {      errorHandler (type: "OnlyOnce")      params() {        File "groovy.log"        Append false        DatePattern ".yyyy-MM-dd"        Threshold "debug"        layout (type: "PatternLayout") {          param (type: "ConversationPattern", value: "%d %-5p [%c] %m%n")        }      }    }    category(name: "org.codehaus") {      priority (value: "debug")    }    root() {      appenders() {        file()      }    }  }}

Conclusion

For me this is a clean approach and it can be extended and used wherever Groovy and logging comes into place. With a little more effort you may read xmls or groovy markup and end up with a rock solid yet configurable solution for your Groovy utils package (Well, you may use it as Category as well, as its static...)

I hope you enjoyed my first post as much as I enjoyed writing up this tip or trick!

With greetings, Gerhard Balthasar

About me

I'm from Germany and I am Groovy addicted since I tried it one month ago as mentioned. I'm working at a medium J2EE software company in Ludwigsburg near Stuttgart and meanwhile I introduced Groovy for helping with some developing tasks successfully there ;-)

Edit

Based upon the comment from Ronald, I updated the relevant parts of this story (see comments for details)

Groovy (programming language) Log4j

Opinions expressed by DZone contributors are their own.

Related

  • Why and How To Integrate Elastic APM in Apache JMeter
  • Transitioning From Groovy to Kotlin for Gradle Android Projects
  • Streamlining Your Workflow With the Jenkins HTTP Request Plugin: A Guide to Replacing CURL in Scripts
  • Surviving the Incident

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!