1

To demonstrate my problem, imagine we have a simple app with a top level package com.foo, and subpackages for ui (com.foo.ui) and server (com.foo.server). I have not been able to cook up a log4j properties file which will ensure the following:

* all ERROR level logging statements in packages including and descending from com.foo are
captured in a separate file under logs/main.log

* any and all messages at any levels (DEBUG,TRACE,INFO,WARN,ERROR) go to both stdout and the file appender.

Below are two log4j configurations I have tried and all the code for this toy app. The challenge I have when running the toy app is that the logs/main.log contains messages at all levels, not just messages at ERROR level.

First attempt

#Define root logger options
log4j.rootLogger=TRACE, file, stdout

#Define stdout appender
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
logrj.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-5p %c{1} - %m%n

#Define rolling file appender
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=logs/main.log
log4j.appender.file.Append=true
log4j.appender.file.ImmediateFlush=true
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=5
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %d{Z} [%t] %-5p (%F:%L) - %m%n
log4j.appender.error-log.filter.b=org.apache.log4j.varia.LevelMatchFilter
log4j.appender.error-log.filter.LevelToMatch = ERROR
log4j.appender.error-log.filter.AcceptOnMatch = true
log4j.appender.error-log.Threshold = ERROR


#Define loggers
log4j.logger.com.foo.log4j=TRACE, file, stdout

Second attempt

#Define root logger options
log4j.rootLogger=TRACE, file, stdout

#Define stdout appender
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
logrj.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-5p %c{1} - %m%n

#Define rolling file appender
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=logs/main.log
log4j.appender.file.Append=true
log4j.appender.file.ImmediateFlush=true
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=5
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %d{Z} [%t] %-5p (%F:%L) - %m%n
log4j.appender.error-log.filter.b=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.error-log.filter.LevelMin = ERROR
log4j.appender.error-log.filter.LevelMax = ERROR
log4j.appender.error-log.filter.AcceptOnMatch = true
log4j.appender.error-log.Threshold = ERROR


#Define loggers
log4j.logger.com.foo.log4j=TRACE, file, stdout

Build dependencies

Here are the build dependencies i used:

plugins {
    // Apply the scala plugin to add support for Scala
    id 'scala'

    // Apply the java-library plugin for API and implementation separation.
    id 'java-library'
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // Use Scala 2.13 in our library project
    implementation 'org.scala-lang:scala-library:2.13.2'
    implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
    implementation 'org.apache.logging.log4j:log4j-core:2.17.0'

    // Use Scalatest for testing our library
    testImplementation 'junit:junit:null'
    testImplementation 'org.scalatest:scalatest_2.13:3.1.2'
    testImplementation 'org.scalatestplus:junit-4-12_2.13:3.1.2.0'
    implementation 'log4j:log4j:1.2.17'

    // Need scala-xml at test runtime
    testRuntimeOnly 'org.scala-lang.modules:scala-xml_2.13:1.2.0'
}

Classes

And here are the three simple little classes in the App:

------------------------------------
package com.foo

import com.foo.server.Server
import com.foo.ui.UI

object Application extends App {
  val server = Server()
  val ui = new UI(server)
  ui.message("hello")
}


------------------------------------
package com.foo.ui

import com.foo.server.Server
import org.apache.log4j.LogManager


class UI(server: Server) { // mine

  val log = LogManager.getLogger(this.getClass)

  def message(string: String): Unit = {
    log.trace("ui got message " + string)
    server.message(string)
  }
}

------------------------------------
package com.foo.server

import org.apache.log4j.LogManager


case class Server() {
  val log = LogManager.getLogger(this.getClass)

  def message(string: String): Unit = {
    log.info("server got message "+ "message")
    log.error("server raised erorr on  message "+ "message")
  }
}
Chris Bedford
  • 2,560
  • 3
  • 28
  • 60
  • Which version of Log4j do you want to use: you have both Log4j 1.x and Log4j 2.x as build dependencies, but both your configuration files and your code uses the (EOLed 8 years ago) version 1.x. – Piotr P. Karwasz May 11 '23 at 23:19
  • @PiotrP.Karwasz - both versions ? oh my ! how dumb. will fix that and retry.. thnx ! – Chris Bedford May 12 '23 at 04:00

1 Answers1

1

Many thanks to @PiotrP.Karwasz for pointing out the issue w/ mixing dependencies in build.gradle. In order to get this working properly, we'd need to fix that. [ Fix is to delete this line that snuck in there: implementation 'log4j:log4j:1.2.17' ]

The other thing I didn't realize was that log4j2 looks for its properties in a separate place than prior version of log4j [ log42.xml vs log4j.xml and log4j2.properties vs. log4j.properties ]. So that said, please refer below to the log4j configuration that was able to ensure that all ERROR level (and ONLY ERROR level) messages in my application appeared under logs/real.log

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE">
    <Appenders>

        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <RollingFile name="real.log" fileName="logs/real.log"
              append="true" immediateFlush="false" filePattern="logs/$${date:yyyy-MM}/%d{MM-dd-yyyy}-%i-real.log">
            <PatternLayout pattern="%d %-5p [%t] %c (%F:%L) - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
        </RollingFile>

    </Appenders>
    <Loggers>
        <Root level="ERROR">
            <AppenderRef ref="real.log" />
            <AppenderRef ref="CONSOLE"/>
        </Root>

        <Logger name="com.foo.ui" level="TRACE" />
    </Loggers>
</Configuration>

Note the key thing is the ThresholdFilter to toss all but ERROR level log statements. I believe this is only supported in latest 2.x log4j, but don't quote me ;^)

Chris Bedford
  • 2,560
  • 3
  • 28
  • 60