Skip to content

Commit 2325a53

Browse files
committed
introduced logging especially for mcp mode
1 parent 3372cb3 commit 2325a53

File tree

11 files changed

+213
-13
lines changed

11 files changed

+213
-13
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ The MCP server exposes the following tools:
160160

161161
#### Troubleshooting
162162

163+
- **Logging**: TDA maintains a log file for troubleshooting. The location depends on your operating system:
164+
- **macOS**: `~/Library/Logs/TDA/tda.log`
165+
- **Windows**: `%LOCALAPPDATA%\TDA\Logs\tda.log`
166+
- **Linux/Unix**: `~/.tda/logs/tda.log`
167+
You can check this file if TDA or the MCP server doesn't behave as expected.
163168
- **Path issues**: Ensure you use absolute paths for the JAR file and the log files you want to parse.
164169
- **Headless mode**: If you see errors related to `java.awt.HeadlessException`, double-check that `-Djava.awt.headless=true` is set.
165170
- **Permissions**: Make sure the user running the MCP server has read permissions for the log files.

tda/src/main/java/de/grimmfrost/tda/TDA.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
import de.grimmfrost.tda.utils.jedit.JEditTextArea;
4242
import de.grimmfrost.tda.utils.jedit.PopupMenu;
4343

44+
import de.grimmfrost.tda.utils.LogManager;
4445
import java.awt.*;
46+
import java.util.logging.Level;
47+
import java.util.logging.Logger;
4548
import java.awt.datatransfer.UnsupportedFlavorException;
4649
import java.awt.dnd.*;
4750
import java.io.FileNotFoundException;
@@ -119,6 +122,7 @@
119122
* @author irockel
120123
*/
121124
public class TDA extends JPanel implements ListSelectionListener, TreeSelectionListener, ActionListener, MenuListener {
125+
private static final Logger LOGGER = LogManager.getLogger(TDA.class);
122126
private static FileDialog fc;
123127
private static JFileChooser sessionFc;
124128
private static final int DIVIDER_SIZE = 4;
@@ -397,9 +401,9 @@ private void getLogfileFromClipboard() {
397401
text = (String)t.getTransferData(DataFlavor.stringFlavor);
398402
}
399403
} catch (UnsupportedFlavorException ex) {
400-
ex.printStackTrace();
404+
LOGGER.log(Level.SEVERE, "Unsupported flavor for clipboard data", ex);
401405
} catch (IOException ex) {
402-
ex.printStackTrace();
406+
LOGGER.log(Level.SEVERE, "IO error reading clipboard data", ex);
403407
}
404408

