View Javadoc
1   package nl.tudelft.simulation.dsol.swing.gui;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Color;
5   import java.util.EnumSet;
6   import java.util.Set;
7   
8   import javax.swing.JPanel;
9   import javax.swing.JScrollPane;
10  import javax.swing.JTextPane;
11  import javax.swing.ScrollPaneConstants;
12  import javax.swing.SwingUtilities;
13  import javax.swing.text.BadLocationException;
14  import javax.swing.text.Document;
15  import javax.swing.text.Element;
16  import javax.swing.text.Style;
17  import javax.swing.text.StyleConstants;
18  import javax.swing.text.StyledDocument;
19  
20  import org.djutils.logger.CategoryLogger;
21  import org.pmw.tinylog.Configuration;
22  import org.pmw.tinylog.Configurator;
23  import org.pmw.tinylog.Level;
24  import org.pmw.tinylog.LogEntry;
25  import org.pmw.tinylog.writers.LogEntryValue;
26  import org.pmw.tinylog.writers.Writer;
27  
28  import nl.tudelft.simulation.dsol.swing.gui.appearance.AppearanceControl;
29  
30  /**
31   * Console for a swing application where the log messages are displayed.
32   * <p>
33   * Copyright (c) 2003-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
34   * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
35   * project is distributed under a three-clause BSD-style license, which can be found at
36   * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
37   * </p>
38   * @author <a href="https://github.com/averbraeck" target="_blank"> Alexander Verbraeck</a>
39   */
40  public class ConsoleLogger extends JPanel implements AppearanceControl
41  {
42      /** */
43      private static final long serialVersionUID = 1L;
44  
45      /** */
46      @SuppressWarnings("checkstyle:visibilitymodifier")
47      protected ConsoleLogWriter consoleLogWriter;
48  
49      /** current message format. */
50      private String messageFormat = CategoryLogger.DEFAULT_MESSAGE_FORMAT;
51  
52      /** the current logging level. */
53      private Level level = Level.INFO;
54      
55      /** the text pane. */
56      private JTextPane textPane;
57  
58      /**
59       * Constructor for Logger Console.
60       * @param logLevel Level the logLevel to use;
61       */
62      public ConsoleLogger(final Level logLevel)
63      {
64          this.level = logLevel;
65          setLayout(new BorderLayout());
66          this.textPane = new JTextPane();
67          this.textPane.setEditable(false);
68          this.textPane.setBackground(Color.WHITE);
69          this.textPane.setOpaque(true);
70          this.consoleLogWriter = new ConsoleLogWriter(this.textPane);
71          Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
72          JScrollPane scrollPane = new JScrollPane(this.textPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
73                  ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
74          scrollPane.setBackground(Color.WHITE);
75          scrollPane.setOpaque(true);
76          add(scrollPane, BorderLayout.CENTER);
77      }
78  
79      /**
80       * Set a new logging format for the message lines of the Console writer. The default message format is:<br>
81       * {class_name}.{method}:{line} {message|indent=4}<br>
82       * <br>
83       * A few popular placeholders that can be used:<br>
84       * - {class} Fully-qualified class name where the logging request is issued<br>
85       * - {class_name} Class name (without package) where the logging request is issued<br>
86       * - {date} Date and time of the logging request, e.g. {date:yyyy-MM-dd HH:mm:ss} [SimpleDateFormat]<br>
87       * - {level} Logging level of the created log entry<br>
88       * - {line} Line number from where the logging request is issued<br>
89       * - {message} Associated message of the created log entry<br>
90       * - {method} Method name from where the logging request is issued<br>
91       * - {package} Package where the logging request is issued<br>
92       * @see <a href="https://tinylog.org/configuration#format">https://tinylog.org/configuration</a>
93       * @param newMessageFormat String; the new formatting pattern to use
94       */
95      public void setLogMessageFormat(final String newMessageFormat)
96      {
97          Configurator.currentConfig().removeWriter(this.consoleLogWriter).activate();
98          this.messageFormat = newMessageFormat;
99          Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
100     }
101 
102     /**
103      * @param newLevel Level; the new log level for the Console
104      */
105     public void setLogLevel(final Level newLevel)
106     {
107         Configurator.currentConfig().removeWriter(this.consoleLogWriter).activate();
108         this.level = newLevel;
109         Configurator.currentConfig().addWriter(this.consoleLogWriter, this.level, this.messageFormat).activate();
110     }
111 
112     /**
113      * Set the maximum number of lines in the console before the first lines will be erased. The number of lines should be at
114      * least 1. If the provided number of lines is less than 1, it wil be set to 1.
115      * @param maxLines int; set the maximum number of lines before the first lines will be erased
116      */
117     public void setMaxLines(final int maxLines)
118     {
119         this.consoleLogWriter.maxLines = Math.max(1, maxLines);
120     }
121 
122     
123     @Override
124     public boolean isBackground()
125     {
126         return true;
127     }
128 
129     /**
130      * LogWriter takes care of writing the log records to the console. <br>
131      * <br>
132      * Copyright (c) 2003-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
133      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank"> www.simulation.tudelft.nl</a>.
134      * The source code and binary code of this software is proprietary information of Delft University of Technology.
135      * @author <a href="https://github.com/averbraeck" target="_blank"> Alexander Verbraeck</a>
136      */
137     public static class ConsoleLogWriter implements Writer
138     {
139         /** the text pane. */
140         @SuppressWarnings("checkstyle:visibilitymodifier")
141         JTextPane textPane;
142 
143         /** the document to write to. */
144         @SuppressWarnings("checkstyle:visibilitymodifier")
145         StyledDocument doc;
146 
147         /** the color style. */
148         @SuppressWarnings("checkstyle:visibilitymodifier")
149         Style style;
150 
151         /** number of lines. */
152         @SuppressWarnings("checkstyle:visibilitymodifier")
153         int nrLines = 0;
154 
155         /** the maximum number of lines before the first lines will be erased. */
156         @SuppressWarnings("checkstyle:visibilitymodifier")
157         protected int maxLines = 20000;
158 
159         /**
160          * @param textPane JTextPane; the text area to write the messages to.
161          */
162         public ConsoleLogWriter(final JTextPane textPane)
163         {
164             this.textPane = textPane;
165             this.doc = textPane.getStyledDocument();
166             this.style = textPane.addStyle("colorStyle", null);
167         }
168 
169         @Override
170         public Set<LogEntryValue> getRequiredLogEntryValues()
171         {
172             return EnumSet.of(LogEntryValue.RENDERED_LOG_ENTRY); // Only the final rendered log entry is required
173         }
174 
175         @Override
176         public void init(final Configuration configuration) throws Exception
177         {
178             // nothing to do
179         }
180 
181         @Override
182         public synchronized void write(final LogEntry logEntry) throws Exception
183         {
184             Runnable runnable = new Runnable()
185             {
186                 @Override
187                 public void run()
188                 {
189                     String[] lines = logEntry.getRenderedLogEntry().split("\\r?\\n");
190 
191                     while (ConsoleLogWriter.this.nrLines > Math.max(0, ConsoleLogWriter.this.maxLines - lines.length))
192                     {
193                         Document document = ConsoleLogWriter.this.doc;
194                         Element root = document.getDefaultRootElement();
195                         Element line = root.getElement(0);
196                         int end = line.getEndOffset();
197 
198                         try
199                         {
200                             document.remove(0, end);
201                             ConsoleLogWriter.this.nrLines--;
202                         }
203                         catch (BadLocationException exception)
204                         {
205                             CategoryLogger.always().error(exception);
206                             break;
207                         }
208                     }
209                     switch (logEntry.getLevel())
210                     {
211                         case TRACE:
212                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.DARK_GRAY);
213                             break;
214 
215                         case DEBUG:
216                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLUE);
217                             break;
218 
219                         case INFO:
220                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLACK);
221                             break;
222 
223                         case WARNING:
224                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.MAGENTA);
225                             break;
226 
227                         case ERROR:
228                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.RED);
229                             break;
230 
231                         default:
232                             break;
233                     }
234                     try
235                     {
236                         for (String line : lines)
237                         {
238                             ConsoleLogWriter.this.doc.insertString(ConsoleLogWriter.this.doc.getLength(), line + "\n",
239                                     ConsoleLogWriter.this.style);
240                             ConsoleLogWriter.this.nrLines++;
241                         }
242                     }
243                     catch (Exception exception)
244                     {
245                         // we cannot log this -- that would generate an infinite loop
246                         System.err.println("Was not able to insert text in the Console");
247                     }
248                     ConsoleLogWriter.this.textPane.setCaretPosition(ConsoleLogWriter.this.doc.getLength());
249                 }
250             };
251             SwingUtilities.invokeLater(runnable);
252         }
253 
254         @Override
255         public void flush() throws Exception
256         {
257             // nothing to do
258         }
259 
260         @Override
261         public void close() throws Exception
262         {
263             // nothing to do
264         }
265 
266     }
267 }