001package apps.jmrit.log; 002 003import java.awt.BorderLayout; 004import java.awt.FlowLayout; 005import java.awt.Dimension; 006import java.awt.event.ActionEvent; 007 008import java.util.*; 009 010import javax.swing.*; 011 012import org.apache.logging.log4j.Level; 013import org.apache.logging.log4j.Logger; 014import org.apache.logging.log4j.LogManager; 015import org.apache.logging.log4j.core.LoggerContext; 016import org.apache.logging.log4j.core.config.*; 017 018/** 019 * Show the current Log4J Logger tree. 020 * 021 * @author Bob Jacobsen Copyright 2010 022 * @author Steve Young Copyright(C) 2023 023 * @since 2.9.4 024 */ 025public class Log4JTreePane extends jmri.util.swing.JmriPanel { 026 027 private JTextArea text; 028 private JScrollPane scroll; 029 private JComboBox<Level> levelSelectionComboBox; 030 private JComboBox<String> categoryComboBox; 031 private final static String ROOT_LEVEL_STRING = Bundle.getMessage("DataItemRootLoggingLevel"); 032 private final static Level[] SELECTABLE_LEVELS = new Level[]{ Level.TRACE, Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR, Level.OFF}; 033 034 /** 035 * Provide a recommended title for an enclosing frame. 036 */ 037 @Override 038 public String getTitle() { 039 return Bundle.getMessage("MenuItemLogTreeAction"); 040 } 041 042 public Log4JTreePane() { 043 setLayout(new BorderLayout()); 044 } 045 046 /** 047 * 2nd stage of initialization, invoked after the constructor is complete. 048 * 049 * Sets up the entire dialog box 050 */ 051 @SuppressWarnings("unchecked") 052 @Override 053 public void initComponents() { 054 055 add(getEditLoggingLevelPanel(), BorderLayout.SOUTH); 056 057 text = new JTextArea(); 058 text.setEditable(false); 059 scroll = new JScrollPane(text); 060 updateTextAreaAndCategorySelect(); 061 062 add(scroll, BorderLayout.CENTER); 063 064 JPanel topP = new JPanel(new FlowLayout()); 065 JButton refreshButton = new JButton(Bundle.getMessage("ButtonRefreshCategories")); 066 refreshButton.addActionListener(this::refreshButtonPressed); 067 topP.add(refreshButton); 068 topP.add(new JLabel(Bundle.getMessage("FieldLogConfiguredInherited"))); 069 add(topP,BorderLayout.NORTH); 070 071 // start scrolled to top 072 text.setCaretPosition(0); 073 JScrollBar b = scroll.getVerticalScrollBar(); 074 b.setValue(b.getMaximum()); 075 } 076 077 /** 078 * Set up the bottom part of the dialog where the user can change logging levels. 079 * @return The JPanel, ready to use. 080 */ 081 private JPanel getEditLoggingLevelPanel() { 082 083 JPanel p = new JPanel(); 084 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); 085 086 // Create a label explaining the combobox. 087 JPanel categoryLabelPanel = new JPanel(new FlowLayout()); 088 categoryLabelPanel.add(new JLabel(Bundle.getMessage("LabelCategoryToChange"))); 089 090 // Create a JComboBox and populate it with logger names 091 categoryComboBox = new JComboBox<>(); 092 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(categoryComboBox); 093 categoryComboBox.setToolTipText(Bundle.getMessage("ToolTipSelectLoggingLevel")); 094 categoryComboBox.setEditable(true); 095 096 JPanel catergoryComboboxPanel = new JPanel(new FlowLayout()); 097 catergoryComboboxPanel.add(categoryComboBox); 098 099 levelSelectionComboBox = new JComboBox<>(SELECTABLE_LEVELS); 100 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(levelSelectionComboBox); 101 levelSelectionComboBox.setSelectedItem(Level.DEBUG); 102 103 // Expand the button slightly to avoid elipsis 104 Dimension preferredSize = levelSelectionComboBox.getPreferredSize(); 105 preferredSize.width = (int) (preferredSize.width * 1.2); 106 levelSelectionComboBox.setPreferredSize(preferredSize); 107 levelSelectionComboBox.setToolTipText(Bundle.getMessage("ToolTipSelectLoggingLevelValue")); 108 109 JPanel levelSelectPanel = new JPanel(new FlowLayout()); 110 levelSelectPanel.add(new JLabel(Bundle.getMessage("LabelNewLevelForAboveCategory"))); 111 levelSelectPanel.add(levelSelectionComboBox); 112 113 JButton setLevelButton = new JButton(Bundle.getMessage("ButtonEditLoggingLevel")); 114 setLevelButton.setToolTipText(Bundle.getMessage("ToolTipEditLoggingLevel")); 115 setLevelButton.addActionListener(this::editButtonPressed); 116 117 JPanel setButtonPanel = new JPanel(new FlowLayout()); 118 setButtonPanel.add(setLevelButton); 119 120 p.add(categoryLabelPanel); 121 p.add(catergoryComboboxPanel); 122 p.add(levelSelectPanel); 123 p.add(setButtonPanel); 124 return p; 125 } 126 127 private void updateTextAreaAndCategorySelect(){ 128 List<LoggerInfo> loggersWithLevels = Log4JTreePane.getAllLoggersWithLevels(); 129 populateTextArea(loggersWithLevels); 130 updateCategoryComboBox(loggersWithLevels); 131 } 132 133 private static List<LoggerInfo> getAllLoggersWithLevels() { 134 HashMap<String, LoggerInfo> hs = new HashMap<>(); 135 LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); 136 for (Logger category : loggerContext.getLoggers()) { 137 hs.put(category.getName(), new LoggerInfo(category.getName() , category.getLevel())); 138 } 139 140 Configuration config = loggerContext.getConfiguration(); 141 Map<String, LoggerConfig> mm = config.getLoggers(); // does not include root logger 142 mm.forEach((key, value) -> { 143 hs.putIfAbsent(key, new LoggerInfo(value.getName() , value.getLevel())); 144 hs.get(key).setInConfig(); 145 }); 146 // ensure root logger in list 147 hs.putIfAbsent("", new LoggerInfo("" , config.getRootLogger().getLevel())); 148 hs.get("").setInConfig(); 149 150 List<LoggerInfo> valuesList = new ArrayList<>(hs.values()); 151 Collections.sort(valuesList); // sorts by real name alphabetically 152 return valuesList; 153 } 154 155 private void populateTextArea(List<LoggerInfo> loggers){ 156 StringBuilder result = new StringBuilder(); 157 for (LoggerInfo s : loggers) { 158 result.append(" ").append(s.toString()).append(System.lineSeparator()); 159 } 160 161 JScrollBar b = scroll.getVerticalScrollBar(); 162 int beforeScroll = b.getValue(); 163 int caret = text.getCaretPosition(); 164 text.setText(result.toString()); 165 text.setCaretPosition(caret); 166 b.setValue(beforeScroll); 167 } 168 169 private void updateCategoryComboBox(List<LoggerInfo> loggers) { 170 String f = (String)categoryComboBox.getSelectedItem(); 171 categoryComboBox.removeAllItems(); 172 for ( LoggerInfo l : loggers ) { 173 categoryComboBox.addItem(l.getLoggerName()); 174 } 175 categoryComboBox.setSelectedItem(f == null ? ROOT_LEVEL_STRING : f); 176 } 177 178 private void editButtonPressed(ActionEvent e) { 179 log.debug("{} pressed", e.getActionCommand()); 180 String f = (String)categoryComboBox.getSelectedItem(); 181 Level l = (Level)levelSelectionComboBox.getSelectedItem(); 182 log.info("changing Logging for {} to {}",f,l); 183 if ( ROOT_LEVEL_STRING.equals(f) ){ 184 f=""; // empty String is actual name for root logger. 185 } 186 Configurator.setLevel(LogManager.getLogger(f), l); 187 updateTextAreaAndCategorySelect(); 188 } 189 190 private void refreshButtonPressed(ActionEvent e){ 191 log.debug("{} pressed", e.getActionCommand()); 192 updateTextAreaAndCategorySelect(); 193 } 194 195 /** 196 * 3rd stage of initialization, invoked after Swing components exist. 197 */ 198 @Override 199 public void initContext(Object context) { 200 } 201 202 private static class LoggerInfo implements Comparable<LoggerInfo> { 203 204 private final String loggerName; 205 private final Level level; 206 private boolean inConfig = false; 207 208 private LoggerInfo(String loggerName, Level level) { 209 this.loggerName = loggerName; 210 this.level = level; 211 } 212 213 String getLoggerName() { 214 return (loggerName.isBlank() ? ROOT_LEVEL_STRING : loggerName); 215 } 216 217 private void setInConfig(){ 218 inConfig = true; 219 } 220 221 @Override 222 public int compareTo(LoggerInfo other) { 223 return this.loggerName.compareTo(other.loggerName); 224 } 225 226 @Override 227 public String toString() { 228 StringBuilder s = new StringBuilder(); 229 s.append(getLoggerName()).append(" "); 230 if ( !inConfig ){ 231 s.append("{ ").append(level.name()).append(" }"); 232 } else { 233 s.append("[ ").append(level.name()).append(" ]"); 234 } 235 return s.toString(); 236 } 237 238 @Override 239 public boolean equals(Object obj) { 240 if (this == obj) { 241 return true; 242 } 243 if (!(obj instanceof LoggerInfo)) { 244 return false; 245 } 246 LoggerInfo other = (LoggerInfo) obj; 247 return Objects.equals(loggerName, other.loggerName) && Objects.equals(level, other.level); 248 } 249 250 @Override 251 public int hashCode() { 252 return 13 * Objects.hashCode(loggerName) * Objects.hashCode(level); 253 } 254 255 } 256 257 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Log4JTreePane.class); 258 259}