Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
Expand All @@ -27,6 +28,8 @@ public class LoggingWithPanacheProcessor {
private static final String JBOSS_LOGGER_DESCRIPTOR = "L" + JBOSS_LOGGER_BINARY_NAME + ";";
private static final String GET_LOGGER_DESCRIPTOR = "(Ljava/lang/String;)" + JBOSS_LOGGER_DESCRIPTOR;

private static final String LAMBDA_METAFACTORY = "java/lang/invoke/LambdaMetafactory";

@BuildStep
public void process(CombinedIndexBuildItem index, BuildProducer<BytecodeTransformerBuildItem> transformers) {
for (ClassInfo clazz : index.getIndex().getKnownUsers(QUARKUS_LOG_DOTNAME)) {
Expand Down Expand Up @@ -169,6 +172,51 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, JBOSS_LOGGER_BINARY_NAME, name, descriptor, false);
}

@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle,
Object... bootstrapMethodArguments) {

// we only transform method references, so skip if this indy doesn't bootstrap with a `LambdaMetafactory`
if (!LAMBDA_METAFACTORY.equals(bootstrapMethodHandle.getOwner())) {
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
return;
}
// skip if this `LambdaMetafactory` handle doesn't belong to `Log`
// (this covers non-logging cases, as well as cases where `Log` is used in a lambda expression)
//
// we access `bootstrapMethodArguments[1]` directly (here and below) because that's how
// the `LambdaMetafactory` is specified (both the standard `metafactory` and the `altMetafactory`):
// the first 3 arguments are provided by the JVM, and in the remaining arguments, the method
// handle is 2nd
boolean isLogging = bootstrapMethodArguments.length > 1
&& bootstrapMethodArguments[1] instanceof Handle handle
&& QUARKUS_LOG_BINARY_NAME.equals(handle.getOwner());
if (!isLogging) {
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
return;
}

// we transform a static invocation to a virtual invocation, so need the target instance on the stack
super.visitFieldInsn(Opcodes.GETSTATIC, classNameBinary, SYNTHETIC_LOGGER_FIELD_NAME,
JBOSS_LOGGER_DESCRIPTOR);

Handle handle = (Handle) bootstrapMethodArguments[1];
bootstrapMethodArguments[1] = new Handle(Opcodes.H_INVOKEVIRTUAL, JBOSS_LOGGER_BINARY_NAME,
handle.getName(), handle.getDesc(), false);

// we transform a static invocation to a virtual invocation,
// so need to prepend the `Logger` type to the descriptor
Type oldDesc = Type.getType(descriptor);
Type[] oldArgs = oldDesc.getArgumentTypes();
Type[] newArgs = new Type[oldArgs.length + 1];
newArgs[0] = Type.getObjectType(JBOSS_LOGGER_BINARY_NAME);
System.arraycopy(oldArgs, 0, newArgs, 1, oldArgs.length);
Type newDesc = Type.getMethodType(oldDesc.getReturnType(), newArgs);

super.visitInvokeDynamicInsn(name, newDesc.getDescriptor(), bootstrapMethodHandle,
bootstrapMethodArguments);
}

