Skip to content

Commit b166223

Browse files
Add network diagnostics logging and migrate to tinylog (Card-Forge#10004)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c935383 commit b166223

File tree

10 files changed

+76
-45
lines changed

10 files changed

+76
-45
lines changed

forge-game/src/main/java/forge/trackable/TrackableTypes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Map.Entry;
66
import java.util.Set;
77

8+
import org.tinylog.Logger;
89
import com.google.common.collect.Lists;
910
import com.google.common.collect.Maps;
1011
import com.google.common.collect.Sets;
@@ -119,7 +120,7 @@ protected void copyChangedProps(TrackableObject from, TrackableObject to, Tracka
119120
}
120121
}
121122
} catch (IndexOutOfBoundsException e) {
122-
System.err.println("got an IndexOutOfBoundsException, trying to continue ...");
123+
Logger.warn("IndexOutOfBoundsException in TrackableTypes, trying to continue");
123124
}
124125
}
125126
}

forge-gui/src/main/java/forge/gamemodes/net/CompatibleObjectDecoder.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
88
import io.netty.handler.codec.serialization.ClassResolver;
99
import net.jpountz.lz4.LZ4BlockInputStream;
10+
import org.tinylog.Logger;
1011

1112
import java.io.ObjectInputStream;
1213
import java.io.StreamCorruptedException;
@@ -25,10 +26,13 @@ public CompatibleObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
2526

2627
@Override
2728
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
29+
int frameStart = in.readerIndex();
2830
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
2931
if (frame == null) {
3032
return null;
3133
}
34+
int frameSize = frame.readableBytes();
35+
long startMs = System.currentTimeMillis();
3236
ObjectInputStream ois = GuiBase.hasPropertyConfig() ?
3337
new ObjectInputStream(new LZ4BlockInputStream(new ByteBufInputStream(frame, true))):
3438
new CObjectInputStream(new LZ4BlockInputStream(new ByteBufInputStream(frame, true)),this.classResolver);
@@ -37,11 +41,17 @@ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception
3741
try {
3842
var5 = ois.readObject();
3943
} catch (StreamCorruptedException e) {
40-
System.err.printf("Version Mismatch: %s%n", e.getMessage());
44+
Logger.error("Version Mismatch: {}", e.getMessage());
4145
} finally {
4246
ois.close();
4347
}
4448

49+
long elapsed = System.currentTimeMillis() - startMs;
50+
if (elapsed > 50 || frameSize > 20_000) {
51+
Logger.info("Decoded {} in {} ms ({} bytes compressed)",
52+
var5 != null ? var5.getClass().getSimpleName() : "null", elapsed, frameSize);
53+
}
54+
4555
return var5;
4656
}
4757
}

forge-gui/src/main/java/forge/gamemodes/net/CompatibleObjectEncoder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.netty.channel.ChannelHandlerContext;
77
import io.netty.handler.codec.MessageToByteEncoder;
88
import net.jpountz.lz4.LZ4BlockOutputStream;
9+
import org.tinylog.Logger;
910

1011
import java.io.ObjectOutputStream;
1112
import java.io.Serializable;
@@ -33,6 +34,10 @@ protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out)
3334
}
3435

3536
int endIdx = out.writerIndex();
36-
out.setInt(startIdx, endIdx - startIdx - 4);
37+
int msgSize = endIdx - startIdx - 4;
38+
out.setInt(startIdx, msgSize);
39+
if (msgSize > 20_000) {
40+
Logger.info("Encoded {} bytes (compressed) for {}", msgSize, msg.getClass().getSimpleName());
41+
}
3742
}
3843
}

forge-gui/src/main/java/forge/gamemodes/net/GameProtocolHandler.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import forge.localinstance.skin.FSkinProp;
88
import io.netty.channel.ChannelHandlerContext;
99
import io.netty.channel.ChannelInboundHandlerAdapter;
10+
import org.tinylog.Logger;
1011

