1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.message; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.PrintStream; |
10 | import java.io.PrintWriter; |
11 | import java.io.Writer; |
12 | import java.text.SimpleDateFormat; |
13 | import java.util.HashMap; |
14 | |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.engine.Constants; |
17 | import org.h2.jdbc.JdbcSQLException; |
18 | import org.h2.store.fs.FileUtils; |
19 | import org.h2.util.IOUtils; |
20 | import org.h2.util.New; |
21 | |
22 | /** |
23 | * The trace mechanism is the logging facility of this database. There is |
24 | * usually one trace system per database. It is called 'trace' because the term |
25 | * 'log' is already used in the database domain and means 'transaction log'. It |
26 | * is possible to write after close was called, but that means for each write |
27 | * the file will be opened and closed again (which is slower). |
28 | */ |
29 | public class TraceSystem implements TraceWriter { |
30 | |
31 | /** |
32 | * The parent trace level should be used. |
33 | */ |
34 | public static final int PARENT = -1; |
35 | |
36 | /** |
37 | * This trace level means nothing should be written. |
38 | */ |
39 | public static final int OFF = 0; |
40 | |
41 | /** |
42 | * This trace level means only errors should be written. |
43 | */ |
44 | public static final int ERROR = 1; |
45 | |
46 | /** |
47 | * This trace level means errors and informational messages should be |
48 | * written. |
49 | */ |
50 | public static final int INFO = 2; |
51 | |
52 | /** |
53 | * This trace level means all type of messages should be written. |
54 | */ |
55 | public static final int DEBUG = 3; |
56 | |
57 | /** |
58 | * This trace level means all type of messages should be written, but |
59 | * instead of using the trace file the messages should be written to SLF4J. |
60 | */ |
61 | public static final int ADAPTER = 4; |
62 | |
63 | /** |
64 | * The default level for system out trace messages. |
65 | */ |
66 | public static final int DEFAULT_TRACE_LEVEL_SYSTEM_OUT = OFF; |
67 | |
68 | /** |
69 | * The default level for file trace messages. |
70 | */ |
71 | public static final int DEFAULT_TRACE_LEVEL_FILE = ERROR; |
72 | |
73 | /** |
74 | * The default maximum trace file size. It is currently 64 MB. Additionally, |
75 | * there could be a .old file of the same size. |
76 | */ |
77 | private static final int DEFAULT_MAX_FILE_SIZE = 64 * 1024 * 1024; |
78 | |
79 | private static final int CHECK_SIZE_EACH_WRITES = 4096; |
80 | |
81 | private int levelSystemOut = DEFAULT_TRACE_LEVEL_SYSTEM_OUT; |
82 | private int levelFile = DEFAULT_TRACE_LEVEL_FILE; |
83 | private int levelMax; |
84 | private int maxFileSize = DEFAULT_MAX_FILE_SIZE; |
85 | private String fileName; |
86 | private HashMap<String, Trace> traces; |
87 | private SimpleDateFormat dateFormat; |
88 | private Writer fileWriter; |
89 | private PrintWriter printWriter; |
90 | private int checkSize; |
91 | private boolean closed; |
92 | private boolean writingErrorLogged; |
93 | private TraceWriter writer = this; |
94 | private PrintStream sysOut = System.out; |
95 | |
96 | /** |
97 | * Create a new trace system object. |
98 | * |
99 | * @param fileName the file name |
100 | */ |
101 | public TraceSystem(String fileName) { |
102 | this.fileName = fileName; |
103 | updateLevel(); |
104 | } |
105 | |
106 | private void updateLevel() { |
107 | levelMax = Math.max(levelSystemOut, levelFile); |
108 | } |
109 | |
110 | /** |
111 | * Set the print stream to use instead of System.out. |
112 | * |
113 | * @param out the new print stream |
114 | */ |
115 | public void setSysOut(PrintStream out) { |
116 | this.sysOut = out; |
117 | } |
118 | |
119 | /** |
120 | * Get or create a trace object for this module. Trace modules with names |
121 | * such as "JDBC[1]" are not cached (modules where the name ends with "]"). |
122 | * All others are cached. |
123 | * |
124 | * @param module the module name |
125 | * @return the trace object |
126 | */ |
127 | public synchronized Trace getTrace(String module) { |
128 | if (module.endsWith("]")) { |
129 | return new Trace(writer, module); |
130 | } |
131 | if (traces == null) { |
132 | traces = New.hashMap(16); |
133 | } |
134 | Trace t = traces.get(module); |
135 | if (t == null) { |
136 | t = new Trace(writer, module); |
137 | traces.put(module, t); |
138 | } |
139 | return t; |
140 | } |
141 | |
142 | @Override |
143 | public boolean isEnabled(int level) { |
144 | return level <= this.levelMax; |
145 | } |
146 | |
147 | /** |
148 | * Set the trace file name. |
149 | * |
150 | * @param name the file name |
151 | */ |
152 | public void setFileName(String name) { |
153 | this.fileName = name; |
154 | } |
155 | |
156 | /** |
157 | * Set the maximum trace file size in bytes. |
158 | * |
159 | * @param max the maximum size |
160 | */ |
161 | public void setMaxFileSize(int max) { |
162 | this.maxFileSize = max; |
163 | } |
164 | |
165 | /** |
166 | * Set the trace level to use for System.out |
167 | * |
168 | * @param level the new level |
169 | */ |
170 | public void setLevelSystemOut(int level) { |
171 | levelSystemOut = level; |
172 | updateLevel(); |
173 | } |
174 | |
175 | /** |
176 | * Set the file trace level. |
177 | * |
178 | * @param level the new level |
179 | */ |
180 | public void setLevelFile(int level) { |
181 | if (level == ADAPTER) { |
182 | String adapterClass = "org.h2.message.TraceWriterAdapter"; |
183 | try { |
184 | writer = (TraceWriter) Class.forName(adapterClass).newInstance(); |
185 | } catch (Throwable e) { |
186 | e = DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, adapterClass); |
187 | write(ERROR, Trace.DATABASE, adapterClass, e); |
188 | return; |
189 | } |
190 | String name = fileName; |
191 | if (name != null) { |
192 | if (name.endsWith(Constants.SUFFIX_TRACE_FILE)) { |
193 | name = name.substring(0, name.length() - Constants.SUFFIX_TRACE_FILE.length()); |
194 | } |
195 | int idx = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); |
196 | if (idx >= 0) { |
197 | name = name.substring(idx + 1); |
198 | } |
199 | writer.setName(name); |
200 | } |
201 | } |
202 | levelFile = level; |
203 | updateLevel(); |
204 | } |
205 | |
206 | public int getLevelFile() { |
207 | return levelFile; |
208 | } |
209 | |
210 | private synchronized String format(String module, String s) { |
211 | if (dateFormat == null) { |
212 | dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss "); |
213 | } |
214 | return dateFormat.format(System.currentTimeMillis()) + module + ": " + s; |
215 | } |
216 | |
217 | @Override |
218 | public void write(int level, String module, String s, Throwable t) { |
219 | if (level <= levelSystemOut || level > this.levelMax) { |
220 | // level <= levelSystemOut: the system out level is set higher |
221 | // level > this.level: the level for this module is set higher |
222 | sysOut.println(format(module, s)); |
223 | if (t != null && levelSystemOut == DEBUG) { |
224 | t.printStackTrace(sysOut); |
225 | } |
226 | } |
227 | if (fileName != null) { |
228 | if (level <= levelFile) { |
229 | writeFile(format(module, s), t); |
230 | } |
231 | } |
232 | } |
233 | |
234 | private synchronized void writeFile(String s, Throwable t) { |
235 | try { |
236 | if (checkSize++ >= CHECK_SIZE_EACH_WRITES) { |
237 | checkSize = 0; |
238 | closeWriter(); |
239 | if (maxFileSize > 0 && FileUtils.size(fileName) > maxFileSize) { |
240 | String old = fileName + ".old"; |
241 | FileUtils.delete(old); |
242 | FileUtils.move(fileName, old); |
243 | } |
244 | } |
245 | if (!openWriter()) { |
246 | return; |
247 | } |
248 | printWriter.println(s); |
249 | if (t != null) { |
250 | if (levelFile == ERROR && t instanceof JdbcSQLException) { |
251 | JdbcSQLException se = (JdbcSQLException) t; |
252 | int code = se.getErrorCode(); |
253 | if (ErrorCode.isCommon(code)) { |
254 | printWriter.println(t.toString()); |
255 | } else { |
256 | t.printStackTrace(printWriter); |
257 | } |
258 | } else { |
259 | t.printStackTrace(printWriter); |
260 | } |
261 | } |
262 | printWriter.flush(); |
263 | if (closed) { |
264 | closeWriter(); |
265 | } |
266 | } catch (Exception e) { |
267 | logWritingError(e); |
268 | } |
269 | } |
270 | |
271 | private void logWritingError(Exception e) { |
272 | if (writingErrorLogged) { |
273 | return; |
274 | } |
275 | writingErrorLogged = true; |
276 | Exception se = DbException.get( |
277 | ErrorCode.TRACE_FILE_ERROR_2, e, fileName, e.toString()); |
278 | // print this error only once |
279 | fileName = null; |
280 | sysOut.println(se); |
281 | se.printStackTrace(); |
282 | } |
283 | |
284 | private boolean openWriter() { |
285 | if (printWriter == null) { |
286 | try { |
287 | FileUtils.createDirectories(FileUtils.getParent(fileName)); |
288 | if (FileUtils.exists(fileName) && !FileUtils.canWrite(fileName)) { |
289 | // read only database: don't log error if the trace file |
290 | // can't be opened |
291 | return false; |
292 | } |
293 | fileWriter = IOUtils.getBufferedWriter( |
294 | FileUtils.newOutputStream(fileName, true)); |
295 | printWriter = new PrintWriter(fileWriter, true); |
296 | } catch (Exception e) { |
297 | logWritingError(e); |
298 | return false; |
299 | } |
300 | } |
301 | return true; |
302 | } |
303 | |
304 | private synchronized void closeWriter() { |
305 | if (printWriter != null) { |
306 | printWriter.flush(); |
307 | printWriter.close(); |
308 | printWriter = null; |
309 | } |
310 | if (fileWriter != null) { |
311 | try { |
312 | fileWriter.close(); |
313 | } catch (IOException e) { |
314 | // ignore |
315 | } |
316 | fileWriter = null; |
317 | } |
318 | } |
319 | |
320 | /** |
321 | * Close the writers, and the files if required. It is still possible to |
322 | * write after closing, however after each write the file is closed again |
323 | * (slowing down tracing). |
324 | */ |
325 | public void close() { |
326 | closeWriter(); |
327 | closed = true; |
328 | } |
329 | |
330 | @Override |
331 | public void setName(String name) { |
332 | // nothing to do (the file name is already set) |
333 | } |
334 | |
335 | } |