private boolean isDirectStackManipulationPossible(Type[] argTypes) {
return argTypes.length == 0
|| argTypes.length == 1 && argTypes[0].getSize() == 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package io.quarkus.logging;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import java.util.stream.Stream;

import jakarta.annotation.PostConstruct;
import jakarta.inject.Singleton;

Expand Down Expand Up @@ -48,4 +54,150 @@ public void reproduceStackDisciplineIssue() {
Log.infov("{0} {1}", "number", 42);
Log.info("string " + now);
}

// https://quarkusio.zulipchat.com/#narrow/channel/187030-users/topic/Using.20logging.2ELog.20only.20possible.20with.20bytecode.20transform
public void reproduceMethodReferenceIssue() {
Stream.of("foo", "bar", "baz", "quux").forEach(Log::info);
BiStream.of("foo", new NoStackTraceTestException(), "bar", new NoStackTraceTestException())
.when(Log::isDebugEnabled)
.forEach(Log::debug);
BiStream.of("foo %s", "bar", "baz %s", "quux").forEach(Log::warnf);
TriStream.of("foo %s %s", "bar", "baz").forEach(Log::infof);
TetraStream.of("foo %s %s %s", "bar", "baz", "quux").forEach(Log::errorf);
PentaStream.of("foo %s %s %s %s", "bar", "baz", "qux", "quux").forEach(Log::infof);
HexaStream.of("foo %s %s %s %s %s", "bar", "baz", "qux", "quux", "quuux").forEach(Log::infof);
}

static class BiStream<T, U> {
private record Item<T, U>(T t, U u) {
}

private final List<Item<T, U>> list;

static <T, U> BiStream<T, U> of(T t1, U u1, T t2, U u2) {
List<Item<T, U>> list = new ArrayList<>();
list.add(new Item<>(t1, u1));
list.add(new Item<>(t2, u2));
return new BiStream<>(list);
}

private BiStream(List<Item<T, U>> list) {
this.list = list;
}

BiStream<T, U> when(BooleanSupplier filter) {
if (filter.getAsBoolean()) {
return this;
}
return new BiStream<>(List.of());
}

void forEach(BiConsumer<T, U> action) {
list.forEach(item -> action.accept(item.t(), item.u()));
}
}

@FunctionalInterface
interface TriConsumer<T, U, V> {
void accept(T t, U u, V v);
}

static class TriStream<T, U, V> {
private record Item<T, U, V>(T t, U u, V v) {
}

private final List<Item<T, U, V>> list;

static <T, U, V> TriStream<T, U, V> of(T t1, U u1, V v1) {
List<Item<T, U, V>> list = new ArrayList<>();
list.add(new Item<>(t1, u1, v1));
return new TriStream<>(list);
}

private TriStream(List<Item<T, U, V>> list) {
this.list = list;
}

void forEach(TriConsumer<T, U, V> action) {
list.forEach(item -> action.accept(item.t(), item.u(), item.v()));
}
}

@FunctionalInterface
interface TetraConsumer<T, U, V, W> {
void accept(T t, U u, V v, W w);
}

static class TetraStream<T, U, V, W> {
private record Item<T, U, V, W>(T t, U u, V v, W w) {
}

private final List<Item<T, U, V, W>> list;

static <T, U, V, W> TetraStream<T, U, V, W> of(T t1, U u1, V v1, W w1) {
List<Item<T, U, V, W>> list = new ArrayList<>();
list.add(new Item<>(t1, u1, v1, w1));
return new TetraStream<>(list);
}

private TetraStream(List<Item<T, U, V, W>> list) {
this.list = list;
}

void forEach(TetraConsumer<T, U, V, W> action) {
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w()));
}
}

@FunctionalInterface
interface PentaConsumer<T, U, V, W, X> {
void accept(T t, U u, V v, W w, X x);
}

static class PentaStream<T, U, V, W, X> {
private record Item<T, U, V, W, X>(T t, U u, V v, W w, X x) {
}

private final List<Item<T, U, V, W, X>> list;

static <T, U, V, W, X> PentaStream<T, U, V, W, X> of(T t1, U u1, V v1, W w1, X x1) {
List<Item<T, U, V, W, X>> list = new ArrayList<>();
list.add(new Item<>(t1, u1, v1, w1, x1));
return new PentaStream<>(list);
}

private PentaStream(List<Item<T, U, V, W, X>> list) {
this.list = list;
}

void forEach(PentaConsumer<T, U, V, W, X> action) {
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w(), item.x()));
}
}

@FunctionalInterface
interface HexaConsumer<T, U, V, W, X, Y> {
void accept(T t, U u, V v, W w, X x, Y y);
}

static class HexaStream<T, U, V, W, X, Y> {
private record Item<T, U, V, W, X, Y>(T t, U u, V v, W w, X x, Y y) {
}

private final List<Item<T, U, V, W, X, Y>> list;

static <T, U, V, W, X, Y> HexaStream<T, U, V, W, X, Y> of(T t1, U u1, V v1, W w1, X x1, Y y1) {
List<Item<T, U, V, W, X, Y>> list = new ArrayList<>();
list.add(new Item<>(t1, u1, v1, w1, x1, y1));
return new HexaStream<>(list);
}

private HexaStream(List<Item<T, U, V, W, X, Y>> list) {
this.list = list;
}

void forEach(HexaConsumer<T, U, V, W, X, Y> action) {
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w(), item.x(), item.y()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@ public class LoggingWithPanacheTest {
"[ERROR] Hello Error: io.quarkus.logging.NoStackTraceTestException",
"[INFO] Hi!",
"[INFO] number 42",
"[INFO] string now");
"[INFO] string now",
"[INFO] foo",
"[INFO] bar",
"[INFO] baz",
"[INFO] quux",
"[DEBUG] foo: io.quarkus.logging.NoStackTraceTestException",
"[DEBUG] bar: io.quarkus.logging.NoStackTraceTestException",
"[WARN] foo bar",
"[WARN] baz quux",
"[INFO] foo bar baz",
"[ERROR] foo bar baz quux",
"[INFO] foo bar baz qux quux",
"[INFO] foo bar baz qux quux quuux");
});

@Inject
Expand All @@ -54,5 +66,6 @@ public void test() {
new LoggingEntity().something();

bean.reproduceStackDisciplineIssue();
bean.reproduceMethodReferenceIssue();
}
}
Loading