1112
import java.io.Serializable;
1213
import java.lang.reflect.InvocationTargetException;
@@ -28,7 +29,7 @@ protected GameProtocolHandler(final boolean runInEdt) {
2829
@Override
2930
public final void channelRead(final ChannelHandlerContext ctx, final Object msg) {
3031
final String[] catchedError = {""};
31-
System.out.println("Received: " + msg);
32+
Logger.info("Received: {}", msg);
3233
if (msg instanceof ReplyEvent) {
3334
final ReplyEvent event = (ReplyEvent) msg;
3435
getReplyPool(ctx).complete(event.getIndex(), event.getReply());
@@ -41,28 +42,36 @@ public final void channelRead(final ChannelHandlerContext ctx, final Object msg)
4142
if (method == null) {
4243
//throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
4344
catchedError[0] += String.format("IllegalStateException: Method %s not found (GameProtocolHandler.java Line 43)\n", protocolMethod.name());
44-
System.err.printf("Method %s not found%n", protocolMethod.name());
45+
Logger.error("Method {} not found", protocolMethod.name());
4546
}
4647

4748
final Object[] args = event.getObjects();
4849
protocolMethod.checkArgs(args);
4950

5051
final Object toInvoke = getToInvoke(ctx);
5152

52-
// Pre-call actions
53+
// Pre-call actions (runs on IO thread — blocks all subsequent messages)
54+
final long beforeCallStart = System.currentTimeMillis();
5355
beforeCall(protocolMethod, args);
56+
final long beforeCallMs = System.currentTimeMillis() - beforeCallStart;
57+
if (beforeCallMs > 50) {
58+
Logger.info("beforeCall({}) took {} ms on IO thread", methodName, beforeCallMs);
59+
}
5460

5561
final Class<?> returnType = protocolMethod.getReturnType();
62+
final long receiveTimeMs = System.currentTimeMillis();
5663
final Runnable toRun = () -> {
64+
final long startMs = System.currentTimeMillis();
65+
final long queueDelayMs = startMs - receiveTimeMs;
5766
if (returnType.equals(Void.TYPE)) {
5867
try {
5968
method.invoke(toInvoke, args);
6069
} catch (final IllegalAccessException | IllegalArgumentException e) {
61-
System.err.printf("Unknown protocol method %s with %d args%n", methodName, args == null ? 0 : args.length);
70+
Logger.error("Unknown protocol method {} with {} args", methodName, args == null ? 0 : args.length);
6271
} catch (final InvocationTargetException e) {
6372
//throw new RuntimeException(e.getTargetException());
6473
catchedError[0] += (String.format("RuntimeException: %s (GameProtocolHandler.java Line 65)\n", e.getTargetException().toString()));
65-
System.err.println(e.getTargetException().toString());
74+
Logger.error("InvocationTargetException: {}", e.getTargetException().toString());
6675
}
6776
} else {
6877
Serializable reply = null;
@@ -72,18 +81,22 @@ public final void channelRead(final ChannelHandlerContext ctx, final Object msg)
7281
protocolMethod.checkReturnValue(theReply);
7382
reply = (Serializable) theReply;
7483
} else if (theReply != null) {
75-
System.err.printf("Non-serializable return type %s for method %s, returning null%n", returnType.getName(), methodName);
84+
Logger.error("Non-serializable return type {} for method {}, returning null", returnType.getName(), methodName);
7685
}
7786
} catch (final IllegalAccessException | IllegalArgumentException e) {
78-
System.err.printf("Unknown protocol method %s with %d args, replying with null%n", methodName, args == null ? 0 : args.length);
87+
Logger.error("Unknown protocol method {} with {} args, replying with null", methodName, args == null ? 0 : args.length);
7988
} catch (final NullPointerException | InvocationTargetException e) {
8089
//throw new RuntimeException(e.getTargetException());
8190
catchedError[0] += e.toString();
8291
SOptionPane.showMessageDialog(catchedError[0], "Error", FSkinProp.ICO_WARNING);
83-
System.err.println(e.toString());
92+
Logger.error("Exception in protocol method {}: {}", methodName, e.toString());
8493
}
8594
getRemote(ctx).send(new ReplyEvent(event.getId(), reply));
8695
}
96+
final long elapsed = System.currentTimeMillis() - startMs;
97+
if (queueDelayMs > 50 || elapsed > 50) {
98+
Logger.info("Protocol {} processed in {} ms (queued {} ms)", methodName, elapsed, queueDelayMs);
99+
}
87100
};
88101

89102
if (runInEdt) {

forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import forge.game.spellability.SpellAbilityView;
1212
import forge.gamemodes.match.NextGameDecision;
1313
import forge.gui.GuiBase;
14+
import org.tinylog.Logger;
1415
import forge.gui.interfaces.IGuiGame;
1516
import forge.interfaces.IGameController;
1617
import forge.localinstance.skin.FSkinProp;
@@ -131,7 +132,7 @@ public Method getMethod() {
131132
}
132133
return candidate;
133134
} catch (final NoSuchMethodException | SecurityException e) {
134-
System.err.printf("Warning: class contains no accessible method named %s%n", name());
135+
Logger.warn("Class contains no accessible method named {}", name());
135136
return getMethodNoArgs();
136137
}
137138
}
@@ -140,7 +141,7 @@ private Method getMethodNoArgs() {
140141
try {
141142
return mode.toInvoke.getMethod(name(), (Class<?>[]) null);
142143
} catch (final NoSuchMethodException | SecurityException e) {
143-
System.err.printf("Warning: class contains no accessible arg-less method named %s%n", name());
144+
Logger.warn("Class contains no accessible arg-less method named {}", name());
144145
return null;
145146
}
146147
}
@@ -161,12 +162,11 @@ public void checkArgs(final Object[] args) {
161162
final Class<?> type = this.args[iArg];
162163
if (!ReflectionUtil.isInstance(arg, type)) {
163164
//throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
164-
System.err.printf("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java)%n", name(), iArg, arg.getClass().getName(), type.getName());
165+
Logger.error("Protocol method {}: illegal argument ({}) of type {}, {} expected", name(), iArg, arg.getClass().getName(), type.getName());
165166
}
166167
}
167168
} catch (Exception e) {
168-
System.out.println(e.getMessage());
169-
e.printStackTrace();
169+
Logger.error(e, "Error checking args for protocol method {}", name());
170170
}
171171
}
172172

