// Copyright (C) 2009 The Android Open Source Project // // 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.google.gerrit.pgm.util; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.google.common.io.ByteStreams; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDate; import java.time.ZoneId; import java.util.concurrent.Future; import java.util.zip.GZIPOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Compresses the old error logs. */ public class LogFileCompressor implements Runnable { private static final Logger log = LoggerFactory.getLogger(LogFileCompressor.class); public static class Module extends LifecycleModule { @Override protected void configure() { listener().to(Lifecycle.class); } } static class Lifecycle implements LifecycleListener { private final WorkQueue queue; private final LogFileCompressor compressor; @Inject Lifecycle(WorkQueue queue, LogFileCompressor compressor) { this.queue = queue; this.compressor = compressor; } @Override public void start() { //compress log once and then schedule compression every day at 11:00pm queue.getDefaultQueue().execute(compressor); ZoneId zone = ZoneId.systemDefault(); LocalDate now = LocalDate.now(zone); long milliSecondsUntil11pm = now.atStartOfDay(zone).plusHours(23).toInstant().toEpochMilli(); @SuppressWarnings("unused") Future<?> possiblyIgnoredError = queue .getDefaultQueue() .scheduleAtFixedRate( compressor, milliSecondsUntil11pm, HOURS.toMillis(24), MILLISECONDS); } @Override public void stop() {} } private final Path logs_dir; @Inject LogFileCompressor(SitePaths site) { logs_dir = resolve(site.logs_dir); } private static Path resolve(Path p) { try { return p.toRealPath().normalize(); } catch (IOException e) { return p.toAbsolutePath().normalize(); } } @Override public void run() { if (!Files.isDirectory(logs_dir)) { return; } try (DirectoryStream<Path> list = Files.newDirectoryStream(logs_dir)) { for (Path entry : list) { if (!isLive(entry) && !isCompressed(entry) && isLogFile(entry)) { compress(entry); } } } catch (IOException e) { log.error("Error listing logs to compress in " + logs_dir, e); } } private boolean isLive(Path entry) { String name = entry.getFileName().toString(); return name.endsWith("_log") || name.endsWith(".log") || name.endsWith(".run") || name.endsWith(".pid"); } private boolean isCompressed(Path entry) { String name = entry.getFileName().toString(); return name.endsWith(".gz") // || name.endsWith(".zip") // || name.endsWith(".bz2"); } private boolean isLogFile(Path entry) { return Files.isRegularFile(entry); } private void compress(Path src) { Path dst = src.resolveSibling(src.getFileName() + ".gz"); Path tmp = src.resolveSibling(".tmp." + src.getFileName()); try { try (InputStream in = Files.newInputStream(src); OutputStream out = new GZIPOutputStream(Files.newOutputStream(tmp))) { ByteStreams.copy(in, out); } tmp.toFile().setReadOnly(); try { Files.move(tmp, dst); } catch (IOException e) { throw new IOException("Cannot rename " + tmp + " to " + dst, e); } Files.delete(src); } catch (IOException e) { log.error("Cannot compress " + src, e); try { Files.deleteIfExists(tmp); } catch (IOException e2) { log.warn("Failed to delete temporary log file " + tmp, e2); } } } @Override public String toString() { return "Log File Compressor"; } }