Package freenet.support

Source Code of freenet.support.FileLoggerHook$OldLogFile

package freenet.support;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.Date;
import java.util.Deque;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.zip.GZIPOutputStream;

import freenet.node.SemiOrderedShutdownHook;
import freenet.node.Version;
import freenet.support.io.FileUtil;

/**
* Converted the old StandardLogger to Ian's loggerhook interface.
*
* @author oskar
*/
public class FileLoggerHook extends LoggerHook implements Closeable {

  /** Verbosity types */
  public static final int DATE = 1,
    CLASS = 2,
    HASHCODE = 3,
    THREAD = 4,
    PRIORITY = 5,
    MESSAGE = 6,
    UNAME = 7;

  private volatile boolean closed = false;
  private boolean closedFinished = false;

  protected int INTERVAL = Calendar.MINUTE;
  protected int INTERVAL_MULTIPLIER = 5;
 
  private static final String ENCODING = "UTF-8";

        private static volatile boolean logMINOR;
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  /** Name of the local host (called uname in Unix-like operating systems). */
  private static String uname;
  static {
    uname = "unknown";
  }

  static synchronized void getUName() {
    if(!uname.equals("unknown")) return;
    System.out.println("Getting uname for logging");
    try {
      InetAddress addr = InetAddress.getLocalHost();
      if (addr != null) {
        uname =
          new StringTokenizer(addr.getHostName(), ".").nextToken();
      }
    } catch (Exception e) {
      // Ignored.
    }
  }
 
  private DateFormat df;
  private int[] fmt;
  private String[] str;

  /** Stream to write data to (compressed if rotate is on) */
  protected OutputStream logStream;
  /** Other stream to write data to (may be null) */
  protected OutputStream altLogStream;

  protected final boolean logOverwrite;

  /* Base filename for rotating logs */
  protected String baseFilename = null;
 
  protected File latestFile;
  protected File previousFile;

  /* Whether to redirect stdout */
  protected boolean redirectStdOut = false;
  /* Whether to redirect stderr */
  protected boolean redirectStdErr = false;

  protected final int MAX_LIST_SIZE;
  protected long MAX_LIST_BYTES = 10 * (1 << 20);
  protected long LIST_WRITE_THRESHOLD;

  /**
   * Something weird happens when the disk gets full, also we don't want to
   * block So run the actual write on another thread
   *
   * Unfortunately, we can't use ConcurrentBlockingQueue because we need to dump stuff when the queue gets
   * too big.
   *
   * FIXME PERFORMANCE: Using an ArrayBlockingQueue avoids some unnecessary memory allocations, but it
   * means we have to take two locks.
   * Seriously consider reverting 88268b99856919df0d42c2787d9ea3674a9f6f0d..e359b4005ef728a159fdee988c483de8ce8f3f6b
   * to go back to one lock and a LinkedList.
   */
  protected final ArrayBlockingQueue<byte[]> list;
  protected long listBytes = 0;

  long maxOldLogfilesDiskUsage;
  protected final Deque<OldLogFile> logFiles = new ArrayDeque<OldLogFile>();
  private long oldLogFilesDiskSpaceUsage = 0;

  private static class OldLogFile {
    public OldLogFile(File currentFilename, long startTime, long endTime, long length) {
      this.filename = currentFilename;
      this.start = startTime;
      this.end = endTime;
      this.size = length;
    }
    final File filename;
    final long start; // inclusive
    final long end; // exclusive
    final long size;
  }
 
  public void setMaxListBytes(long len) {
    synchronized(list) {
      MAX_LIST_BYTES = len;
      LIST_WRITE_THRESHOLD = MAX_LIST_BYTES / 4;
    }
  }

  public void setInterval(String intervalName) throws IntervalParseException {
    StringBuilder sb = new StringBuilder(intervalName.length());
    for(int i=0;i<intervalName.length();i++) {
      char c = intervalName.charAt(i);
      if(!Character.isDigit(c)) break;
      sb.append(c);
    }
    if(sb.length() > 0) {
      String prefix = sb.toString();
      intervalName = intervalName.substring(prefix.length());
      INTERVAL_MULTIPLIER = Integer.parseInt(prefix);
    } else {
      INTERVAL_MULTIPLIER = 1;
    }
    if (intervalName.endsWith("S")) {
      intervalName = intervalName.substring(0, intervalName.length()-1);
    }
    if (intervalName.equalsIgnoreCase("MINUTE"))
      INTERVAL = Calendar.MINUTE;
    else if (intervalName.equalsIgnoreCase("HOUR"))
      INTERVAL = Calendar.HOUR;
    else if (intervalName.equalsIgnoreCase("DAY"))
      INTERVAL = Calendar.DAY_OF_MONTH;
    else if (intervalName.equalsIgnoreCase("WEEK"))
      INTERVAL = Calendar.WEEK_OF_YEAR;
    else if (intervalName.equalsIgnoreCase("MONTH"))
      INTERVAL = Calendar.MONTH;
    else if (intervalName.equalsIgnoreCase("YEAR"))
      INTERVAL = Calendar.YEAR;
    else
      throw new IntervalParseException("invalid interval " + intervalName);
    System.out.println("Set interval to "+INTERVAL+" and multiplier to "+INTERVAL_MULTIPLIER);
  }

