> On Jul 9, 2022, at 11:16 AM, Ralph Goers <ralph.go...@dslextreme.com> wrote:
> 
> I’ve been thinking about this a lot.  There are a few issues.
> 
> 1. I’d like to not have to generate new source code for every class doing 
> logging. 
> I am not sure how Lombok does it but I will want to investigate that.
> 2. Your solution looks like it is still done at runtime. I would like to do 
> it at compile 
> time. My though is to use the annotation processor and generate a new class 
> that 
> captures all the stack trace elements.I would then replace every logging call 
> with 
> a LogBuilder call. So
>    logger.info <http://logger.info/>(“Hello {}”, user);
> would become
>    
> logger.atInfo().withLocation(Log4jLocations.locations.get(“ThisFQCN”)).log(“Hello
>  {}”, user);

Obviously this isn’t quite right. The key would need to be something like 
“a.b.c.ClassName-1” so each Location has its own key.

> 
> So, like Lombok, any class wanting this would be annotated with @Log4j2, but 
> in addition to getting a logger declared they would also have the locations 
> generated.
> 
> The issues I have are a) I haven’t figured out how Logbok performs its magic 
> without 
> generating new code or affecting line numbers, b) I haven’t figured out how 
> to get 
> the location information while in the annotation processor. However, to solve 
> b I think 
> it is just a matter of walking through the source code, which has to be done 
> anyway 
> to find all the logger calls.
> 
> Ralph
> 
>> On Jul 9, 2022, at 3:04 AM, Volkan Yazıcı <vol...@yazi.ci 
>> <mailto:vol...@yazi.ci>> wrote:
>> 
>> Inspired by this SO post <https://stackoverflow.com/a/72437386/1278899 
>> <https://stackoverflow.com/a/72437386/1278899>> and
>> with some help from Piotr <https://stackoverflow.com/a/72916795/1278899 
>> <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 
>> <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
>>  
>> <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
>>  
>> <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
>>  
>> <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
>>  
>> <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.)
> 

Reply via email to