diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 01fb832eb..f19b9b2d6 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -116,7 +116,8 @@ public class JadxCLIArgs implements IJadxArgs { if (isVerbose()) { ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + // remove INFO ThresholdFilter + rootLogger.getAppender("STDOUT").clearAllFilters(); } } catch (JadxException e) { System.err.println("ERROR: " + e.getMessage()); diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml index 6f8e7d5eb..6a1e61641 100644 --- a/jadx-cli/src/main/resources/logback.xml +++ b/jadx-cli/src/main/resources/logback.xml @@ -1,12 +1,15 @@ + + INFO + %-5level - %msg%n - + diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java index 2c8f93604..0a1c8fe84 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java @@ -3,6 +3,7 @@ package jadx.gui; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.ui.MainWindow; +import jadx.gui.utils.LogCollector; import javax.swing.SwingUtilities; import javax.swing.UIManager; @@ -15,6 +16,7 @@ public class JadxGUI { public static void main(String[] args) { try { + LogCollector.register(); final JadxSettings jadxArgs = JadxSettingsAdapter.load(); // overwrite loaded settings by command line arguments if (!jadxArgs.processArgs(args)) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java new file mode 100644 index 000000000..04346086e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java @@ -0,0 +1,108 @@ +package jadx.gui.ui; + +import ch.qos.logback.classic.Level; +import jadx.gui.utils.LogCollector; +import jadx.gui.utils.NLS; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; + +class LogViewer extends JDialog { + private static final long serialVersionUID = -2188700277429054641L; + private static final Level[] LEVEL_ITEMS = {Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR}; + + private static Level level = Level.WARN; + private RSyntaxTextArea textPane; + + public LogViewer() { + initUI(); + registerLogListener(); + } + + public final void initUI() { + textPane = new RSyntaxTextArea(); + textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + JPanel controlPane = new JPanel(); + controlPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + final JComboBox cb = new JComboBox(LEVEL_ITEMS); + cb.setSelectedItem(level); + cb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int i = cb.getSelectedIndex(); + level = LEVEL_ITEMS[i]; + registerLogListener(); + } + }); + JLabel levelLabel = new JLabel(NLS.str("log.level")); + levelLabel.setLabelFor(cb); + controlPane.add(levelLabel); + controlPane.add(cb); + + JScrollPane scrollPane = new JScrollPane(textPane); + + JButton close = new JButton(NLS.str("tabs.close")); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + close(); + } + }); + close.setAlignmentX(0.5f); + + Container contentPane = getContentPane(); + contentPane.add(controlPane, BorderLayout.PAGE_START); + contentPane.add(scrollPane, BorderLayout.CENTER); + contentPane.add(close, BorderLayout.PAGE_END); + + setTitle("Log Viewer"); + pack(); + setSize(800, 600); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setModalityType(ModalityType.MODELESS); + setLocationRelativeTo(null); + } + + private void registerLogListener() { + LogCollector logCollector = LogCollector.getInstance(); + logCollector.resetListener(); + textPane.setText(""); + logCollector.registerListener(new LogCollector.ILogListener() { + @Override + public Level getFilterLevel() { + return level; + } + + @Override + public void onAppend(final String logStr) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + textPane.append(logStr); + textPane.updateUI(); + } + }); + } + }); + } + + private void close() { + LogCollector.getInstance().resetListener(); + dispose(); + } + + public static void main(String[] args) { + new LogViewer().setVisible(true); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index ed7514276..0a390ccb1 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -84,6 +84,7 @@ public class MainWindow extends JFrame { private static final ImageIcon ICON_FORWARD = Utils.openIcon("icon_forward"); private static final ImageIcon ICON_PREF = Utils.openIcon("wrench"); private static final ImageIcon ICON_DEOBF = Utils.openIcon("lock_edit"); + private static final ImageIcon ICON_LOG = Utils.openIcon("report"); private final JadxWrapper wrapper; private final JadxSettings settings; @@ -375,6 +376,19 @@ public class MainWindow extends JFrame { }; find.addActionListener(findAction); + JMenu tools = new JMenu(NLS.str("menu.tools")); + tools.setMnemonic(KeyEvent.VK_T); + + JMenuItem logItem = new JMenuItem(NLS.str("menu.log"), ICON_LOG); + ActionListener logAction = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new LogViewer().setVisible(true); + } + }; + logItem.addActionListener(logAction); + tools.add(logItem); + JMenu help = new JMenu(NLS.str("menu.help")); help.setMnemonic(KeyEvent.VK_H); @@ -390,6 +404,7 @@ public class MainWindow extends JFrame { menuBar.add(file); menuBar.add(view); menuBar.add(nav); + menuBar.add(tools); menuBar.add(help); setJMenuBar(menuBar); @@ -470,6 +485,10 @@ public class MainWindow extends JFrame { } }); + JButton logBtn = new JButton(ICON_LOG); + logBtn.setToolTipText(NLS.str("menu.log")); + logBtn.addActionListener(logAction); + updateLink = new Link("", JadxUpdate.JADX_RELEASES_URL); updateLink.setVisible(false); @@ -495,6 +514,9 @@ public class MainWindow extends JFrame { toolbar.add(deobfToggleBtn); toolbar.addSeparator(); + toolbar.add(logBtn); + toolbar.addSeparator(); + toolbar.add(prefButton); toolbar.addSeparator(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 82c77dc4f..c9a94b7be 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -9,6 +9,7 @@ import jadx.gui.treemodel.JNode; import jadx.gui.utils.NLS; import jadx.gui.utils.NameIndex; import jadx.gui.utils.Position; +import jadx.gui.utils.TextStandardActions; import javax.swing.BorderFactory; import javax.swing.Box; @@ -198,7 +199,7 @@ public class SearchDialog extends JDialog { @Override public Component getListCellRendererComponent(JList list, - Object obj, int index, boolean isSelected, boolean cellHasFocus) { + Object obj, int index, boolean isSelected, boolean cellHasFocus) { if (!(obj instanceof JNode)) { return null; } @@ -317,7 +318,7 @@ public class SearchDialog extends JDialog { setSize(700, 500); setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setModalityType(ModalityType.APPLICATION_MODAL); + setModalityType(ModalityType.MODELESS); } private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/LogCollector.java b/jadx-gui/src/main/java/jadx/gui/utils/LogCollector.java new file mode 100644 index 000000000..cbacbb148 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/LogCollector.java @@ -0,0 +1,92 @@ +package jadx.gui.utils; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.read.CyclicBufferAppender; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.LoggerFactory; + +public class LogCollector extends CyclicBufferAppender { + private static LogCollector instance = new LogCollector(); + + public static LogCollector getInstance() { + return instance; + } + + public static void register() { + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + LoggerContext loggerContext = rootLogger.getLoggerContext(); + + PatternLayout layout = new PatternLayout(); + layout.setContext(loggerContext); + layout.setPattern("%-5level: %msg%n"); + layout.start(); + + instance.setContext(loggerContext); + instance.setLayout(layout); + instance.start(); + + rootLogger.addAppender(instance); + } + + public interface ILogListener { + + Level getFilterLevel(); + + void onAppend(String logStr); + } + + private Layout layout; + + @Nullable + private ILogListener listener; + + public LogCollector() { + setName("LogCollector"); + setMaxSize(50000); + } + + @Override + protected void append(ILoggingEvent event) { + super.append(event); + if (listener != null + && event.getLevel().isGreaterOrEqual(listener.getFilterLevel())) { + synchronized (this) { + listener.onAppend(layout.doLayout(event)); + } + } + } + + public void setLayout(Layout layout) { + this.layout = layout; + } + + public void registerListener(@NotNull ILogListener listener) { + this.listener = listener; + synchronized (this) { + listener.onAppend(init(listener.getFilterLevel())); + } + } + + public void resetListener() { + this.listener = null; + } + + private String init(Level filterLevel) { + StringBuilder sb = new StringBuilder(); + int length = getLength(); + for (int i = 0; i < length; i++) { + ILoggingEvent event = get(i); + if (event.getLevel().isGreaterOrEqual(filterLevel)) { + sb.append(layout.doLayout(event)); + } + } + return sb.toString(); + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 7ab25d097..dcb29b10d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -8,6 +8,8 @@ menu.flatten=Show flatten packages menu.navigation=Navigation menu.search=Search ... menu.find_in_file=Find in ... +menu.tools=Tools +menu.log=Log Viewer menu.help=Help menu.about=About menu.update_label=New version %s available! @@ -69,6 +71,8 @@ preferences.cancel=Cancel msg.open_file=Please open file msg.saving_sources=Saving sources +log.level=Log level: + popup.undo=Undo popup.redo=Redo popup.cut=Cut diff --git a/jadx-gui/src/main/resources/icons-16/report.png b/jadx-gui/src/main/resources/icons-16/report.png new file mode 100644 index 000000000..779ad58ef Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/report.png differ