This question already has answers here:
Setting log level of message at runtime in slf4j
(18 answers)
Closed 7 years ago.
We are looking to use SLF4J, but one thing we found was that you can't specify the level as an argument, i.e
Logger.log(Level.INFO, "messsage");
You have to do this
logger.info("message");
this prevents being able to pass everything through a method, so you can tack other properties to all log messages in a class.
public class Test
{
public Test(SomeObj obj)
{
log(Level.INFO, "message");
}
public void anotherMethod()
{
log(Level.DEBUG, "another message");
}
private void log(Level level, String message)
{
logger.log(level, message + obj.someString());
}
}
Is there a way to achieve this using SLF4j ?
Write a wrapper around the slf4j call and create your own enum for the six log levels. Then in your wrapper, use a switch to call the correct slf4j call.
void myLog(Level level, String message)
{
switch (level)
{
case FATAL:
log.fatal(message);
break;
case ERROR:
log.error(message);
break;
....
}
}
The answer is No. Refer to this discussion.
Your usecase screams for the delegation pattern. Basically you wedge your own implementation of Logger between your code and SLF4J and "extend" the relevant methods:
class MyLogger implements Logger {
Logger realLogger;
Object userData;
MyLogger(Class clazz, Object userData){
this.realLogger = LoggerFactory.getLogger(clazz);
}
public void debug(String msg) {
realLogger.debug(msg + userData.someString());
}
// many more methods, perhaps per java.lang.reflect.Proxy
}
This is use in the business code like this:
public class Test
{
Logger log;
public Test(SomeObj obj)
{
log = new MyLogger(Test.class, obj);
log.logInfo("message");
}
public void anotherMethod()
{
logDebug("another message");
}
}
For optimal reusability of the MyLogger class SomeObj should either use Object.toString() or it should implement an interface which MyLogger can use to get the message addendum.
Well, technically SLF4J doesn't offer you a logger.log(Level, message) method. But I found a way around that. [edit: uses introspection]
Using the below code snippet you can get the native logger that slf4j found and wrapped for you at runtime. If you'll recall, slf4j is simply a wrapper around an slf4j implementation from another provider (either, jdkLogging, Log4J, JCL, etc...). So here:
public Object getNativeLogger( org.slf4j.Logger logger ) {
Object result = null;
if ( logger.getClass().getName().equals("org.slf4j.impl.Log4jLoggerAdapter")) {
try {
Field f = Log4jLoggerAdapter.class.getDeclaredField("logger");
f.setAccessible( true );
result = (org.apache.log4j.Logger)f.get(logger);
}
catch( Exception e ) {
System.out.println("Unable to access native log4j logger");
}
}
else if ( logger.getClass().getName().equals("org.slf4j.impl.JDK14LoggerAdapter")) {
try {
Field f = Jdk14Logger.class.getDeclaredField("logger");
f.setAccessible( true );
result = (Jdk14Logger)f.get(logger);
}
catch( Exception e ) {
System.out.println("Unable to access native log4j logger");
}
}
else if (..... other native loggers slf4j supports)....
}
return result;
}
Then you can use it like this:
Object l = getNativeLogger(mySlf4jLogger);
if ( l instanceof org.apache.log4j.Logger ) {
org.apache.log4j.Logger logger = (org.apache.log4j.Logger) l;
logger.log( CUSTOMLog4JLevel, message);
}
else if( .... other implementations that you care about ...)...
So while it's not technically within slf4j, it is possible to do it using slf4j as your primary logging interface.
Related
Let's say I have an abstract class, called Logger:
public abstract class AbstractLogger {
public enum Levels {
DEBUG, INFO, WARNING, ERROR
}
public void debug(String message) {
Levels level = Levels.DEBUG;
log(level, message);
}
public void info(String message) {
Levels level = Levels.INFO;
log(level, message);
}
public void warning(String message) {
Levels level = Levels.WARNING;
log(level, message); }
public void error(String message) {
Levels level = Levels.ERROR;
log(level, message); }
public void log(Levels level, String message) {}
}
And I also have classes that inherit this class, such as FileAppenderLogger:
public class FileAppenderLogger extends AbstractLogger {
private final Path logPath;
public FileAppender(Path logPath) {
this.logPath = logPath;
createLogFile();
}
private void createLogFile() {
try {
File logFile = new File(logPath.toString());
if (logFile.createNewFile()) {
System.out.println("File created: " + logFile.getName());
} else {
System.out.println("File already exists.");
}
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
#Override
public void log(Levels level, String message) {
try {
FileWriter myWriter = new FileWriter(this.logPath.toString());
myWriter.write(message+"\n");
myWriter.close();
System.out.println("Successfully wrote to the file.");
} catch (IOException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
#Override
public void debug(String message) {
super.info(message);
}
#Override
public void info(String message) {
super.info(message);
}
#Override
public void warning(String message) {
super.warning(message);
}
#Override
public void error(String message) {
super.error(message);
}
}
Now, let's say I need to extend Logger to support new Log level, such as "FATAL", and also extend its children, such as FileAppenderLogger to support it, without modify any of those classes, only extend them.
what could be the best practice for that (if I still want to preserve non generic methods such as ".info(String s)" or ".debug(String s))?
What design pattern may I use here?
I'm open for changes regard this problem.
Thank you!
Simply add it to AbstractLogger:
public abstract class AbstractLogger {
public enum Levels {
DEBUG, INFO, WARNING, ERROR, /* added */ FATAL,
}
public void fatal(String message) {
log(Levels.FATAL, message);
}
}
Given that the types that extend AbstractLogger all already implement the log method, then 'things will just work' - possibly some of the implementations cannot deal with the fact that a new log level has now appeared. Assuming they were appropriately programmed, they'll throw. Your FileAppenderLogger class, for example, would just continue to work without requiring any change or even recompilation.
The key design pattern to make this work is that all those non-generic methods such as .error(x) are light wrappers that all send the data to a single method that does the real work - log. But, you already do that.
NB: Reinventing the wheel is a bad idea. Logging frameworks already exist, use an existing one instead.
NB2: Idiomatic java dictates you call your enum types the singular - it should be Level, not Levels. The type name describes, well, a type name. It's called String, not Strings, because an instance of java.lang.String represents one string. The class itself represents all strings, but that doesn't mean it should be called Strings. Similarly, an instance of the Levels enum represents a single level. Hence, it should be named Level, not Levels.
Instead of using enum for level, you can make class LogLevel and make classes that extend it, for example LogLevelError, LogLevelFatal, then in log method: this.logLevel.log(message);. Of course, it look strange, but this is the way I see to add new log levels. Also, as said by #rzwitserloot :"NB: Reinventing the wheel is a bad idea. Logging frameworks already exist, use an existing one instead". They are much faster, optimized, and 'time-tested'.
You can't add more values to your enum, that's not possible in java. I would suggest to either use a String for levels, or declare your own Level class, so you can add more levels.
public class Level {
private final String levelName;
//getter, etc.
}
To extend the functionality of your AbstractLogger, without modifying it, you can wrap it in another class and declare the additional methods, fatal() in this case.
public class ExtendedLogger extends AbstractLogger {
private final AbstractLogger abstractLogger;
public ExtendedLogger(AbstractLogger abstractLogger) {
this.abstractLogger = abstractLogger;
}
#Override
public void debug(String message) {
abstractLogger.debug(message);
}
//info, warning and rest of methods
#Override
public void log(Levels level, String message) {
abstractLogger.log(level, message);
}
public void fatal(String message) {
//implement
}
}
First: logger libraries are numerous, and the first reform was the introduction of java.util.Logger to unify things a bit. Still not the dead of the other logging libraries.
Then came - especially for libraries - the underestimated System.Logger: a Logger façade that can be discover logging implementations. This allows publishing a library, use Logging, but leave the actual logging library choice to the library user.
So in that context meddling in class hierarchies and enum constants is counter-productive to say the least.
What you can do is a specific configuration, say for some packages, implement a specific file handler (FileHandler, Handler), and reserve ERROR for your own FATALISH when using *Exception classes or such.
Though seemingly simple, using FileWriter in the Logger child was not intended to be done as such. You should leave it to configuring the usage to your own Handler class.
Unfortunately the solution does not exist. You'll better write a prototype to test your specific configuration.
Is there any way for a SecurityManager in Java to selectively grant ReflectPermission("suppressAccessChecks") depending on the details of what setAccessible() is being called on? I don't see any way for this to be done.
For some sandboxed code, it would be very useful (such as for running various dynamic JVM languages) to allow the setAccessible() reflection API to be called, but only when setAccessible() is called on a method/field of a class that originates in the sandboxed code.
Does anyone have any alternative suggestions other than selective granting of ReflectPermission("suppressAccessChecks") if this isn't possible? Perhaps it would be safe to grant in all cases if SecurityManager.checkMemberAccess() is sufficiently restrictive?
Maybe looking at the call stack would be enough for your purposes? Something like:
import java.lang.reflect.ReflectPermission;
import java.security.Permission;
public class Test {
private static int foo;
public static void main(String[] args) throws Exception {
System.setSecurityManager(new SecurityManager() {
#Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) {
for (StackTraceElement elem : Thread.currentThread().getStackTrace()) {
if ("Test".equals(elem.getClassName()) && "badSetAccessible".equals(elem.getMethodName())) {
throw new SecurityException();
}
}
}
}
});
goodSetAccessible(); // works
badSetAccessible(); // throws SecurityException
}
private static void goodSetAccessible() throws Exception {
Test.class.getDeclaredField("foo").setAccessible(true);
}
private static void badSetAccessible() throws Exception {
Test.class.getDeclaredField("foo").setAccessible(true);
}
}
This is possible using byte code weaving with a library like Byte Buddy. Instead of using the standard ReflectPermission("suppressAccessChecks") permission, you can create a custom permission and replace the AccessibleObject.setAccessible methods with custom methods that check your custom permission using Byte Buddy transformations.
One possible way for this custom permission to work is for it to base access on the classloader of the caller and the object that access is being modified on. Using this allows isolated code (code loaded from a separate with it's own class loader) to call setAccessible on classes in its own jar, but not Standard Java classes or your own application classes.
Such a permission might look like:
public class UserSetAccessiblePermission extends Permission {
private final ClassLoader loader;
public UserSetAccessiblePermission(ClassLoader loader) {
super("userSetAccessible");
this.loader = loader;
}
#Override
public boolean implies(Permission permission) {
if (!(permission instanceof UserSetAccessiblePermission)) {
return false;
}
UserSetAccessiblePermission that = (UserSetAccessiblePermission) permission;
return that.loader == this.loader;
}
// equals and hashCode omitted
#Override
public String getActions() {
return "";
}
}
This is how I chose to implement this permission, but it could instead be a package or class whitelist or blacklist.
Now with this permission you can create a stub class that will replace the AccessibleObject.setAcessible method to instead use this permission.
public class AccessibleObjectStub {
private final static Permission STANDARD_ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
public static void setAccessible(#This AccessibleObject ao, boolean flag)
throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
Permission permission = STANDARD_ACCESS_PERMISSION;
if (isFromUserLoader(ao)) {
try {
permission = getUserAccessPermission(ao);
} catch (Exception e) {
// Ignore. Use standard permission.
}
}
sm.checkPermission(permission);
}
}
private static Permission getUserAccessPermission(AccessibleObject ao)
throws IllegalAccessException, InvocationTargetException, InstantiationException,
NoSuchMethodException, ClassNotFoundException {
ClassLoader aoClassLoader = getAccessibleObjectLoader(ao);
return new UserSetAccessiblePermission(aoClassLoader);
}
private static ClassLoader getAccessibleObjectLoader(AccessibleObject ao) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
#Override
public ClassLoader run() {
if (ao instanceof Executable) {
return ((Executable) ao).getDeclaringClass().getClassLoader();
} else if (ao instanceof Field) {
return ((Field) ao).getDeclaringClass().getClassLoader();
}
throw new IllegalStateException("Unknown AccessibleObject type: " + ao.getClass());
}
});
}
private static boolean isFromUserLoader(AccessibleObject ao) {
ClassLoader loader = getAccessibleObjectLoader(ao);
if (loader == null) {
return false;
}
// Check that the class loader instance is of a custom type
return UserClassLoaders.isUserClassLoader(loader);
}
}
With these two classes in place you can now use Byte Buddy to build a transformer for transforming the Java AccessibleObject to use your stub.
The first step to create the transformer is to create a Byte Buddy type pool that includes the bootstrap classes and a jar file containing your stubs.
final TypePool bootstrapTypePool = TypePool.Default.of(
new ClassFileLocator.Compound(
new ClassFileLocator.ForJarFile(jarFile),
ClassFileLocator.ForClassLoader.of(null)));
Next use reflections to get a reference to the AccessObject.setAccessible0 method. This is a private method that actually modifies the accessibility if the call to setAccessible passes permission checks.
Method setAccessible0Method;
try {
String setAccessible0MethodName = "setAccessible0";
Class[] paramTypes = new Class[2];
paramTypes[0] = AccessibleObject.class;
paramTypes[1] = boolean.class;
setAccessible0Method = AccessibleObject.class
.getDeclaredMethod(setAccessible0MethodName, paramTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
With these two pieces the transformer can be built.
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription, ClassLoader classLoader) {
return builder.method(
ElementMatchers.named("setAccessible")
.and(ElementMatchers.takesArguments(boolean.class)))
.intercept(MethodDelegation.to(
bootstrapTypePool.describe(
"com.leacox.sandbox.security.stub.java.lang.reflect.AccessibleObjectStub")
.resolve())
.andThen(MethodCall.invoke(setAccessible0Method).withThis().withAllArguments()));
}
}
The final step is to then install the Byte Buddy Java agent and perform the transformation. The jar containing the stubs must also be appended to the bootstrap class path. This is necessary because the AccessibleObject class will be loaded by the bootstrap loader, thus any stubs must be loaded there as well.
Instrumentation instrumentation = ByteBuddyAgent.install();
// Append the jar containing the stub replacement to the bootstrap classpath
instrumentation.appendToBootstrapClassLoaderSearch(jarFile);
AgentBuilder agentBuilder = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.ignore(none()); // disable default ignores so we can transform Java classes
.type(ElementMatchers.named("java.lang.reflect.AccessibleObject"))
.transform(transformer)
.installOnByteBuddyAgent();
This will work when using a SecurityManager and isolating both the stubs classes and the code that you are applying the selective permissions to in separate jars that are loaded at runtime. Having to load the jars at runtime rather than having them as standard dependencies or bundled libs complicates things a bit, but this seems to be a requirement for isolating untrusted code when using the SecurityManager.
My Github repo sandbox-runtime has a full, in-depth, example of a sandboxed runtime environment with execution of isolated untrusted code and more selective reflection permissions. I also have a blog post with more detail on just the selective setAccessible permissions pieces.
FWI: Since setAccessible seems only to have a valid use-case with serialization, I would think you might often get away with simply denying it outright.
That said, I am interested in how one does this sort of thing in general because I too have to write a security manager to block dynamically loaded code from doing things that our application container code needs to be able to do.
I was wondering, if it is possible to extend the standard java logger (java.util.logging.Logger;) for another logger level.
The goal is, that there should show up "ERROR" instead of "SEVERE" in the log files.
Is that possible?
Or do I have to use a different logger instead (e.g. Log4j)?
Thanks a lot!
If you just want to print something different than the standard you could set your own formatter, see http://tutorials.jenkov.com/java-logging/formatters.html
If you want to add an additional log level you can do so by subclassing java.util.logging.Level:
public class MyErrorLevel extends java.util.logging.Level {
public MyErrorLevel() {
super("ERROR", 1000);
}
}
Ok, Andreas Vogler's version works. Create this class:
public class MyErrorLevel extends java.util.logging.Level
{
public MyErrorLevel()
{
super("ERROR", 1000);
}
}
To use it in your running program code, you have to do it that way:
logger.log(new MyErrorLevel(),"My Error number one");
If you need more than one errorLevel you can do it that way:
public class MyErrorLevel extends Level
{
public static MyErrorLevel ERROR = new MyErrorLevel ("ERROR", 950);
public static MyErrorLevel SERIOUS_ERROR = new MyErrorLevel("SERIOUS_ERROR", 980);
//...and so on...
private MyErrorLevel(String name, int value)
{
super (name, value);
}
}
In your program code, you can use it like this:
logger.log(MyErrorLevel.ERROR, "my other error");
logger.log(MyErrorLevel.SERIOUS_ERROR, "my significant Error");
Now, if you don't want to specify your own classname (MyErrorLevel.SERIOUS_ERROR) everytime and instead you want to use 'standard-methods' (e. g. like the already existing method logger.info("my information")) you may think about extending the logger itself with new methods. This should (as far as my understanding goes) basically work like that:
public class MyLogger extends Logger
{
public MyLogger(String name, String resourceBundleName)
{
super(name, resourceBundleName);
}
public void error(String msg)
{
super.log(MyErrorLevel.ERROR, msg);
}
public void error(String msg)
{
super.log(MyErrorLevel.SERIOUS_ERROR, msg);
}
}
Now you should be able to call these methods in your code like that:
myLogger.error("my error")
myLogger.seriousError("my serious error")
But I wasnt able to do it:
I couldn't initialize my own logger with:
MyLogger myLogger = MyLogger.getLogger("MyModifiedLogger");
This doesn't compile because of type mismatch (Cannont convert from logger to MyLogger).
I also tried:
MyLogger myLogger = (MyLogger)Logger.getLogger("MyModifiedLogger");
This results in an error message while running:
java.util.logging.Logger cannot be cast to utility.MyLogger
So somehow my extension failed. Any ideas what I am missing?
I have a class with database calls, and I generally want to log every method called (with arguments) in this class with log4j:
logger.debug("foo(id="+id+") initiated");
Is it possible to do this automatically? Maybe by using some sort of annotation in start of each method instead of writing every single logger.debug?
Today I have to update my logging.debug every time I change arguments or method name.
Try #Loggable annotation and an AspectJ aspect from jcabi-aspects (I'm a developer):
#Loggable(Loggable.INFO)
public String load(URL url) {
return url.openConnection().getContent();
}
All method calls are logged through SLF4J.
This blog post explains it step by step: Java Method Logging with AOP and Annotations
If you have interfaces declaring the methods you want to log calls to, you can use the standard Proxy API to achieve what you want.
The Proxy API would allow you to wrap your actual implementation in a new, proxy class, that would log the call, and the forward the call to implementation. You just have to implement one InvocationHandler that does the logging and the forwarding.
For example,
interface Calculator {
int add(int a, int b);
}
class CalculatorImpl implements Calculator {
#Override public int add(int a, int b) { return a+b; }
}
class LoggingInvocationHandler implements InvocationHandler {
private final Object delegate;
public LoggingInvocationHandler(final Object delegate) {
this.delegate = delegate;
}
#Override invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method: " + method + ", args: " + args);
return method.invoke(delegate, args);
}
}
class X {
public static void main(String... args) {
final Calculator calc = new CalculatorImpl();
final Calculator loggingCalc =
(Calculator) Proxy.newProxyInstance(X.class.getClassLoader(),
new Class[] {Calculator.class},
new LoggingInvocationHandler (calc));
loggingCalc.add(2, 3); // shall print to the screen
}
}
You can also easily log the return values and exceptions thrown by the methods, just by changing the code in the InvocationHandler. Also, you could use any logging framework you like instead of System.out.println as in the example.
To log return values and exceptions, you could do something like:
#Override invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method: " + method + ", args: " + args);
try {
final Object ret = method.invoke(delegate, args);
System.out.println("return: " + ret);
return ret;
} catch (Throwable t) {
System.out.println("thrown: " + t);
throw t;
}
}
One possible solution would be to use aspectj. Idea would be to attach aspect to every method you wish to log, and perform logging is aspect instead of a method. One example of aspectj logging is right here in stackoverflow.
I have a code:
abstract void run();
public void start() {
Logger log = Logger.getLogger(Test.class.getName());
try {
run();
} catch (Exception e) {
log.warn(e.getMessage());
}
}
I can execute start() for example:
object1.start();
object2.start();
object3.start();
How can I check in start() which object (name object) started the method start()?
There is no automatic way to do this; objects in Java don't have to have a name.
One solution is:
public void start(String name) {
Logger log = Logger.getLogger(name);
}
This works because a logger can have an arbitrary name. Many loggers use the class's name because that's a simple + automatic way to name loggers. But that's not necessary.
Assuming you're trying to get "object1", "object2" and "object3" as the names (it's not clear), there are two things you ought to be aware of:
Those are variables, not objects. Multiple variables with different names could refer to the same object.
Objects don't generally have names - you can add your own name field, but you'll have to do that explicitly.
new Exception().getStackTrace()[1]
see StackTraceElement
If you mean to get the string "object1": this is logically not sensible. This is the name of a variable not the object it refers to, and does not exist at runtime. In fact, the compiler may optimize it away completely. Plus, it is ambiguous:
Consider the following code:
object2 = object1;
object2.start();
Should the instance now see the name "object1" or "object2"? It is both!
If you want your objects to have a "name", use a self-managed field to store it.
object1.setName("object2");
or do the same in the constructor:
MyObject object1 = new MyObject("object1");
You can't get the name of the variable at runtime, if that is what you want
I would start the Log outside the start() method
Logger log = Logger.getLogger(object1);
object1.start(log);
Then your start() method will look like this:
public void start(Logger log) {
try {
run();
} catch (Exception e) {
log.warn(e.getMessage());
}
}
Or, if you can't change the start() signature:
Logger log;
public void setLogger(Logger log){
this.log = log;
}
public void start() {
try {
run();
} catch (Exception e) {
log.warn(e.getMessage());
}
}
Then:
Logger log = Logger.getLogger(object1);
object1.setLogger(log);
object1.start();