All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.server.logging.GFFileHandler Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]

package com.sun.enterprise.server.logging;

import com.sun.appserv.server.util.Version;
import com.sun.common.util.logging.BooleanLatch;
import com.sun.common.util.logging.GFLogRecord;
import com.sun.common.util.logging.LoggingOutputStream;
import com.sun.enterprise.admin.monitor.callflow.Agent;
import com.sun.enterprise.module.bootstrap.EarlyLogHandler;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.v3.logging.AgentFormatterDelegate;
import fish.payara.enterprise.server.logging.JSONLogFormatter;
import fish.payara.enterprise.server.logging.PayaraNotificationLogRotationTimer;;
import java.io.*;
import java.security.PrivilegedAction;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
import java.util.zip.GZIPOutputStream;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.glassfish.api.logging.Task;
import org.glassfish.config.support.TranslatedConfigView;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.hk2.api.PreDestroy;
import org.glassfish.server.ServerEnvironmentImpl;
import org.jvnet.hk2.annotations.ContractsProvided;
import org.jvnet.hk2.annotations.Optional;
import org.jvnet.hk2.annotations.Service;

import static java.security.AccessController.doPrivileged;
import org.glassfish.internal.api.Globals;

/**
 * GFFileHandler publishes formatted log Messages to a FILE.
 *
 * @author Jerome Dochez
 * @author Carla Mott
 */