@@ -181,7 +181,7 @@ public void checkReturnValue(final Object value) {
181181
}
182182
if (!ReflectionUtil.isInstance(value, returnType)) {
183183
//throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName()));
184-
System.err.printf("IllegalStateException: Protocol method %s: illegal return object type %s returned by client, expected %s (ProtocolMethod.java)%n", name(), value.getClass().getName(), getReturnType().getName());
184+
Logger.error("Protocol method {}: illegal return type {} from client, expected {}", name(), value.getClass().getName(), getReturnType().getName());
185185
}
186186
}
187187
}

forge-gui/src/main/java/forge/gamemodes/net/client/FGameClient.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import forge.gamemodes.net.CompatibleObjectEncoder;
77
import forge.gamemodes.net.ReplyPool;
88
import forge.gamemodes.net.event.*;
9+
import org.tinylog.Logger;
910
import forge.gui.interfaces.IGuiGame;
1011
import forge.interfaces.ILobbyListener;
1112
import io.netty.bootstrap.Bootstrap;
@@ -72,15 +73,13 @@ public void initChannel(final SocketChannel ch) throws Exception {
7273
try {
7374
ch.sync();
7475
} catch (final InterruptedException e) {
75-
System.out.println(e.getMessage());
76-
e.printStackTrace();
76+
Logger.error(e, "Client channel interrupted");
7777
} finally {
7878
group.shutdownGracefully();
7979
}
8080
}).start();
8181
} catch (final InterruptedException e) {
82-
System.out.println(e.getMessage());
83-
e.printStackTrace();
82+
Logger.error(e, "Client connect interrupted");
8483
}
8584
}
8685

@@ -94,7 +93,7 @@ public void send(final NetEvent event) {
9493
if (disconnectSimulated) {
9594
return;
9695
}
97-
System.out.println("Client sent " + event);
96+
Logger.info("Client sent {}", event);
9897
channel.writeAndFlush(event);
9998
}
10099

@@ -104,7 +103,7 @@ public void send(final NetEvent event) {
104103
* will detect the silence and close the connection.
105104
*/
106105
public void simulateDisconnect() {
107-
System.out.println("[simulateDisconnect] Suspending all network writes.");
106+
Logger.info("[simulateDisconnect] Suspending all network writes.");
108107
disconnectSimulated = true;
109108
// Remove the IdleStateHandler to stop heartbeats, and add an outbound
110109
// handler that drops ALL writes (including game replies that bypass
@@ -115,11 +114,11 @@ public void simulateDisconnect() {
115114
channel.pipeline().addFirst("writeBlocker", new ChannelOutboundHandlerAdapter() {
116115
@Override
117116
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
118-
System.out.println("[writeBlocker] Dropped: " + msg.getClass().getSimpleName());
117+
Logger.info("[writeBlocker] Dropped: {}", msg.getClass().getSimpleName());
119118
promise.setSuccess();
120119
}
121120
});
122-
System.out.println("[simulateDisconnect] Pipeline modified: IdleStateHandler removed, writeBlocker added.");
121+
Logger.info("[simulateDisconnect] Pipeline modified: IdleStateHandler removed, writeBlocker added.");
123122
});
124123
}
125124

forge-gui/src/main/java/forge/gamemodes/net/client/GameClientHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import forge.gamemodes.match.LobbySlot;
99
import forge.gamemodes.net.GameProtocolHandler;
1010
import forge.gamemodes.net.IRemote;
11+
import org.tinylog.Logger;
1112
import forge.gamemodes.net.ProtocolMethod;
1213
import forge.gamemodes.net.ReplyPool;
1314
import forge.gamemodes.net.event.LoginEvent;
@@ -301,7 +302,7 @@ else if (obj instanceof PlayerZoneUpdates) {
301302
private void replicatePlayerView(final PlayerView newPlayerView) {
302303
PlayerView existingPlayerView = tracker.getObj(TrackableTypes.PlayerViewType, newPlayerView.getId());
303304
existingPlayerView.copyChangedProps(newPlayerView);
304-
System.err.println("replicated PlayerView properties - " + existingPlayerView.toString());
305+
Logger.info("Replicated PlayerView properties - {}", existingPlayerView);
305306
}
306307

307308
@Override

0 commit comments

Comments
 (0)