/** * Copyright 2016, Xiaomi. * All rights reserved. * Author: xiajun@xiaomi.com */ package com.xiaomi.infra.galaxy.lcs.log.core.file; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.TreeMap; import com.google.common.base.Preconditions; import com.xiaomi.infra.galaxy.lcs.log.core.ILogger; import com.xiaomi.infra.galaxy.talos.client.serialization.MessageSerialization; import com.xiaomi.infra.galaxy.talos.client.serialization.MessageSerializationFactory; import com.xiaomi.infra.galaxy.talos.thrift.Message; public class LCSFileWriter { private ILogger logger; private String topicName; private String rootFilePath; private long maxFileNumber; private long rotateFileBytes; private long rotateFileIntervalMillis; private String topicFilePath; private long curFileNumber; private long curFileIndex; private String curFilePath; private long curFileBytes; private long lastRotateFileTimestamp; private FileOutputStream fileOutputStream; private DataOutputStream dataOutputStream; public LCSFileWriter(ILogger logger, String topicName, String rootFilePath, long maxFileNumber, long rotateFileBytes, long rotateFileIntervalMillis) { this.logger = logger; this.topicName = topicName; this.rootFilePath = rootFilePath; this.maxFileNumber = maxFileNumber; this.rotateFileBytes = rotateFileBytes; this.rotateFileIntervalMillis = rotateFileIntervalMillis; } public void init() { if (rootFilePath.isEmpty()) { throw new RuntimeException("please set rootFilePath"); } if (!rootFilePath.startsWith("/")) { throw new RuntimeException("please set rootFilePath as the absolute file path"); } File file = new File(rootFilePath); if (!file.exists()) { throw new RuntimeException("please make sure rootFilePath: " + rootFilePath + " exist"); } topicFilePath = FileUtils.formatTopicFilePath(rootFilePath, topicName); // ensure absoluteFilePath exist; file = new File(topicFilePath); if (!file.exists()) { if (!file.mkdir()) { throw new RuntimeException("create dir: " + topicFilePath + " failed"); } } String topicTempFilePath = FileUtils.formatTempTopicFilePath(topicFilePath); // ensure absoluteFilePath exist; file = new File(topicTempFilePath); if (!file.exists()) { if (!file.mkdir()) { throw new RuntimeException("create dir: " + topicTempFilePath + " failed"); } } lastRotateFileTimestamp = 0; curFileIndex = -1; } public void close() { try { closeCurFile(); logger.info("Topic: " + topicName + " close curFile:" + curFilePath + " success"); } catch (Exception e) { logger.info("Topic: " + topicName + "close curFile: " + curFilePath + " failed", e); } } /** * we can call writeMessage with emptyMessageList in order to rotate file * @param messageList * @throws java.io.IOException */ public void writeMessage(List<Message> messageList) throws IOException { if (shouldRotateFile()) { try { rotateFile(); } catch (IOException e) { logger.error("Topic: " + topicName + " rotateFile failed", e); throw e; } } if (messageList.size() == 0) { return; } for (Message message : messageList) { try { MessageSerialization.serializeMessage(message, dataOutputStream, MessageSerializationFactory.getDefaultMessageVersion()); } catch (IOException e) { logger.error("Topic: " + topicName + " writeMessage to file: " + curFilePath + " failed", e); try { rotateFile(); } catch (Exception e1) { logger.error("Topic: " + topicName + " rotateFile failed", e); } throw e; } } try { dataOutputStream.flush(); curFileBytes = dataOutputStream.size(); } catch (IOException e) { logger.error("Topic: " + topicName + " flushMessage to file: " + curFilePath + " failed", e); try { rotateFile(); } catch (Exception e1) { logger.error("Topic: " + topicName + " rotateFile failed", e); } throw e; } } private boolean shouldRotateFile() { if (fileOutputStream == null) { return true; } if (curFileBytes >= rotateFileBytes) { return true; } if (curFileBytes > 0 && System.currentTimeMillis() - lastRotateFileTimestamp >= rotateFileIntervalMillis) { return true; } return false; } private void rotateFile() throws IOException { closeCurFile(); // rename temp file; TreeMap<String, File> fileTreeMap = FileUtils.listFile(FileUtils.formatTempTopicFilePath(topicFilePath), topicName); if (fileTreeMap.size() != 0) { Preconditions.checkArgument(fileTreeMap.size() == 1, "fileTreeMap have more than one data: " + fileTreeMap); String tempFilePath = fileTreeMap.lastEntry().getValue().getAbsolutePath(); String filePath = FileUtils.formatFilePath(topicFilePath, topicName, FileUtils.getFileIndexByTempFilePath(tempFilePath)); FileUtils.renameTempFilePath(tempFilePath, filePath); } // reset fileNumber and fileIndex; fileTreeMap = FileUtils.listFile(topicFilePath, topicName); curFileNumber = fileTreeMap.size(); if (fileTreeMap.size() != 0) { String lastFilePath = fileTreeMap.lastEntry().getValue().getAbsolutePath(); if (curFileIndex == -1) { curFileIndex = FileUtils.getFileIndexByFilePath(lastFilePath); } else { Preconditions.checkArgument(curFileIndex == FileUtils.getFileIndexByFilePath(lastFilePath)); } } else { curFileIndex = -1; } // check weather rotate file; if (curFileNumber >= maxFileNumber) { throw new IOException("Too many buffer files, curBufferFileNumber: " + curFileNumber + ", maxBufferFileNumber: " + maxFileNumber); } // create cur file curFileNumber++; curFileIndex++; curFilePath = FileUtils.formatTempFilePath(topicFilePath, topicName, curFileIndex); FileUtils.createFile(curFilePath); fileOutputStream = new FileOutputStream(curFilePath); dataOutputStream = new DataOutputStream(fileOutputStream); curFileBytes = 0; lastRotateFileTimestamp = System.currentTimeMillis(); logger.info("Topic: " + topicName + " rotate new file: " + curFilePath + ", curFileNumber: " + curFileNumber); } private void closeCurFile() throws IOException { // close cur file; if (fileOutputStream != null) { fileOutputStream.flush(); fileOutputStream.close(); fileOutputStream = null; dataOutputStream.close(); dataOutputStream = null; } } }