@Service
@Singleton
@ContractsProvided(
    {GFFileHandler.class, java.util.logging.Handler.class,
        LogEventBroadcaster.class, LoggingRuntime.class}
)
public class GFFileHandler extends StreamHandler implements
    PostConstruct, PreDestroy, LogEventBroadcaster, LoggingRuntime {

    private static final int DEFAULT_ROTATION_LIMIT_BYTES = 2000000;
    public static final int DISABLE_LOG_FILE_ROTATION_VALUE = 0;

    private final static LocalStringManagerImpl LOCAL_STRINGS =
        new LocalStringManagerImpl(GFFileHandler.class);

    private static final String RECORD_BEGIN_MARKER = "[#|";
    private static final String RECORD_END_MARKER = "|#]";
    private static final String RECORD_FIELD_SEPARATOR = "|";
    private static final String RECORD_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    @Inject
    protected ServerEnvironmentImpl env;

    @Inject @Optional
    private Agent agent;

    // This is a OutputStream to keep track of number of bytes
    // written out to the stream
    private MeteredStream meter;

    protected static final String LOGS_DIR = "logs";
    private static final String LOG_FILE_NAME = "server.log";
    private static final String GZIP_EXTENSION = ".gz";

    private String absoluteServerLogName = null;
    private File absoluteFile = null;
    private int flushFrequency = 1;
    private int maxHistoryFiles = 10;
    private boolean logToFile = true;
    private boolean rotationOnDateChange;
    private String excludeFields;
    private Integer rotationLimitAttrValue;
    private Long rotationTimeLimitValue;
    private boolean compressionOnRotation;
    private boolean multiLineMode;
    private String fileHandlerFormatter = "";
    private String currentFileHandlerFormatter = "";
    private boolean logStandardStreams;

    private PrintStream oStdOutBackup = System.out;
    private PrintStream oStdErrBackup = System.err;
    private LoggingOutputStream stdoutOutputStream=null;
    private LoggingOutputStream stderrOutputStream=null;

    /** Initially the LogRotation will be off until the domain.xml value is read. */
    private int limitForFileRotation = 0;

    private BlockingQueue pendingRecords = new ArrayBlockingQueue<>(10000);

    /**Rotation can be done in 3 ways: 
    *
  1. Based on the Size: Rotate when some Threshold number of bytes are * written to server.log
  2. *
  3. Based on the Time: Rotate ever 'n' minutes, mostly 24 hrs
  4. *
  5. Rotate now
* For mechanisms 2 and 3 we will use this flag. The rotate() will always * be fired from the publish( ) method for consistency */ private AtomicBoolean rotationRequested = new AtomicBoolean(false); private final Object rotationLock = new Object(); private static final String LOG_ROTATE_DATE_FORMAT = "yyyy-MM-dd'T'HH-mm-ss"; private static final String DEFAULT_LOG_FILE_FORMATTER_CLASS_NAME = UniformLogFormatter.class.getName(); public static final int MINIMUM_ROTATION_LIMIT_VALUE = 500*1000; private BooleanLatch done = new BooleanLatch(); private boolean dayBasedFileRotation = false; private List logEventListeners = new ArrayList<>(); private Thread pump; protected String logFileProperty = ""; private final LogManager manager = LogManager.getLogManager(); private final String className = getClass().getName(); private static final String GF_FILE_HANDLER = GFFileHandler.class.getCanonicalName() ; private LogRecord logRecord = new LogRecord(Level.INFO, LogFacade.GF_VERSION_INFO); @Override public void postConstruct() { String filename = evaluateFileName(); File logFile = new File(filename); absoluteServerLogName = filename; if (!logFile.isAbsolute()) { logFile = new File(env.getInstanceRoot(), filename); absoluteServerLogName = env.getInstanceRoot() + File.separator + filename; } changeFileName(logFile); // Reading just few lines of log file to get the log formatter used. String strLine; int odlFormatter = 0; int uniformLogFormatter = 0; int jsonLogFormatter = 0; int otherFormatter = 0; boolean mustRotate = false; try (BufferedReader br = new BufferedReader(new FileReader(logFile))) { while ((strLine = br.readLine()) != null) { strLine = strLine.trim(); if (!strLine.equals("")) { if (LogFormatHelper.isUniformFormatLogHeader(strLine)) { // for ufl formatter uniformLogFormatter++; } else if (LogFormatHelper.isODLFormatLogHeader(strLine)) { // for ODL formatter odlFormatter++; } else if (LogFormatHelper.isJSONFormatLogHeader(strLine)) { //for JSON Log format jsonLogFormatter++; } else { otherFormatter++; // for other formatter } // Rotate on startup for custom log files if (otherFormatter > 0) { mustRotate = true; } // Read only first log record line and break out of the loop break; } } } catch (Exception e) { ErrorManager em = getErrorManager(); if (em != null) { em.error(e.getMessage(), e, ErrorManager.GENERIC_FAILURE); } } if (odlFormatter > 0) { currentFileHandlerFormatter = "com.sun.enterprise.server.logging.ODLLogFormatter"; } else if (uniformLogFormatter > 0) { currentFileHandlerFormatter = "com.sun.enterprise.server.logging.UniformLogFormatter"; } else if (jsonLogFormatter > 0) { currentFileHandlerFormatter = "fish.payara.enterprise.server.logging.JSONLogFormatter"; } String propertyValue = manager.getProperty(className + ".logtoFile"); boolean logToFile = true; if (propertyValue != null) { logToFile = Boolean.parseBoolean(propertyValue); } setLogToFile(logToFile); logRecord.setParameters(new Object[]{Version.getFullVersion()}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); propertyValue = manager.getProperty(className + ".rotationOnDateChange"); timeBasedRotation(propertyValue); propertyValue = manager.getProperty(className + ".rotationLimitInBytes"); rotationOnFileSizeLimit(propertyValue); //setLevel(Level.ALL); propertyValue = manager.getProperty(className + ".flushFrequency"); if (propertyValue != null) { try { flushFrequency = Integer.parseInt(propertyValue); } catch (NumberFormatException e) { logRecord = new LogRecord(Level.WARNING, LogFacade.INVALID_ATTRIBUTE_VALUE); logRecord.setParameters(new Object[]{propertyValue, "flushFrequency"}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } } if (flushFrequency <= 0) { flushFrequency = 1; } propertyValue = manager.getProperty(className + ".maxHistoryFiles"); try { if (propertyValue != null) { maxHistoryFiles = Integer.parseInt(propertyValue); } } catch (NumberFormatException e) { logRecord = new LogRecord(Level.WARNING, LogFacade.INVALID_ATTRIBUTE_VALUE); logRecord.setParameters(new Object[]{propertyValue, "maxHistoryFiles"}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } if (maxHistoryFiles < 0) { maxHistoryFiles = 10; } propertyValue = manager.getProperty(className + ".compressOnRotation"); compressionOnRotation = false; if (propertyValue != null) { compressionOnRotation = Boolean.parseBoolean(propertyValue); } propertyValue = manager.getProperty(className + ".logStandardStreams"); if (propertyValue != null) { logStandardStreams = Boolean.parseBoolean(propertyValue); if (logStandardStreams) { logStandardStreams(); } } String formatterName = manager.getProperty(className + ".formatter"); formatterName = (formatterName == null) ? DEFAULT_LOG_FILE_FORMATTER_CLASS_NAME : formatterName; // Below snapshot of the code is used to rotate server.log file on startup. It is used to avoid different format // log messages logged under same server.log file. fileHandlerFormatter = formatterName; if (mustRotate) { rotate(); } else if (fileHandlerFormatter != null && !fileHandlerFormatter.equals(currentFileHandlerFormatter)) { rotate(); } excludeFields = manager.getProperty(LogManagerService.EXCLUDE_FIELDS_PROPERTY); multiLineMode = Boolean.parseBoolean(manager.getProperty(LogManagerService.MULTI_LINE_MODE_PROPERTY)); configureLogFormatter(formatterName, excludeFields, multiLineMode); } private void configureLogFormatter(String formatterName, String excludeFields, boolean multiLineMode) { if (UniformLogFormatter.class.getName().equals(formatterName)) { configureUniformLogFormatter(excludeFields, multiLineMode); } else if (ODLLogFormatter.class.getName().equals(formatterName)) { configureODLFormatter(excludeFields, multiLineMode); } else if (JSONLogFormatter.class.getName().equals(formatterName)) { configureJSONFormatter(excludeFields); } else { // Custom formatter is configured in logging.properties // Check if the user specified formatter is in play else // log an error message Formatter currentFormatter = this.getFormatter(); if (currentFormatter == null || !currentFormatter.getClass().getName().equals(formatterName)) { Formatter formatter = findFormatterService(formatterName); if (formatter == null) { logRecord = new LogRecord(Level.SEVERE, LogFacade.INVALID_FORMATTER_CLASS_NAME); logRecord.setParameters(new Object[]{formatterName}); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); // Fall back to the GlassFish default configureDefaultFormatter(excludeFields, multiLineMode); } else { setFormatter(formatter); } } } formatterName = this.getFormatter().getClass().getName(); logRecord = new LogRecord(Level.INFO, LogFacade.LOG_FORMATTER_INFO); logRecord.setParameters(new Object[]{formatterName}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } private void timeBasedRotation(String propertyValue) { rotationOnDateChange = false; if (propertyValue != null) { rotationOnDateChange = Boolean.parseBoolean(propertyValue); } if (rotationOnDateChange) { rotationOnDateChange(); } else { rotationTimeLimitValue = 0L; try { propertyValue = manager.getProperty(className + ".rotationTimelimitInMinutes"); if (propertyValue != null) { rotationTimeLimitValue = Long.parseLong(propertyValue); } } catch (NumberFormatException e) { logRecord = new LogRecord(Level.WARNING, LogFacade.INVALID_ATTRIBUTE_VALUE); logRecord.setParameters(new Object[]{propertyValue, "rotationTimelimitInMinutes"}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } rotationOnTimeLimit(); } } private void rotationOnDateChange() { dayBasedFileRotation = true; rotationTimeLimitValue = 0L; int millisecondsInDay = 1000 * 60 * 60 * 24; Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy"); long systemTime = System.currentTimeMillis(); String nextDate = dateFormat.format(date.getTime() + millisecondsInDay); Date nextDay; try { nextDay = dateFormat.parse(nextDate); } catch (ParseException e) { nextDay = new Date(); logRecord = new LogRecord(Level.WARNING, LogFacade.DATE_PARSING_FAILED); logRecord.setParameters(new Object[]{nextDate}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } long nextDaySystemTime = nextDay.getTime(); rotationTimeLimitValue = nextDaySystemTime - systemTime; Task rotationTask = () -> { rotate(); return null; }; if (className.equals(GF_FILE_HANDLER)) { LogRotationTimer.getInstance().startTimer( new LogRotationTimerTask(rotationTask, rotationTimeLimitValue / 60000)); } else { PayaraNotificationLogRotationTimer.getInstance().startTimer( new LogRotationTimerTask(rotationTask, rotationTimeLimitValue / 60000)); } } private void rotationOnTimeLimit() { if (rotationTimeLimitValue > 0) { Task rotationTask = () -> { rotate(); return null; }; if (className.equals(GF_FILE_HANDLER)) { LogRotationTimer.getInstance().startTimer( new LogRotationTimerTask(rotationTask, rotationTimeLimitValue)); } else { PayaraNotificationLogRotationTimer.getInstance().startTimer( new LogRotationTimerTask(rotationTask, rotationTimeLimitValue)); } } } private void rotationOnFileSizeLimit(String propertyValue) { try { if (propertyValue != null) { rotationLimitAttrValue = Integer.parseInt(propertyValue); } else { rotationLimitAttrValue = DEFAULT_ROTATION_LIMIT_BYTES; } } catch (NumberFormatException e) { logRecord = new LogRecord(Level.WARNING, LogFacade.INVALID_ATTRIBUTE_VALUE); logRecord.setParameters(new Object[]{propertyValue, "rotationLimitInBytes"}); logRecord.setResourceBundle(ResourceBundle.getBundle(LogFacade.LOGGING_RB_NAME)); logRecord.setThreadID((int) Thread.currentThread().getId()); logRecord.setLoggerName(LogFacade.LOGGING_LOGGER_NAME); EarlyLogHandler.earlyMessages.add(logRecord); } // We set the LogRotation limit here. The rotation limit is the // Threshold for the number of bytes in the log file after which // it will be rotated. if (rotationLimitAttrValue >= MINIMUM_ROTATION_LIMIT_VALUE || rotationLimitAttrValue == DISABLE_LOG_FILE_ROTATION_VALUE) { setLimitForRotation(rotationLimitAttrValue); } } protected String evaluateFileName() { String cname = getClass().getName(); LogManager manager = LogManager.getLogManager(); logFileProperty = manager.getProperty(cname + ".file"); if(logFileProperty==null || logFileProperty.trim().equals("")) { logFileProperty = env.getInstanceRoot().getAbsolutePath() + File.separator + LOGS_DIR + File.separator + LOG_FILE_NAME; } return TranslatedConfigView.expandConfigValue(logFileProperty); } Formatter findFormatterService(String formatterName) { List formatterServices = Globals.getDefaultHabitat().getAllServices(Formatter.class); for (Formatter formatter : formatterServices) { if (formatter.getClass().getName().equals(formatterName)) { return formatter; } } return null; } private void configureDefaultFormatter(String excludeFields, boolean multiLineMode) { configureUniformLogFormatter(excludeFields, multiLineMode); } private void configureODLFormatter(String excludeFields, boolean multiLineMode) { // this loop is used for ODL formatter ODLLogFormatter formatterClass; // set the formatter if (agent != null) { formatterClass = new ODLLogFormatter(new AgentFormatterDelegate(agent), excludeFields); setFormatter(formatterClass); } else { formatterClass = new ODLLogFormatter(excludeFields); setFormatter(formatterClass); } formatterClass.setMultiLineMode(multiLineMode); formatterClass.noAnsi(); formatterClass.setLogEventBroadcaster(this); } private void configureUniformLogFormatter(String excludeFields, boolean multiLineMode) { LogManager manager = LogManager.getLogManager(); String cname = getClass().getName(); // this loop is used for UFL formatter UniformLogFormatter formatterClass; // set the formatter if (agent != null) { formatterClass = new UniformLogFormatter(new AgentFormatterDelegate(agent), excludeFields); setFormatter(formatterClass); } else { formatterClass = new UniformLogFormatter(excludeFields); setFormatter(formatterClass); } formatterClass.setMultiLineMode(multiLineMode); formatterClass.setLogEventBroadcaster(this); formatterClass.noAnsi(); String recordBeginMarker = manager.getProperty(cname + ".logFormatBeginMarker"); if (recordBeginMarker == null || ("").equals(recordBeginMarker)) { recordBeginMarker = RECORD_BEGIN_MARKER; } String recordEndMarker = manager.getProperty(cname + ".logFormatEndMarker"); if (recordEndMarker == null || ("").equals(recordEndMarker)) { recordEndMarker = RECORD_END_MARKER; } String recordFieldSeparator = manager.getProperty(cname + ".logFormatFieldSeparator"); if (recordFieldSeparator == null || ("").equals(recordFieldSeparator) || recordFieldSeparator.length() > 1) { recordFieldSeparator = RECORD_FIELD_SEPARATOR; } String recordDateFormat = manager.getProperty(cname + ".logFormatDateFormat"); if (recordDateFormat != null && !("").equals(recordDateFormat)) { SimpleDateFormat sdf = new SimpleDateFormat(recordDateFormat); try { sdf.format(new Date()); } catch (Exception e) { recordDateFormat = RECORD_DATE_FORMAT; } } else { recordDateFormat = RECORD_DATE_FORMAT; } formatterClass.setRecordBeginMarker(recordBeginMarker); formatterClass.setRecordEndMarker(recordEndMarker); formatterClass.setRecordDateFormat(recordDateFormat); formatterClass.setRecordFieldSeparator(recordFieldSeparator); } void initializePump() { pump = new Thread() { //Not using the PayaraExecutorService here as it prevents shutdown happening quickly, see PAYARA-4118 @Override public void run() { while (!done.isSignalled() && logToFile) { try { log(); } catch (Exception e) { // GLASSFISH-19125 // Continue the loop without exiting } } } }; pump.setName("GFFileHandler log pump"); pump.setDaemon(true); pump.start(); } @Override public void preDestroy() { // stop the Queue consumer thread. if (LogFacade.LOGGING_LOGGER.isLoggable(Level.FINE)) { LogFacade.LOGGING_LOGGER.fine("Logger handler killed"); } System.setOut(oStdOutBackup); System.setErr(oStdErrBackup); try { if (stdoutOutputStream != null) { stdoutOutputStream.close(); } if (stderrOutputStream != null) { stderrOutputStream.close(); } } catch (IOException ex) { ex.printStackTrace(); } done.tryReleaseShared(1); if (pump != null) { pump.interrupt(); } // drain and return all drainAllPendingRecords(); flush(); } private void drainAllPendingRecords() { drainPendingRecords(0); } /** * Drains the amount of {@link LogRecord}s in the pending records queue. * If passed in the amount <= 0 all of the records get drained. * @param flushAmount number of records to drain from the queue of pending records. */ private void drainPendingRecords(int flushAmount) { if (!pendingRecords.isEmpty()) { Collection records; if (flushAmount > 0) { records = new ArrayList<>(flushAmount); pendingRecords.drainTo(records, flushAmount); } else { records = new ArrayList<>(pendingRecords.size()); pendingRecords.drainTo(records); } for (LogRecord record : records) { super.publish(record); } } } /** * This method is invoked from LogManager.reInitializeLoggers() to * change the location of the file. */ void changeFileName(File file) { // If the file name is same as the current file name, there // is no need to change the filename if (file.equals(absoluteFile)) { return; } synchronized (rotationLock) { super.flush(); super.close(); try { openFile(file); absoluteFile = file; } catch (IOException ix) { new ErrorManager().error( "FATAL ERROR: COULD NOT OPEN LOG FILE. " + "Please Check to make sure that the directory for " + "Logfile exists. Currently reverting back to use the " + " default server.log", ix, ErrorManager.OPEN_FAILURE); try { // Reverting back to the old server.log openFile(absoluteFile); } catch (Exception e) { new ErrorManager().error( "FATAL ERROR: COULD NOT RE-OPEN SERVER LOG FILE. ", e, ErrorManager.OPEN_FAILURE); } } } } /** * A simple getter to access the current log file written by * this FileHandler. */ @Override public File getCurrentLogFile() { return absoluteFile; } /** * A package private method to set the limit for File Rotation. */ private synchronized void setLimitForRotation(int rotationLimitInBytes) { limitForFileRotation = rotationLimitInBytes; } private void configureJSONFormatter(String excludeFields) { // this loop is used for JSON formatter JSONLogFormatter formatterClass; // set the formatter if (agent != null) { formatterClass = new JSONLogFormatter(new AgentFormatterDelegate(agent), excludeFields); setFormatter(formatterClass); } else { formatterClass = new JSONLogFormatter(excludeFields); setFormatter(formatterClass); } formatterClass.setLogEventBroadcaster(this); } /** NOTE: This private class is copied from java.util.logging.FileHandler * A metered stream is a subclass of OutputStream that * (a) forwards all its output to a target stream * (b) keeps track of how many bytes have been written */ private static final class MeteredStream extends OutputStream { private volatile boolean isOpen; OutputStream out; long written; MeteredStream(OutputStream out, long written) { this.out = out; this.written = written; isOpen = true; } @Override public void write(int b) throws IOException { out.write(b); written++; } @Override public void write(byte[] buff) throws IOException { out.write(buff); written += buff.length; } @Override public void write(byte[] buff, int off, int len) throws IOException { out.write(buff, off, len); written += len; } @Override public void flush() throws IOException { out.flush(); } @Override public void close() throws IOException { if (isOpen) { isOpen = false; flush(); out.close(); } } } /** * Creates the file and initialized MeteredStream and passes it on to * Superclass (java.util.logging.StreamHandler). */ private void openFile(File file) throws IOException { // check that the parent directory exists. File parent = file.getParentFile(); if (!parent.exists() && !parent.mkdirs()) { throw new IOException(LOCAL_STRINGS.getLocalString("parent.dir.create.failed", "Failed to create the parent dir {0}", parent.getAbsolutePath())); } FileOutputStream fout = new FileOutputStream(file, true); BufferedOutputStream bout = new BufferedOutputStream(fout); meter = new MeteredStream(bout, file.length()); setOutputStream(meter); } /** * Request Rotation called from Rotation Timer Task or LogMBean */ void requestRotation() { rotationRequested.set(true); } /** * cleanup the history log file based on attributes set under logging.properties file". *

* If it is defined with valid number, we only keep that number of history logfiles; * If "max_history_files" is defined without value, then default that number to be 10; * If "max_history_files" is defined with value 0, any number of history files are kept. */ public void cleanUpHistoryLogFiles() { if (maxHistoryFiles == 0) return; synchronized (rotationLock) { File dir = absoluteFile.getParentFile(); String logFileName = absoluteFile.getName(); if (dir == null) return; File[] fset = dir.listFiles(); List candidates = new ArrayList<>(); for (int i = 0; fset != null && i < fset.length; i++) { if (!logFileName.equals(fset[i].getName()) && fset[i].isFile() && fset[i].getName().startsWith(logFileName)) { candidates.add(fset[i].getAbsolutePath()); } } if (candidates.size() <= maxHistoryFiles) return; Object[] paths = candidates.toArray(); java.util.Arrays.sort(paths); try { for (int i = 0; i < paths.length - maxHistoryFiles; i++) { File logFile = new File((String) paths[i]); boolean delFile = logFile.delete(); if (!delFile) { throw new IOException("Could not delete log file: " + logFile.getAbsolutePath()); } } } catch (Exception e) { new ErrorManager().error( "FATAL ERROR: COULD NOT DELETE LOG FILE.", e, ErrorManager.GENERIC_FAILURE); } } } /** * A Simple rotate method to close the old file and start the new one * when the limit is reached. */ public void rotate() { final GFFileHandler thisInstance = this; doPrivileged((PrivilegedAction) () -> { synchronized (thisInstance.rotationLock) { if (thisInstance.meter != null && thisInstance.meter.written <= 0) { return null; } thisInstance.flush(); thisInstance.close(); try { if (!absoluteFile.exists()) { File creatingDeletedLogFile = new File( absoluteFile.getAbsolutePath()); if (creatingDeletedLogFile.createNewFile()) { absoluteFile = creatingDeletedLogFile; } } else { File oldFile = absoluteFile; StringBuffer renamedFileName = new StringBuffer( absoluteFile + "_"); new SimpleDateFormat(LOG_ROTATE_DATE_FORMAT) .format(new Date(), renamedFileName, new FieldPosition(0)); File rotatedFile = new File(renamedFileName.toString()); boolean renameSuccess = oldFile.renameTo(rotatedFile); if (!renameSuccess) { // If we don't succeed with file rename which // most likely can happen on Windows because // of multiple file handles opened. We go through // Plan B to copy bytes explicitly to a renamed // file. FileUtils.copy(absoluteFile, rotatedFile); File freshServerLogFile = getLogFileName(); // We do this to make sure that server.log // contents are flushed out to start from a // clean file again after the rename.. FileOutputStream fo = new FileOutputStream( freshServerLogFile); fo.close(); } FileOutputStream oldFileFO = new FileOutputStream(oldFile); oldFileFO.close(); openFile(getLogFileName()); absoluteFile = getLogFileName(); // This will ensure that the log rotation timer // will be restarted if there is a value set // for time based log rotation restartTimeBasedLogRotation(); if (compressionOnRotation) { boolean compressed = gzipFile(rotatedFile); if (compressed) { boolean deleted = rotatedFile.delete(); if (!deleted) { throw new IOException("Could not delete uncompressed log file: " + rotatedFile.getAbsolutePath()); } } else { throw new IOException("Could not compress log file: " + rotatedFile.getAbsolutePath()); } } cleanUpHistoryLogFiles(); } } catch (IOException ix) { new ErrorManager().error("Error, could not rotate log file", ix, ErrorManager.GENERIC_FAILURE); } return null; } } ); } private void restartTimeBasedLogRotation() { if (dayBasedFileRotation) { if (className.equals(GF_FILE_HANDLER)) { LogRotationTimer.getInstance() .restartTimerForDayBasedRotation(); } else { PayaraNotificationLogRotationTimer.getInstance() .restartTimerForDayBasedRotation(); } } else { if (className.equals(GF_FILE_HANDLER)) { LogRotationTimer.getInstance() .restartTimer(); } else { PayaraNotificationLogRotationTimer.getInstance() .restartTimer(); } } } /** * 5005 * Retrieves the LogRecord from our Queue and store them in the file */ public void log() { if (logToFile) { LogRecord record; // take is blocking so we take one record off the queue try { record = pendingRecords.take(); super.publish(record); } catch (InterruptedException e) { return; } if (flushFrequency > 1) { // now try to read more. we end up blocking on the above take call if nothing is in the queue drainPendingRecords(flushFrequency - 1); } flush(); if ((rotationRequested.get()) || ((limitForFileRotation > 0) && (meter.written >= limitForFileRotation))) { // If we have written more than the limit set for the // file, or rotation requested from the Timer Task or LogMBean // start fresh with a new file after renaming the old file. synchronized (rotationLock) { rotate(); rotationRequested.set(false); } } } } /** * Publishes the logrecord storing it in our queue */ @Override public void publish(LogRecord record) { // the queue has shutdown, we are not processing any more records if (done.isSignalled()) { return; } // JUL LogRecord does not capture thread-name. Create a wrapper to // capture the name of the logging thread so that a formatter can // output correct thread-name if done asynchronously. Note that // this fix is limited to records published through this handler only. // *** // PAYARA-406 Check if the LogRecord passed in is already a GFLogRecord, // and just cast the passed record if it is GFLogRecord wrappedRecord = GFLogRecord.wrap(record, true); if (logToFile) { try { pendingRecords.add(wrappedRecord); } catch (IllegalStateException e) { // queue is full, start waiting. new ErrorManager().error("GFFileHandler: Queue full. Waiting to submit.", e, ErrorManager.GENERIC_FAILURE); try { pendingRecords.put(wrappedRecord); } catch (InterruptedException e1) { // too bad, record is lost... new ErrorManager().error("GFFileHandler: Waiting was interrupted. Log record lost.", e1, ErrorManager.GENERIC_FAILURE); } } } Formatter formatter = this.getFormatter(); if (!(formatter instanceof LogEventBroadcaster)) { LogEvent logEvent = new LogEventImpl(record); informLogEventListeners(logEvent); } } protected File getLogFileName() { return new File(absoluteServerLogName); } public boolean addLogEventListener(LogEventListener listener) { if (logEventListeners.contains(listener)) { return false; } return logEventListeners.add(listener); } public boolean removeLogEventListener(LogEventListener listener) { return logEventListeners.remove(listener); } @Override public void informLogEventListeners(LogEvent logEvent) { for (LogEventListener listener : logEventListeners) { listener.messageLogged(logEvent); } } private boolean gzipFile(File infile) { boolean status = false; try ( FileInputStream fis = new FileInputStream(infile); FileOutputStream fos = new FileOutputStream(infile.getCanonicalPath() + GZIP_EXTENSION); GZIPOutputStream gzos = new GZIPOutputStream(fos) ) { byte[] buffer = new byte[1024]; int len; while ((len=fis.read(buffer)) != -1 ) { gzos.write(buffer, 0, len); } gzos.finish(); status = true; } catch (IOException ix) { new ErrorManager().error("Error gzipping log file", ix, ErrorManager.GENERIC_FAILURE); } return status; } private void logStandardStreams() { // redirect stderr and stdout, a better way to do this //http://blogs.sun.com/nickstephen/entry/java_redirecting_system_out_and Logger _ologger = LogFacade.STDOUT_LOGGER; stdoutOutputStream = new LoggingOutputStream(_ologger, Level.INFO); LoggingOutputStream.LoggingPrintStream pout = stdoutOutputStream.new LoggingPrintStream(stdoutOutputStream); System.setOut(pout); Logger _elogger = LogFacade.STDERR_LOGGER; stderrOutputStream = new LoggingOutputStream(_elogger, Level.SEVERE); LoggingOutputStream.LoggingPrintStream perr = stderrOutputStream.new LoggingPrintStream(stderrOutputStream); System.setErr(perr); } public synchronized void setLogFile(String fileName) { String logFileName = TranslatedConfigView.expandConfigValue(fileName); File logFile = new File(logFileName); if (!logFile.isAbsolute()) { logFile = new File(env.getInstanceRoot(), logFileName); } changeFileName(logFile); absoluteServerLogName = logFileName; } public synchronized void setLogToFile(boolean logToFile) { this.logToFile = logToFile; initializePump(); } public synchronized void setRotationOnDateChange(boolean rotationOnDateChange) { this.rotationOnDateChange = rotationOnDateChange; restartTimeBasedLogRotation(); rotationOnDateChange(); } public synchronized void setMultiLineMode(boolean multiLineMode) { this.multiLineMode = multiLineMode; // Rotate log file to avoid different log format to be displayed in same log file rotate(); configureLogFormatter(fileHandlerFormatter, excludeFields, multiLineMode); } public synchronized void setFileHandlerFormatter(String fileHandlerFormatter) { this.fileHandlerFormatter = fileHandlerFormatter; // Rotate log file to avoid different log format to be displayed in same log file rotate(); configureLogFormatter(fileHandlerFormatter, excludeFields, multiLineMode); } public synchronized void setExcludeFields(String excludeFields) { this.excludeFields = excludeFields; // Rotate log file to avoid different log format to be displayed in same log file rotate(); configureLogFormatter(fileHandlerFormatter, excludeFields, multiLineMode); } public synchronized void setRotationLimitAttrValue(Integer rotationLimitAttrValue) { this.rotationLimitAttrValue = rotationLimitAttrValue; // We set the LogRotation limit here. The rotation limit is the // Threshold for the number of bytes in the log file after which // it will be rotated. if (rotationLimitAttrValue >= MINIMUM_ROTATION_LIMIT_VALUE || rotationLimitAttrValue == DISABLE_LOG_FILE_ROTATION_VALUE) { setLimitForRotation(rotationLimitAttrValue); } } public synchronized void setRotationTimeLimitValue(Long rotationTimeLimitValue) { this.rotationTimeLimitValue = rotationTimeLimitValue; restartTimeBasedLogRotation(); rotationOnTimeLimit(); } public synchronized void setMaxHistoryFiles(int maxHistoryFiles) { this.maxHistoryFiles = maxHistoryFiles; } public synchronized void setFlushFrequency(int flushFrequency) { this.flushFrequency = flushFrequency; } public synchronized void setCompressionOnRotation(boolean compressionOnRotation) { this.compressionOnRotation = compressionOnRotation; } public synchronized void setLogStandardStreams(boolean logStandardStreams) { this.logStandardStreams = logStandardStreams; if (logStandardStreams) { logStandardStreams(); } else { System.setOut(oStdOutBackup); System.setErr(oStdErrBackup); } } }