Author: ravn Date: Sun Dec 28 00:37:28 2008 New Revision: 1263 Modified: slf4j/trunk/slf4j-ext/src/main/java/org/slf4j/instrumentation/LogTransformer.java slf4j/trunk/slf4j-site/src/site/pages/extensions.html
Log: Revised documentation and ensured that log4j and logback classes are not instrumented Modified: slf4j/trunk/slf4j-ext/src/main/java/org/slf4j/instrumentation/LogTransformer.java ============================================================================== --- slf4j/trunk/slf4j-ext/src/main/java/org/slf4j/instrumentation/LogTransformer.java (original) +++ slf4j/trunk/slf4j-ext/src/main/java/org/slf4j/instrumentation/LogTransformer.java Sun Dec 28 00:37:28 2008 @@ -30,268 +30,272 @@ */ public class LogTransformer implements ClassFileTransformer { - /** - * Builder provides a flexible way of configuring some of many options on the - * parent class instead of providing many constructors. - * - * {...@link http - * ://rwhansen.blogspot.com/2007/07/theres-builder-pattern-that-joshua.html} - * - */ - public static class Builder { - - /** - * Build and return the LogTransformer corresponding to the options set in - * this Builder. - * - * @return - */ - public LogTransformer build() { - if (verbose) { - System.err.println("Creating LogTransformer"); - } - return new LogTransformer(this); - } - - boolean addEntryExit; - - /** - * Should each method log entry (with parameters) and exit (with parameters - * and returnvalue)? - * - * @param b - * value of flag - * @return - */ - public Builder addEntryExit(boolean b) { - addEntryExit = b; - return this; - } - - boolean addVariableAssignment; - - // private Builder addVariableAssignment(boolean b) { - // System.err.println("cannot currently log variable assignments."); - // addVariableAssignment = b; - // return this; - // } - - boolean verbose; - - /** - * Should LogTransformer be verbose in what it does? This currently list the - * names of the classes being processed. - * - * @param b - * @return - */ - public Builder verbose(boolean b) { - verbose = b; - return this; - } - - String[] ignore = {"org/slf4j/"}; - - public Builder ignore(String[] strings) { - this.ignore = strings; - return this; - } - - private String level = "info"; - - public Builder level(String level) { - level = level.toLowerCase(); - if (level.equals("info") || level.equals("debug") - || level.equals("trace")) { - this.level = level; - } else { - if (verbose) { - System.err.println("level not info/debug/trace : " + level); - } - } - return this; - } - } - - private String level; - private String levelEnabled; - - private LogTransformer(Builder builder) { - String s = "WARNING: javassist not available on classpath for javaagent, log statements will not be added"; - try { - if (Class.forName("javassist.ClassPool") == null) { - System.err.println(s); - } - } catch (ClassNotFoundException e) { - System.err.println(s); - } - - this.addEntryExit = builder.addEntryExit; - // this.addVariableAssignment = builder.addVariableAssignment; - this.verbose = builder.verbose; - this.ignore = builder.ignore; - this.level = builder.level; - this.levelEnabled = "is" + builder.level.substring(0, 1).toUpperCase() - + builder.level.substring(1) + "Enabled"; - } - - private boolean addEntryExit; - // private boolean addVariableAssignment; - private boolean verbose; - private String[] ignore; - - public byte[] transform(ClassLoader loader, String className, Class<?> clazz, - ProtectionDomain domain, byte[] bytes) { - - try { - return transform0(className, clazz, domain, bytes); - } catch (Exception e) { - System.err.println("Could not instrument " + className); - e.printStackTrace(); - return bytes; - } - } - - /** - * transform0 sees if the className starts with any of the namespaces to - * ignore, if so it is returned unchanged. Otherwise it is processed by - * doClass(...) - * - * @param className - * @param clazz - * @param domain - * @param bytes - * @return - */ - - private byte[] transform0(String className, Class<?> clazz, - ProtectionDomain domain, byte[] bytes) { - - try { - for (int i = 0; i < ignore.length; i++) { - if (className.startsWith(ignore[i])) { - return bytes; - } - } - String slf4jName = "org.slf4j.LoggerFactory"; - try { - if (domain != null && domain.getClassLoader() != null) { - domain.getClassLoader().loadClass(slf4jName); - } else { - if (verbose) { - System.err.println("Skipping " + className - + " as it doesn't have a domain or a class loader."); - } - return bytes; - } - } catch (ClassNotFoundException e) { - if (verbose) { - System.err.println("Skipping " + className - + " as slf4j is not available to it"); - } - return bytes; - } - if (verbose) { - System.err.println("Processing " + className); - } - return doClass(className, clazz, bytes); - } catch (Throwable e) { - System.out.println("e = " + e); - return bytes; - } - } - - private String loggerName; - - /** - * doClass() process a single class by first creates a class description from - * the byte codes. If it is a class (i.e. not an interface) the methods - * defined have bodies, and a static final logger object is added with the - * name of this class as an argument, and each method then gets processed with - * doMethod(...) to have logger calls added. - * - * @param name - * class name (slashes separate, not dots) - * @param clazz - * @param b - * @return - */ - private byte[] doClass(String name, Class<?> clazz, byte[] b) { - ClassPool pool = ClassPool.getDefault(); - CtClass cl = null; - try { - cl = pool.makeClass(new ByteArrayInputStream(b)); - if (cl.isInterface() == false) { - - loggerName = "_____log"; - - // We have to declare the log variable. - - String pattern1 = "private static org.slf4j.Logger {};"; - String loggerDefinition = format(pattern1, loggerName); - CtField field = CtField.make(loggerDefinition, cl); - - // and assign it the appropriate value. - - String pattern2 = "org.slf4j.LoggerFactory.getLogger({}.class);"; - String replace = name.replace('/', '.'); - String getLogger = format(pattern2, replace); - - cl.addField(field, getLogger); - - // then check every behaviour (which includes methods). We are only - // interested in non-empty ones, as they have code. - // NOTE: This will be changed, as empty methods should be - // instrumented too. - - CtBehavior[] methods = cl.getDeclaredBehaviors(); - for (int i = 0; i < methods.length; i++) { - if (methods[i].isEmpty() == false) { - doMethod(methods[i]); - } - } - b = cl.toBytecode(); - } - } catch (Exception e) { - System.err.println("Could not instrument " + name + ", " + e); - e.printStackTrace(System.err); - } finally { - if (cl != null) { - cl.detach(); - } - } - return b; - } - - /** - * process a single method - this means add entry/exit logging if requested. - * It is only called for methods with a body. - * - * @param method - * method to work on - * @throws NotFoundException - * @throws CannotCompileException - */ - private void doMethod(CtBehavior method) throws NotFoundException, - CannotCompileException { - - String signature = JavassistHelper.getSignature(method); - String returnValue = JavassistHelper.returnValue(method); - - if (addEntryExit) { - String messagePattern = "if ({}.{}()) {}.{}(\">> {}\");"; - Object[] arg1 = new Object[] { loggerName, levelEnabled, loggerName, - level, signature }; - String before = MessageFormatter.arrayFormat(messagePattern, arg1); - // System.out.println(before); - method.insertBefore(before); - - String messagePattern2 = "if ({}.{}()) {}.{}(\"<< {}{}\");"; - Object[] arg2 = new Object[] { loggerName, levelEnabled, loggerName, - level, signature, returnValue }; - String after = MessageFormatter.arrayFormat(messagePattern2, arg2); - // System.out.println(after); - method.insertAfter(after); - } - } + /** + * Builder provides a flexible way of configuring some of many options on + * the parent class instead of providing many constructors. + * + * {...@link http + * ://rwhansen.blogspot.com/2007/07/theres-builder-pattern-that-joshua.html} + * + */ + public static class Builder { + + /** + * Build and return the LogTransformer corresponding to the options set + * in this Builder. + * + * @return + */ + public LogTransformer build() { + if (verbose) { + System.err.println("Creating LogTransformer"); + } + return new LogTransformer(this); + } + + boolean addEntryExit; + + /** + * Should each method log entry (with parameters) and exit (with + * parameters and returnvalue)? + * + * @param b + * value of flag + * @return + */ + public Builder addEntryExit(boolean b) { + addEntryExit = b; + return this; + } + + boolean addVariableAssignment; + + // private Builder addVariableAssignment(boolean b) { + // System.err.println("cannot currently log variable assignments."); + // addVariableAssignment = b; + // return this; + // } + + boolean verbose; + + /** + * Should LogTransformer be verbose in what it does? This currently list + * the names of the classes being processed. + * + * @param b + * @return + */ + public Builder verbose(boolean b) { + verbose = b; + return this; + } + + String[] ignore = { "org/slf4j/", "ch/qos/logback/", + "org/apache/log4j/" }; + + public Builder ignore(String[] strings) { + this.ignore = strings; + return this; + } + + private String level = "info"; + + public Builder level(String level) { + level = level.toLowerCase(); + if (level.equals("info") || level.equals("debug") + || level.equals("trace")) { + this.level = level; + } else { + if (verbose) { + System.err.println("level not info/debug/trace : " + level); + } + } + return this; + } + } + + private String level; + private String levelEnabled; + + private LogTransformer(Builder builder) { + String s = "WARNING: javassist not available on classpath for javaagent, log statements will not be added"; + try { + if (Class.forName("javassist.ClassPool") == null) { + System.err.println(s); + } + } catch (ClassNotFoundException e) { + System.err.println(s); + } + + this.addEntryExit = builder.addEntryExit; + // this.addVariableAssignment = builder.addVariableAssignment; + this.verbose = builder.verbose; + this.ignore = builder.ignore; + this.level = builder.level; + this.levelEnabled = "is" + builder.level.substring(0, 1).toUpperCase() + + builder.level.substring(1) + "Enabled"; + } + + private boolean addEntryExit; + // private boolean addVariableAssignment; + private boolean verbose; + private String[] ignore; + + public byte[] transform(ClassLoader loader, String className, + Class<?> clazz, ProtectionDomain domain, byte[] bytes) { + + try { + return transform0(className, clazz, domain, bytes); + } catch (Exception e) { + System.err.println("Could not instrument " + className); + e.printStackTrace(); + return bytes; + } + } + + /** + * transform0 sees if the className starts with any of the namespaces to + * ignore, if so it is returned unchanged. Otherwise it is processed by + * doClass(...) + * + * @param className + * @param clazz + * @param domain + * @param bytes + * @return + */ + + private byte[] transform0(String className, Class<?> clazz, + ProtectionDomain domain, byte[] bytes) { + + try { + for (int i = 0; i < ignore.length; i++) { + if (className.startsWith(ignore[i])) { + return bytes; + } + } + String slf4jName = "org.slf4j.LoggerFactory"; + try { + if (domain != null && domain.getClassLoader() != null) { + domain.getClassLoader().loadClass(slf4jName); + } else { + if (verbose) { + System.err + .println("Skipping " + + className + + " as it doesn't have a domain or a class loader."); + } + return bytes; + } + } catch (ClassNotFoundException e) { + if (verbose) { + System.err.println("Skipping " + className + + " as slf4j is not available to it"); + } + return bytes; + } + if (verbose) { + System.err.println("Processing " + className); + } + return doClass(className, clazz, bytes); + } catch (Throwable e) { + System.out.println("e = " + e); + return bytes; + } + } + + private String loggerName; + + /** + * doClass() process a single class by first creates a class description + * from the byte codes. If it is a class (i.e. not an interface) the methods + * defined have bodies, and a static final logger object is added with the + * name of this class as an argument, and each method then gets processed + * with doMethod(...) to have logger calls added. + * + * @param name + * class name (slashes separate, not dots) + * @param clazz + * @param b + * @return + */ + private byte[] doClass(String name, Class<?> clazz, byte[] b) { + ClassPool pool = ClassPool.getDefault(); + CtClass cl = null; + try { + cl = pool.makeClass(new ByteArrayInputStream(b)); + if (cl.isInterface() == false) { + + loggerName = "_____log"; + + // We have to declare the log variable. + + String pattern1 = "private static org.slf4j.Logger {};"; + String loggerDefinition = format(pattern1, loggerName); + CtField field = CtField.make(loggerDefinition, cl); + + // and assign it the appropriate value. + + String pattern2 = "org.slf4j.LoggerFactory.getLogger({}.class);"; + String replace = name.replace('/', '.'); + String getLogger = format(pattern2, replace); + + cl.addField(field, getLogger); + + // then check every behaviour (which includes methods). We are + // only + // interested in non-empty ones, as they have code. + // NOTE: This will be changed, as empty methods should be + // instrumented too. + + CtBehavior[] methods = cl.getDeclaredBehaviors(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].isEmpty() == false) { + doMethod(methods[i]); + } + } + b = cl.toBytecode(); + } + } catch (Exception e) { + System.err.println("Could not instrument " + name + ", " + e); + e.printStackTrace(System.err); + } finally { + if (cl != null) { + cl.detach(); + } + } + return b; + } + + /** + * process a single method - this means add entry/exit logging if requested. + * It is only called for methods with a body. + * + * @param method + * method to work on + * @throws NotFoundException + * @throws CannotCompileException + */ + private void doMethod(CtBehavior method) throws NotFoundException, + CannotCompileException { + + String signature = JavassistHelper.getSignature(method); + String returnValue = JavassistHelper.returnValue(method); + + if (addEntryExit) { + String messagePattern = "if ({}.{}()) {}.{}(\">> {}\");"; + Object[] arg1 = new Object[] { loggerName, levelEnabled, + loggerName, level, signature }; + String before = MessageFormatter.arrayFormat(messagePattern, arg1); + // System.out.println(before); + method.insertBefore(before); + + String messagePattern2 = "if ({}.{}()) {}.{}(\"<< {}{}\");"; + Object[] arg2 = new Object[] { loggerName, levelEnabled, + loggerName, level, signature, returnValue }; + String after = MessageFormatter.arrayFormat(messagePattern2, arg2); + // System.out.println(after); + method.insertAfter(after); + } + } } \ No newline at end of file Modified: slf4j/trunk/slf4j-site/src/site/pages/extensions.html ============================================================================== --- slf4j/trunk/slf4j-site/src/site/pages/extensions.html (original) +++ slf4j/trunk/slf4j-site/src/site/pages/extensions.html Sun Dec 28 00:37:28 2008 @@ -386,7 +386,7 @@ logging in a standardized manner. </p> - <p>Note thar XLogger instances are obrained to through the + <p>Note that XLogger instances are obtained to through the <a href="apidocs/org/slf4j/ext/XLoggerFactory.html"><code>XLoggerFactory</code></a> utility class.</p> @@ -676,7 +676,8 @@ <dt><b>ignore</b>=X:Y:...</dt> <dd>(Advanced) Provide full list of colon separated prefixes of class names NOT to add logging to. The default list is - "sun/:java/:javax/:org/slf4j/:ch/qos/logback/:org/apache/log4j/:apple/:com/sun/". + "org/slf4j/:ch/qos/logback/:org/apache/log4j/". This does not override the fact that a class must be able to access the + slf4j-api classes in order to do logging, so if these classes are not visible to a given class it is not instrumented. </dd> </dl> @@ -701,19 +702,18 @@ </ul> <p>A warning message is printed if the javassist library was not - found by the agent. + found by the agent, and options requiring byte code transformations will not work. </p> <h3>Misc notes</h3> <ul> - <li>A java agent does not "see" any classes already loaded by the + <li>A java agent is not invoked on any classes already loaded by the class loader.</li> - <li>Any exceptions in the java agent that would normally have been - printed, are silently swallowed by the JVM.</li> - <li>The javaagent does not do any logging itself, and the slf4j - backend does not need to be available to the agent. </li> + <li>Exceptions in the java agent that would normally have been + printed, may be silently swallowed by the JVM.</li> + <li>The javaagent only logs to System.err.</li> <li>The name of the logger variable is fixed (to a value unlikely to be used) so if that name is already used, a failure occures. This should be changed to determine an unused name and use that instead.</li> _______________________________________________ dev mailing list dev@slf4j.org http://www.slf4j.org/mailman/listinfo/dev