  public static class IntervalParseException extends Exception {

    private static final long serialVersionUID = 69847854744673572L;

    public IntervalParseException(String string) {
      super(string);
    }

  }
 
  /**
   * The extra parameter int digit is to be used for creating a logfile name
   * when a log exists already with the same date.
   * @param c
   * @param digit
   *      log file name suffix. ignored if this is {@code < 0}
   * @param compressed
   * @return
   */
  protected String getHourLogName(Calendar c, int digit, boolean compressed){
    StringBuilder buf = new StringBuilder(50);
    buf.append(baseFilename).append('-');
    buf.append(Version.buildNumber());
    buf.append('-');
    buf.append(c.get(Calendar.YEAR)).append('-');
    pad2digits(buf, c.get(Calendar.MONTH) + 1);
    buf.append('-');
    pad2digits(buf, c.get(Calendar.DAY_OF_MONTH));
    buf.append('-');
    pad2digits(buf, c.get(Calendar.HOUR_OF_DAY));
    if (INTERVAL == Calendar.MINUTE) {
      buf.append('-');
      pad2digits(buf, c.get(Calendar.MINUTE));
    }
    if (digit > 0) {
      buf.append("-");
      buf.append(digit);
    }
    buf.append(".log");
    if(compressed) buf.append(".gz");
    return buf.toString();
  }

  private StringBuilder pad2digits(StringBuilder buf, int x) {
    String s = Integer.toString(x);
    if (s.length() == 1) {
      buf.append('0');
    }
    buf.append(s);
    return buf;
  }
 
  // Unless we are writing flat out, everything will hit disk within this period.
  private long flushTime = 1000; // Default is 1 second. Will be set by setMaxBacklogNotBusy().

  class WriterThread extends Thread {
    WriterThread() {
      super("Log File Writer Thread");
    }

