/*
* Copyright 2014-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;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.io.PathListing;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.logging.Handler;
import java.util.logging.LogManager;
import java.util.logging.Logger; // NOPMD
import org.stringtemplate.v4.ST;
/**
* Constructed by java.util.logging.LogManager via the system property
* java.util.logging.LogManager.config.
*
* <p>Extends LogManager's support for a single logging.properties file to support a three-level
* config. Each existent property file is concatenated together and used as a single LogManager
* configuration, with later entries overriding earlier ones.
*
* <p>1) $BUCK_DIRECTORY/config/logging.properties.st 2) $PROJECT_ROOT/.bucklogging.properties 3)
* $PROJECT_ROOT/.bucklogging.local.properties
*/
public class LogConfig {
private static final byte[] NEWLINE = {'\n'};
/** Default constructor, called by LogManager. */
public LogConfig() throws IOException {
setupLogging(
LogConfigSetup.builder()
.from(LogConfigSetup.DEFAULT_SETUP)
.setLogFilePrefix("launch-")
.setCount(1)
.build());
}
/**
* Creates the log output directory and concatenates logging.properties files together to
* configure or re-configure LogManager.
*/
public static synchronized void setupLogging(LogConfigSetup logConfigSetup) throws IOException {
// Bug JDK-6244047: The default FileHandler does not handle the directory not existing,
// so we have to create it before any log statements actually run.
Files.createDirectories(logConfigSetup.getLogDir());
if (logConfigSetup.getRotateLog()) {
try {
deleteOldLogFiles(logConfigSetup);
} catch (IOException e) {
System.err.format("Error deleting old log files (ignored): %s\n", e.getMessage());
}
}
ImmutableList.Builder<InputStream> inputStreamsBuilder = ImmutableList.builder();
if (!LogConfigPaths.MAIN_PATH.isPresent()) {
System.err.format(
"Error: Couldn't read system property %s (it should be set by buck_common or buck.cmd)\n",
LogConfigPaths.BUCK_CONFIG_STRING_TEMPLATE_FILE_PROPERTY);
} else {
if (!addInputStreamForTemplate(
LogConfigPaths.MAIN_PATH.get(), logConfigSetup, inputStreamsBuilder)) {
System.err.format(
"Error: Couldn't open logging properties file %s\n", LogConfigPaths.MAIN_PATH.get());
}
}
// We ignore the return value for these files; they don't need to exist.
addInputStreamForPath(LogConfigPaths.PROJECT_PATH, inputStreamsBuilder);
addInputStreamForPath(LogConfigPaths.LOCAL_PATH, inputStreamsBuilder);
// Concatenate each of the files together and read them in as a single properties file
// for log settings.
try (InputStream is =
new SequenceInputStream(Iterators.asEnumeration(inputStreamsBuilder.build().iterator()))) {
LogManager.getLogManager().readConfiguration(is);
}
}
public static void flushLogs() {
Logger rootLogger = LogManager.getLogManager().getLogger("");
if (rootLogger == null) {
return;
}
Handler[] handlers = rootLogger.getHandlers();
if (handlers == null) {
return;
}
for (Handler h : Arrays.asList(handlers)) {
h.flush();
}
}
private static boolean addInputStreamForTemplate(
Path path,
LogConfigSetup logConfigSetup,
ImmutableList.Builder<InputStream> inputStreamsBuilder)
throws IOException {
try {
String template = new String(Files.readAllBytes(path), Charsets.UTF_8);
ST st = new ST(template);
st.add(
"default_file_pattern",
MorePaths.pathWithUnixSeparators(logConfigSetup.getLogFilePath()).toString());
st.add("default_count", logConfigSetup.getCount());
st.add("default_max_size_bytes", logConfigSetup.getMaxLogSizeBytes());
String result = st.render();
inputStreamsBuilder.add(new ByteArrayInputStream(result.getBytes(Charsets.UTF_8)));
inputStreamsBuilder.add(new ByteArrayInputStream(NEWLINE));
return true;
} catch (FileNotFoundException e) {
return false;
}
}
private static boolean addInputStreamForPath(
Path path, ImmutableList.Builder<InputStream> inputStreamsBuilder) {
try {
inputStreamsBuilder.add(new FileInputStream(path.toString()));
// Handle the case where a file doesn't end with a newline.
inputStreamsBuilder.add(new ByteArrayInputStream(NEWLINE));
return true;
} catch (FileNotFoundException e) {
return false;
}
}
private static void deleteOldLogFiles(LogConfigSetup logConfigSetup) throws IOException {
for (Path path :
PathListing.listMatchingPathsWithFilters(
logConfigSetup.getLogDir(),
logConfigSetup.getLogFilePrefix() + "*.log*",
PathListing.GET_PATH_MODIFIED_TIME,
PathListing.FilterMode.EXCLUDE,
Optional.empty(),
Optional.of(logConfigSetup.getMaxLogSizeBytes()))) {
Files.deleteIfExists(path);
}
}
}