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
Related
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
We’re using log4j2 to do logging throughout our application and now I want to add some additional functions to the LogManager…at the same time, I hoped it would be possible to hide the “implementation details” of the LogManager for the rest of the application, so that instead of importing the log4j2 Logger everywhere, I can expose my own interface or class, from my own proprietary LogManager (that way, it would be possible to refactor or replace the way I store log messages without affecting the entire application).
I can create my own LogManager, called LM, easily like this:
package com.xxx.yyy.logging;
import org.apache.logging.log4j.LogManager;
public class LM extends LogManager {
…add own methods here…
}
But when I call:
LM.getLogger(Application.class)
It returns an object of type org.apache.logging.log4j.Logger – is there an easy way to “wrap and expose” this interface via my own package, so that the rest of the application don’t have to be concerned with log4j?
I’ve tried something like:
package com.xxx.yyy.logging;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.message.MessageFactory;
public class LMlogger extends org.apache.logging.log4j.core.Logger implements Logger {
protected LMlogger(LoggerContext context, String name,
MessageFactory messageFactory) {
super(context, name, messageFactory);
// TODO Auto-generated constructor stub
}
};
…in order to implement the class returned as well as the expected interface, but still I can’t “down-cast” the returned Logger-object to my own LMlogger (which makes sense, as my class is the sub-class). But is there another way to keep the log4j2 implemenation details in one place only, without having to wrap each and every method of the Logger-class?
You can use the tool that log4j2 provides to generate custom Logger wrappers.
This tool was intended to add convenience methods for custom log levels or hide existing log levels, but you can use it for any purpose.
It also hides the LogManager from the client code (your app), and the generated code is in whatever package you specify, so client code won't be aware it is using log4j2.
You may need to regenerate the wrapper when you upgrade log4j2 after it had API changes (which is rare, but there will be additional API in 2.6).
Look at the following way:
Create a same-name Class org.apache.logging.log4j.LogManager in your project, and copy the source code of the original org.apache.logging.log4j.LogManager in log4j lib. Then edit the code and add what you want.
Logger or other class in the same way.
But you must make sure that your project must be loaded before the log4j lib.
I know that removing logger calls with Proguard works for Android applications.
How can one accomplish this in standard Java application?
import java.util.logging.Logger;
public class Clazz {
private static final Logger LOGGER = Logger.getLogger(Clazz.class.getName());
public void foo() {
LOGGER.info("bar");
}
}
in my Proguard configuration I have the following:
-assumenosideeffects class java.util.logging.Logger { *; }
-whyareyoukeeping class java.util.logging.Logger
which gives the following output when running:
[proguard] java.util.logging.Logger
[proguard] is a library class.
I understand that it's a library but I want to strip all calls to it anyway. Is this possible with Proguard? If not, why? How come this works so conveniently for Android, does the logger field or lack of it have something to do with this?
You should be able to remove logging calls like this, assuming you haven't disabled optimization -- it's the optimization step that removes unnecessary and unwanted calls. ProGuard can't remove the Logger class itself, since it is a run-time library class, as you've seen.
You mustn't use a wildcard for matching the methods though, since this includes essential methods like wait() and finalize() (affecting all classes). You'll have to enumerate the methods that you want to remove. For instance:
-assumenosideeffects class java.util.logging.Logger {
void info(java.lang.String);
}
This is a stupidly simple example, and yet for some reason it's not working. I must be missing something obvious.
I am trying to make a very simple log4j 2.0 example program. I have added these two jars to the classpath:
log4j-api-2.0-beta8.jar
log4j-core-2.0-beta8.jar
And have done the simplest example possible, using the default configuration:
package testlog;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestLog {
static Logger logger = LogManager.getLogger(TestLog.class.getName());
public static void main(String[] args) {
logger.trace("Hello World");
System.out.println("Test over");
}
}
But for some reason, all I get is 'Test over', I never get the Hello World, anywhere I can find anyway. Am I looking in the wrong place? It;s my understanding that with the default configuration it should be printed to the console, with the 'Test Over'. I have changed the log level to info, still the same. I have tried pulling out the Logger functionality into a class, still the same. I am following this tutorial on the log4j documentation page:
http://logging.apache.org/log4j/2.x/manual/configuration.html#AutomaticConfiguration
I can't understand what could possibly be wrong. Could anyone shed any light on what I've done wrong? Thanks in advance.
Update The logger DOES work with logger.error(). This would mean it is a problem with the default filters/level no?
You missed that line in the documentation:
Note that by default Log4j assigns the root logger to Level.ERROR.
Do a
logger.error("Hello World");
and it will be displayed.
If you want to display info and/or trace levels, you have to configure your logger.
I want to distribute a lib relying on the SLF4J logger interface. What is the best practice way to obtain the logger which integrate nicely into any other project? Sorry for the unstructured question style, I'm still trying to figure out how all this stuff is glued together.
In other projects I always use this piece of code, because I want to obtain a custom logger:
private final static Logger LOGGER = LoggerFactory.getILoggerFactory().getLogger(NAME_OF_APP);
If I create the class org.slf4j.impl.StaticLoggerBinder and have it some other lib, does the therein defined factory get used even if I just call LoggerFactory.getLogger(NAME_OF_APP) or is some default slf4j factory used?
I want the user to be able to use his own factory and logger, so which way is to perfere, and most of all why?
I'm not sure I fully understand what you are trying to do.
SLF4J is composed of two parts. First the API which you use in your lib to code your logging calls. And secondly the implementation which you use during your development to do logging, but DO NOT set as a dependency of the lib.
Because SLF4J looks for the implementations on the class path the developers using our lib can simple include any implementation they want. Sometimes is some quite strange ways :-) They can use a range of prebuilt implementations or code their own. It's up to them.
I don't think you need to do anything more than just use SLF4J's API as is.
From http://slf4j.org/manual.html
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
So, always use LoggerFactory.getLogger(...). The argument allow the logger backend to determine the behavior of the logger returned to you.
I am not sure I fully understand what your scenario is.
But from my viewpoint, what you want is a distributed logging component.
A simple approach to do that is a socket appender. And a full feature component for distributed logging may like Facebook's scribe.
Use Logger static wrapper from jcabi-log:
import com.jcabi.log.Logger;
public class MyLibraryClass {
public void foo() {
Logger.info(this, "some information");
}
}
All logs will be sent through SLF4J.