    @Override
    @SuppressWarnings("fallthrough")
    public void run() {
      File currentFilename = null;
      byte[] o = null;
      long thisTime;
      long lastTime = -1;
      long startTime;
      long nextHour = -1;
      GregorianCalendar gc = null;
      if (baseFilename != null) {
        latestFile = new File(baseFilename+"-latest.log");
        previousFile = new File(baseFilename+"-previous.log");
        gc = new GregorianCalendar();
        switch (INTERVAL) {
          case Calendar.YEAR :
            gc.set(Calendar.MONTH, 0);
          case Calendar.MONTH :
            gc.set(Calendar.DAY_OF_MONTH, 0);
          case Calendar.WEEK_OF_YEAR :
            if (INTERVAL == Calendar.WEEK_OF_YEAR)
              gc.set(Calendar.DAY_OF_WEEK, 0);
          case Calendar.DAY_OF_MONTH :
            gc.set(Calendar.HOUR, 0);
          case Calendar.HOUR :
            gc.set(Calendar.MINUTE, 0);
          case Calendar.MINUTE :
            gc.set(Calendar.SECOND, 0);
            gc.set(Calendar.MILLISECOND, 0);
        }
        if(INTERVAL_MULTIPLIER > 1) {
          int x = gc.get(INTERVAL);
          gc.set(INTERVAL, (x / INTERVAL_MULTIPLIER) * INTERVAL_MULTIPLIER);
        }
        findOldLogFiles((GregorianCalendar)gc.clone());
        currentFilename = new File(getHourLogName(gc, -1, true));
        synchronized(logFiles) {
          if((!logFiles.isEmpty()) && logFiles.getLast().filename.equals(currentFilename)) {
            logFiles.removeLast();
          }
        }
        logStream = openNewLogFile(currentFilename, true);
        if(latestFile != null) {
          altLogStream = openNewLogFile(latestFile, false);
        }
        System.err.println("Created log files");
        startTime = gc.getTimeInMillis();
          if(logMINOR)
            Logger.minor(this, "Start time: "+gc+" -> "+startTime);
        lastTime = startTime;
        gc.add(INTERVAL, INTERVAL_MULTIPLIER);
        nextHour = gc.getTimeInMillis();
      }
      long timeWaitingForSync = -1;
      long flush;
      synchronized(this) {
        flush = flushTime;
      }
      while (true) {
        try {
          thisTime = System.currentTimeMillis();
          if (baseFilename != null) {
            if ((thisTime > nextHour) || switchedBaseFilename) {
              currentFilename = rotateLog(currentFilename, lastTime, nextHour, gc);
             
              gc.add(INTERVAL, INTERVAL_MULTIPLIER);
              lastTime = nextHour;
              nextHour = gc.getTimeInMillis();

              if(switchedBaseFilename) {
                synchronized(FileLoggerHook.class) {
                  switchedBaseFilename = false;
                }
              }
            }
          }
          boolean died = false;
          boolean timeoutFlush = false;
          synchronized (list) {
            flush = flushTime;
            long maxWait;
            if(timeWaitingForSync == -1)
              maxWait = Long.MAX_VALUE;
            else
              maxWait = timeWaitingForSync + flush;
            o = list.poll();
            while(o == null) {
              if (closed) {
                died = true;
                break;
              }
              try {
                if(thisTime < maxWait) {
                  // Wait no more than 500ms since the CloserThread might be waiting for closedFinished.
                  list.wait(Math.min(500L, maxWait - thisTime));
                  thisTime = System.currentTimeMillis();
                  if(listBytes < LIST_WRITE_THRESHOLD) {
                    // Don't write at all until the lower bytes threshold is exceeded, or the time threshold is.
                    assert((listBytes == 0) == (list.peek() == null));
                    if(listBytes != 0 && maxWait == Long.MAX_VALUE)
                      maxWait = thisTime + flush;
                    if(closed) // If closing, write stuff ASAP.
                      o = list.poll();
                    else if(maxWait != Long.MAX_VALUE) {
                      continue;
                    }
                  } else {
                    // Do NOT use list.poll(timeout) because it uses a separate lock.
                    o = list.poll();
                  }
                }
              } catch (InterruptedException e) {
                // Ignored.
              }
              if(o == null) {
                if(timeWaitingForSync == -1) {
                  timeWaitingForSync = thisTime;
                  maxWait = thisTime + flush;
                }
                if(thisTime >= maxWait) {
                  timeoutFlush = true;
                  timeWaitingForSync = -1; // We have stuff to write, we are no longer waiting.
                  break;
                }
              } else break;
            }
            if(o != null) {
              listBytes -= o.length + LINE_OVERHEAD;
            }
          }
          if(timeoutFlush || died) {
            // Flush to disk
            myWrite(logStream, null);
                if(altLogStream != null)
                  myWrite(altLogStream, null);
          }
          if(died) {
            try {
              logStream.close();
            } catch (IOException e) {
              System.err.println("Failed to close log stream: "+e);
            }
            if(altLogStream != null) {
              try {
                altLogStream.close();
              } catch (IOException e) {
                System.err.println("Failed to close compressed log stream: "+e);
              }
            }
            synchronized(list) {
              closedFinished = true;
              list.notifyAll();
            }
            return;
          }
          if(o == null) continue;
          myWrite(logStream,  o);
              if(altLogStream != null)
                myWrite(altLogStream, o);
        } catch (OutOfMemoryError e) {
          System.err.println(e.getClass());
          System.err.println(e.getMessage());
          e.printStackTrace();
            // FIXME
          //freenet.node.Main.dumpInterestingObjects();
        } catch (Throwable t) {
          System.err.println("FileLoggerHook log writer caught " + t);
          t.printStackTrace(System.err);
        }
      }
    }

    private File rotateLog(File currentFilename, long lastTime, long nextHour, GregorianCalendar gc) {
          // Switch logs
          try {
            logStream.flush();
            if(altLogStream != null) altLogStream.flush();
          } catch (IOException e) {
            System.err.println(
              "Flushing on change caught " + e);
          }
          try {
            logStream.close();
          } catch (IOException e) {
            System.err.println(
                "Closing on change caught " + e);
          }
          long length = currentFilename.length();
          OldLogFile olf = new OldLogFile(currentFilename, lastTime, nextHour, length);
          synchronized(logFiles) {
            logFiles.addLast(olf);
          }
          oldLogFilesDiskSpaceUsage += length;
          trimOldLogFiles();
          // Rotate primary log stream
          currentFilename = new File(getHourLogName(gc, -1, true));
          logStream = openNewLogFile(currentFilename, true);
          if(latestFile != null) {
            try {
              altLogStream.close();
            } catch (IOException e) {
              System.err.println(
                  "Closing alt on change caught " + e);
            }
            if(previousFile != null && latestFile.exists())
              FileUtil.renameTo(latestFile, previousFile);
            latestFile.delete();
            altLogStream = openNewLogFile(latestFile, false);
          }
          return currentFilename;
        }

