/* * Copyright 2013 Eediom 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 org.araqne.log.api; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; public class RotationFileLogger extends AbstractLogger implements Reconfigurable { private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(RotationFileLogger.class); private final File dataDir; private String charset; private Receiver receiver = new Receiver(); private MultilineLogExtractor extractor; public RotationFileLogger(LoggerSpecification spec, LoggerFactory factory) { super(spec, factory); this.dataDir = new File(System.getProperty("araqne.data.dir"), "araqne-log-api"); this.dataDir.mkdirs(); setExtracotr(); // try migration at boot File oldLastFile = getLastLogFile(); if (oldLastFile.exists()) { LastState lastState = getLastStateFromFile(); setStates(lastState.serialize()); oldLastFile.renameTo(new File(oldLastFile.getAbsolutePath() + ".migrated")); } } private void setExtracotr() { MultilineLogExtractor extractor = new MultilineLogExtractor(this, receiver); // optional Map<String, String> configs = getConfigs(); String datePatternRegex = configs.get("date_pattern"); if (datePatternRegex != null) { extractor.setDateMatcher(Pattern.compile(datePatternRegex).matcher("")); } // optional String dateLocale = configs.get("date_locale"); if (dateLocale == null) dateLocale = "en"; // optional String dateFormatString = getConfigs().get("date_format"); String timeZone = getConfigs().get("timezone"); if (dateFormatString != null) extractor.setDateFormat(new SimpleDateFormat(dateFormatString, new Locale(dateLocale)), timeZone); // optional String beginRegex = configs.get("begin_regex"); if (beginRegex != null) extractor.setBeginMatcher(Pattern.compile(beginRegex).matcher("")); String endRegex = configs.get("end_regex"); if (endRegex != null) extractor.setEndMatcher(Pattern.compile(endRegex).matcher("")); // optional charset = configs.get("charset"); if (charset == null) charset = "utf-8"; extractor.setCharset(charset); this.extractor = extractor; } @Override public void onConfigChange(Map<String, String> oldConfigs, Map<String, String> newConfigs) { setExtracotr(); if (!oldConfigs.get("file_path").equals(newConfigs.get("file_path"))) { setStates(new HashMap<String, Object>()); } } @Override protected void runOnce() { LastState oldState = null; if (!getStates().isEmpty()) { oldState = LastState.deserialize(getStates()); } String filePath = getConfigs().get("file_path"); File f = new File(filePath); if (!f.exists()) { slog.debug("araqne log api: rotation logger [{}] file [{}] not found", getFullName(), filePath); return; } if (!f.canRead()) { slog.debug("araqne log api: rotation logger [{}] file [{}] no read permission", getFullName(), filePath); return; } String firstLine = readFirstLine(f); long fileLength = f.length(); long offset = 0; if (oldState != null) { if (firstLine == null || !firstLine.equals(oldState.firstLine) || fileLength < oldState.lastLength) offset = 0; else offset = oldState.lastPosition; } AtomicLong lastPosition = new AtomicLong(offset); FileInputStream is = null; try { is = new FileInputStream(f); is.skip(offset); extractor.extract(is, lastPosition); } catch (Throwable t) { slog.error("araqne log api: rotation logger [" + getFullName() + "] cannot read file", t); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } LastState newState = new LastState(firstLine, lastPosition.get(), fileLength); setStates(newState.serialize()); } } private String readFirstLine(File f) { FileInputStream is = null; BufferedReader br = null; try { is = new FileInputStream(f); br = new BufferedReader(new InputStreamReader(is, charset)); return br.readLine(); } catch (Throwable t) { slog.error("araqne log api: cannot read first line, logger [" + getFullName() + "]", t); return null; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } if (is != null) { try { is.close(); } catch (IOException e) { } } } } private LastState getLastStateFromFile() { FileInputStream is = null; BufferedReader br = null; try { File f = getLastLogFile(); if (!f.exists() || !f.canRead()) return null; is = new FileInputStream(f); br = new BufferedReader(new InputStreamReader(is, "utf-8")); long len = Long.valueOf(br.readLine()); long pos = Long.valueOf(br.readLine()); String line = br.readLine(); return new LastState(line, pos, len); } catch (Throwable t) { slog.error("araqne log api: cannot read last position", t); return null; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } } } protected File getLastLogFile() { return new File(dataDir, "rotation-" + getName() + ".lastlog"); } private static class LastState { private String firstLine; private long lastPosition; private long lastLength; public LastState(String firstLine, long lastPosition, long lastLength) { this.firstLine = firstLine; this.lastPosition = lastPosition; this.lastLength = lastLength; } public static LastState deserialize(Map<String, Object> m) { String firstLine = (String) m.get("first_line"); long lastPosition = Long.valueOf(m.get("last_position").toString()); long lastLength = Long.valueOf(m.get("last_length").toString()); return new LastState(firstLine, lastPosition, lastLength); } public Map<String, Object> serialize() { HashMap<String, Object> m = new HashMap<String, Object>(); m.put("first_line", firstLine); m.put("last_position", lastPosition); m.put("last_length", lastLength); return m; } } private class Receiver extends AbstractLogPipe { @Override public void onLog(Logger logger, Log log) { write(log); } @Override public void onLogBatch(Logger logger, Log[] logs) { writeBatch(logs); } } }