/* * Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved * http://www.griddynamics.com * * This library is free software; you can redistribute it and/or modify it under the terms of * the Apache License; either * version 2.0 of the License, or any later version. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.griddynamics.jagger.storage.fs.logging; import com.google.common.base.Preconditions; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.io.Closeables; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.griddynamics.jagger.storage.FileStorage; import com.griddynamics.jagger.storage.Namespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author Alexey Kiselyov, Vladimir Shulga * Date: 20.07.11 */ public abstract class BufferedLogWriter implements LogWriter { private final Logger log = LoggerFactory.getLogger(BufferedLogWriter.class); private final int flushSize; private final int bufferSize; private final Log FLUSH_LOG = new Log("FLUSH_LOG", null); private FileStorage fileStorage; private ArrayBlockingQueue<Log> buffer; private Object lock = new Object(); private ExecutorService executor = Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setDaemon(true).build()); public BufferedLogWriter(int flushSize, int bufferSize, FileStorage fileStorage){ this.flushSize = flushSize; this.fileStorage = fileStorage; this.bufferSize = bufferSize; buffer = new ArrayBlockingQueue<Log>(bufferSize, false); executor.submit(new FlushingWriter()); } @Override public void log(String sessionId, String dir, String kernelId, Serializable logEntry) { String path = Namespace.of(sessionId, dir, kernelId).toString(); log(path, logEntry); } public void log(String path, Serializable logEntry) { Preconditions.checkNotNull(logEntry, "Null is not supported"); try { buffer.put(new Log(path, logEntry)); } catch (InterruptedException e) { log.error("Failed to write {} by path: {}", new Object[]{logEntry, path}, e); Thread.currentThread().interrupt(); } } @Override public synchronized void flush() { try { synchronized (lock){ buffer.put(FLUSH_LOG); lock.wait(); } } catch (InterruptedException e) { log.error("Failed during flushing", e); Thread.currentThread().interrupt(); } } private class FlushingWriter implements Runnable{ @Override public void run() { ArrayList<Log> temp = new ArrayList<Log>(flushSize+flushSize/2); boolean shutdown = false; while (!shutdown){ Multimap<String, Serializable> map = LinkedHashMultimap.create(); int size = 0; boolean need_to_flash = false; try { while (size < flushSize){ temp.add(buffer.take()); size++; size += buffer.drainTo(temp); need_to_flash = drainToMap(temp, map); if (need_to_flash){ break; } } } catch (InterruptedException e) { shutdown = true; }finally { writeToFileStorage(map, need_to_flash); } } } private boolean drainToMap(Collection<Log> logs, Multimap<String, Serializable> map){ boolean need_to_flush = false; for (Log current : logs){ if (isFlushLog(current)){ need_to_flush = true; continue; } map.put(current.getPath(), current.getValue()); } logs.clear(); return need_to_flush; } private boolean isFlushLog(Log current){ return current == FLUSH_LOG; } private void writeToFileStorage(Multimap<String, Serializable> map, boolean unlockFlush){ long startTime = System.currentTimeMillis(); log.debug("Flush queue. flushId {}. Current queue size is {}", startTime, map.size()); try { for (String logFilePath : map.keySet()) { Collection<Serializable> fileQueue = map.get(logFilePath); if (fileQueue.isEmpty()) { continue; } OutputStream os = null; LogWriterOutput objectOutput = null; try { if (fileStorage.exists(logFilePath)) { os = fileStorage.append(logFilePath); } else { os = fileStorage.create(logFilePath); } os = new BufferedOutputStream(os); objectOutput = getOutput(os); for(Serializable serializable: fileQueue){ objectOutput.writeObject(serializable); } }catch (Exception e){ log.error("Error during saving data with path "+ logFilePath + " to fileStorage", e); } finally { try { Closeables.close(objectOutput, true); Closeables.close(os, true); } catch (IOException e) { log.error(e.getMessage(), e); } } } } finally { log.debug("Flush queue finished. flushId {}.Flush took: {}", startTime, (System.currentTimeMillis() - startTime)); if (unlockFlush) synchronized (lock){ lock.notifyAll(); } } } } private class Log{ private String path; private Serializable value; public Log(String path, Serializable value){ this.path = path; this.value = value; } public String getPath(){ return path; } public Serializable getValue(){ return value; } } }