    // Check every minute
    static final int maxSleepTime = 60 * 1000;
    /**
     * @param b
     *            the bytes to write, null to flush
     */
    protected void myWrite(OutputStream os, byte[] b) {
      long sleepTime = 1000;
      while (true) {
        boolean thrown = false;
        try {
          if (b != null)
            os.write(b);
          else
            os.flush();
        } catch (IOException e) {
          System.err.println(
            "Exception writing to log: "
              + e
              + ", sleeping "
              + sleepTime);
          thrown = true;
        }
        if (thrown) {
          try {
            Thread.sleep(sleepTime);
          } catch (InterruptedException e) {
          }
          sleepTime += sleepTime;
          if (sleepTime > maxSleepTime)
            sleepTime = maxSleepTime;
        } else
          return;
      }
    }

    protected OutputStream openNewLogFile(File filename, boolean compress) {
      while (true) {
        long sleepTime = 1000;
        try {
          OutputStream o = new FileOutputStream(filename, !logOverwrite);
          if(compress) {
            // buffer -> gzip -> buffer -> file
            o = new BufferedOutputStream(o, 512*1024); // to file
            o = new GZIPOutputStream(o);
            // gzip block size is 32kB
            o = new BufferedOutputStream(o, 65536); // to gzipper
          } else {
            // buffer -> file
            o = new BufferedOutputStream(o, 512*1024);
          }
          o.write(BOM);
          return o;
        } catch (IOException e) {
          System.err.println(
            "Could not create FOS " + filename + ": " + e);
          System.err.println(
            "Sleeping " + sleepTime / 1000 + " seconds");
          try {
            Thread.sleep(sleepTime);
          } catch (InterruptedException ex) {
          }
          sleepTime += sleepTime;
        }
      }
    }
  }
 
  private static final byte[] BOM;
 
  static {
    try {
      BOM = "\uFEFF".getBytes(ENCODING);
    } catch (UnsupportedEncodingException e) {
      throw new Error(e);
    }
  }

  protected int runningCompressors = 0;
  protected Object runningCompressorsSync = new Object();

  private Date myDate = new Date();

  /**
   * Create a Logger to append to the given file. If the file does not exist
   * it will be created.
   *
   * @param filename
   *            the name of the file to log to.
   * @param fmt
   *            log message format string
   * @param dfmt
   *            date format string
   * @param threshold
   *            Lowest logged priority
   * @param assumeWorking
   *            If false, check whether stderr and stdout are writable and if
   *            not, redirect them to the log file
   * @exception IOException
   *                if the file couldn't be opened for append.
   * @throws IntervalParseException
   */
  public FileLoggerHook(
    String filename,
    String fmt,
    String dfmt,
    String logRotateInterval,
    LogLevel threshold,
    boolean assumeWorking,
    boolean logOverwrite,
    long maxOldLogfilesDiskUsage, int maxListSize)
    throws IOException, IntervalParseException {
    this(
      false,
      filename,
      fmt,
      dfmt,
      logRotateInterval,
      threshold,
      assumeWorking,
      logOverwrite,
      maxOldLogfilesDiskUsage,
      maxListSize);
  }
 
  private final Object trimOldLogFilesLock = new Object();
 
  public void trimOldLogFiles() {
    synchronized(trimOldLogFilesLock) {
      while(oldLogFilesDiskSpaceUsage > maxOldLogfilesDiskUsage) {
        OldLogFile olf;
        // TODO: creates a double lock situation, but only here. I think this is okay because the inner lock is only used for trivial things.
        synchronized(logFiles) {
          if(logFiles.isEmpty()) {
            System.err.println("ERROR: INCONSISTENT LOGGER TOTALS: Log file list is empty but still used "+oldLogFilesDiskSpaceUsage+" bytes!");
          }
          olf = logFiles.removeFirst();
        }
        olf.filename.delete();
        oldLogFilesDiskSpaceUsage -= olf.size;
          if(logMINOR)
            Logger.minor(this, "Deleting "+olf.filename+" - saving "+olf.size+
            " bytes, disk usage now: "+oldLogFilesDiskSpaceUsage+" of "+maxOldLogfilesDiskUsage);
      }
    }
  }

