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-2024 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://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
37   * </p>
38   * @author <a href="https://www.tudelft.nl/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     /** {@inheritDoc} */
124     @Override
125     public boolean isBackground()
126     {
127         return true;
128     }
129 
130     /**
131      * LogWriter takes care of writing the log records to the console. <br>
132      * <br>
133      * Copyright (c) 2003-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
134      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank"> www.simulation.tudelft.nl</a>.
135      * The source code and binary code of this software is proprietary information of Delft University of Technology.
136      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
137      */
138     public static class ConsoleLogWriter implements Writer
139     {
140         /** the text pane. */
141         @SuppressWarnings("checkstyle:visibilitymodifier")
142         JTextPane textPane;
143 
144         /** the document to write to. */
145         @SuppressWarnings("checkstyle:visibilitymodifier")
146         StyledDocument doc;
147 
148         /** the color style. */
149         @SuppressWarnings("checkstyle:visibilitymodifier")
150         Style style;
151 
152         /** number of lines. */
153         @SuppressWarnings("checkstyle:visibilitymodifier")
154         int nrLines = 0;
155 
156         /** the maximum number of lines before the first lines will be erased. */
157         @SuppressWarnings("checkstyle:visibilitymodifier")
158         protected int maxLines = 20000;
159 
160         /**
161          * @param textPane JTextPane; the text area to write the messages to.
162          */
163         public ConsoleLogWriter(final JTextPane textPane)
164         {
165             this.textPane = textPane;
166             this.doc = textPane.getStyledDocument();
167             this.style = textPane.addStyle("colorStyle", null);
168         }
169 
170         /** {@inheritDoc} */
171         @Override
172         public Set<LogEntryValue> getRequiredLogEntryValues()
173         {
174             return EnumSet.of(LogEntryValue.RENDERED_LOG_ENTRY); // Only the final rendered log entry is required
175         }
176 
177         /** {@inheritDoc} */
178         @Override
179         public void init(final Configuration configuration) throws Exception
180         {
181             // nothing to do
182         }
183 
184         /** {@inheritDoc} */
185         @Override
186         public synchronized void write(final LogEntry logEntry) throws Exception
187         {
188             Runnable runnable = new Runnable()
189             {
190                 @Override
191                 public void run()
192                 {
193                     String[] lines = logEntry.getRenderedLogEntry().split("\\r?\\n");
194 
195                     while (ConsoleLogWriter.this.nrLines > Math.max(0, ConsoleLogWriter.this.maxLines - lines.length))
196                     {
197                         Document document = ConsoleLogWriter.this.doc;
198                         Element root = document.getDefaultRootElement();
199                         Element line = root.getElement(0);
200                         int end = line.getEndOffset();
201 
202                         try
203                         {
204                             document.remove(0, end);
205                             ConsoleLogWriter.this.nrLines--;
206                         }
207                         catch (BadLocationException exception)
208                         {
209                             CategoryLogger.always().error(exception);
210                             break;
211                         }
212                     }
213                     switch (logEntry.getLevel())
214                     {
215                         case TRACE:
216                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.DARK_GRAY);
217                             break;
218 
219                         case DEBUG:
220                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLUE);
221                             break;
222 
223                         case INFO:
224                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.BLACK);
225                             break;
226 
227                         case WARNING:
228                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.MAGENTA);
229                             break;
230 
231                         case ERROR:
232                             StyleConstants.setForeground(ConsoleLogWriter.this.style, Color.RED);
233                             break;
234 
235                         default:
236                             break;
237                     }
238                     try
239                     {
240                         for (String line : lines)
241                         {
242                             ConsoleLogWriter.this.doc.insertString(ConsoleLogWriter.this.doc.getLength(), line + "\n",
243                                     ConsoleLogWriter.this.style);
244                             ConsoleLogWriter.this.nrLines++;
245                         }
246                     }
247                     catch (Exception exception)
248                     {
249                         // we cannot log this -- that would generate an infinite loop
250                         System.err.println("Was not able to insert text in the Console");
251                     }
252                     ConsoleLogWriter.this.textPane.setCaretPosition(ConsoleLogWriter.this.doc.getLength());
253                 }
254             };
255             SwingUtilities.invokeLater(runnable);
256         }
257 
258         /** {@inheritDoc} */
259         @Override
260         public void flush() throws Exception
261         {
262             // nothing to do
263         }
264 
265         /** {@inheritDoc} */
266         @Override
267         public void close() throws Exception
268         {
269             // nothing to do
270         }
271 
272     }
273 }