/* * 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; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import com.jkoolcloud.tnt4j.core.Snapshot; import com.jkoolcloud.tnt4j.core.TTL; 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 default abstract class for {@link EventSink}. Developers should subclass from this class * for all event sink implementations. * </p> * * * @version $Revision: 9 $ * * @see TTL * @see EventSink * @see EventSinkStats * @see SinkError * @see SinkErrorListener * @see SinkLogEvent * @see SinkLogEventListener */ public abstract class AbstractEventSink implements EventSink, EventSinkStats { protected ArrayList<SinkErrorListener> errorListeners = new ArrayList<SinkErrorListener>(10); protected ArrayList<SinkLogEventListener> logListeners = new ArrayList<SinkLogEventListener>(10); protected ArrayList<SinkEventFilter> filters = new ArrayList<SinkEventFilter>(10); private String name; private Source source; private boolean filterCheck = true; private long ttl = TTL.TTL_CONTEXT; private EventLimiter limiter; private EventFormatter formatter; private Throwable lastError; private long lastErrorTime = 0; private boolean errorState = false; // internal event sink statistics private AtomicLong loggedActivities = new AtomicLong(0); private AtomicLong loggedEvents = new AtomicLong(0); private AtomicLong loggedMsgs = new AtomicLong(0); private AtomicLong sinkWrites = new AtomicLong(0); private AtomicLong lastTime = new AtomicLong(0); private AtomicLong loggedSnaps = new AtomicLong(0); private AtomicLong errorCount = new AtomicLong(0); private AtomicLong skipCount = new AtomicLong(0); /** * Create an event sink with a given name * * @param nm event sink name */ public AbstractEventSink(String nm) { this.name = nm; } /** * Create an event sink with a given name * and event formatter and default {@link TTL} * * @param nm event sink name * @param fmt event formatter instance */ public AbstractEventSink(String nm, EventFormatter fmt) { this.name = nm; this.formatter = fmt; } /** * Create an event sink with a given name * and event formatter * * @param nm event sink name * @param ttl time to live for events written to this sink * @param fmt event formatter instance * * @see TTL */ public AbstractEventSink(String nm, long ttl, EventFormatter fmt) { this.name = nm; this.ttl = ttl; this.formatter = fmt; } @Override public Throwable setErrorState(Throwable ex) { Throwable prevError = lastError; errorState = ex != null; if (ex != null) { lastError = ex; lastErrorTime = System.currentTimeMillis(); errorCount.incrementAndGet(); } return prevError; } @Override public boolean errorState() { return errorState; } @Override public Throwable getLastError() { return lastError; } @Override public long getLastErrorTime() { return lastErrorTime; } @Override public long getErrorCount() { return errorCount.get(); } @Override public void setSource(Source src) { source = src; } @Override public Source getSource() { return source; } @Override public String getName() { return name; } @Override public EventFormatter getEventFormatter() { return formatter; } @Override public Map<String, Object> getStats() { LinkedHashMap<String, Object> stats = new LinkedHashMap<String, Object>(); getStats(stats); return stats; } @Override public KeyValueStats getStats(Map<String, Object> stats) { stats.put(Utils.qualify(this, KEY_LOGGED_ACTIVITIES), loggedActivities.get()); stats.put(Utils.qualify(this, KEY_LOGGED_EVENTS), loggedEvents.get()); stats.put(Utils.qualify(this, KEY_LOGGED_SNAPSHOTS), loggedSnaps.get()); stats.put(Utils.qualify(this, KEY_SINK_ERROR_COUNT), errorCount.get()); stats.put(Utils.qualify(this, KEY_SINK_ERROR_STATE), errorState()); if (lastError != null) { stats.put(Utils.qualify(this, KEY_SINK_ERROR_MSG), lastError.getMessage()); stats.put(Utils.qualify(this, KEY_SINK_ERROR_TIMESTAMP), new Date(lastErrorTime)); } stats.put(Utils.qualify(this, KEY_LOGGED_MSGS), loggedMsgs.get()); stats.put(Utils.qualify(this, KEY_SINK_WRITES), sinkWrites.get()); stats.put(Utils.qualify(this, KEY_SKIPPED_COUNT), skipCount.get()); if (lastTime.get() > 0) { stats.put(Utils.qualify(this, KEY_LAST_TIMESTAMP), new Date(lastTime.get())); stats.put(Utils.qualify(this, KEY_LAST_AGE), (System.currentTimeMillis() - lastTime.get())); } if (limiter != null) { stats.put(Utils.qualify(this, KEY_LIMITER_ENABLED), limiter.getLimiter().isEnabled()); stats.put(Utils.qualify(this, KEY_LIMITER_MPS), limiter.getLimiter().getMPS()); stats.put(Utils.qualify(this, KEY_LIMITER_BPS), limiter.getLimiter().getBPS()); stats.put(Utils.qualify(this, KEY_LIMITER_MAX_MPS), limiter.getLimiter().getMaxMPS()); stats.put(Utils.qualify(this, KEY_LIMITER_MAX_BPS), limiter.getLimiter().getMaxBPS()); stats.put(Utils.qualify(this, KEY_LIMITER_TOTAL_MSGS), limiter.getLimiter().getTotalMsgs()); stats.put(Utils.qualify(this, KEY_LIMITER_TOTAL_BYTES), limiter.getLimiter().getTotalBytes()); stats.put(Utils.qualify(this, KEY_LIMITER_TOTAL_DENIED), limiter.getLimiter().getDenyCount()); stats.put(Utils.qualify(this, KEY_LIMITER_TOTAL_DELAYS), limiter.getLimiter().getDelayCount()); stats.put(Utils.qualify(this, KEY_LIMITER_LAST_DELAY_TIME), limiter.getLimiter().getLastDelayTime()); stats.put(Utils.qualify(this, KEY_LIMITER_TOTAL_DELAY_TIME), limiter.getLimiter().getTotalDelayTime()); } return this; } @Override public void resetStats() { loggedActivities.set(0); loggedEvents.set(0); loggedSnaps.set(0); errorCount.set(0); loggedMsgs.set(0); sinkWrites.set(0); skipCount.set(0); } @Override public void addSinkLogEventListener(SinkLogEventListener listener) { synchronized (logListeners) { logListeners.add(listener); } } @Override public void removeSinkLogEventListener(SinkLogEventListener listener) { synchronized (logListeners) { logListeners.remove(listener); } } @Override public void addSinkErrorListener(SinkErrorListener listener) { synchronized (errorListeners) { errorListeners.add(listener); } } @Override public void removeSinkErrorListener(SinkErrorListener listener) { synchronized (errorListeners) { errorListeners.remove(listener); } } /** * Subclasses should use this helper class to trigger log event notifications during logging process. * * @param event * sink logging event to be sent to all listeners * @see SinkLogEvent */ protected void notifyListeners(SinkLogEvent event) { synchronized (logListeners) { for (SinkLogEventListener listener : logListeners) { listener.sinkLogEvent(event); } } } /** * Subclasses should use this helper class to trigger error notifications during logging process. * * @param event * sink error event to be sent to all listeners * @see SinkError */ protected void notifyListeners(SinkError event) { synchronized (errorListeners) { for (SinkErrorListener listener : errorListeners) { listener.sinkError(event); } } } /** * Subclasses should use this helper class to trigger error notifications during logging process. * * @param msg * sink message associated with the sink operation * @param ex * exception to be reported to all registered event listeners */ protected void notifyListeners(SinkLogEvent msg, Throwable ex) { setErrorState(ex); if (!errorListeners.isEmpty()) { SinkError event = new SinkError(this, msg, ex); notifyListeners(event); } else if (ex != null){ ex.printStackTrace(System.err); } } @Override public EventSink filterOnLog(boolean flag) { filterCheck = flag; return this; } @Override public boolean isLoggable(OpLevel level, String msg, Object... args) { return isLoggable(getSource(), level, msg, args); } @Override public boolean isLoggable(Source source, OpLevel level, String msg, Object... args) { return isLoggable(getTTL(), source, level, msg, args); } @Override public boolean isLoggable(long ttl, Source source, OpLevel level, String msg, Object... args) { boolean pass = isSet(level); if (filters.isEmpty()) return pass; for (SinkEventFilter filter : filters) { pass = (pass && filter.filter(this, ttl, source, level, msg, args)); if (!pass) { skipCount.incrementAndGet(); break; } } return pass; } @Override public boolean isLoggable(Snapshot snapshot) { boolean pass = isSet(snapshot.getSeverity()); if (filters.isEmpty()) return pass; for (SinkEventFilter filter : filters) { pass = (pass && filter.filter(this, snapshot)); if (!pass) { skipCount.incrementAndGet(); break; } } return pass; } @Override public boolean isLoggable(TrackingActivity activity) { boolean pass = isSet(activity.getSeverity()); if (filters.isEmpty()) return pass; for (SinkEventFilter filter : filters) { pass = (pass && filter.filter(this, activity)); if (!pass) { skipCount.incrementAndGet(); break; } } return pass; } @Override public boolean isLoggable(TrackingEvent event) { boolean pass = isSet(event.getSeverity()); if (filters.isEmpty()) return pass; for (SinkEventFilter filter : filters) { pass = (pass && filter.filter(this, event)); if (!pass) { skipCount.incrementAndGet(); break; } } return pass; } @Override public void addSinkEventFilter(SinkEventFilter filter) { synchronized (filters) { filters.add(filter); } } @Override public void removeSinkEventFilter(SinkEventFilter filter) { synchronized (filters) { filters.remove(filter); } } @Override public void log(TrackingActivity activity) { _checkState(); boolean doLog = filterCheck? isLoggable(activity): true; if (doLog) { try { if (!_limiter(1, 0)) { return; } if (ttl != TTL.TTL_CONTEXT) { activity.setTTL(ttl); } _log(activity); loggedActivities.incrementAndGet(); loggedSnaps.addAndGet(activity.getSnapshotCount()); lastTime.set(System.currentTimeMillis()); errorState = false; if (!logListeners.isEmpty()) { notifyListeners(new SinkLogEvent(this, activity)); } } catch (Throwable ex) { notifyListeners(new SinkLogEvent(this, activity), ex); } } } @Override public void log(TrackingEvent event) { _checkState(); boolean doLog = filterCheck? isLoggable(event): true; if (doLog) { try { if (!_limiter(1, event.getSize())) { return; } if (ttl != TTL.TTL_CONTEXT) { event.setTTL(ttl); } _log(event.sign()); loggedEvents.incrementAndGet(); loggedSnaps.addAndGet(event.getOperation().getSnapshotCount()); lastTime.set(System.currentTimeMillis()); errorState = false; if (!logListeners.isEmpty()) { notifyListeners(new SinkLogEvent(this, event)); } } catch (Throwable ex) { notifyListeners(new SinkLogEvent(this, event), ex); } } } @Override public void log(Snapshot snapshot) { _checkState(); boolean doLog = filterCheck? isLoggable(snapshot): true; if (doLog) { try { if (!_limiter(1, 0)) { return; } if (ttl != TTL.TTL_CONTEXT) { snapshot.setTTL(ttl); } _log(snapshot); loggedSnaps.incrementAndGet(); lastTime.set(System.currentTimeMillis()); errorState = false; if (!logListeners.isEmpty()) { notifyListeners(new SinkLogEvent(this, snapshot)); } } catch (Throwable ex) { notifyListeners(new SinkLogEvent(this, snapshot), ex); } } } @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, src, sev, msg, args); } @Override public void log(long ttl_sec, Source src, OpLevel sev, String msg, Object... args) { _checkState(); boolean doLog = filterCheck? isLoggable(ttl_sec, source, sev, msg): true; if (doLog) { long nttl = ((ttl_sec != TTL.TTL_CONTEXT)? ttl_sec: TTL.TTL_DEFAULT); try { if (!_limiter(1, msg.length())) { return; } _log(nttl, src, sev, msg, args); loggedMsgs.incrementAndGet(); lastTime.set(System.currentTimeMillis()); errorState = false; if (!logListeners.isEmpty()) { notifyListeners(new SinkLogEvent(this, src, sev, nttl, msg, args)); } } catch (Throwable ex) { notifyListeners(new SinkLogEvent(this, src, sev, nttl, msg, args), ex); } } } @Override public void write(Object msg, Object...args) throws IOException, InterruptedException { try { if (!_limiter(msg)) { return; } _write(msg, args); sinkWrites.incrementAndGet(); lastTime.set(System.currentTimeMillis()); errorState = false; if (!logListeners.isEmpty()) { notifyListeners(new SinkLogEvent(this, getSource(), OpLevel.NONE, (ttl != TTL.TTL_CONTEXT)? ttl: TTL.TTL_DEFAULT, msg, args)); } } catch (Throwable ex) { notifyListeners(new SinkLogEvent(this, getSource(), OpLevel.NONE, (ttl != TTL.TTL_CONTEXT)? ttl: TTL.TTL_DEFAULT, msg, args), ex); } } @Override public long getTTL() { return ttl; } @Override public void setTTL(long ttl) { this.ttl = ttl; } @Override public void setLimiter(EventLimiter limit) { this.limiter = limit; } @Override public EventLimiter getLimiter() { return limiter; } @Override public void flush() throws IOException { } /** * Check state of the sink before logging occurs. * * @param sink event sink * @throws IllegalStateException if sink is in wrong state */ public static void checkState(EventSink sink) throws IllegalStateException { if (sink == null || !sink.isOpen()) throw new IllegalStateException("Sink closed or unavailable: sink=" + sink); } /** * 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 { checkState(this); } /** * Applies rate limiting on mps/bps * * @param msgCount messages sent * @param byteCount bytes sent * @return true if permit obtained, false otherwise */ protected boolean _limiter(int msgCount, int byteCount) { if (limiter != null) { return limiter.obtain(msgCount, byteCount); } return true; } /** * Applies rate limiting on mps/bps * * @param obj object to be sent * @return true if permit obtained, false otherwise */ protected boolean _limiter(Object obj) { if (limiter != null) { return limiter.obtain(1, String.valueOf(obj).length()); } return true; } /** * Override this method to add actual implementation for all subclasses. * * @param event to be sent to the sink * @throws Exception if error logging tracking event * @see TrackingEvent */ protected abstract void _log(TrackingEvent event) throws Exception; /** * Override this method to add actual implementation for all subclasses. * * @param activity to be sent to the sink * @throws Exception if error logging tracking activity * @see TrackingActivity */ protected abstract void _log(TrackingActivity activity) throws Exception;; /** * Override this method to add actual implementation for all subclasses. * * @param snapshot string message to be logged * @throws Exception if error logging snapshot * @see OpLevel */ protected abstract void _log(Snapshot snapshot) throws Exception; /** * Override this method to add actual implementation for all subclasses. * * @param ttl * time to live in seconds {@link TTL} * @param src * event source handle * @param sev * message severity to log * @param msg * string message to be logged * @param args * arguments passed along the message * @throws Exception if logging message * @see OpLevel */ protected abstract void _log(long ttl, Source src, OpLevel sev, String msg, Object... args) throws Exception; /** * Override this method to add actual implementation for all subclasses. * * @param msg * string message to be logged * @param args * arguments passed along the message * @throws IOException if error writing to sink * @throws InterruptedException if interrupted during write operation */ protected abstract void _write(Object msg, Object...args) throws IOException, InterruptedException; }