  /** Initialize oldLogFiles */
  public void findOldLogFiles(GregorianCalendar gc) {
    gc = (GregorianCalendar) gc.clone();
    File currentFilename = new File(getHourLogName(gc, -1, true));
    System.out.println("Finding old log files. New log file is "+currentFilename);
    File numericSameDateFilename;
    int slashIndex = baseFilename.lastIndexOf(File.separatorChar);
    File dir;
    String prefix;
    if(slashIndex == -1) {
      dir = new File(System.getProperty("user.dir"));
      prefix = baseFilename.toLowerCase();
    } else {
      dir = new File(baseFilename.substring(0, slashIndex));
      prefix = baseFilename.substring(slashIndex+1).toLowerCase();
    }
    File[] files = dir.listFiles();
    if(files == null) return;
    java.util.Arrays.sort(files);
    long lastStartTime = -1;
    File oldFile = null;
        if(latestFile.exists())
          FileUtil.renameTo(latestFile, previousFile);

    for(File f: files) {
      String name = f.getName();
      if(name.toLowerCase().startsWith(prefix)) {
        if(name.equals(previousFile.getName()) || name.equals(latestFile.getName())) {
          continue;
        }
        if(!name.endsWith(".log.gz")) {
          if(logMINOR) Logger.minor(this, "Does not end in .log.gz: "+name);
          f.delete();
          continue;
        } else {
          name = name.substring(0, name.length()-".log.gz".length());
        }
        name = name.substring(prefix.length());
        if((name.length() == 0) || (name.charAt(0) != '-')) {
          if(logMINOR) Logger.minor(this, "Deleting unrecognized: "+name+" ("+f.getPath()+ ')');
          f.delete();
          continue;
        } else
          name = name.substring(1);
        String[] tokens = name.split("-");
        int[] nums = new int[tokens.length];
        for(int j=0;j<tokens.length;j++) {
          try {
            nums[j] = Integer.parseInt(tokens[j]);
          } catch (NumberFormatException e) {
            Logger.normal(this, "Could not parse: "+tokens[j]+" into number from "+name);
            // Broken
            f.delete();
            continue;
          }
        }
        if(nums.length > 1)
          gc.set(Calendar.YEAR, nums[1]);
        if(nums.length > 2)
          gc.set(Calendar.MONTH, nums[2]-1);
        if(nums.length > 3)
          gc.set(Calendar.DAY_OF_MONTH, nums[3]);
        if(nums.length > 4)
          gc.set(Calendar.HOUR_OF_DAY, nums[4]);
        if(nums.length > 5)
          gc.set(Calendar.MINUTE, nums[5]);
        gc.set(Calendar.SECOND, 0);
        gc.set(Calendar.MILLISECOND, 0);
        long startTime = gc.getTimeInMillis();
        if(oldFile != null) {
          long l = oldFile.length();
          OldLogFile olf = new OldLogFile(oldFile, lastStartTime, startTime, l);
          synchronized(logFiles) {
            logFiles.addLast(olf);
          }
          synchronized(trimOldLogFilesLock) {
            oldLogFilesDiskSpaceUsage += l;
          }
        }
        lastStartTime = startTime;
        oldFile = f;
      } else {
        // Nothing to do with us
        Logger.normal(this, "Unknown file: "+name+" in the log directory");
      }
    }
    //If a compressed log file already exists for a given date,
    //add a number to the end of the file that already exists
    if(currentFilename != null && currentFilename.exists()) {
      System.out.println("Old log file exists for this time period: "+currentFilename);
      for(int a = 1;; a++){
        numericSameDateFilename = new File(getHourLogName(gc, a, true));
        if(numericSameDateFilename == null || !numericSameDateFilename.exists()) {
          if(numericSameDateFilename != null) {
            System.out.println("Renaming to: "+numericSameDateFilename);
            FileUtil.renameTo(currentFilename, numericSameDateFilename);
          }
          break;
        }
      }
    }
    if(oldFile != null) {
      long l = oldFile.length();
      OldLogFile olf = new OldLogFile(oldFile, lastStartTime, System.currentTimeMillis(), l);
      synchronized(logFiles) {
        logFiles.addLast(olf);
      }
      synchronized(trimOldLogFilesLock) {
        oldLogFilesDiskSpaceUsage += l;
      }
    }
    trimOldLogFiles();
  }

  public FileLoggerHook(
      String filename,
      String fmt,
      String dfmt,
      String threshold,
      String logRotateInterval,
      boolean assumeWorking,
      boolean logOverwrite,
      long maxOldLogFilesDiskUsage,
      int maxListSize)
      throws IOException, InvalidThresholdException, IntervalParseException {
      this(filename,
        fmt,
        dfmt,
        logRotateInterval,
        LogLevel.valueOf(threshold.toUpperCase()),
        assumeWorking,
        logOverwrite,
        maxOldLogFilesDiskUsage,
        maxListSize);
    }

  private void checkStdStreams() {
    // Redirect System.err and System.out to the Logger Printstream
    // if they don't exist (like when running under javaw)
    System.out.print(" \b");
    if (System.out.checkError()) {
      redirectStdOut = true;
    }
    System.err.print(" \b");
    if (System.err.checkError()) {
      redirectStdErr = true;
    }
  }

  public FileLoggerHook(
    OutputStream os,
    String fmt,
    String dfmt,
    LogLevel threshold) throws IntervalParseException {
    this(os, fmt, dfmt, threshold, true);
    logStream = os;
  }
 
