This AppTransforming class <https://github.com/vy/asm-playground/blob/master/src/main/java/com/vlkan/v2/AppTransforming.java>, transforms the following
private static final Logger LOGGER = LogManager.getLogger(); public static void main(String[] args) { LOGGER.info("should log at line 11"); System.out.println("nothing to see here"); LOGGER.info("should log at line 13"); f(); } private static void f() { System.out.println("adding some indirection"); LOGGER.info("should log at line 19"); } to this: private static final Logger LOGGER = LogManager.getLogger(); public static void main(String[] args) { LOGGER.atInfo().withLocation(Log4jLocationRegistry.get(0)).log("should log at line 11"); System.out.println("nothing to see here"); LOGGER.atInfo().withLocation(Log4jLocationRegistry.get(1)).log("should log at line 13"); f(); } private static void f() { System.out.println("adding some indirection"); LOGGER.atInfo().withLocation(Log4jLocationRegistry.get(2)).log("should log at line 19"); } My next step will be to run this at compile time. (Will keep you posted.) Please note the specialization on `Logger#info(String)` call in `AppTransforming`. Since Ralph did not like thread-locals, we need to implement every such possible specialization of the public API. On Sat, Jul 9, 2022 at 12:04 PM Volkan Yazıcı <vol...@yazi.ci> wrote: > Inspired by this SO post <https://stackoverflow.com/a/72437386/1278899> > and with some help from Piotr > <https://stackoverflow.com/a/72916795/1278899>, I have drafted an example > where I redefine a class such that every logger call is preceded with a > static source location capture. The experiment aims to replace the current > source location capture that uses an exception-based expensive mechanism > with inlining the source location using bytecode weaving. The sources are > publicly available on GitHub. > <https://github.com/vy/asm-playground/tree/master/src/main/java/com/vlkan> > In a nutshell, the magic is as follows: > > I have a logger library (Log4j.java > <https://github.com/vy/asm-playground/blob/master/src/main/java/com/vlkan/Log4j.java>) > as follows: > > public static final ThreadLocal<SourceLocation> LOCATION_REF = > ThreadLocal.withInitial(SourceLocation::new); > > public static void log() { > SourceLocation location = LOCATION_REF.get(); > boolean locationProvided = location.lineNumber > 0; > if (!locationProvided) { > StackTraceElement[] stackTraceElements = new > Throwable().getStackTrace(); > // Skip the first element pointing to this method. > StackTraceElement stackTraceElement = stackTraceElements[1]; > location.init( > stackTraceElement.getFileName(), > stackTraceElement.getClassName(), > stackTraceElement.getMethodName(), > stackTraceElement.getLineNumber()); > } > System.out.format( > "[%s] %s%n", > location, > locationProvided ? "provided location" : "populated > location"); > } > > Here note how `log()` uses a thread-local to see if there is already a > `SourceLocation` provided. If so, it leverages that, otherwise it populates > the source location using the stack trace of an exception. > > Below is my actual application (AppActual.java > <https://github.com/vy/asm-playground/blob/master/src/main/java/com/vlkan/AppActual.java>), > that is, what the actual/existing user code looks like: > > public static void main(String[] args) { > System.out.println("should log at line 9"); > log(); > System.out.println("nothing to see here"); > System.out.println("should log at line 12"); > log(); > f(); > } > > private static void f() { > System.out.println("adding some indirection"); > System.out.println("should log at line 19"); > log(); > } > > I want to transform this into the following expected form ( > AppExpected.java > <https://github.com/vy/asm-playground/blob/master/src/main/java/com/vlkan/AppExpected.java>) > that exploits the `LOCATION_REF` thread-local to inline the source location > information: > > public static void main(String[] args) { > System.out.println("should log at line 9"); > LOCATION_REF.get().init("AppExpected.java", > "com.vlkan.AppExpected", "main", 9); > log(); > System.out.println("nothing to see here"); > System.out.println("should log at line 12"); > LOCATION_REF.get().init("AppExpected.java", > "com.vlkan.AppExpected", "main", 12); > log(); > f(); > } > > private static void f() { > System.out.println("adding some indirection"); > System.out.println("should log at line 19"); > LOCATION_REF.get().init("AppExpected.java", > "com.vlkan.AppExpected", "main", 19); > log(); > } > > And... 👉 AppTransforming.java > <https://github.com/vy/asm-playground/blob/master/src/main/java/com/vlkan/AppTransforming.java> > 👈, my dear friends, performs exactly this transformation: converts > `AppActual` bytecode into `AppExpected`. 😍 > > I think we can extend this experiment to implement zero-cost source > location capture for Log4j. Though I will appreciate your help on some > loose ends. Assuming we have a bullet-proof mechanism to inline source > location capture given a class, what is the right way to ship this? As a > Maven plugin that kicks in at compile time? Wouldn't that make this feature > impossible to use without recompiling user sources? As a runtime utility? > If so, what about the cost of classpath scanning & weaving? If the bytecode > weaving only intercepts at Log4j API calls, this won't work for Log4j 1 > bridge, SLF4J, or any other indirect access to the Log4j API. What do you > think? I have used a thread-local to pass the source location to the > caller, is there a better alternative? (Putting aside the dynamic-scoped > variables to be shipped with Loom.) >