source: trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorOutputWriter.java @ 997

Last change on this file since 997 was 997, checked in by pharms, 12 years ago
  • corrected encoding of output files
File size: 11.4 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.htmlmonitor;
16
17import java.io.File;
18import java.io.FileOutputStream;
19import java.io.IOException;
20import java.io.OutputStreamWriter;
21import java.io.PrintWriter;
22import java.text.DecimalFormat;
23
24import de.ugoe.cs.util.console.Console;
25
26/**
27 * <p>
28 * dumps messages to a log file belonging to a specific client id. In the provided base log
29 * directory, it creates a subdirectory with the client id. In this directory it creates
30 * appropriate log files. The name of each finished log file starts with the "htmlmonitor_"
31 * followed by the client id and an index of the log file. An unfinished log file has no index yet.
32 * A log file is finished if
33 * <ul>
34 *   <li>the client session is closed by a timeout</li>
35 *   <li>the HTML monitor is normally shut down</li>
36 *   <li>on startup an unfinished log file is detected.</li>
37 *   <li>the {@link #MAXIMUM_LOG_FILE_SIZE} is reached</li>
38 * </ul>
39 * </p>
40 *
41 * @author Patrick Harms
42 * @version 1.0
43 *
44 */
45public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener {
46   
47    /**
48     * the maximum size of an individual log file
49     */
50    private static final int MAXIMUM_LOG_FILE_SIZE = 10000000;
51
52    /**
53     * the default log base directory if none is provided through the constructor
54     */
55    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";
56
57    /**
58     * the currently used log file base directory
59     */
60    private File logFileBaseDir;
61
62    /**
63     * the is of the client of which all messages are logged through this writer
64     */
65    private String clientId;
66
67    /**
68     * the log file into which all messages are currently written
69     */
70    private File logFile;
71
72    /**
73     * an output writer to be used for writing into the log file
74     */
75    private PrintWriter outputWriter;
76
77    /**
78     * the time stamp of the last action taken on this writer (such as logging a message)
79     */
80    private long lastUpdate;
81
82    /**
83     * <p>
84     * initializes the writer with the log file base directory and the id of the client for which
85     * this writer logs the messages.
86     * </p>
87     *
88     * @param logFileBaseDir the log file base directory, or null if the default directory shall
89     *                       be taken
90     * @param clientId       the ID of the client, for which this writer logs
91     */
92    public HtmlMonitorOutputWriter(String logFileBaseDir, String clientId) {
93        if (logFileBaseDir == null) {
94            this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR);
95        }
96        else {
97            this.logFileBaseDir = new File(logFileBaseDir);
98        }
99       
100        this.clientId = clientId;
101       
102        lastUpdate = System.currentTimeMillis();
103    }
104
105    /* (non-Javadoc)
106     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
107     */
108    @Override
109    public synchronized void init() throws HtmlMonitorException {
110        if (outputWriter != null) {
111            throw new IllegalStateException("already initialized. Call close() first");
112        }
113       
114        synchronized (HtmlMonitorOutputWriter.class) {
115            try {
116                File clientLogDir = new File(logFileBaseDir, clientId);
117           
118                if (!clientLogDir.exists()) {
119                    if (!clientLogDir.mkdirs()) {
120                        throw new HtmlMonitorException("client log file directory " + clientLogDir +
121                                                       " can not be created");
122                    }
123                }
124                else if (!clientLogDir.isDirectory()) {
125                    throw new HtmlMonitorException("client log file directory " + clientLogDir +
126                                                   " already exists as a file");
127                }
128               
129                logFile = new File(clientLogDir, getLogFileName(-1));
130               
131                if (logFile.exists()) {
132                    rotateLogFile();
133                }
134           
135                createLogWriter();
136            }
137            catch (IOException e) {
138                throw new HtmlMonitorException("could not open logfile " + logFile, e);
139            }
140        }
141       
142        lastUpdate = System.currentTimeMillis();
143    }
144
145    /**
146     * <p>
147     * used to calculate a log file name. If the provided index is smaller 0, then no index
148     * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log".
149     * </p>
150     *
151     * @param index the index of the log file or -1 one, if no index shall be added
152     *
153     * @return the file name as described
154     */
155    private String getLogFileName(int index) {
156        String result = "htmlmonitor_" + clientId;
157       
158        if (index >= 0) {
159            result += "_" + new DecimalFormat("000" ).format(index);
160        }
161       
162        result += ".log";
163       
164        return result;
165    }
166
167    /* (non-Javadoc)
168     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
169     */
170    @Override
171    public synchronized void start() throws IllegalStateException, HtmlMonitorException {
172        lastUpdate = System.currentTimeMillis();
173    }
174
175    /* (non-Javadoc)
176     * @see HtmlMonitorMessageListener#handleMessage(HtmlClientInfos, HtmlEvent[])
177     */
178    @Override
179    public void handleMessage(HtmlClientInfos clientInfos, HtmlEvent[] events) {
180        if (outputWriter == null) {
181            throw new IllegalStateException("not initialized. Call init() first");
182        }
183       
184        for (HtmlEvent event : events) {
185            dumpEvent(event);
186        }
187       
188        outputWriter.flush();
189       
190        try {
191            considerLogRotate();
192        }
193        catch (IOException e) {
194            throw new IllegalStateException("could not perform log rotation: " + e, e);
195        }
196       
197        lastUpdate = System.currentTimeMillis();
198    }
199
200    /**
201     * <p>
202     * formats a received event and writes it to the log file. One event results in one line
203     * in the log file containing all infos of the event.
204     * </p>
205     *
206     * @param event to be written to the log file
207     */
208    private void dumpEvent(HtmlEvent event) {
209        dumpString(event.getClientInfos().getClientId());
210        outputWriter.print(' ');
211        dumpString(event.getTime().toString());
212        outputWriter.print(' ');
213        dumpString(event.getClientInfos().getTitle());
214        outputWriter.print(' ');
215        dumpString(event.getClientInfos().getUrl().toString());
216        outputWriter.print(' ');
217        dumpString(event.getClientInfos().getUserAgent());
218        outputWriter.print(' ');
219        dumpString(event.getEventType());
220        outputWriter.print(' ');
221        dumpString(event.getPath());
222
223        if (event.getCoordinates() != null) {
224            outputWriter.print(' ');
225           
226            StringBuffer value = new StringBuffer();
227            for (int i = 0; i < event.getCoordinates().length; i++) {
228                if (i > 0) {
229                    value.append(',');
230                }
231                value.append(event.getCoordinates()[i]);
232            }
233           
234            dumpString(value.toString());
235        }
236
237        if (event.getKey() != null) {
238            outputWriter.print(' ');
239            dumpString(event.getKey().toString());
240        }
241           
242        if (event.getScrollPosition() != null) {
243            outputWriter.print(' ');
244           
245            StringBuffer value = new StringBuffer();
246            for (int i = 0; i < event.getScrollPosition().length; i++) {
247                if (i > 0) {
248                    value.append(',');
249                }
250                value.append(event.getScrollPosition()[i]);
251            }
252           
253            dumpString(value.toString());
254        }
255
256        if (event.getSelectedValue() != null) {
257            outputWriter.print(' ');
258            dumpString(event.getSelectedValue());
259        }
260           
261        outputWriter.println();
262    }
263
264    /**
265     * <p>
266     * convenience method to dump a string with trailing and leading " as well as replaced
267     * backslashes, ", and newlines
268     * </p>
269     *
270     * @param clientId2
271     */
272    private void dumpString(String str) {
273        String value = str;
274        value = value.replaceAll("\\\\", "\\\\\\\\");
275        value = value.replaceAll("\\\"", "\\\\\\\"");
276        value = value.replaceAll("\n", "\\\\n");
277       
278        outputWriter.print('"');
279        outputWriter.print(value);
280        outputWriter.print('"');
281    }
282
283    /**
284     * <p>
285     * checks, if the log file exeeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current
286     * log file is closed, the next log file name is determined and this new file is opend for
287     * writing.
288     * </p>
289     */
290    private synchronized void considerLogRotate() throws IOException {
291        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
292            closeLogWriter();
293            rotateLogFile();
294            createLogWriter();
295        }
296    }
297
298    /**
299     * <p>
300     * renames the current log file to a new log file with the next available index. It further
301     * sets the current log file to the default name, i.e. without index.
302     * </p>
303     */
304    private void rotateLogFile() {
305        File clientLogDir = logFile.getParentFile();
306        File checkFile;
307
308        int logFileIndex = -1;
309        do {
310            logFileIndex++;
311           
312            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
313        }
314        while (checkFile.exists());
315   
316        if (!logFile.renameTo(checkFile)) {
317            Console.printerrln("could not rename log file " + logFile + " to " + checkFile +
318                               ". Will not perform log rotation.");
319        }
320        else {
321            logFileIndex++;
322            logFile = new File(clientLogDir, getLogFileName(-1));
323        }
324    }
325
326    /**
327     * <p>
328     * instantiates a writer to be used for writing messages into the log file.
329     * </p>
330     */
331    private void createLogWriter() throws IOException {
332        FileOutputStream fis = new FileOutputStream(logFile);
333        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
334    }
335
336    /**
337     * <p>
338     * closed the current writer if it is open.
339     * </p>
340     */
341    private void closeLogWriter() {
342        if (outputWriter != null) {
343            outputWriter.flush();
344            outputWriter.close();
345            outputWriter = null;
346        }
347    }
348
349    /* (non-Javadoc)
350     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
351     */
352    @Override
353    public synchronized void stop() {
354        closeLogWriter();
355        rotateLogFile();
356
357        lastUpdate = System.currentTimeMillis();
358    }
359
360    /**
361     * <p>
362     * return the time stamp of the last activity that happened on this writer.
363     * </p>
364     *
365     * @return as described
366     */
367    public long getLastUpdate() {
368        return lastUpdate;
369    }
370}
Note: See TracBrowser for help on using the repository browser.