/* * Copyright 2016-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.log.memory; import com.facebook.buck.log.LogFormatter; import com.facebook.buck.log.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.List; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.LogRecord; /** * <code>MemoryHandler</code> maintains a circular buffer of LogRecords. The underlying circular * buffer implementation is implemented directly from java.util.logging.MemoryHandler, but this * handler extends the default JUL handler by allowing LogRecords to be handled in batch. Logs are * only written to file if a LogRecord at or above the push level is recorded. */ public class MemoryHandler extends Handler { private static final Logger LOG = Logger.get(MemoryHandler.class); private static final LogManager LOG_MANAGER = LogManager.getLogManager(); private static final Level DEFAULT_LEVEL = Level.ALL; private static final Level DEFAULT_PUSH_LEVEL = Level.SEVERE; private static final Integer DEFAULT_BUFFER_SIZE = 100; private static final Formatter DEFAULT_FORMATTER = new LogFormatter(); private final LogRecord[] buffer; private int start; private int count; private Level pushLevel; /** * Constructs a <code>MemoryHandler</code> specified by:. * * <ul> * <li>com.facebook.buck.cli.bootstrapper.MemoryHandler.level specifies the level for the * <tt>Handler</tt>. * <li>com.facebook.buck.cli.bootstrapper.MemoryHandler.size defines the buffer size. * <li>com.facebook.buck.cli.bootstrapper.MemoryHandler.push defines the <tt>pushLevel</tt>. * <li>com.facebook.buck.cli.bootstrapper.MemoryHandler.formatter defines the <tt>formatter</tt> * for log records. * </ul> */ public MemoryHandler() { this(getLogLevelProperty(), getBufferSizeProperty(), getPushLevelProperty()); } @VisibleForTesting MemoryHandler(Level logLevel, int bufferSize, Level pushLevel) { Preconditions.checkState(bufferSize >= 0); Preconditions.checkNotNull(pushLevel); buffer = new LogRecord[bufferSize]; this.pushLevel = pushLevel; setLevel(logLevel); setFormatter(DEFAULT_FORMATTER); } private static Level getLogLevelProperty() { String levelStr = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".level"); if (levelStr != null) { return Level.parse(levelStr); } else { LOG.info("No log level specified so default log level %s will be used", DEFAULT_LEVEL); return DEFAULT_LEVEL; } } private static Integer getBufferSizeProperty() { String size = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".size"); if (size != null) { try { int intSize = Integer.parseInt(size); if (intSize > 0) { return intSize; } } catch (NumberFormatException e) { LOG.warn( "Invalid buffer size specified in logging.properties (must be > 0), using " + "default buffer size of %s", DEFAULT_BUFFER_SIZE); } } LOG.info("No buffer size specified so default size %s will be used", DEFAULT_BUFFER_SIZE); return DEFAULT_BUFFER_SIZE; } private static Level getPushLevelProperty() { String levelStr = LOG_MANAGER.getProperty(MemoryHandler.class.getName() + ".push"); if (levelStr != null) { return Level.parse(levelStr); } else { LOG.info("No push level specified so default push level %s will be used", DEFAULT_PUSH_LEVEL); return DEFAULT_PUSH_LEVEL; } } @Override public void publish(LogRecord record) { if (!isLoggable(record)) { return; } List<LogRecord> recordsToLog = null; synchronized (buffer) { int ix = (start + count) % buffer.length; buffer[ix] = record; if (count < buffer.length) { count++; } else { start++; start %= buffer.length; } if (record.getLevel().intValue() >= pushLevel.intValue()) { recordsToLog = new ArrayList<>(); while (count > 0) { LogRecord oldRecord = buffer[start]; recordsToLog.add(oldRecord); buffer[start] = null; start++; start %= buffer.length; count--; } } } } @Override public void flush() {} @Override public void close() throws SecurityException {} }