/* * Copyright 2014-2015 JKOOL, LLC. * * 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.jkoolcloud.tnt4j.sink.impl; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; import com.jkoolcloud.tnt4j.core.Snapshot; import com.jkoolcloud.tnt4j.core.TTL; import com.jkoolcloud.tnt4j.sink.AbstractEventSink; import com.jkoolcloud.tnt4j.sink.EventLimiter; import com.jkoolcloud.tnt4j.sink.EventSink; import com.jkoolcloud.tnt4j.sink.EventSinkFactory; import com.jkoolcloud.tnt4j.sink.IOShutdown; import com.jkoolcloud.tnt4j.sink.SinkError; import com.jkoolcloud.tnt4j.sink.SinkErrorListener; import com.jkoolcloud.tnt4j.sink.SinkEventFilter; import com.jkoolcloud.tnt4j.sink.SinkLogEvent; import com.jkoolcloud.tnt4j.sink.SinkLogEventListener; import com.jkoolcloud.tnt4j.source.Source; import com.jkoolcloud.tnt4j.core.KeyValueStats; import com.jkoolcloud.tnt4j.core.OpLevel; import com.jkoolcloud.tnt4j.format.EventFormatter; import com.jkoolcloud.tnt4j.tracker.TrackingActivity; import com.jkoolcloud.tnt4j.tracker.TrackingEvent; import com.jkoolcloud.tnt4j.utils.Utils; /** * <p> * This class implements a buffered event sink, which buffers events into a memory queue and then * flushes it to a specified out sink using a separate thread. {@code BufferedEvenSink} decouples * writer from the actual sink write and can improve performance during bursts. * </p> * * * @version $Revision: 1 $ * * @see EventSink * @see SinkError * @see SinkErrorListener * @see SinkLogEvent * @see SinkLogEventListener */ public class BufferedEventSink implements EventSink, IOShutdown { static final String KEY_OBJECTS_TOTAL = "buffered-objects-total"; static final String KEY_OBJECTS_DROPPED = "buffered-objects-dropped"; static final String KEY_OBJECTS_SKIPPED = "buffered-objects-skipped"; static final String KEY_OBJECTS_REQUEUED = "buffered-objects-requeued"; static final String KEY_FLUSH_COUNT = "buffered-flush-count"; static final String KEY_TOTAL_ERRORS = "buffered-errors-total"; private long ttl = TTL.TTL_CONTEXT; private long signalTimeout = 5000; private boolean block = false; private Source source; private EventSink outSink = null; private BufferedEventSinkFactory factory; // sink stat counters private AtomicLong totalCount = new AtomicLong(0); private AtomicLong signalCount = new AtomicLong(0); private AtomicLong skipCount = new AtomicLong(0); private AtomicLong dropCount = new AtomicLong(0); private AtomicLong rqCount = new AtomicLong(0); private AtomicLong errorCount = new AtomicLong(0); /** * Create a buffered sink instance with a specified out sink * maximum capacity. Event will be dropped if capacity is exceeded. * Obtain drop counts and queue sizes using <code>getDropCount()</code> method. * * @param f buffered event sink factory * @param sink out sink where events/log message are written out * @param blocking set to true to block if full, false to drop messages if full * */ public BufferedEventSink(BufferedEventSinkFactory f, EventSink sink, boolean blocking) { factory = f; outSink = sink; block = blocking; outSink.addSinkErrorListener(new BufferedSinkErrorListener()); outSink.filterOnLog(false); // disable filtering on the underlying sink (prevent double filters) } /** * Set maximum signal timeout. * * @param duration time * @param units time unit */ public void setSignalTimeout(long duration, TimeUnit units) { signalTimeout = units.toMillis(duration); } /** * Set maximum signal timeout in milliseconds * * @param duration time */ public void setSignalTimeout(long duration) { setSignalTimeout(duration, TimeUnit.MILLISECONDS); } /** * Get maximum signal timeout in milliseconds * * @return maximum flush timeout in milliseconds */ public long getSignalTimeout() { return signalTimeout; } /** * Obtain total number of events/log messages dropped since last reset. * * @return total number of dropped messages since last reset */ public long getDropCount() { return dropCount.get(); } /** * Obtain total number of events/log messages re-queued since last reset. * * @return total number of re-queued messages since last reset */ public long getRqCount() { return rqCount.get(); } /** * Obtain total number of events/log messages skipped since last reset. * Events are skipped when don't pass sink filters. * * @return total number of skipped messages since last reset */ public long getSkipCount() { return skipCount.get(); } @Override public void setLimiter(EventLimiter limiter) { outSink.setLimiter(limiter); } @Override public EventLimiter getLimiter() { return outSink.getLimiter(); } @Override public String getName() { return outSink.getName(); } @Override public void addSinkErrorListener(SinkErrorListener listener) { outSink.addSinkErrorListener(listener); } @Override public void addSinkEventFilter(SinkEventFilter listener) { outSink.addSinkEventFilter(listener); } @Override public void addSinkLogEventListener(SinkLogEventListener listener) { outSink.addSinkLogEventListener(listener); } @Override public boolean isSet(OpLevel sev) { return outSink.isSet(sev); } @Override public void write(Object msg, Object... args) throws IOException, InterruptedException { _checkState(); String txtMsg = String.valueOf(msg); if (isLoggable(OpLevel.NONE, txtMsg, args)) { SinkLogEvent sinkEvent = new SinkLogEvent(outSink, getSource(), OpLevel.NONE, (ttl != TTL.TTL_CONTEXT)? ttl: TTL.TTL_DEFAULT, txtMsg, resolveArguments(args)); _writeEvent(sinkEvent, block); } else { skipCount.incrementAndGet(); } } @Override public void log(TrackingActivity activity) { _checkState(); if (isLoggable(activity)) { if (ttl != TTL.TTL_CONTEXT) activity.setTTL(ttl); SinkLogEvent sinkEvent = new SinkLogEvent(outSink, activity); _writeEvent(sinkEvent, block); } else { skipCount.incrementAndGet(); } } @Override public void log(TrackingEvent event) { _checkState(); if (isLoggable(event)) { if (ttl != TTL.TTL_CONTEXT) event.setTTL(ttl); SinkLogEvent sinkEvent = new SinkLogEvent(outSink, event); _writeEvent(sinkEvent, block); } else { skipCount.incrementAndGet(); } } @Override public void log(Snapshot snapshot) { _checkState(); if (isLoggable(snapshot)) { if (ttl != TTL.TTL_CONTEXT) snapshot.setTTL(ttl); SinkLogEvent sinkEvent = new SinkLogEvent(outSink, snapshot); _writeEvent(sinkEvent, block); } else { skipCount.incrementAndGet(); } } @Override public void log(OpLevel sev, String msg, Object... args) { log(source, sev, msg, args); } @Override public void log(Source src, OpLevel sev, String msg, Object... args) { log((ttl != TTL.TTL_CONTEXT)? ttl: TTL.TTL_DEFAULT, src, sev, msg, args); } @Override public void log(long ttl_sec, Source src, OpLevel sev, String msg, Object... args) { _checkState(); if (isLoggable(sev, msg, args)) { SinkLogEvent sinkEvent = new SinkLogEvent(outSink, src, sev, ttl_sec, msg, resolveArguments(args)); _writeEvent(sinkEvent, block); } else { skipCount.incrementAndGet(); } } private void _writeEvent(SinkLogEvent sinkEvent, boolean sync) { totalCount.incrementAndGet(); if (sync) { try { factory.getPooledLogger().put(sinkEvent); } catch (Throwable ex) { dropCount.incrementAndGet(); } } else { boolean flag = factory.getPooledLogger().offer(sinkEvent); if (!flag) { dropCount.incrementAndGet(); } } } @Override public void removeSinkErrorListener(SinkErrorListener listener) { outSink.removeSinkErrorListener(listener); } @Override public void removeSinkEventFilter(SinkEventFilter listener) { outSink.removeSinkEventFilter(listener); } @Override public void removeSinkLogEventListener(SinkLogEventListener listener) { outSink.removeSinkLogEventListener(listener); } @Override public Object getSinkHandle() { return outSink; } @Override public boolean isOpen() { return true; } @Override public void open() { // open asynchronously PooledLogger should handle it // outSink.open(); } @Override public void close() throws IOException { signal(SinkLogEvent.SIGNAL_CLOSE, signalTimeout, TimeUnit.MILLISECONDS); } @Override public Map<String, Object> getStats() { Map<String, Object> stats = outSink.getStats(); getStats(stats); return stats; } @Override public KeyValueStats getStats(Map<String, Object> stats) { stats.put(Utils.qualify(this, KEY_OBJECTS_TOTAL), totalCount.get()); stats.put(Utils.qualify(this, KEY_OBJECTS_DROPPED), dropCount.get()); stats.put(Utils.qualify(this, KEY_OBJECTS_SKIPPED), skipCount.get()); stats.put(Utils.qualify(this, KEY_OBJECTS_REQUEUED), rqCount.get()); stats.put(Utils.qualify(this, KEY_FLUSH_COUNT), signalCount.get()); stats.put(Utils.qualify(this, KEY_TOTAL_ERRORS), errorCount.get()); factory.getPooledLogger().getStats(stats); return outSink.getStats(stats); } @Override public void resetStats() { totalCount.set(0); signalCount.set(0); dropCount.set(0); rqCount.set(0); errorCount.set(0); skipCount.set(0); outSink.resetStats(); } @Override public EventFormatter getEventFormatter() { return outSink.getEventFormatter(); } @Override public void setSource(Source src) { source = src; if (outSink != null) { outSink.setSource(source); } } @Override public Source getSource() { return source; } /** * Convert object array into an array of strings * * @param args array of objects * @return array of string objects */ protected Object [] resolveArguments(Object...args) { if (args == null || args.length == 0) return null; for (int i = 0; i < args.length; i++) { if (!(args[i] instanceof Throwable)) { args[i] = String.valueOf(args[i]); } } return args; } /** * Obtain {@link EventSinkFactory} associated with * this sink. * * @return sink factory instance */ public EventSinkFactory getFactory() { return this.factory; } /** * Override this method to check state of the sink before logging occurs. * * @throws IllegalStateException if sink is in wrong state */ protected void _checkState() throws IllegalStateException { AbstractEventSink.checkState(this); } @Override public boolean isLoggable(OpLevel level, String msg, Object... args) { return outSink.isLoggable(level, msg, args); } @Override public boolean isLoggable(Source source, OpLevel level, String msg, Object... args) { return outSink.isLoggable(getTTL(), source, level, msg, args); } @Override public boolean isLoggable(long ttl_sec, Source source, OpLevel level, String msg, Object... args) { return outSink.isLoggable(ttl_sec, source, level, msg, args); } @Override public boolean isLoggable(Snapshot snapshot) { return outSink.isLoggable(snapshot); } @Override public boolean isLoggable(TrackingActivity activity) { return outSink.isLoggable(activity); } @Override public boolean isLoggable(TrackingEvent event) { return outSink.isLoggable(event); } @Override public EventSink filterOnLog(boolean flag) { return outSink.filterOnLog(false); } @Override public long getTTL() { return ttl; } @Override public void setTTL(long ttl) { this.ttl = ttl; } @Override public boolean errorState() { return outSink.errorState(); } @Override public Throwable getLastError() { return outSink.getLastError(); } @Override public long getLastErrorTime() { return outSink.getLastErrorTime(); } @Override public long getErrorCount() { return outSink.getErrorCount(); } @Override public Throwable setErrorState(Throwable e) { return outSink.setErrorState(e); } @Override public void flush() throws IOException { signal(SinkLogEvent.SIGNAL_FLUSH, signalTimeout, TimeUnit.MILLISECONDS); } @Override public void shutdown(Throwable ex) throws IOException { factory.getPooledLogger().shutdown(ex); signal(SinkLogEvent.SIGNAL_SHUTDOWN, signalTimeout, TimeUnit.MILLISECONDS); } /** * Send a signal to the underlying sink and wait for * signal to be processed up to a max time. * * @param signalType type of signal to send * @param wait max time to wait * @param tunit time unit for wait * @return sink factory instance */ protected BufferedEventSink signal(int signalType, long wait, TimeUnit tunit) throws IOException { signalCount.incrementAndGet(); _writeEvent(new SinkLogEvent(outSink, Thread.currentThread(), signalType), true); LockSupport.parkNanos(this, tunit.toNanos(wait)); return this; } /** * Handle failed event by retrying to process it or * dropping it depending on if it is possible to retry * processing. * * @param ev sink error event * @return sink factory instance */ protected BufferedEventSink handleError(SinkError ev) { errorCount.incrementAndGet(); if (!factory.getPooledLogger().isDQfull()) { SinkLogEvent event = ev.getSinkEvent(); factory.getPooledLogger().putDelayed(event); rqCount.incrementAndGet(); } else { dropCount.incrementAndGet(); } return this; } }