405409
if(text != null) {
@@ -444,17 +448,17 @@ private String parseWelcomeURL(InputStream is) {
444448
resultString = resultString.replaceFirst("<!-- ##recentsessions## -->", getAsTable("opensession://", PrefManager.get().getRecentSessions()));
445449
} catch (IllegalArgumentException ex) {
446450
// hack to prevent crashing of the app because off unparsed replacer.
447-
ex.printStackTrace();
451+
LOGGER.log(Level.SEVERE, "Failed to parse welcome page replacers", ex);
448452
} catch (IOException ex) {
449-
ex.printStackTrace();
453+
LOGGER.log(Level.SEVERE, "IO error reading welcome page", ex);
450454
} finally {
451455
try {
452456
if(br != null) {
453457
br.close();
454458
is.close();
455459
}
456460
} catch (IOException ex) {
457-
ex.printStackTrace();
461+
LOGGER.log(Level.SEVERE, "Error closing stream", ex);
458462
}
459463
}
460464
// remove unparsed replacers.
@@ -2315,6 +2319,7 @@ public void windowClosed(WindowEvent e) {
23152319
* main startup method for TDA
23162320
*/
23172321
public static void main(String[] args) {
2322+
LogManager.init();
23182323
if (args.length > 0 && "--mcp".equals(args[0])) {
23192324
String[] mcpArgs = new String[args.length - 1];
23202325
System.arraycopy(args, 1, mcpArgs, 0, args.length - 1);

tda/src/main/java/de/grimmfrost/tda/mcp/HeadlessAnalysisProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,25 @@
88
import de.grimmfrost.tda.parser.DumpParserFactory;
99
import de.grimmfrost.tda.parser.SunJDKParser;
1010
import de.grimmfrost.tda.utils.DateMatcher;
11+
import de.grimmfrost.tda.utils.LogManager;
1112
import javax.swing.tree.DefaultMutableTreeNode;
1213
import javax.swing.tree.TreePath;
1314
import java.io.*;
1415
import java.util.*;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
1518

1619
/**
1720
* Headless analysis provider for TDA.
1821
*/
1922
public class HeadlessAnalysisProvider {
23+
private static final Logger LOGGER = LogManager.getLogger(HeadlessAnalysisProvider.class);
2024
private final Map<String, Map<String, String>> threadStore = new HashMap<>();
2125
private final List<DefaultMutableTreeNode> topNodes = new ArrayList<>();
2226
private String currentLogFile;
2327

2428
public void parseLogFile(String filePath) throws IOException {
29+
LOGGER.info("Parsing log file: " + filePath);
2530
this.currentLogFile = filePath;
2631
File file = new File(filePath);
2732
if (!file.exists()) {

tda/src/main/java/de/grimmfrost/tda/mcp/MCPServer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
import com.google.gson.Gson;
44
import com.google.gson.JsonObject;
55
import com.google.gson.JsonParser;
6+
import de.grimmfrost.tda.utils.LogManager;
67
import java.io.*;
78
import java.util.*;
9+
import java.util.logging.Level;
10+
import java.util.logging.Logger;
811

912
/**
1013
* MCP Server for TDA thread dump analysis.
1114
*/
1215
public class MCPServer {
16+
private static final Logger LOGGER = LogManager.getLogger(MCPServer.class);
1317
private static final Gson gson = new Gson();
1418
private static final HeadlessAnalysisProvider provider = new HeadlessAnalysisProvider();
1519

1620
public static void main(String[] args) {
21+
LogManager.init();
1722
System.setProperty("java.awt.headless", "true");
1823

1924
// Output capabilities to stderr for debugging/logging if needed,
@@ -48,6 +53,7 @@ public static void main(String[] args) {
4853
sendResponse(request.get("id").getAsInt(), result);
4954
}
5055
} catch (Exception e) {
56+
LOGGER.log(Level.SEVERE, "Error processing MCP request", e);
5157
sendError(requestHasId(line) ? getId(line) : -1, e.getMessage());
5258
}
5359
}

tda/src/main/java/de/grimmfrost/tda/parser/DumpParserFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,23 @@
2222
package de.grimmfrost.tda.parser;
2323

2424
import de.grimmfrost.tda.utils.DateMatcher;
25+
import de.grimmfrost.tda.utils.LogManager;
2526
import de.grimmfrost.tda.utils.PrefManager;
2627
import java.io.BufferedReader;
2728
import java.io.IOException;
2829
import java.io.InputStream;
2930
import java.io.InputStreamReader;
3031
import java.util.Map;
32+
import java.util.logging.Level;
33+
import java.util.logging.Logger;
3134

3235
/**
3336
* Factory for the dump parsers.
3437
*
3538
* @author irockel
3639
*/
3740
public class DumpParserFactory {
41+
private static final Logger LOGGER = LogManager.getLogger(DumpParserFactory.class);
3842
private static DumpParserFactory instance = null;
3943

4044
/**
@@ -112,8 +116,9 @@ public DumpParser getDumpParserForLogfile(InputStream dumpFileStream, Map<String
112116
if (currentDumpParser != null) {
113117
bis.reset();
114118
}
119+
LOGGER.log(Level.INFO, "parsing logfile using " + (currentDumpParser != null ? currentDumpParser.getClass().getName() : "<none>"));
115120
} catch (IOException ex) {
116-
ex.printStackTrace();
121+
LOGGER.log(Level.SEVERE, "IO error detecting parser for logfile", ex);
117122
}
118123
return currentDumpParser;
119124
}

tda/src/main/java/de/grimmfrost/tda/parser/JCmdJSONParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@
1212
import java.io.IOException;
1313
import java.io.InputStream;
1414
import java.util.Map;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
17+
import de.grimmfrost.tda.utils.LogManager;
1518
import javax.swing.tree.DefaultMutableTreeNode;
1619
import javax.swing.tree.MutableTreeNode;
1720

1821
/**
1922
* Parser for JSON thread dumps generated by jcmd.
2023
*/
2124
public class JCmdJSONParser extends AbstractDumpParser {
25+
private static final Logger LOGGER = LogManager.getLogger(JCmdJSONParser.class);
2226
private static final Gson gson = new Gson();
2327
private boolean hasMore = true;
2428
private int counter = 1;
@@ -99,7 +103,7 @@ public MutableTreeNode parseNext() {
99103

100104
return threadDump;
101105
} catch (IOException e) {
102-
e.printStackTrace();
106+
LOGGER.log(Level.SEVERE, "IO error parsing JSON thread dump", e);
103107
return null;
104108
}
105109
}

tda/src/main/java/de/grimmfrost/tda/parser/SunJDKParser.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import de.grimmfrost.tda.TDA;
2424
import de.grimmfrost.tda.model.*;
2525
import de.grimmfrost.tda.utils.DateMatcher;
26+
import de.grimmfrost.tda.utils.LogManager;
2627
import de.grimmfrost.tda.utils.HistogramTableModel;
2728
import de.grimmfrost.tda.utils.IconFactory;
2829
import java.io.BufferedReader;
@@ -39,6 +40,8 @@
3940
import java.util.Vector;
4041
import java.util.regex.Matcher;
4142
import javax.swing.JOptionPane;
43+
import java.util.logging.Level;
44+
import java.util.logging.Logger;
4245
import javax.swing.tree.DefaultMutableTreeNode;
4346
import javax.swing.tree.MutableTreeNode;
4447

@@ -49,6 +52,7 @@
4952
* @author irockel
5053
*/
5154
public class SunJDKParser extends AbstractDumpParser {
55+
private static final Logger LOGGER = LogManager.getLogger(SunJDKParser.class);
5256

5357
private MutableTreeNode nextDump = null;
5458
private Map<String, Map<String, String>> threadStore = null;
@@ -502,7 +506,7 @@ public MutableTreeNode parseNext() {
502506
"Error during Parsing Thread Dump", JOptionPane.ERROR_MESSAGE);
503507
retry = true;
504508
} catch (IOException e) {
505-
e.printStackTrace();
509+
LOGGER.log(Level.SEVERE, "IO error parsing thread dump", e);
506510
}
507511
} while (retry);
508512

@@ -967,7 +971,7 @@ public void parseLoggcFile(InputStream loggcFileStream, DefaultMutableTreeNode r
967971
}
968972
}
969973
} catch (IOException ex) {
970-
ex.printStackTrace();
974+
LOGGER.log(Level.SEVERE, "IO error parsing loggc file", ex);
971975
}
972976
}
973977

tda/src/main/java/de/grimmfrost/tda/utils/AppInfo.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
package de.grimmfrost.tda.utils;
2121
import java.io.InputStream;
2222
import java.util.Properties;
23+
import java.util.logging.Level;
24+
import java.util.logging.Logger;
2325

2426
/**
2527
* provides static application information like name and version
2628
* @author irockel
2729
*/
2830
public class AppInfo {
31+
private static final Logger LOGGER = LogManager.getLogger(AppInfo.class);
2932
private static final String APP_SHORT_NAME = "TDA";
3033
private static final String APP_FULL_NAME = "Thread Dump Analyzer";
3134
private static String VERSION = "unknown";
@@ -41,7 +44,7 @@ public class AppInfo {
4144
}
4245
} catch (Exception e) {
4346
// fallback to unknown or log error
44-
e.printStackTrace();
47+
LOGGER.log(Level.SEVERE, "Failed to load version properties", e);
4548
}
4649
}
4750

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package de.grimmfrost.tda.utils;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.util.logging.*;
6+
import javax.swing.SwingUtilities;
7+
8+
/**
9+
* Manages logging for TDA.
10+
*/
11+
public class LogManager {
12+
private static final Logger LOGGER = Logger.getLogger("de.grimmfrost.tda");
13+
private static boolean initialized = false;
14+
private static String logFilePath = null;
15+
16+
private static class ErrorHandler extends Handler {
17+
@Override
18+
public void publish(LogRecord record) {
19+
if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
20+
SwingUtilities.invokeLater(() -> {
21+
StatusBar statusBar = StatusBar.getInstance();
22+
if (statusBar != null) {
23+
statusBar.showErrorIndicator();
24+
}
25+
});
26+
}
27+
}
28+
29+
@Override
30+
public void flush() {}
31+
32+
@Override
33+
public void close() throws SecurityException {}
34+
}
35+
36+
/**
37+
* Initializes logging. Should be called early in the application lifecycle.
38+
*/
39+
public static synchronized void init() {
40+
if (initialized) {
41+
return;
42+
}
43+
44+
try {
45+
String logDir = getLogDirectory();
46+
File dir = new File(logDir);
47+
if (!dir.exists()) {
48+
dir.mkdirs();
49+
}
50+
51+
File logFile = new File(dir, "tda.log");
52+
logFilePath = logFile.getAbsolutePath();
53+
54+
FileHandler fileHandler = new FileHandler(logFilePath, 1024 * 1024, 5, true);
55+
fileHandler.setFormatter(new SimpleFormatter());
56+
fileHandler.setLevel(Level.ALL);
57+
58+
Logger rootLogger = Logger.getLogger("de.grimmfrost.tda");
59+
rootLogger.addHandler(fileHandler);
60+
rootLogger.setLevel(Level.INFO);
61+
62+
// Also log to console for MCP as it might be useful for some clients (though MCP uses stdout for protocol)
63+
// But we should be careful not to pollute stdout if it's used for MCP protocol.
64+
// MCPServer uses System.out for JSON-RPC. Logging to System.err is safer.
65+
66+
Handler[] handlers = rootLogger.getHandlers();
67+
for (Handler handler : handlers) {
68+
if (handler instanceof ConsoleHandler) {
69+
rootLogger.removeHandler(handler);
70+
}
71+
}
72+
73+
ConsoleHandler consoleHandler = new ConsoleHandler();
74+
consoleHandler.setLevel(Level.WARNING);
75+
rootLogger.addHandler(consoleHandler);
76+
77+
rootLogger.addHandler(new ErrorHandler());
78+
79+
initialized = true;
80+
LOGGER.info("Logging initialized. Log file: " + logFilePath);
81+
} catch (IOException e) {
82+
System.err.println("Failed to initialize logging: " + e.getMessage());
83+
}
84+
}
85+
86+
public static String getLogFilePath() {
87+
return logFilePath;
88+
}
89+
90+
private static String getLogDirectory() {
91+
String os = System.getProperty("os.name").toLowerCase();
92+
String userHome = System.getProperty("user.home");
93+
94+
if (os.contains("win")) {
95+
String appData = System.getenv("LOCALAPPDATA");
96+
if (appData != null) {
97+
return appData + File.separator + "TDA" + File.separator + "Logs";
98+
}
99+
return userHome + File.separator + "AppData" + File.separator + "Local" + File.separator + "TDA" + File.separator + "Logs";
100+
} else if (os.contains("mac")) {
101+
return userHome + File.separator + "Library" + File.separator + "Logs" + File.separator + "TDA";
102+
} else {
103+
// Linux/Unix
104+
return userHome + File.separator + ".tda" + File.separator + "logs";
105+
}
106+
}
107+
108+
public static Logger getLogger(Class<?> clazz) {
109+
return Logger.getLogger(clazz.getName());
110+
}
111+
}

tda/src/main/java/de/grimmfrost/tda/utils/PrefManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.awt.Dimension;
3030
import java.awt.Point;
3131
import java.io.File;
32+
import java.util.logging.Level;
33+
import java.util.logging.Logger;
3234
import java.util.ArrayList;
3335
import java.util.Iterator;
3436
import java.util.prefs.BackingStoreException;
@@ -44,6 +46,7 @@
4446
* @author irockel
4547
*/
4648
public class PrefManager {
49+
private static final Logger LOGGER = LogManager.getLogger(PrefManager.class);
4750
public static final String PARAM_DELIM = "\u00A7\u00A7\u00A7\u00A7";
4851

4952
public static final String FILTER_SEP = "\u00ac\u00ac\u00ac\u00ac";

0 commit comments

Comments
 (0)