/* * 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.addthis.hydra.minion; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Collections; import java.util.Optional; import java.util.stream.Stream; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import com.addthis.basis.util.LessBytes; import com.addthis.maljson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class LogUtils { private static final Logger log = LoggerFactory.getLogger(LogUtils.class); /** Streams task log files from newest to oldest. The returned Stream should be closed. */ public static Stream<Path> streamTaskLogsByName(JobTask task) throws IOException { Path logDir = task.logDir.toPath(); return Files.list(logDir) .filter(path -> Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) .sorted(Collections.reverseOrder()); } public static Optional<Path> getNthNewestLog(JobTask task, int runsAgo, String suffix) throws IOException { // short circuit for the most common case if (runsAgo == 0) { if ("err".equals(suffix)) { return Optional.of(task.logErr.toPath()); } else if ("out".equals(suffix)) { return Optional.of(task.logOut.toPath()); } } try (Stream<Path> paths = streamTaskLogsByName(task) .filter(path -> path.toString().endsWith(suffix)) .skip(runsAgo)) { return paths.findFirst(); } } public static JSONObject readLogLines(JobTask task, int startOffset, int lines, int runsAgo, String suffix) { try { Optional<Path> logFile = getNthNewestLog(task, runsAgo, suffix); if (!logFile.isPresent()) { log.info("no log file found for task {}, {} runs ago, with suffix {}", task, runsAgo, suffix); return new JSONObject(); } return readLogLines(logFile.get().toFile(), startOffset, lines); } catch (Exception ex) { log.warn("exception while trying to serve task logs via http", ex); return new JSONObject(); } } public static JSONObject readLogLines(File file, int startOffset, int lines) { JSONObject json = new JSONObject(); String content = ""; long off = 0; long endOffset = 0; int linesRead = 0; int bytesRead = 0; try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { long len = raf.length(); //if startoffset is negative, tail the content if (startOffset < 0 || startOffset > len) { off = len; while (lines > 0 && --off >= 0) { raf.seek(off); if (off == 0 || raf.read() == '\n') { lines--; linesRead++; } } bytesRead = (int) (len - off); byte[] buf = new byte[bytesRead]; raf.read(buf); content = LessBytes.toString(buf); endOffset = len; } else if (len > 0 && startOffset < len) { off = startOffset; while (lines > 0 && off < len) { raf.seek(off++); if (raf.read() == '\n') { lines--; linesRead++; } } bytesRead = (int) (off - startOffset); byte[] buf = new byte[bytesRead]; raf.seek(startOffset); raf.read(buf); content = LessBytes.toString(buf); endOffset = off; } else if (startOffset == len) { endOffset = len; linesRead = 0; content = ""; } json.put("offset", endOffset); json.put("lines", linesRead); json.put("lastModified", file.lastModified()); json.put("out", content); } catch (Exception e) { log.warn("", e); } return json; } public static String tail(File file, int lines) { try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { long len = raf.length(); if (len <= 0) { return ""; } long off = len; while (lines > 0 && --off >= 0) { raf.seek(off); if (off == 0 || raf.read() == '\n') { lines--; } } byte[] buf = new byte[(int) (len - off)]; raf.read(buf); return LessBytes.toString(buf); } catch (Exception e) { log.warn("", e); } return ""; } public static String head(File file, int lines) { try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { long len = raf.length(); if (len <= 0) { return ""; } long off = 0; while (lines > 0 && off < len) { raf.seek(off++); if (raf.read() == '\n') { lines--; } } byte[] buf = new byte[(int) off]; raf.seek(0); raf.read(buf); return LessBytes.toString(buf); } catch (Exception e) { log.warn("", e); } return ""; } private LogUtils() {} }