  public FileLoggerHook(
      OutputStream os,
      String fmt,
      String dfmt,
      String threshold) throws InvalidThresholdException, IntervalParseException {
      this(os, fmt, dfmt, LogLevel.valueOf(threshold.toUpperCase()), true);
      logStream = os;
    }

  /**
   * Create a Logger to send log output to the given PrintStream.
   *
   * @param stream
   *            the PrintStream to send log output to.
   * @param fmt
   *            log message format string
   * @param dfmt
   *            date format string
   * @param threshold
   *            Lowest logged priority
   * @throws IntervalParseException
   */
  public FileLoggerHook(
    OutputStream stream,
    String fmt,
    String dfmt,
    LogLevel threshold,
    boolean overwrite) throws IntervalParseException {
    this(fmt, dfmt, threshold, "HOUR", overwrite, -1, 10000);
    logStream = stream;
  }

  public void start() {
    if(redirectStdOut) {
      try {
        System.setOut(new PrintStream(new OutputStreamLogger(LogLevel.NORMAL, "Stdout: ", ENCODING), false, ENCODING));
        if(redirectStdErr)
          System.setErr(new PrintStream(new OutputStreamLogger(LogLevel.ERROR, "Stderr: ", ENCODING), false, ENCODING));
      } catch (UnsupportedEncodingException e) {
        throw new Error(e);
      }
    }
    WriterThread wt = new WriterThread();
    wt.setDaemon(true);
    CloserThread ct = new CloserThread();
    SemiOrderedShutdownHook.get().addLateJob(ct);
    wt.start();
  }
 
  public FileLoggerHook(
    boolean rotate,
    String baseFilename,
    String fmt,
    String dfmt,
    String logRotateInterval,
    LogLevel threshold,
    boolean assumeWorking,
    boolean logOverwrite,
    long maxOldLogfilesDiskUsage, int maxListSize)
    throws IOException, IntervalParseException {
    this(fmt, dfmt, threshold, logRotateInterval, logOverwrite, maxOldLogfilesDiskUsage, maxListSize);
    //System.err.println("Creating FileLoggerHook with threshold
    // "+threshold);
    if (!assumeWorking)
      checkStdStreams();
    if (rotate) {
      this.baseFilename = baseFilename;
    } else {
      logStream = new BufferedOutputStream(new FileOutputStream(baseFilename, !logOverwrite), 65536);
    }
  }
 
  public FileLoggerHook(
      boolean rotate,
      String baseFilename,
      String fmt,
      String dfmt,
      String threshold,
      String logRotateInterval,
      boolean assumeWorking,
      boolean logOverwrite,
      long maxOldLogFilesDiskUsage, int maxListSize) throws IOException, InvalidThresholdException, IntervalParseException{
    this(rotate,baseFilename,fmt,dfmt,logRotateInterval,LogLevel.valueOf(threshold.toUpperCase()),assumeWorking,logOverwrite,maxOldLogFilesDiskUsage,maxListSize);
  }

  private FileLoggerHook(String fmt, String dfmt, LogLevel threshold, String logRotateInterval, boolean overwrite, long maxOldLogfilesDiskUsage, int maxListSize) throws IntervalParseException {
    super(threshold);
    this.maxOldLogfilesDiskUsage = maxOldLogfilesDiskUsage;
    this.logOverwrite = overwrite;
    setInterval(logRotateInterval);
   
    MAX_LIST_SIZE = maxListSize;
    list = new ArrayBlockingQueue<byte[]>(MAX_LIST_SIZE);
   
    setDateFormat(dfmt);
    setLogFormat(fmt);
  }

  private void setLogFormat(String fmt) {
    if ((fmt == null) || (fmt.length() == 0))
      fmt = "d:c:h:t:p:m";
    char[] f = fmt.toCharArray();

    ArrayList<Integer> fmtVec = new ArrayList<Integer>();
    ArrayList<String> strVec = new ArrayList<String>();

    StringBuilder sb = new StringBuilder();

    boolean comment = false;
    for (char fi: f) {
      int type = numberOf(fi);
      if(type == UNAME)
        getUName();
      if (!comment && (type != 0)) {
        if (sb.length() > 0) {
          strVec.add(sb.toString());
          fmtVec.add(0);
          sb = new StringBuilder();
        }
        fmtVec.add(type);
      } else if (fi == '\\') {
        comment = true;
      } else {
        comment = false;
        sb.append(fi);
      }
    }
    if (sb.length() > 0) {
      strVec.add(sb.toString());
      fmtVec.add(0);
    }

    this.fmt = new int[fmtVec.size()];
    int size = fmtVec.size();
    for (int i = 0; i < size; ++i)
      this.fmt[i] = fmtVec.get(i);

    this.str = new String[strVec.size()];
    str = strVec.toArray(str);
  }

