Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.pgm.util; 16 : 17 : import static java.util.concurrent.TimeUnit.HOURS; 18 : import static java.util.concurrent.TimeUnit.MILLISECONDS; 19 : 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.common.io.ByteStreams; 22 : import com.google.gerrit.extensions.events.LifecycleListener; 23 : import com.google.gerrit.lifecycle.LifecycleModule; 24 : import com.google.gerrit.server.config.GerritServerConfig; 25 : import com.google.gerrit.server.config.SitePaths; 26 : import com.google.gerrit.server.git.WorkQueue; 27 : import com.google.inject.Inject; 28 : import java.io.IOException; 29 : import java.io.InputStream; 30 : import java.io.OutputStream; 31 : import java.nio.file.DirectoryStream; 32 : import java.nio.file.Files; 33 : import java.nio.file.Path; 34 : import java.time.LocalDateTime; 35 : import java.time.ZoneId; 36 : import java.time.temporal.ChronoUnit; 37 : import java.util.concurrent.Future; 38 : import java.util.zip.GZIPOutputStream; 39 : import org.eclipse.jgit.lib.Config; 40 : 41 : /** Compresses the old error logs. */ 42 : public class LogFileCompressor implements Runnable { 43 138 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 44 : 45 138 : public static class LogFileCompressorModule extends LifecycleModule { 46 : @Override 47 : protected void configure() { 48 138 : listener().to(Lifecycle.class); 49 138 : } 50 : } 51 : 52 : static class Lifecycle implements LifecycleListener { 53 : private final WorkQueue queue; 54 : private final LogFileCompressor compressor; 55 : private final boolean enabled; 56 : 57 : @Inject 58 138 : Lifecycle(WorkQueue queue, LogFileCompressor compressor, @GerritServerConfig Config config) { 59 138 : this.queue = queue; 60 138 : this.compressor = compressor; 61 138 : this.enabled = config.getBoolean("log", "compress", true); 62 138 : } 63 : 64 : @Override 65 : public void start() { 66 138 : if (!enabled) { 67 0 : return; 68 : } 69 : // compress log once and then schedule compression every day at 11:00pm 70 138 : queue.getDefaultQueue().execute(compressor); 71 138 : ZoneId zone = ZoneId.systemDefault(); 72 138 : LocalDateTime now = LocalDateTime.now(zone); 73 138 : long milliSecondsUntil11pm = 74 138 : now.until(now.withHour(23).withMinute(0).withSecond(0).withNano(0), ChronoUnit.MILLIS); 75 : @SuppressWarnings("unused") 76 138 : Future<?> possiblyIgnoredError = 77 : queue 78 138 : .getDefaultQueue() 79 138 : .scheduleAtFixedRate( 80 138 : compressor, milliSecondsUntil11pm, HOURS.toMillis(24), MILLISECONDS); 81 138 : } 82 : 83 : @Override 84 138 : public void stop() {} 85 : } 86 : 87 : private final Path logs_dir; 88 : 89 : @Inject 90 138 : LogFileCompressor(SitePaths site) { 91 138 : logs_dir = resolve(site.logs_dir); 92 138 : } 93 : 94 : private static Path resolve(Path p) { 95 : try { 96 138 : return p.toRealPath().normalize(); 97 0 : } catch (IOException e) { 98 0 : return p.toAbsolutePath().normalize(); 99 : } 100 : } 101 : 102 : @Override 103 : public void run() { 104 : try { 105 138 : if (!Files.isDirectory(logs_dir)) { 106 0 : return; 107 : } 108 138 : try (DirectoryStream<Path> list = Files.newDirectoryStream(logs_dir)) { 109 138 : for (Path entry : list) { 110 8 : if (!isLive(entry) && !isCompressed(entry) && isLogFile(entry)) { 111 0 : compress(entry); 112 : } 113 8 : } 114 0 : } catch (IOException e) { 115 0 : logger.atSevere().withCause(e).log("Error listing logs to compress in %s", logs_dir); 116 138 : } 117 0 : } catch (Exception e) { 118 0 : logger.atSevere().withCause(e).log("Failed to compress log files: %s", e.getMessage()); 119 138 : } 120 138 : } 121 : 122 : private boolean isLive(Path entry) { 123 8 : String name = entry.getFileName().toString(); 124 8 : return name.endsWith("_log") 125 0 : || name.endsWith(".log") 126 0 : || name.endsWith(".run") 127 0 : || name.endsWith(".pid") 128 8 : || name.endsWith(".json"); 129 : } 130 : 131 : private boolean isCompressed(Path entry) { 132 0 : String name = entry.getFileName().toString(); 133 0 : return name.endsWith(".gz") // 134 0 : || name.endsWith(".zip") // 135 0 : || name.endsWith(".bz2"); 136 : } 137 : 138 : private boolean isLogFile(Path entry) { 139 0 : return Files.isRegularFile(entry); 140 : } 141 : 142 : private void compress(Path src) { 143 0 : Path dst = src.resolveSibling(src.getFileName() + ".gz"); 144 0 : Path tmp = src.resolveSibling(".tmp." + src.getFileName()); 145 : try { 146 0 : try (InputStream in = Files.newInputStream(src); 147 0 : OutputStream out = new GZIPOutputStream(Files.newOutputStream(tmp))) { 148 0 : ByteStreams.copy(in, out); 149 : } 150 0 : tmp.toFile().setReadOnly(); 151 : try { 152 0 : Files.move(tmp, dst); 153 0 : } catch (IOException e) { 154 0 : throw new IOException("Cannot rename " + tmp + " to " + dst, e); 155 0 : } 156 0 : Files.delete(src); 157 0 : } catch (IOException e) { 158 0 : logger.atSevere().withCause(e).log("Cannot compress %s", src); 159 : try { 160 0 : Files.deleteIfExists(tmp); 161 0 : } catch (IOException e2) { 162 0 : logger.atWarning().withCause(e2).log("Failed to delete temporary log file %s", tmp); 163 0 : } 164 0 : } 165 0 : } 166 : 167 : @Override 168 : public String toString() { 169 132 : return "Log File Compressor"; 170 : } 171 : }