I want to decorate logger like below:
public class CustomLogger implements org.slf4j.Logger {
private final Logger logger;
public CustomLogger(Class clazz) {
logger = getLoggerInLogback(clazz);
}
...
}
When I call org.slf4j.LoggerFactory.getLogger(clazz), I want this method to return a instance of CustomLogger. In this way, I can add additional behavior to logger without changing code.
But how can I make the method to return a instance of CustomLogger?
Related
I want to test a method whether it's creating correct logs or not. My class looks like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
class EventLogHandler {
private final Logger logger = LoggerFactory.getLogger(EventLogHandler.class);
private final Marker eventMarker = MarkerFactory.getMarker("EVENT");
public void handle(final Event event) {
final String log = SomeOtherClass.createLog(event);
logger.info(eventMarker, log);
}
}
I've seen some examples/solutions for testing logs but all of them are using Log4j, which we are not using in the project. I can only use Log4j2 of spring-boot, Slf4j and Logback classic.
How can I test that handle(...) method with my existing dependencies?
With the current implementation you cannot test/verify the invocations on logger without engaging a logging system and asserting on its output e.g. introduce logback and configure it with a stdout appender and capture stdout and assert against it etc.
In order to test your class without doing any of that you have to get your hands on the logger instance in use in EventLogHandler. The current implementation makes this difficult by constructing the logger like this:
private final Logger logger = LoggerFactory.getLogger(EventLogHandler.class);
A common approach to testing in this scenario is to refactor the creation of logger in such a way that you can inject a mocked instance into EventLogHandler when running your tests. For example:
class EventLogHandler {
private final Marker eventMarker = MarkerFactory.getMarker("EVENT");
private final Logger logger;
public EventLogHandler() {
this(LoggerFactory.getLogger(EventLogHandler.class));
}
// probably only used by your test case
public EventLogHandler(Logger logger) {
this.logger = logger;
}
public void handle(final Event event) {
logger.info(eventMarker, log);
}
}
Then test it like so:
#Test
public void someTest() {
Logger logger = Mockito.mock(Logger.class);
EventLogHandler sut = new EventLogHandler(logger);
sut.handle(event);
// verify that the right state is extracted from the given event and that the correct marker is used
Mockito.verify(logger).info(..., ...);
}
A less common alternative would be to use Powermock to allow you to mock this call: LoggerFactory.getLogger(EventLogHandler.class); and then use Mockito to verify calls onto it in the same way as is shown above..
I am using final Logger LOGGER = LoggerFactory.getLogger(clazz); to get the LOGGER object of org.slf4j. I am facing difficulty in overriding any default implemented method of this class.
I am trying to override non-static method of the conrete class. Lets say, Class MyLogger { Logger LOGGER = LoggerFactory.getLogger(clazz); LOGGER.debug("Some message"); } Now debug method is the non-static method declared in the LOGGER class for which some concrete implementation has been provided. The problem here is I am seeing lots of implementation classes when I try to search for the references. So in order to override debug method what should I do
Updated the original class like this:
public class MyLogger implements Logger {
private static final Logger LOGGER = LoggerFactory.getLogger(MyLogger.class);
public static org.slf4j.Logger init(Class clazz) {
final Logger loggerOut = LoggerFactory.getLogger(clazz);
setContainerId();
LOGGER.debug("Logger is initialized for [{}].", clazz.getName());
return loggerOut;
}
public void debug(String msg, Object arg1)
{
LOGGER.debug("My message",arg1);
}
}
Still not able to get result. Please suggest what am I missing here?
Static methods cannot be overriden. If you see here the functions of LoggerFactory in documentation
When I try to inject my Logger producer in an enum, I get a NPE. How can I inject a Logger in an enum?
Example:
public enum MyEnum {
HI("Hi there!"),
HELLO("Hello mister!");
#Inject
private Logger log;
private final String greeting;
private MyEnum(String greeting) {
this.greeting = greeting;
// this.log = LoggerFactory.getLogger(this.getClass());
}
public String getGreeting() {
log.debug("Method getGreeting called");
return this.greeting;
}
}
This class gives me a NPE on the log.debug() line. When I remove the #Inject and uncomment the this.log line it works.
Testcase looks like this:
#RunWith(Arquillian.class)
public class CoverKindTest {
#Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClass(MyEnum.class)
.addClass(LoggerProducer.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}
#Test
public void testEnum() {
MyEnum myEnum = MyEnum.HI;
String greeting = myEnum.getGreeting();
assertThat("Should give the greeting.", greeting, is("Hi there!"));
}
}
Complete testable project for this question can be found here, MyEnum.class is the original question, MyEnum1.class is the solution without injection (working, but not what I am looking for) and MyEnum2.class is a suggested answer.
Edit: Updated the GitHub repo with a working solution.
https://github.com/martijnburger/how-to-inject-a-logger-in-an-enum
This direct injection won't work since enum is static.
You can either create a new logger in your enum class
private static final Logger log = Logger.getLogger(Myenum.class.getName());
Found a solution that works!
I created a helper class as follows:
public class LoggerHelper {
private static Logger logger;
private void injectLogger(#Observes #Initialized(ApplicationScoped.class) Object context,
Logger logger) {
LoggerHelper.logger = logger;
}
public static Logger getLogger() {
return logger;
}
}
Now I can inject the logger in the Enum using:
private final Logger log = LoggerHelper.getLogger();
Enums cannot be injected, as they're static.
If you still want injection (instead of just creating the Logger), then you need to create a static class inside your enum that has either setter or constructor injection. When the setter or constructor gets called by the DI framework, take the value it gives you and assign it yourself to a static value in the enum.
The enum can access it now as needed. Beware though, the value will be null if your class was not injected yet.
Something like this:
public enum MyEnum {
HI("Hi there!"),
HELLO("Hello mister!");
private static Logger log;
private final String greeting;
private MyEnum(String greeting) {
this.greeting = greeting;
}
public String getGreeting() {
log.debug("Method getGreeting called");
return this.greeting;
}
#Component
public static class InjectionHelper {
#Inject
public InjectionHelper(Logger log) {
MyEnum.log = log;
}
}
}
You can create a Singleton Service class (created by your respective DI Framework - say Spring), Inject this log inside that class and use it in your enum.
Here is a sample code which works. (Replace the Service annotation with a bean tag for this class in XML, if you're using XML way of doing it. Also you can neglect the lombok #Getter annotation and replace it with a static getter)
// Service which is actually a Utility class but have DI Managed Beans.
#Service("staticService")
public class StaticService {
#Getter
private static Logger log;
#Inject
StaticService(Logger log) {
StaticService.log = log;
}
}
Now in your corresponding Enum:
public String getGreeting() {
StaticService.getLog().debug("Method getGreeting called");
return this.greeting;
}
I used a similar pattern for one of my classes (In Spring) and the injection worked.
Logic:
We first create an instance of the class (singleton instance) and inject the needed variable in the constructor.
Since this gets called during spring initialization (During Application startup), the log is initialized and we manually assign the static variable of log with the injected object in the constructor.
Note:
Don't forget the Inject annotation in the constructor Args.
Better to not provide a setter method to this log object. As its static and shared, we don't want people to replace this value post-construction. (Making it final is not an option as its static).
I'm trying to recieve the logger for my class:
public static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(this);
But using "this" there causes "Cannot use this in a static context" error.
Anyone know how to fix this?
EDIT: I must be able to access the logger from all classes in my program, so it must be public.
Notice I changed modifier from public to private:
public class FooBar {
private static final Logger log = Logger.getLogger(FooBar.class);
//or (if you are using java.util.logging):
private static final Logger log = Logger.getLogger(FooBar.class.getName());
}
For org.appache.log4j:
private static final Logger LOG = Logger.getLogger(MyClass.class);
For java.util.Logging
private static final Logger LOG = Logger.getLogger(MyClass.class.getName());
I'm using an enum singleton, but implementing logging is troublesome. This:
public enum Foo {
INSTANCE;
private final Logger log = Logger.getLogger(Foo.class.getName());
...
}
The logger is instantiated in the way that I would instantiate a logger for a normal Java class, but of course I get the following error:
Foo.java: illegal reference to static field from initializer
Is there an equivalent way to log in enum singletons?
In answer to your question, just make the logger static...
BTW, I think its standard practice to use a static logger even for object instances. In other words, the logger is on the class; all objects use the static logger references.
See
http://logging.apache.org/log4j/1.2/manual.html
Most of the examples of using a logger in there have the logger as a static property...
Log dynamically:
Logger.getLogger(Foo.class.getName()).info("log info");
A bit shorter: use a method: logger().debug(...)
private static Logger logger()
{
if(logger == null)
{
logger = Logger.getLogger(AnEnum.class);
}
return logger;
}
/** Logger **/
private static Logger logger;