/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.message;

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.jdbc.JdbcException;
import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;

The trace mechanism is the logging facility of this database. There is usually one trace system per database. It is called 'trace' because the term 'log' is already used in the database domain and means 'transaction log'. It is possible to write after close was called, but that means for each write the file will be opened and closed again (which is slower).
/** * The trace mechanism is the logging facility of this database. There is * usually one trace system per database. It is called 'trace' because the term * 'log' is already used in the database domain and means 'transaction log'. It * is possible to write after close was called, but that means for each write * the file will be opened and closed again (which is slower). */
public class TraceSystem implements TraceWriter {
The parent trace level should be used.
/** * The parent trace level should be used. */
public static final int PARENT = -1;
This trace level means nothing should be written.
/** * This trace level means nothing should be written. */
public static final int OFF = 0;
This trace level means only errors should be written.
/** * This trace level means only errors should be written. */
public static final int ERROR = 1;
This trace level means errors and informational messages should be written.
/** * This trace level means errors and informational messages should be * written. */
public static final int INFO = 2;
This trace level means all type of messages should be written.
/** * This trace level means all type of messages should be written. */
public static final int DEBUG = 3;
This trace level means all type of messages should be written, but instead of using the trace file the messages should be written to SLF4J.
/** * This trace level means all type of messages should be written, but * instead of using the trace file the messages should be written to SLF4J. */
public static final int ADAPTER = 4;
The default level for system out trace messages.
/** * The default level for system out trace messages. */
public static final int DEFAULT_TRACE_LEVEL_SYSTEM_OUT = OFF;
The default level for file trace messages.
/** * The default level for file trace messages. */
public static final int DEFAULT_TRACE_LEVEL_FILE = ERROR;
The default maximum trace file size. It is currently 64 MB. Additionally, there could be a .old file of the same size.
/** * The default maximum trace file size. It is currently 64 MB. Additionally, * there could be a .old file of the same size. */
private static final int DEFAULT_MAX_FILE_SIZE = 64 * 1024 * 1024; private static final int CHECK_SIZE_EACH_WRITES = 4096; private int levelSystemOut = DEFAULT_TRACE_LEVEL_SYSTEM_OUT; private int levelFile = DEFAULT_TRACE_LEVEL_FILE; private int levelMax; private int maxFileSize = DEFAULT_MAX_FILE_SIZE; private String fileName; private final AtomicReferenceArray<Trace> traces = new AtomicReferenceArray<>(Trace.MODULE_NAMES.length); private SimpleDateFormat dateFormat; private Writer fileWriter; private PrintWriter printWriter; private int checkSize; private boolean closed; private boolean writingErrorLogged; private TraceWriter writer = this; private PrintStream sysOut = System.out;
Create a new trace system object.
Params:
  • fileName – the file name
/** * Create a new trace system object. * * @param fileName the file name */
public TraceSystem(String fileName) { this.fileName = fileName; updateLevel(); } private void updateLevel() { levelMax = Math.max(levelSystemOut, levelFile); }
Set the print stream to use instead of System.out.
Params:
  • out – the new print stream
/** * Set the print stream to use instead of System.out. * * @param out the new print stream */
public void setSysOut(PrintStream out) { this.sysOut = out; }
Get or create a trace object for this module id. Trace modules with id are cached.
Params:
  • moduleId – module id
Returns:the trace object
/** * Get or create a trace object for this module id. Trace modules with id * are cached. * * @param moduleId module id * @return the trace object */
public Trace getTrace(int moduleId) { Trace t = traces.get(moduleId); if (t == null) { t = new Trace(writer, moduleId); if (!traces.compareAndSet(moduleId, null, t)) { t = traces.get(moduleId); } } return t; }
Create a trace object for this module. Trace modules with names are not cached.
Params:
  • module – the module name
Returns:the trace object
/** * Create a trace object for this module. Trace modules with names are not * cached. * * @param module the module name * @return the trace object */
public Trace getTrace(String module) { return new Trace(writer, module); } @Override public boolean isEnabled(int level) { if (levelMax == ADAPTER) { return writer.isEnabled(level); } return level <= this.levelMax; }
Set the trace file name.
Params:
  • name – the file name
/** * Set the trace file name. * * @param name the file name */
public void setFileName(String name) { this.fileName = name; }
Set the maximum trace file size in bytes.
Params:
  • max – the maximum size
/** * Set the maximum trace file size in bytes. * * @param max the maximum size */
public void setMaxFileSize(int max) { this.maxFileSize = max; }
Set the trace level to use for System.out
Params:
  • level – the new level
/** * Set the trace level to use for System.out * * @param level the new level */
public void setLevelSystemOut(int level) { levelSystemOut = level; updateLevel(); }
Set the file trace level.
Params:
  • level – the new level
/** * Set the file trace level. * * @param level the new level */
public void setLevelFile(int level) { if (level == ADAPTER) { String adapterClass = "org.h2.message.TraceWriterAdapter"; try { writer = (TraceWriter) Class.forName(adapterClass).getDeclaredConstructor().newInstance(); } catch (Throwable e) { e = DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, adapterClass); write(ERROR, Trace.DATABASE, adapterClass, e); return; } String name = fileName; if (name != null) { if (name.endsWith(Constants.SUFFIX_TRACE_FILE)) { name = name.substring(0, name.length() - Constants.SUFFIX_TRACE_FILE.length()); } int idx = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); if (idx >= 0) { name = name.substring(idx + 1); } writer.setName(name); } } levelFile = level; updateLevel(); } public int getLevelFile() { return levelFile; } private synchronized String format(String module, String s) { if (dateFormat == null) { dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss "); } return dateFormat.format(System.currentTimeMillis()) + module + ": " + s; } @Override public void write(int level, int moduleId, String s, Throwable t) { write(level, Trace.MODULE_NAMES[moduleId], s, t); } @Override public void write(int level, String module, String s, Throwable t) { if (level <= levelSystemOut || level > this.levelMax) { // level <= levelSystemOut: the system out level is set higher // level > this.level: the level for this module is set higher sysOut.println(format(module, s)); if (t != null && levelSystemOut == DEBUG) { t.printStackTrace(sysOut); } } if (fileName != null) { if (level <= levelFile) { writeFile(format(module, s), t); } } } private synchronized void writeFile(String s, Throwable t) { try { if (checkSize++ >= CHECK_SIZE_EACH_WRITES) { checkSize = 0; closeWriter(); if (maxFileSize > 0 && FileUtils.size(fileName) > maxFileSize) { String old = fileName + ".old"; FileUtils.delete(old); FileUtils.move(fileName, old); } } if (!openWriter()) { return; } printWriter.println(s); if (t != null) { if (levelFile == ERROR && t instanceof JdbcException) { JdbcException se = (JdbcException) t; int code = se.getErrorCode(); if (ErrorCode.isCommon(code)) { printWriter.println(t.toString()); } else { t.printStackTrace(printWriter); } } else { t.printStackTrace(printWriter); } } printWriter.flush(); if (closed) { closeWriter(); } } catch (Exception e) { logWritingError(e); } } private void logWritingError(Exception e) { if (writingErrorLogged) { return; } writingErrorLogged = true; Exception se = DbException.get( ErrorCode.TRACE_FILE_ERROR_2, e, fileName, e.toString()); // print this error only once fileName = null; sysOut.println(se); se.printStackTrace(); } private boolean openWriter() { if (printWriter == null) { try { FileUtils.createDirectories(FileUtils.getParent(fileName)); if (FileUtils.exists(fileName) && !FileUtils.canWrite(fileName)) { // read only database: don't log error if the trace file // can't be opened return false; } fileWriter = IOUtils.getBufferedWriter( FileUtils.newOutputStream(fileName, true)); printWriter = new PrintWriter(fileWriter, true); } catch (Exception e) { logWritingError(e); return false; } } return true; } private synchronized void closeWriter() { if (printWriter != null) { printWriter.flush(); printWriter.close(); printWriter = null; } if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { // ignore } fileWriter = null; } }
Close the writers, and the files if required. It is still possible to write after closing, however after each write the file is closed again (slowing down tracing).
/** * Close the writers, and the files if required. It is still possible to * write after closing, however after each write the file is closed again * (slowing down tracing). */
public void close() { closeWriter(); closed = true; } @Override public void setName(String name) { // nothing to do (the file name is already set) } }