  private void setDateFormat(String dfmt) {
    if ((dfmt != null) && (dfmt.length() != 0)) {
      try {
        df = new SimpleDateFormat(dfmt);
      } catch (RuntimeException e) {
        df = DateFormat.getDateTimeInstance();
      }
    } else
      df = DateFormat.getDateTimeInstance();

    df.setTimeZone(TimeZone.getTimeZone("UTC"));
  }

  @Override
  public void log(Object o, Class<?> c, String msg, Throwable e, LogLevel priority) {
    if (!instanceShouldLog(priority, c))
      return;

    if (closed)
      return;
   
    StringBuilder sb = new StringBuilder( e == null ? 512 : 1024 );
    int sctr = 0;

    for (int f: fmt) {
      switch (f) {
        case 0 :
          sb.append(str[sctr++]);
          break;
        case DATE :
          long now = System.currentTimeMillis();
          synchronized (this) {
            myDate.setTime(now);
            sb.append(df.format(myDate));
          }
          break;
        case CLASS :
          sb.append(c == null ? "<none>" : c.getName());
          break;
        case HASHCODE :
          sb.append(
            o == null
              ? "<none>"
              : Integer.toHexString(o.hashCode()));
          break;
        case THREAD :
          sb.append(Thread.currentThread().getName());
          break;
        case PRIORITY :
          sb.append(priority.name());
          break;
        case MESSAGE :
          sb.append(msg);
          break;
        case UNAME :
          sb.append(uname);
          break;
      }
    }
    sb.append('\n');

    // Write stacktrace if available
    for(int j=0;j<20 && e != null;j++) {
      sb.append(e.toString());
     
      StackTraceElement[] trace = e.getStackTrace();
     
      if(trace == null)
        sb.append("(null)\n");
      else if(trace.length == 0)
        sb.append("(no stack trace)\n");
      else {
        sb.append('\n');
        for(StackTraceElement elt: trace) {
          sb.append("\tat ");
          sb.append(elt.toString());
          sb.append('\n');
        }
      }
     
      Throwable cause = e.getCause();
      if(cause != e) e = cause;
      else break;
    }

    try {
      logString(sb.toString().getBytes(ENCODING));
    } catch (UnsupportedEncodingException e1) {
      throw new Error(e1);
    }
  }

  /** Memory allocation overhead (estimated through experimentation with bsh) */
  private static final int LINE_OVERHEAD = 60;
 
  public void logString(byte[] b) throws UnsupportedEncodingException {
    synchronized (list) {
      int sz = list.size();
      if(!list.offer(b)) {
        byte[] ss = list.poll();
        if(ss != null) listBytes -= ss.length + LINE_OVERHEAD;
        ss = list.poll();
        if(ss != null) listBytes -= ss.length + LINE_OVERHEAD;
        String err =
          "GRRR: ERROR: Logging too fast, chopped "
            + 2
            + " entries, "
            + listBytes
            + " bytes in memory\n";
        byte[] buf = err.getBytes(ENCODING);
        if(list.offer(buf))
          listBytes += (buf.length + LINE_OVERHEAD);
        if(list.offer(b))
          listBytes += (b.length + LINE_OVERHEAD);
      } else
        listBytes += (b.length + LINE_OVERHEAD);
      int x = 0;
      if (listBytes > MAX_LIST_BYTES) {
        while ((list.size() > (MAX_LIST_SIZE * 0.9F))
          || (listBytes > (MAX_LIST_BYTES * 0.9F))) {
          byte[] ss;
          ss = list.poll();
          listBytes -= (ss.length + LINE_OVERHEAD);
          x++;
        }
        String err =
          "GRRR: ERROR: Logging too fast, chopped "
            + x
            + " entries, "
            + listBytes
            + " bytes in memory\n";
        byte[] buf = err.getBytes(ENCODING);
        if(!list.offer(buf)) {
          byte[] ss = list.poll();
          if(ss != null) listBytes -= ss.length + LINE_OVERHEAD;
          if(list.offer(buf))
            listBytes += (buf.length + LINE_OVERHEAD);
        } else
          listBytes += (buf.length + LINE_OVERHEAD);
      }
      if (sz == 0)
        list.notifyAll();
    }
  }

  public long listBytes() {
    synchronized (list) {
      return listBytes;
    }
  }

  public static int numberOf(char c) {
    switch (c) {
      case 'd' :
        return DATE;
      case 'c' :
        return CLASS;
      case 'h' :
        return HASHCODE;
      case 't' :
        return THREAD;
      case 'p' :
        return PRIORITY;
      case 'm' :
        return MESSAGE;
      case 'u' :
        return UNAME;
      default :
        return 0;
    }
  }

  @Override
  public void close() {
    closed = true;
  }

