/** * Copyright 2014 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.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import org.araqne.log.api.impl.FileUtils; public class MultiRotationFileLogger extends AbstractLogger implements Reconfigurable { private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(MultiRotationFileLogger.class); public MultiRotationFileLogger(LoggerSpecification spec, LoggerFactory factory) { super(spec, factory); } @Override public void onConfigChange(Map<String, String> oldConfigs, Map<String, String> newConfigs) { // purge old states if base path is changed if (!oldConfigs.get("base_path").equals(newConfigs.get("base_path")) || !oldConfigs.get("filename_pattern").equals(newConfigs.get("filename_pattern"))) { setStates(new HashMap<String, Object>()); } } @Override protected void runOnce() { Map<String, String> configs = getConfigs(); String basePath = configs.get("base_path"); String charset = configs.get("charset"); if (charset == null) charset = "utf-8"; Pattern fileNamePattern = Pattern.compile(configs.get("filename_pattern")); Receiver receiver = new Receiver(); receiver.fileTag = configs.get("file_tag"); MultilineLogExtractor extractor = MultilineLogExtractor.build(this, receiver); List<File> logFiles = FileUtils.matches(basePath, fileNamePattern); Map<String, RotationState> rotationStates = RotationStateHelper.deserialize(getStates()); for (File f : logFiles) { receiver.fileName = f.getName(); processFile(f, charset, extractor, rotationStates); } setStates(RotationStateHelper.serialize(rotationStates)); } private void processFile(File f, String charset, MultilineLogExtractor extractor, Map<String, RotationState> rotationStates) { if (!f.canRead()) { slog.debug("araqne log api: multi-rotation logger [{}] file no read permission", getFullName(), f.getAbsolutePath()); return; } RotationState oldState = rotationStates.get(f.getName()); String firstLine = readFirstLine(f, charset); long fileLength = f.length(); long offset = 0; if (oldState != null) { if (firstLine == null || !firstLine.equals(oldState.getFirstLine()) || fileLength < oldState.getLastLength()) offset = 0; else offset = oldState.getLastPosition(); } 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: multi-rotation logger [" + getFullName() + "] cannot read file", t); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } RotationState newState = new RotationState(firstLine, lastPosition.get(), fileLength); rotationStates.put(f.getName(), newState); } } private String readFirstLine(File f, String charset) { 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, multi-rotation 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 class Receiver extends AbstractLogPipe { private String fileTag; private String fileName; @Override public void onLog(Logger logger, Log log) { if (fileTag != null) log.getParams().put(fileTag, fileName); write(log); } @Override public void onLogBatch(Logger logger, Log[] logs) { if (fileTag != null) { for (Log log : logs) log.getParams().put(fileTag, fileName); } writeBatch(logs); } } }