1 // ******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025.
6 //
7 // This file is part of Force Field X.
8 //
9 // Force Field X is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License version 3 as published by
11 // the Free Software Foundation.
12 //
13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 // details.
17 //
18 // You should have received a copy of the GNU General Public License along with
19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20 // Place, Suite 330, Boston, MA 02111-1307 USA
21 //
22 // Linking this library statically or dynamically with other modules is making a
23 // combined work based on this library. Thus, the terms and conditions of the
24 // GNU General Public License cover the whole combination.
25 //
26 // As a special exception, the copyright holders of this library give you
27 // permission to link this library with independent modules to produce an
28 // executable, regardless of the license terms of these independent modules, and
29 // to copy and distribute the resulting executable under terms of your choice,
30 // provided that you also meet, for each linked independent module, the terms
31 // and conditions of the license of that module. An independent module is a
32 // module which is not derived from or based on this library. If you modify this
33 // library, you may extend this exception to your version of the library, but
34 // you are not obligated to do so. If you do not wish to do so, delete this
35 // exception statement from your version.
36 //
37 // ******************************************************************************
38 package ffx.ui;
39
40 import edu.rit.pj.Comm;
41 import ffx.potential.Utilities;
42 import ffx.ui.MainPanel.ExitStatus;
43 import java.awt.GraphicsEnvironment;
44 import java.io.File;
45 import java.io.FileOutputStream;
46 import java.io.PrintStream;
47 import java.util.logging.ErrorManager;
48 import java.util.logging.Handler;
49 import java.util.logging.Level;
50 import java.util.logging.LogRecord;
51
52 /**
53 * The default ConsoleHanlder publishes logging to System.err. This class publishes to System.out,
54 * which is normally intercepted by the Force Field X Shell.
55 *
56 * <p>The formatter used reduces verbosity relative to the default SimpleFormatter.
57 *
58 * @author Michael J. Schnieders
59 * @since 1.0
60 */
61 public class LogHandler extends Handler {
62
63 /**
64 * If true, FFX is running in a headless environment.
65 */
66 private static final boolean headless = GraphicsEnvironment.isHeadless();
67
68 /**
69 * If the MainPanel variable has been set, we can publish records to the ModelingShell.
70 */
71 private MainPanel mainPanel = null;
72
73 /**
74 * If true, we have received a Record at the Level.SEVERE and FFX will exit.
75 */
76 private boolean fatal = false;
77
78 /**
79 * Construct the Force Field X Log Handler.
80 *
81 * @since 1.0
82 */
83 public LogHandler() {
84 setLevel(Level.ALL);
85
86 // Log all messages to the file specified by "ffx.log.file" if in Headless mode.
87 // For MPI jobs, a separate file is used for each process.
88 boolean prependRank = false;
89 if (headless) {
90 String log = System.getProperty("ffx.log.file", "");
91 if (log != null && !log.equalsIgnoreCase("")) {
92 Comm comm = Comm.world();
93 int np = comm.size();
94 int rank = comm.rank();
95
96 // Define the log file directory and filename.
97 String logFile;
98 if (np == 1) {
99 logFile = new File(log).getAbsolutePath();
100 } else {
101 prependRank = true;
102 File dir = new File(Integer.toString(rank));
103 if (!dir.exists()) {
104 dir.mkdir();
105 }
106 logFile = dir.getAbsolutePath() + File.separator + log;
107 }
108
109 try {
110 PrintStream printStream = new PrintStream(new FileOutputStream(logFile, true));
111 System.setOut(printStream);
112 System.setErr(printStream);
113 } catch (Exception e) {
114 System.err.println(Utilities.stackTraceToString(e));
115 }
116 }
117 }
118
119 // If each process has a separate log file, then it's not necessary to prepend a rank.
120 if (prependRank) {
121 setFormatter(new LogFormatter(false));
122 } else {
123 setFormatter(new LogFormatter());
124 }
125
126 }
127
128 /**
129 * {@inheritDoc}
130 *
131 * <p>Flush, but do not close System.out or the Shell.
132 *
133 * @since 1.0
134 */
135 @Override
136 public void close() {
137 flush();
138 }
139
140 /** {@inheritDoc} */
141 @Override
142 public void flush() {
143 System.out.flush();
144 System.err.flush();
145 if (mainPanel.getModelingShell() != null) {
146 // Scroll to visible!
147 }
148 }
149
150 /**
151 * {@inheritDoc}
152 *
153 * <p>Publish a LogRecord.
154 *
155 * @since 1.0.
156 */
157 @Override
158 public synchronized void publish(LogRecord record) {
159
160 // Check if the record is loggable and that we have not already encountered a fatal error.
161 if (!isLoggable(record) || fatal) {
162 return;
163 }
164
165 String msg;
166 try {
167 msg = getFormatter().format(record);
168 } catch (Exception e) {
169 // Report the exception to any registered ErrorManager.
170 reportError(null, e, ErrorManager.FORMAT_FAILURE);
171 return;
172 }
173
174 try {
175 // FFX logs severe messages to System.err and then exits.
176 if (record.getLevel() == Level.SEVERE) {
177 // Set the fatal flag to true; this is the final record that will be logged.
178 fatal = true;
179
180 // Log the message to System.err.
181 System.err.println(msg);
182 Throwable throwable = record.getThrown();
183 if (throwable != null) {
184 System.err.printf(" %s%n", throwable);
185 }
186
187 System.err.println(" Force Field X will not continue.");
188 System.err.println(" Shutting down...");
189 flush();
190 mainPanel.setExitType(ExitStatus.SEVERE);
191 mainPanel.exit();
192 }
193
194 ModelingShell shell = null;
195 if (mainPanel != null) {
196 shell = mainPanel.getModelingShell();
197 }
198
199 if (!headless && shell != null) {
200 shell.appendOutputNl(msg, shell.getResultStyle());
201 } else {
202 System.out.println(msg);
203 }
204 } catch (Exception e) {
205 // Report the exception to any registered ErrorManager.
206 reportError(null, e, ErrorManager.WRITE_FAILURE);
207 }
208 }
209
210 /**
211 * A reference to the Force Field X MainPanel container to shut down if we encounter a fatal
212 * (SEVERE) exception. If we are not in Headless mode, then LogRecords can be published to the
213 * ModelingShell.
214 *
215 * @param mainPanel the Force Field X MainPanel.
216 * @since 1.0
217 */
218 public void setMainPanel(MainPanel mainPanel) {
219 this.mainPanel = mainPanel;
220 }
221 }