I am placing a logging.properties in the WEB-INF/classes dir of tomcat
I would like to log to two different files. For example: org.pkg1 goes to one file and org.pkg2 goes to a separate file.
I can get one file configured, but not two. Is that possible?
I finally figured this out. In tomcat they extend java util logging ("JULI") to enable this functionality. Here's a logging.properties file that I put in the WEB-INF directory that finally accomplished what I was after......:
handlers=1console.java.util.logging.ConsoleHandler, 2jsp.org.apache.juli.FileHandler, 3financials.org.apache.juli.FileHandler
.handlers=1a.java.util.logging.ConsoleHandler
jsp.level=ALL
jsp.handlers=2jsp.org.apache.juli.FileHandler
org.apache.jasper.level = FINE
org.apache.jasper.handlers=2jsp.org.apache.juli.FileHandler
org.apache.jsp.level = FINE
org.apache.jsp.handlers=2jsp.org.apache.juli.FileHandler
com.paypal.level=ALL
com.paypal.handlers=3financials.org.apache.juli.FileHandler
3financials.org.apache.juli.FileHandler.level=ALL
3financials.org.apache.juli.FileHandler.directory=${catalina.base}/logs
3financials.org.apache.juli.FileHandler.prefix=financials.
2jsp.org.apache.juli.FileHandler.level=ALL
2jsp.org.apache.juli.FileHandler.directory=${catalina.base}/logs
2jsp.org.apache.juli.FileHandler.prefix=jsp.
1console.java.util.logging.ConsoleHandler.level=FINE
1console.java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
Speaking of logging.properties configuration, I did not found any mechanism to use more that one appender. I made simple workaround that works for me.
public class CustomAFileHandler extends FileHandler {
public DebugFileHandler() throws IOException, SecurityException {
super();
}
}
public class CustomBFileHandler extends FileHandler {
public DebugFileHandler() throws IOException, SecurityException {
super();
}
}
And my logging.properties
...
handlers=<pkg_name>.CustomAFileHandler, <pkg_name>.CustomBFileHandler, java.util.logging.ConsoleHandler
<pkg_name>.CustomAFileHandler.level=ALL
<pkg_name>.CustomAFileHandler.pattern=%h/A%u.log
<pkg_name>.CustomAFileHandler.limit=50000
<pkg_name>.CustomAFileHandler.count=1
<pkg_name>.CustomAFileHandler.formatter=java.util.logging.SimpleFormatter
<pkg_name>.CustomBFileHandler.level=ALL
<pkg_name>.CustomBFileHandler.pattern=%h/B%u.log
<pkg_name>.CustomBFileHandler.limit=50000
<pkg_name>.CustomBFileHandler.count=1
<pkg_name>.CustomBFileHandler.formatter=java.util.logging.SimpleFormatter
...
There's no easy way to get two handlers of the same type with java.util.logging classes that have different arguments. Probably the simplest way to do this is to create a FileHandler subclass in your logging.properties that passes the appropriate arguments to enable your logging to take place, such as:
org.pkg1.handlers=java.util.logging.FileHandler
org.pkg2.handlers=org.pkg2.FileHandler
java.util.logging.FileHandler.pattern="org_pkg1_%u.%g.log"
org.pkg2.FileHandler.pattern="org_pkg2_%u.%g.log"
org/pkg2/FileHandler.java:
package org.pkg2;
import java.util.logging.*;
public class FileHandler extends java.util.logging.FileHandler {
public FileHandler() {
super(LogManager.getLogManager().getProperty("org.pkg2.FileHandler.pattern"));
}
}
It is possible using pure jdk also (try with jdk 7 or jdk 8).
Just create custom file handler; use that similar to "java.util.logging.FileHandler".
public class JULTestingFileHandler extends FileHandler {
public JULTestingFileHandler() throws IOException, SecurityException
{
super();
}
}
user properties file;
com.xxx.handlers = com.xxx.JULXXXFileHandler
com.xxx.JULXXXFileHandler.pattern = ./logs/test1_test2.%u.%g.log
Having the same problem myself with java.util.logging and not quite satisfied with the given answers, I just found in the documentation:
2.2 Changing the Configuration
Here's a small program that dynamically adjusts the logging
configuration to send output to a specific file and to get lots of
information on wombats. The pattern "%t" means the system temporary
directory.
public static void main(String[] args) {
Handler fh = new FileHandler("%t/wombat.log");
Logger.getLogger("").addHandler(fh);
Logger.getLogger("com.wombat").setLevel(Level.FINEST);
...
}
So, it seems you can't do it just from the .properties file as can't instantiate several appenders, but you can do it programmatically. Also it should be possible using the LoggerManager
Related
I've found a few examples (even on Stack Overflow) of some programmatic configuration of Logback logging appenders, but as much as I've incorporated into my own setup hasn't worked for me so far. Some examples produce an actual Logger instance, but considering I've already got a Logger being statically instantiated within my class, I want to be able to programmatically enable an Appender that I've defined for unit testing purposes.
Here is my custom appender:
package org.example.logging;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.util.ArrayList;
import java.util.List;
// Credit to https://stackoverflow.com/a/29077499/5476186
public class TestAppender extends AppenderBase<ILoggingEvent> {
private static List<ILoggingEvent> events = new ArrayList<>();
#Override
protected void append(ILoggingEvent e) {
events.add(e);
}
public static List<ILoggingEvent> events() {
return List.copyOf(events);
}
public static void clear() {
events.clear();
}
}
And in my testing code, I'm trying to configure my TestAppender to "kick in" so that, after invoking this method in my test setup, I can capture the logs and validate them:
package org.example.logging;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
// ...
// Mostly modeled after https://stackoverflow.com/a/7825548/5476186
private static void startAppender() {
LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();
TestAppender appender = new TestAppender();
appender.setContext(logCtx);
appender.setName("TEST");
// I was hoping this would statically allow the appender to kick in,
// but all of the examples then attach this appender to a Logger instance.
appender.start();
}
Obviously, this isn't working for me. So I guess I have two contingent questions.
Is this possible and, if so, how can I make it work?
If this is not possible, what's the cleanest way to accomplish what I'm trying to do? (Enable/disable appenders during testing without having to manually mess with a config file.)
In one of the threads linked above, I found this answer which looks like one possible solution is to modify the text in the configuration file and to force a reload, but that doesn't seem super clean to me. Another option would be to create my own wrapper Logger factory which I could use to provide loggers with my TestAppender during test execution with dependency injection. I'll probably be creating a wrapper anyway, even though I'm using SLF4J.
Side note: I know that my test code as currently written is pretty tightly coupled with Logback instead of SLF4J, so I'm open to criticism/advice on that issue, too.
If you're using slf4j in your production code, then there is already a project that can help in testing: Its called slf4j-test
In a nutshell, it provides an API to retrieve a "test logger" in the test that will keep all the logged messages in memory so that you'll be able to verify them.
So that you:
Execute a method that logs something
Retrieve a test logger
call getLoggingEvents() on the test logger and verify the logged events
The link that I've provided contains an example of the API as well as maven integration example.
If, alternatively you would like to use logback directly for the tests or something, there is already a ListAppender shipped as a part of logback distribution that allows retrieval of events that have passed through the appender. You can add it programmatically to the logger and use inside the test.
Here you can find a comprehensive example of doing that
I have a global logger which is used in a few classes so that I can log everything in a single file. All works good, however I want to be able to disable the Logger from UI. I tried setting the Level to OFF like shown below, which stops the logging but an empty log file is still created ( I am using File Appender with the logger).
Is there any easy way to avoid creating the log file when Level is OFF ?
public class Main {
public static Logger LOGGER = LogManager.getLogger("GLOBAL");
public static void main(String[] args) {
Configurator.setLevel("GLOBAL", Level.OFF);
//Rest of code
}
}
The LogManager.getLogger("GLOBAL") creates the log file while reading the log4j configuration and initializing it. So, there is no way you can stop it from doing it when you are at Configurator.setLevel("GLOBAL", Level.OFF);. IMO, you have 2 options:
1) Elegant way: Initialize the LogManager in your code by passing the configuration at runtime LogManager.getLogManager().readConfiguration. You could refer here for detailed implementation.
2) Ugly way: Delete the log file when you set the LEVEL.OFF
I have a Java 8 Maven webapp project that I am running using jetty-runner.jar. Everything works fine, except I can't control the logging levels (INFO, WARNING, FINE, FINER etc.). Am using java.util.logging and OS is Win7.
I've tried the following:
Created a logging.properties file in src/main/resources folder (ends up in WEB-INF/classes directory of the war).
Renamed logging.properties to jetty-logging.properties
Used -Djava.util.logging.config.file=WEB-INF/classes/logging.properties (when the file was so named) with the command to run jetty-runner
java -Djava.util.logging.config.file=WEB-INF/classes/logging.prop
erties -jar target/dependency/jetty-runner.jar target/xyz.war
Also tried -Djava.util.logging.config.file=/WEB-INF/classes/logging.properties (with a / in front of WEB-INF)
My logging file is simply:
.level = WARNING
I haven't had to mess around with logging much before (except GAE projects), so not sure what I am doing wrong.
Since you are trying to load the logging.properties from inside of the WAR file you have to use the java.util.logging.config.class. Per the documentation:
If the "java.util.logging.config.class" property is set, then the property value is treated as a class name. The given class will be loaded, an object will be instantiated, and that object's constructor is responsible for reading in the initial configuration. (That object may use other system properties to control its configuration.) The alternate configuration class can use readConfiguration(InputStream) to define properties in the LogManager.
Using that property you can use Class.getResourceAsStream to locate the file inside of the WAR file. Here is an example:
package foo.bar.baz;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
public class JulConfig {
/**
* Install this as the -Djava.util.logging.config.class=foo.bar.baz.JulConfig
* -Djava.util.logging.config.file=WEB-INF/classes/logging.properties
* #throws Exception if there is a problem.
*/
public JulConfig() throws Exception {
String key = "java.util.logging.config.file";
String file = System.getProperty(key, "logging.properties");
final InputStream in = JulConfig.class.getResourceAsStream(file);
if (in != null) {
try {
LogManager.getLogManager().readConfiguration(in);
//System.clearProperty(key); //Optional.
} finally {
try {
in.close();
} catch (IOException ignore) {
}
}
} else {
throw new FileNotFoundException(file);
}
}
}
The reason you have to do this is because the LogManager uses java.io.File to locate the logging.properties.
I thought I would use the new ResourceBundleControlProvider framework in Java 8 to fix something which Oracle themselves will never fix - the default encoding used when reading resource bundles.
So I made a control:
package com.acme.resources;
import java.io.IOException;
import java.util.Locale;
import java.util.ResourceBundle;
public class AcmeResourceBundleControl extends ResourceBundle.Control
{
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException
{
throw new UnsupportedOperationException("TODO");
}
}
Then I made a provider:
package com.acme.resources;
import java.util.ResourceBundle;
import java.util.spi.ResourceBundleControlProvider;
public class AcmeResourceBundleControlProvider implements ResourceBundleControlProvider
{
private static final ResourceBundle.Control CONTROL = new AcmeResourceBundleControl();
#Override
public ResourceBundle.Control getControl(String baseName)
{
if (baseName.startsWith("com.acme."))
{
return CONTROL;
}
else
{
return null;
}
}
}
Then in META-INF/services/java.util.spi.ResourceBundleControlProvider:
com.acme.resources.AcmeResourceBundleControlProvider
Then I just tried to run our application from IDEA and I find that it never loads my provider (otherwise the exception would be raised.)
I have checked the names and they all seem to match up. I have checked the compiler output directory IDEA is using and it does contain the service file. I wrote a simple test program which just tries to look up the service:
public static void main(String[] args)
{
for (ResourceBundleControlProvider provider :
ServiceLoader.load(ResourceBundleControlProvider.class))
{
System.out.println(provider.getClass());
}
}
This does print out one entry which is the name of my implementation class. So the issue is not in the service file.
If I breakpoint inside ResourceBundle, I seem to be able to access the custom provider class. Initial forays into the debugger show that ServiceLoader isn't finding any implementations, but I can't figure out why. I'm sure there is some dodgy class loader magic going on which results in not loading my class. :(
Some scary documentation on the Javadoc makes it sound like it might have to be installed as a global extension. If that really is the case, it's a bit of a shame, because it seemed like a useful way to override the default (and in my opinion broken) behaviour. But I also read the tutorial on the matter and it didn't seem to be describing anything like that (unless the good behaviour was pulled out of Java 8 at the very last minute and the docs are out of date!)
The tutorial does state that the JAR containing the ResourceBundleControlProvider must be in the JVM's system extension directory. Section 6 of the tutorial describes the requirement:
java -Djava.ext.dirs=lib -cp build RBCPTest
When you install a Java extension, you typically put the JAR file of the extension in the lib/ext directory of your JRE. However, this command specifies the directory that contains Java extensions with the system property java.ext.dirs.
The JavaDoc for ServiceLoader.loadInstalled() also states that providers on the application's class path are ignored.
Your problem is that the java.util.ResourceBundle that comes with the JVM does a ServiceLoader.loadInstalled(ResourceBundleControlProvider.class) to obtain a list of providers in the static initializer, and uses the thus obtained list ever after.
Here is my code
package com.my;
import org.apache.log4j.spi.LoggerFactory;
import java.io.*;
import java.util.logging.*;
public class Log {
public static void main(String[] args) {
try{
FileHandler hand = new FileHandler("vk.log");
Logger log = Logger.getLogger("log_file");
log.addHandler(hand);
log.warning("Doing carefully!");
log.info("Doing something ...");
log.severe("Doing strictily ");
System.out.println(log.getName());
}
catch(IOException e){
System.out.println(e)
}
}
}
Your code should work if you delete the superfluous log.getLogger(""); statement and fix the imports.
A couple of comments:
If you have multiple loggers you can selectively turn them on and off. It is conventional to create multiple loggers based on class or package names; e.g.
Logger log = Logger.getLogger(this.getClass());
or
Logger log = Logger.getLogger(SomeClass.class);
You are instantiating and associating the handler programmatically. It is a better idea to put the logging configurations into an XML or properties file, and use one of the configurers to load it and wire up the logging handlers. This allows you ... or the user ... to adjust the logging without modifying your code.
You should probably READ the log4j introduction document that explains the above and other things about using log4j.
The above assumes that you were trying to use log4j. Is you are really trying to use java.util.logging, some details are not exactly right. (And, IMO, you would be better off with using log4j or one of its offspring.)
Your code is more or less fine (check the imports) and should work correctly if you remove the line:
log.getLogger("");
A working implementation of your class would then be:
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
public class test {
public static void main(String[] args) {
try {
FileHandler hand = new FileHandler("vk.log");
Logger log = Logger.getLogger("log_file");
log.addHandler(hand);
log.warning("Doing carefully!");
log.info("Doing something ...");
log.severe("Doing strictily ");
System.out.println(log.getName());
} catch (IOException e) {
// Handle error.
}
}
}
Can you explain further your problem?
Here are a couple suggestions.
Watch your imports, you are mixing
Log4j or java.util.logging imports
no need to call getLogger() twice
Do something with your exceptions,
even if that means using a
System.out.println() e.printStackTrace() in this test
case. If there were problems thrown,
you were hiding them.