001package jmri.jmrit.operations.trains; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.*; 006import java.nio.charset.StandardCharsets; 007import java.text.ParseException; 008import java.text.SimpleDateFormat; 009import java.util.*; 010 011import org.apache.commons.csv.CSVFormat; 012import org.apache.commons.csv.CSVPrinter; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016import jmri.InstanceManager; 017import jmri.InstanceManagerAutoDefault; 018import jmri.jmrit.XmlFile; 019import jmri.jmrit.operations.setup.*; 020 021/** 022 * Logs train movements and status to a file. 023 * 024 * @author Daniel Boudreau Copyright (C) 2010, 2013 025 */ 026public class TrainLogger extends XmlFile implements InstanceManagerAutoDefault, PropertyChangeListener { 027 028 File _fileLogger; 029 private boolean _trainLog = false; // when true logging train movements 030 031 public TrainLogger() { 032 } 033 034 public void enableTrainLogging(boolean enable) { 035 if (enable) { 036 addTrainListeners(); 037 } else { 038 removeTrainListeners(); 039 } 040 } 041 042 private void createFile() { 043 if (!Setup.isTrainLoggerEnabled()) { 044 return; 045 } 046 if (_fileLogger != null) { 047 return; // log file has already been created 048 } // create the logging file for this session 049 try { 050 if (!checkFile(getFullLoggerFileName())) { 051 // The file/directory does not exist, create it before writing 052 _fileLogger = new java.io.File(getFullLoggerFileName()); 053 File parentDir = _fileLogger.getParentFile(); 054 if (!parentDir.exists()) { 055 if (!parentDir.mkdirs()) { 056 log.error("logger directory not created"); 057 } 058 } 059 if (_fileLogger.createNewFile()) { 060 log.debug("new file created"); 061 // add header 062 fileOut(getHeader()); 063 } 064 } else { 065 _fileLogger = new java.io.File(getFullLoggerFileName()); 066 } 067 } catch (Exception e) { 068 log.error("Exception while making logging directory", e); 069 } 070 071 } 072 073 private void store(Train train) { 074 // create train file if needed 075 createFile(); 076 // Note that train status can contain a comma 077 List<Object> line = Arrays.asList(new Object[]{train.getName(), 078 train.getDescription(), 079 train.getCurrentLocationName(), 080 train.getNextLocationName(), 081 train.getStatus(), 082 train.getBuildFailedMessage(), 083 getTime()}); 084 fileOut(line); 085 } 086 087 private List<Object> getHeader() { 088 return Arrays.asList(new Object[]{Bundle.getMessage("Name"), 089 Bundle.getMessage("Description"), 090 Bundle.getMessage("Current"), 091 Bundle.getMessage("NextLocation"), 092 Bundle.getMessage("Status"), 093 Bundle.getMessage("BuildMessages"), 094 Bundle.getMessage("DateAndTime")}); 095 } 096 097 /* 098 * Appends one line to file. 099 */ 100 private void fileOut(List<Object> line) { 101 if (_fileLogger == null) { 102 log.error("Log file doesn't exist"); 103 return; 104 } 105 106 // FileOutputStream is set to append 107 try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter( 108 new FileOutputStream(_fileLogger, true), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) { 109 log.debug("Log: {}", line); 110 fileOut.printRecord(line); 111 fileOut.flush(); 112 fileOut.close(); 113 } catch (IOException e) { 114 log.error("Exception while opening log file: {}", e.getLocalizedMessage()); 115 } 116 } 117 118 private void addTrainListeners() { 119 if (Setup.isTrainLoggerEnabled() && !_trainLog) { 120 log.debug("Train Logger adding train listerners"); 121 _trainLog = true; 122 List<Train> trains = InstanceManager.getDefault(TrainManager.class).getTrainsByIdList(); 123 trains.forEach(train -> train.addPropertyChangeListener(this)); 124 // listen for new trains being added 125 InstanceManager.getDefault(TrainManager.class).addPropertyChangeListener(this); 126 } 127 } 128 129 private void removeTrainListeners() { 130 log.debug("Train Logger removing train listerners"); 131 if (_trainLog) { 132 List<Train> trains = InstanceManager.getDefault(TrainManager.class).getTrainsByIdList(); 133 trains.forEach(train -> train.removePropertyChangeListener(this)); 134 InstanceManager.getDefault(TrainManager.class).removePropertyChangeListener(this); 135 } 136 _trainLog = false; 137 } 138 139 public void dispose() { 140 removeTrainListeners(); 141 } 142 143 @Override 144 public void propertyChange(PropertyChangeEvent e) { 145 if (e.getPropertyName().equals(Train.STATUS_CHANGED_PROPERTY) 146 || e.getPropertyName().equals(Train.TRAIN_LOCATION_CHANGED_PROPERTY)) { 147 if (Control.SHOW_PROPERTY) { 148 log.debug("Train logger sees property change for train {}", e.getSource()); 149 } 150 store((Train) e.getSource()); 151 } 152 if (e.getPropertyName().equals(TrainManager.LISTLENGTH_CHANGED_PROPERTY)) { 153 if ((Integer) e.getNewValue() > (Integer) e.getOldValue()) { 154 // a car or engine has been added 155 removeTrainListeners(); 156 addTrainListeners(); 157 } 158 } 159 } 160 161 public String getFullLoggerFileName() { 162 return loggingDirectory + File.separator + getFileName(); 163 } 164 165 private String operationsDirectory = 166 OperationsSetupXml.getFileLocation() + OperationsSetupXml.getOperationsDirectoryName(); 167 private String loggingDirectory = operationsDirectory + File.separator + "logger" + File.separator + "trains"; // NOI18N 168 169 public String getDirectoryName() { 170 return loggingDirectory; 171 } 172 173 public void setDirectoryName(String name) { 174 loggingDirectory = name; 175 } 176 177 private String fileName; 178 179 public String getFileName() { 180 if (fileName == null) { 181 fileName = Bundle.getMessage("Trains") + "_" + getDate() + ".csv"; // NOI18N 182 } 183 return fileName; 184 } 185 186 private String getDate() { 187 Date date = Calendar.getInstance().getTime(); 188 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd"); // NOI18N 189 return simpleDateFormat.format(date); 190 } 191 192 /** 193 * Return the date and time in an MS Excel friendly format yyyy/MM/dd 194 * HH:mm:ss 195 */ 196 private String getTime() { 197 String time = Calendar.getInstance().getTime().toString(); 198 SimpleDateFormat dt = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); // NOI18N 199 SimpleDateFormat dtout = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N 200 try { 201 return dtout.format(dt.parse(time)); 202 } catch (ParseException e) { 203 return time; // there was an issue, use the old format 204 } 205 } 206 207 private final static Logger log = LoggerFactory.getLogger(TrainLogger.class); 208}