Skip to content

Commit 878285c

Browse files
authored
Merge pull request #46479 from Ladicek/logging-method-references-fix
Logging with Panache: add support for method references of `Log` methods
2 parents 890fe3f + 44b3ce4 commit 878285c

File tree

3 files changed

+214
-1
lines changed

3 files changed

+214
-1
lines changed

core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingWithPanacheProcessor.java

+48
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.objectweb.asm.ClassReader;
66
import org.objectweb.asm.ClassVisitor;
77
import org.objectweb.asm.FieldVisitor;
8+
import org.objectweb.asm.Handle;
89
import org.objectweb.asm.MethodVisitor;
910
import org.objectweb.asm.Opcodes;
1011
import org.objectweb.asm.Type;
@@ -27,6 +28,8 @@ public class LoggingWithPanacheProcessor {
2728
private static final String JBOSS_LOGGER_DESCRIPTOR = "L" + JBOSS_LOGGER_BINARY_NAME + ";";
2829
private static final String GET_LOGGER_DESCRIPTOR = "(Ljava/lang/String;)" + JBOSS_LOGGER_DESCRIPTOR;
2930

31+
private static final String LAMBDA_METAFACTORY = "java/lang/invoke/LambdaMetafactory";
32+
3033
@BuildStep
3134
public void process(CombinedIndexBuildItem index, BuildProducer<BytecodeTransformerBuildItem> transformers) {
3235
for (ClassInfo clazz : index.getIndex().getKnownUsers(QUARKUS_LOG_DOTNAME)) {
@@ -169,6 +172,51 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri
169172
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, JBOSS_LOGGER_BINARY_NAME, name, descriptor, false);
170173
}
171174

175+
@Override
176+
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle,
177+
Object... bootstrapMethodArguments) {
178+
179+
// we only transform method references, so skip if this indy doesn't bootstrap with a `LambdaMetafactory`
180+
if (!LAMBDA_METAFACTORY.equals(bootstrapMethodHandle.getOwner())) {
181+
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
182+
return;
183+
}
184+
// skip if this `LambdaMetafactory` handle doesn't belong to `Log`
185+
// (this covers non-logging cases, as well as cases where `Log` is used in a lambda expression)
186+
//
187+
// we access `bootstrapMethodArguments[1]` directly (here and below) because that's how
188+
// the `LambdaMetafactory` is specified (both the standard `metafactory` and the `altMetafactory`):
189+
// the first 3 arguments are provided by the JVM, and in the remaining arguments, the method
190+
// handle is 2nd
191+
boolean isLogging = bootstrapMethodArguments.length > 1
192+
&& bootstrapMethodArguments[1] instanceof Handle handle
193+
&& QUARKUS_LOG_BINARY_NAME.equals(handle.getOwner());
194+
if (!isLogging) {
195+
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
196+
return;
197+
}
198+
199+
// we transform a static invocation to a virtual invocation, so need the target instance on the stack
200+
super.visitFieldInsn(Opcodes.GETSTATIC, classNameBinary, SYNTHETIC_LOGGER_FIELD_NAME,
201+
JBOSS_LOGGER_DESCRIPTOR);
202+
203+
Handle handle = (Handle) bootstrapMethodArguments[1];
204+
bootstrapMethodArguments[1] = new Handle(Opcodes.H_INVOKEVIRTUAL, JBOSS_LOGGER_BINARY_NAME,
205+
handle.getName(), handle.getDesc(), false);
206+
207+
// we transform a static invocation to a virtual invocation,
208+
// so need to prepend the `Logger` type to the descriptor
209+
Type oldDesc = Type.getType(descriptor);
210+
Type[] oldArgs = oldDesc.getArgumentTypes();
211+
Type[] newArgs = new Type[oldArgs.length + 1];
212+
newArgs[0] = Type.getObjectType(JBOSS_LOGGER_BINARY_NAME);
213+
System.arraycopy(oldArgs, 0, newArgs, 1, oldArgs.length);
214+
Type newDesc = Type.getMethodType(oldDesc.getReturnType(), newArgs);
215+
216+
super.visitInvokeDynamicInsn(name, newDesc.getDescriptor(), bootstrapMethodHandle,
217+
bootstrapMethodArguments);
218+
}
219+
172220
private boolean isDirectStackManipulationPossible(Type[] argTypes) {
173221
return argTypes.length == 0
174222
|| argTypes.length == 1 && argTypes[0].getSize() == 1

integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingBean.java

+152
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package io.quarkus.logging;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.function.BiConsumer;
6+
import java.util.function.BooleanSupplier;
7+
import java.util.stream.Stream;
8+
39
import jakarta.annotation.PostConstruct;
410
import jakarta.inject.Singleton;
511

@@ -48,4 +54,150 @@ public void reproduceStackDisciplineIssue() {
4854
Log.infov("{0} {1}", "number", 42);
4955
Log.info("string " + now);
5056
}
57+
58+
// https://quarkusio.zulipchat.com/#narrow/channel/187030-users/topic/Using.20logging.2ELog.20only.20possible.20with.20bytecode.20transform
59+
public void reproduceMethodReferenceIssue() {
60+
Stream.of("foo", "bar", "baz", "quux").forEach(Log::info);
61+
BiStream.of("foo", new NoStackTraceTestException(), "bar", new NoStackTraceTestException())
62+
.when(Log::isDebugEnabled)
63+
.forEach(Log::debug);
64+
BiStream.of("foo %s", "bar", "baz %s", "quux").forEach(Log::warnf);
65+
TriStream.of("foo %s %s", "bar", "baz").forEach(Log::infof);
66+
TetraStream.of("foo %s %s %s", "bar", "baz", "quux").forEach(Log::errorf);
67+
PentaStream.of("foo %s %s %s %s", "bar", "baz", "qux", "quux").forEach(Log::infof);
68+
HexaStream.of("foo %s %s %s %s %s", "bar", "baz", "qux", "quux", "quuux").forEach(Log::infof);
69+
}
70+
71+
static class BiStream<T, U> {
72+
private record Item<T, U>(T t, U u) {
73+
}
74+
75+
private final List<Item<T, U>> list;
76+
77+
static <T, U> BiStream<T, U> of(T t1, U u1, T t2, U u2) {
78+
List<Item<T, U>> list = new ArrayList<>();
79+
list.add(new Item<>(t1, u1));
80+
list.add(new Item<>(t2, u2));
81+
return new BiStream<>(list);
82+
}
83+
84+
private BiStream(List<Item<T, U>> list) {
85+
this.list = list;
86+
}
87+
88+
BiStream<T, U> when(BooleanSupplier filter) {
89+
if (filter.getAsBoolean()) {
90+
return this;
91+
}
92+
return new BiStream<>(List.of());
93+
}
94+
95+
void forEach(BiConsumer<T, U> action) {
96+
list.forEach(item -> action.accept(item.t(), item.u()));
97+
}
98+
}
99+
100+
@FunctionalInterface
101+
interface TriConsumer<T, U, V> {
102+
void accept(T t, U u, V v);
103+
}
104+
105+
static class TriStream<T, U, V> {
106+
private record Item<T, U, V>(T t, U u, V v) {
107+
}
108+
109+
private final List<Item<T, U, V>> list;
110+
111+
static <T, U, V> TriStream<T, U, V> of(T t1, U u1, V v1) {
112+
List<Item<T, U, V>> list = new ArrayList<>();
113+
list.add(new Item<>(t1, u1, v1));
114+
return new TriStream<>(list);
115+
}
116+
117+
private TriStream(List<Item<T, U, V>> list) {
118+
this.list = list;
119+
}
120+
121+
void forEach(TriConsumer<T, U, V> action) {
122+
list.forEach(item -> action.accept(item.t(), item.u(), item.v()));
123+
}
124+
}
125+
126+
@FunctionalInterface
127+
interface TetraConsumer<T, U, V, W> {
128+
void accept(T t, U u, V v, W w);
129+
}
130+
131+
static class TetraStream<T, U, V, W> {
132+
private record Item<T, U, V, W>(T t, U u, V v, W w) {
133+
}
134+
135+
private final List<Item<T, U, V, W>> list;
136+
137+
static <T, U, V, W> TetraStream<T, U, V, W> of(T t1, U u1, V v1, W w1) {
138+
List<Item<T, U, V, W>> list = new ArrayList<>();
139+
list.add(new Item<>(t1, u1, v1, w1));
140+
return new TetraStream<>(list);
141+
}
142+
143+
private TetraStream(List<Item<T, U, V, W>> list) {
144+
this.list = list;
145+
}
146+
147+
void forEach(TetraConsumer<T, U, V, W> action) {
148+
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w()));
149+
}
150+
}
151+
152+
@FunctionalInterface
153+
interface PentaConsumer<T, U, V, W, X> {
154+
void accept(T t, U u, V v, W w, X x);
155+
}
156+
157+
static class PentaStream<T, U, V, W, X> {
158+
private record Item<T, U, V, W, X>(T t, U u, V v, W w, X x) {
159+
}
160+
161+
private final List<Item<T, U, V, W, X>> list;
162+
163+
static <T, U, V, W, X> PentaStream<T, U, V, W, X> of(T t1, U u1, V v1, W w1, X x1) {
164+
List<Item<T, U, V, W, X>> list = new ArrayList<>();
165+
list.add(new Item<>(t1, u1, v1, w1, x1));
166+
return new PentaStream<>(list);
167+
}
168+
169+
private PentaStream(List<Item<T, U, V, W, X>> list) {
170+
this.list = list;
171+
}
172+
173+
void forEach(PentaConsumer<T, U, V, W, X> action) {
174+
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w(), item.x()));
175+
}
176+
}
177+
178+
@FunctionalInterface
179+
interface HexaConsumer<T, U, V, W, X, Y> {
180+
void accept(T t, U u, V v, W w, X x, Y y);
181+
}
182+
183+
static class HexaStream<T, U, V, W, X, Y> {
184+
private record Item<T, U, V, W, X, Y>(T t, U u, V v, W w, X x, Y y) {
185+
}
186+
187+
private final List<Item<T, U, V, W, X, Y>> list;
188+
189+
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) {
190+
List<Item<T, U, V, W, X, Y>> list = new ArrayList<>();
191+
list.add(new Item<>(t1, u1, v1, w1, x1, y1));
192+
return new HexaStream<>(list);
193+
}
194+
195+
private HexaStream(List<Item<T, U, V, W, X, Y>> list) {
196+
this.list = list;
197+
}
198+
199+
void forEach(HexaConsumer<T, U, V, W, X, Y> action) {
200+
list.forEach(item -> action.accept(item.t(), item.u(), item.v(), item.w(), item.x(), item.y()));
201+
}
202+
}
51203
}

integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingWithPanacheTest.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,19 @@ public class LoggingWithPanacheTest {
4242
"[ERROR] Hello Error: io.quarkus.logging.NoStackTraceTestException",
4343
"[INFO] Hi!",
4444
"[INFO] number 42",
45-
"[INFO] string now");
45+
"[INFO] string now",
46+
"[INFO] foo",
47+
"[INFO] bar",
48+
"[INFO] baz",
49+
"[INFO] quux",
50+
"[DEBUG] foo: io.quarkus.logging.NoStackTraceTestException",
51+
"[DEBUG] bar: io.quarkus.logging.NoStackTraceTestException",
52+
"[WARN] foo bar",
53+
"[WARN] baz quux",
54+
"[INFO] foo bar baz",
55+
"[ERROR] foo bar baz quux",
56+
"[INFO] foo bar baz qux quux",
57+
"[INFO] foo bar baz qux quux quuux");
4658
});
4759

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

5668
bean.reproduceStackDisciplineIssue();
69+
bean.reproduceMethodReferenceIssue();
5770
}
5871
}

0 commit comments

Comments
 (0)