  class CloserThread extends Thread {
    @Override
    public void run() {
      synchronized(list) {
        closed = true;
        long deadline = System.currentTimeMillis() + SECONDS.toMillis(10);
        while(!closedFinished) {
          int wait = (int) (deadline - System.currentTimeMillis());
          if(wait <= 0) return;
          try {
            list.wait(wait);
          } catch (InterruptedException e) {
            // Ok.
          }
        }
        System.out.println("Completed writing logs to disk.");
      }
    }
  }

  /**
   * Print a human- and script- readable list of available log files.
   * @throws IOException
   */
  public void listAvailableLogs(OutputStreamWriter writer) throws IOException {
    OldLogFile[] oldLogFiles;
    synchronized(logFiles) {
      oldLogFiles = logFiles.toArray(new OldLogFile[logFiles.size()]);
    }
    DateFormat tempDF = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.ENGLISH);
    tempDF.setTimeZone(TimeZone.getTimeZone("GMT"));
    for(OldLogFile olf: oldLogFiles) {
      writer.write(olf.filename.getName()+" : "+tempDF.format(new Date(olf.start))+" to "+tempDF.format(new Date(olf.end))+ " - "+olf.size+" bytes\n");
    }
  }

  public void sendLogByContainedDate(long time, OutputStream os) throws IOException {
    OldLogFile toReturn = null;
    synchronized(logFiles) {
      for(OldLogFile olf : logFiles) {
          if(logMINOR)
            Logger.minor(this, "Checking "+time+" against "+olf.filename+" : start="+olf.start+", end="+olf.end);
        if((time >= olf.start) && (time < olf.end)) {
          toReturn = olf;
          if(logMINOR) Logger.minor(this, "Found "+olf);
          break;
        }
      }
      if(toReturn == null)
        return; // couldn't find it
    }
    FileInputStream fis = new FileInputStream(toReturn.filename);
    DataInputStream dis = new DataInputStream(fis);
    long written = 0;
    long size = toReturn.size;
    byte[] buf = new byte[4096];
    while(written < size) {
      int toRead = (int) Math.min(buf.length, (size - written));
      try {
        dis.readFully(buf, 0, toRead);
      } catch (IOException e) {
        Logger.error(this, "Could not read bytes "+written+" to "+(written + toRead)+" from file "+toReturn.filename+" which is supposed to be "+size+" bytes ("+toReturn.filename.length()+ ')');
        return;
      }
      os.write(buf, 0, toRead);
      written += toRead;
    }
    dis.close();
    fis.close();
  }

  /** Set the maximum size of old (gzipped) log files to keep.
   * Will start to prune old files immediately, but this will likely not be completed
   * by the time the function returns as it is run off-thread.
   */
  public void setMaxOldLogsSize(long val) {
    synchronized(trimOldLogFilesLock) {
      maxOldLogfilesDiskUsage = val;
    }
    Runnable r = new Runnable() {
      @Override
      public void run() {
        trimOldLogFiles();
      }
    };
    Thread t = new Thread(r, "Shrink logs");
    t.setDaemon(true);
    t.start();
  }

  private boolean switchedBaseFilename;
 
  public void switchBaseFilename(String filename) {
    synchronized(this) {
      this.baseFilename = filename;
      switchedBaseFilename = true;
    }
  }

  public void waitForSwitch() {
    long now = System.currentTimeMillis();
    synchronized(this) {
      if(!switchedBaseFilename) return;
      long startTime = now;
      long endTime = startTime + 10000;
      while(((now = System.currentTimeMillis()) < endTime) && !switchedBaseFilename) {
        try {
          wait(Math.max(1, endTime-now));
        } catch (InterruptedException e) {
          // Ignore
        }
      }
    }
  }

  public void deleteAllOldLogFiles() {
    synchronized(trimOldLogFilesLock) {
      while(true) {
        OldLogFile olf;
        synchronized(logFiles) {
          if(logFiles.isEmpty()) return;
          olf = logFiles.removeFirst();
        }
        olf.filename.delete();
        oldLogFilesDiskSpaceUsage -= olf.size;
        if(logMINOR)
          Logger.minor(this, "Deleting "+olf.filename+" - saving "+olf.size+
              " bytes, disk usage now: "+oldLogFilesDiskSpaceUsage+" of "+maxOldLogfilesDiskUsage);
      }
    }
  }

  /**
   * This is used by the lost-lock deadlock detector so MUST NOT TAKE A LOCK ever!
   */
  public boolean hasRedirectedStdOutErrNoLock() {
    return redirectStdOut || redirectStdErr;
  }

  public synchronized void setMaxBacklogNotBusy(long val) {
    flushTime = val;
  }
}
TOP

Related Classes of freenet.support.FileLoggerHook$OldLogFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.