/*
* Copyright 2010 NCHOVY
*
* 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.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;
import org.araqne.api.DateFormat;
import org.araqne.codec.EncodingRule;
public abstract class AbstractLogger implements Logger, Runnable {
private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(AbstractLogger.class.getName());
private static final int INFINITE = 0;
private String fullName;
private String namespace;
private String name;
private String factoryFullName;
private String factoryNamespace;
private String factoryName;
private String description;
private CopyOnWriteArraySet<LogPipe> pipes;
private Thread t;
private int interval;
private TimeRange timeRange;
private Map<String, String> config;
private volatile LoggerStatus status = LoggerStatus.Stopped;
private volatile boolean enabled = false;
private volatile boolean doStop = false;
private volatile boolean stopped = true;
private volatile boolean pending = false;
private volatile boolean manualStart = false;
private volatile boolean stopCallbacked = false;
private volatile Date lastStartDate;
private volatile Date lastRunDate;
private volatile Date lastLogDate;
private volatile Date lastWriteDate;
private volatile Log lastLog;
private AtomicLong logCounter;
private AtomicLong logVolumes;
private AtomicLong dropCounter;
private AtomicLong dropVolumes;
private Set<LoggerEventListener> eventListeners;
private LogTransformer transformer;
private LoggerFactory factory;
private LoggerStopReason lastStopReason;
private Set<String> unresolvedLoggers = new CopyOnWriteArraySet<String>();
private Throwable temporaryFailure;
/**
* @since 1.7.0
*/
public AbstractLogger(LoggerSpecification spec, LoggerFactory factory) {
// logger factory info
this.factoryNamespace = factory.getNamespace();
this.factoryName = factory.getName();
this.factoryFullName = factoryNamespace + "\\" + factoryName;
this.factory = factory;
// logger info
this.namespace = spec.getNamespace();
this.name = spec.getName();
this.fullName = namespace + "\\" + name;
this.description = spec.getDescription();
this.config = spec.getConfig();
reloadStates();
this.pipes = new CopyOnWriteArraySet<LogPipe>();
this.eventListeners = new CopyOnWriteArraySet<LoggerEventListener>();
}
@Override
public String getFullName() {
return fullName;
}
@Override
public String getNamespace() {
return namespace;
}
@Override
public String getName() {
return name;
}
@Override
public String getFactoryFullName() {
return factoryFullName;
}
@Override
public String getFactoryName() {
return factoryName;
}
@Override
public String getFactoryNamespace() {
return factoryNamespace;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isPassive() {
return false;
}
@Override
public Date getLastStartDate() {
return lastStartDate;
}
@Override
public Date getLastRunDate() {
return lastRunDate;
}
@Override
public Date getLastLogDate() {
return lastLogDate;
}
@Override
public Date getLastWriteDate() {
return lastWriteDate;
}
@Override
public Log getLastLog() {
return lastLog;
}
@Override
public LoggerStopReason getStopReason() {
return lastStopReason;
}
public Throwable getTemporaryFailure() {
return temporaryFailure;
}
public void setTemporaryFailure(Throwable temporaryFailure) {
boolean changed = false;
Throwable lhs = this.temporaryFailure;
Throwable rhs = temporaryFailure;
if (lhs == null && rhs == null) {
changed = false;
} else if (checkOneSideNull(lhs, rhs)) {
changed = true;
} else if (checkOneSideNull(lhs.getMessage(), rhs.getMessage())
|| ((lhs.getMessage() != null && rhs.getMessage() != null && !lhs.getMessage().equals(rhs.getMessage())))) {
changed = true;
}
this.temporaryFailure = temporaryFailure;
if (changed) {
for (LoggerEventListener callback : eventListeners) {
try {
callback.onFailureChange(this);
} catch (Throwable t) {
slog.warn("araqne log api; logger [" + getFullName() + "] callback should not throw any exception", t);
}
}
}
}
private boolean checkOneSideNull(Object lhs, Object rhs) {
return (lhs != null && rhs == null) || (lhs == null && rhs != null);
}
@Override
public long getLogCount() {
return logCounter.get();
}
@Override
public long getDropCount() {
return dropCounter.get();
}
@Override
public long getDropVolume() {
return dropVolumes.get();
}
@Override
public long getLogVolume() {
return logVolumes.get();
}
@Override
public long getUpdateCount() {
LastStateService lss = factory.getLastStateService();
LastState state = lss.getState(getFullName());
if (state == null)
return 0;
return state.getUpdateCount();
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public boolean isRunning() {
return !stopped;
}
public boolean isPending() {
return pending;
}
@Override
public void setPending(boolean pending) {
this.pending = pending;
if (pending) {
if (!unresolvedLoggers.isEmpty())
lastStopReason = LoggerStopReason.LOGGER_DEPENDENCY;
else if (config.get("transformer") != null && transformer == null)
lastStopReason = LoggerStopReason.TRANSFORMER_DEPENDENCY;
}
}
@Override
public boolean isManualStart() {
return manualStart;
}
@Override
public void setManualStart(boolean manualStart) {
this.manualStart = manualStart;
notifyConfigChange();
}
@Override
public LoggerStatus getStatus() {
return status;
}
@Override
public int getInterval() {
return interval;
}
@Override
public TimeRange getTimeRange() {
return timeRange;
}
@Override
public void setTimeRange(TimeRange timeRange) {
this.timeRange = timeRange;
for (LoggerEventListener callback : eventListeners) {
try {
callback.onSetTimeRange(this);
} catch (Throwable t) {
slog.warn("araqne log api; logger [" + getFullName() + "] callback should not throw any exception", t);
}
}
}
@Override
public LoggerFactory getFactory() {
return factory;
}
/**
* start passive logger
*/
@Override
public void start(LoggerStartReason reason) {
verifyPending();
if (!isPassive())
throw new IllegalStateException("not passive mode. use start(interval)");
pending = false;
stopped = false;
invokeStartCallback(reason);
}
/**
* start active logger
*/
@Override
public void start(LoggerStartReason reason, int interval) {
verifyPending();
if (isPassive()) {
start(reason);
return;
}
if (!stopped)
throw new IllegalStateException("logger is already running");
status = LoggerStatus.Starting;
this.interval = interval;
invokeStartCallback(reason);
if (getExecutor() == null) {
t = new Thread(this, "Logger [" + fullName + "]");
t.start();
} else {
stopped = false;
getExecutor().execute(this);
}
pending = false;
}
private void verifyPending() {
if ((config.get("transformer") != null) && (transformer == null))
throw new IllegalStateException("pending transformer");
if (!unresolvedLoggers.isEmpty())
throw new IllegalStateException("pending logger");
}
protected ExecutorService getExecutor() {
return null;
}
private void invokeStartCallback(LoggerStartReason reason) {
if (reason == LoggerStartReason.USER_REQUEST)
enabled = true;
lastStopReason = null;
stopCallbacked = false;
onStart(reason);
lastStartDate = new Date();
status = LoggerStatus.Running;
for (LoggerEventListener callback : eventListeners) {
try {
callback.onStart(this);
} catch (Throwable t) {
slog.warn("araqne log api; logger [" + getFullName() + "] callback should not throw any exception", t);
}
}
}
@Override
public void stop(LoggerStopReason reason) {
if (lastStopReason == null)
lastStopReason = reason;
if (isPassive()) {
invokeStopCallback(reason);
stopped = true;
status = LoggerStatus.Stopped;
this.pending = ((reason == LoggerStopReason.TRANSFORMER_DEPENDENCY)
|| (reason == LoggerStopReason.LOGGER_DEPENDENCY));
} else
stop(reason, INFINITE);
}
@Override
public void stop(LoggerStopReason reason, int maxWaitTime) {
if (lastStopReason == null)
lastStopReason = reason;
if (isPassive()) {
stop(reason);
return;
}
status = LoggerStatus.Stopping;
doStop = true;
// e.g. close socket at onStop() can unblock waiting connect() call
invokeStopCallback(reason);
if (t != null) {
if (!t.isAlive()) {
t = null;
return;
}
t.interrupt();
t = null;
}
if (getExecutor() == null) {
long begin = new Date().getTime();
try {
while (true) {
if (stopped)
break;
if (maxWaitTime != 0 && new Date().getTime() - begin > maxWaitTime)
break;
Thread.sleep(50);
}
status = LoggerStatus.Stopped;
stopped = true;
} catch (InterruptedException e) {
}
} else {
status = LoggerStatus.Stopped;
stopped = true;
}
this.pending = pending;
}
/**
* called in explicit stop() call context
*/
private void invokeStopCallback(LoggerStopReason reason) {
if (lastStopReason == null)
lastStopReason = reason;
if (stopCallbacked)
return;
stopCallbacked = true;
if (reason == LoggerStopReason.USER_REQUEST) {
enabled = false;
}
try {
onStop(lastStopReason);
} catch (Exception e) {
slog.warn("araqne log api: [" + fullName + "] stop callback should not throw any exception", e);
}
for (LoggerEventListener callback : eventListeners) {
try {
callback.onStop(this, lastStopReason);
} catch (Exception e) {
slog.warn("logger callback should not throw any exception", e);
}
}
}
protected abstract void runOnce();
// can be overridden
protected void onStart(LoggerStartReason reason) {
}
// can be overridden
protected void onStop(LoggerStopReason reason) {
}
// can be overridden
protected void onResetStates() {
}
@Override
public void run() {
if (getExecutor() == null) {
stopped = false;
try {
while (true) {
try {
if (doStop)
break;
if (timeRange != null && !timeRange.isInRange(new Date())) {
if (slog.isDebugEnabled())
slog.debug("araqne log api : skip logger [{}] run, waiting time range [{}]", getFullName(),
timeRange);
Thread.sleep(1000);
continue;
}
long startedAt = System.currentTimeMillis();
runOnce();
if (doStop)
break;
long elapsed = System.currentTimeMillis() - startedAt;
lastRunDate = new Date();
if (interval - elapsed < 0)
continue;
Thread.sleep(interval - elapsed);
} catch (InterruptedException e) {
}
}
} catch (Throwable t) {
slog.error("araqne log api: logger [" + getFullName() + "] stopped", t);
} finally {
status = LoggerStatus.Stopped;
stopped = true;
doStop = false;
}
} else {
if (!enabled)
return;
if (lastRunDate != null) {
long millis = lastRunDate.getTime() + (long) interval - System.currentTimeMillis();
if (millis > 0) {
try {
Thread.sleep(Math.min(millis, 500));
} catch (InterruptedException e) {
}
if (millis > 500) {
ExecutorService executor = getExecutor();
if (executor != null)
executor.execute(this);
return;
}
}
}
runOnce();
lastRunDate = new Date();
ExecutorService executor = getExecutor();
if (executor != null)
executor.execute(this);
}
}
protected void writeBatch(Log[] logs) {
// call method to support overriding (ex. base remote logger)
if (!isRunning())
return;
int addCount = 0;
int dropCount = 0;
long logVolume = 0;
long dropVolume = 0;
for (int i = 0; i < logs.length; i++) {
Log log = logs[i];
if (log == null)
continue;
long volume = getDataLength(log);
// update last log date
lastLogDate = log.getDate();
lastLog = log;
addCount++;
logVolume += volume;
// transform
if (transformer != null)
log = transformer.transform(log);
logs[i] = log;
// transform may return null to filter log
if (log == null) {
dropCount++;
dropVolume += volume;
continue;
}
}
if (addCount > dropCount)
lastWriteDate = new Date();
logCounter.addAndGet(addCount);
dropCounter.addAndGet(dropCount);
logVolumes.addAndGet(logVolume);
dropVolumes.addAndGet(dropVolume);
// notify all
for (LogPipe pipe : pipes) {
try {
pipe.onLogBatch(this, logs);
} catch (LoggerStopException e) {
LoggerStopReason reason = LoggerStopReason.STOP_EXCEPTION;
if (e.getCause() != null && e.getCause().getMessage() != null
&& e.getCause().getMessage().contains("archive not opened"))
reason = LoggerStopReason.LOW_DISK;
this.slog.warn("araqne-log-api: stopping logger [" + getFullName() + "] by exception", e);
if (isPassive())
stop(reason);
else {
doStop = true;
status = LoggerStatus.Stopping;
invokeStopCallback(reason);
}
} catch (Throwable t) {
if (t.getMessage() != null && t.getMessage().startsWith("invalid time"))
this.slog.warn("araqne-log-api: log pipe should not throw exception" + t.getMessage());
else
this.slog.warn("araqne-log-api: log pipe should not throw exception", t);
}
}
}
private long getDataLength(Log log) {
if (log instanceof SimpleLog) {
SimpleLog sl = (SimpleLog) log;
if (sl.getDataLength() == 0)
return EncodingRule.lengthOfMap(log.getParams());
else
return sl.getDataLength();
} else {
return EncodingRule.lengthOfMap(log.getParams());
}
}
protected void write(Log log) {
// call method to support overriding (ex. base remote logger)
if (!isRunning())
return;
long volume = getDataLength(log);
// update last log date
lastLogDate = log.getDate();
lastLog = log;
logCounter.incrementAndGet();
logVolumes.addAndGet(volume);
// transform
if (transformer != null)
log = transformer.transform(log);
// transform may return null to filter log
if (log == null) {
dropCounter.incrementAndGet();
dropVolumes.addAndGet(volume);
return;
}
lastWriteDate = new Date();
// notify all
for (
LogPipe pipe : pipes)
{
try {
pipe.onLog(this, log);
} catch (LoggerStopException e) {
this.slog.warn("araqne-log-api: stopping logger [" + getFullName() + "] by exception", e);
if (isPassive())
stop(LoggerStopReason.STOP_EXCEPTION);
else {
doStop = true;
status = LoggerStatus.Stopping;
invokeStopCallback(LoggerStopReason.STOP_EXCEPTION);
}
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().startsWith("invalid time"))
this.slog.warn("araqne-log-api: log pipe should not throw exception" + e.getMessage());
else
this.slog.warn("araqne-log-api: log pipe should not throw exception", e);
}
}
}
@Override
public void updateConfigs(Map<String, String> configs) {
if (!(this instanceof Reconfigurable))
throw new UnsupportedOperationException("logger is not reconfigurable: " + getFullName());
// clone old configs
Map<String, String> oldConfigs = new HashMap<String, String>(this.config);
Map<String, String> newConfigs = configs;
// validate new configs
for (LoggerConfigOption option : factory.getConfigOptions()) {
if (!(option instanceof Mutable)) {
String name = option.getName();
String oldValue = oldConfigs.get(name);
String newValue = newConfigs.get(name);
if ((oldValue == null && newValue != null) || (oldValue != null && !oldValue.equals(newValue)))
throw new IllegalArgumentException("option " + name + " is not mutable");
}
}
this.config = configs;
try {
((Reconfigurable) this).onConfigChange(oldConfigs, newConfigs);
} catch (Throwable t) {
this.config = oldConfigs;
throw new IllegalStateException(t);
}
notifyConfigChange();
}
private void notifyConfigChange() {
for (LoggerEventListener callback : eventListeners) {
try {
callback.onUpdated(this, config);
} catch (Exception e) {
slog.error("araqne log api: logger event callback should not throw any exception", e);
}
}
}
/**
* Use getConfigs() instead
*/
@Deprecated
@Override
public Map<String, String> getConfig() {
return new HashMap<String, String>(config);
}
@Override
public Map<String, String> getConfigs() {
return new HashMap<String, String>(config);
}
@Override
public Map<String, Object> getStates() {
LastStateService lastStateService = factory.getLastStateService();
if (lastStateService == null)
return new HashMap<String, Object>();
LastState s = lastStateService.getState(getFullName());
if (s == null)
return new HashMap<String, Object>();
return s.getProperties();
}
@Override
public void setStates(Map<String, Object> state) {
LastStateService lastStateService = factory.getLastStateService();
if (lastStateService == null)
throw new IllegalStateException("last status service not found");
LastState s = new LastState();
s.setLoggerName(getFullName());
s.setInterval(interval);
if (timeRange != null) {
s.setStartTime(timeRange.getStartTime());
s.setEndTime(timeRange.getEndTime());
}
s.setLogCount(logCounter.get());
s.setDropCount(dropCounter.get());
s.setLogVolume(logVolumes.get());
s.setDropVolume(dropVolumes.get());
s.setLastLogDate(lastLogDate);
s.setPending(pending);
s.setEnabled(enabled);
s.setRunning(status == LoggerStatus.Running);
s.setProperties(state);
lastStateService.setState(s);
slog.trace("araqne log api: running state saved: {}", getFullName());
}
@Override
public void resetStates() {
logCounter.set(0);
dropCounter.set(0);
logVolumes.set(0);
dropVolumes.set(0);
lastLogDate = null;
setStates(new HashMap<String, Object>());
try {
onResetStates();
} catch (Throwable t) {
slog.warn("araqne log api: logger [" + getFullName() + "] throws exception at onResetStates()", t);
}
}
@Override
public void reloadStates() {
LastStateService lss = factory.getLastStateService();
LastState state = lss.getState(fullName);
long lastLogCount = 0;
long lastDropCount = 0;
long lastLogVolume = 0;
long lastDropVolume = 0;
if (state != null) {
this.interval = state.getInterval();
this.enabled = state.isEnabled();
if (state.getStartTime() != null && state.getEndTime() != null)
this.timeRange = new TimeRange(state.getStartTime(), state.getEndTime());
lastLogCount = state.getLogCount();
lastDropCount = state.getDropCount();
lastLogVolume = state.getLogVolume();
lastDropVolume = state.getDropVolume();
lastLogDate = state.getLastLogDate();
}
this.logCounter = new AtomicLong(lastLogCount);
this.dropCounter = new AtomicLong(lastDropCount);
this.logVolumes = new AtomicLong(lastLogVolume);
this.dropVolumes = new AtomicLong(lastDropVolume);
this.lastLogDate = lastLogDate;
}
@Override
public LogTransformer getTransformer() {
return transformer;
}
@Override
public void setTransformer(LogTransformer transformer) {
this.transformer = transformer;
boolean transformerResolved = (config.get("transformer") == null) || (transformer != null);
if (!manualStart && enabled && isPending() && transformerResolved && unresolvedLoggers.isEmpty()) {
start(LoggerStartReason.DEPENDENCY_RESOLVED, getInterval());
}
if ((enabled) && (!transformerResolved)) {
stop(LoggerStopReason.TRANSFORMER_DEPENDENCY, 5000);
}
}
@Override
public void addLogPipe(LogPipe pipe) {
if (pipe == null)
throw new IllegalArgumentException("pipe should be not null");
pipes.add(pipe);
}
@Override
public void removeLogPipe(LogPipe pipe) {
if (pipe == null)
throw new IllegalArgumentException("pipe should be not null");
pipes.remove(pipe);
}
@Override
public void addEventListener(LoggerEventListener callback) {
if (callback == null)
throw new IllegalArgumentException("logger event listener must be not null");
eventListeners.add(callback);
}
@Override
public void removeEventListener(LoggerEventListener callback) {
if (callback == null)
throw new IllegalArgumentException("logger event listener must be not null");
eventListeners.remove(callback);
}
@Override
public void clearEventListeners() {
eventListeners.clear();
}
public Set<String> getUnresolvedLoggers() {
return new HashSet<String>(unresolvedLoggers);
}
@Override
public boolean hasUnresolvedLoggers() {
return !unresolvedLoggers.isEmpty();
}
public void addUnresolvedLogger(String fullName) {
if (this.slog.isDebugEnabled()) {
this.slog.debug("araqne log api: logger [{}] has unresolved logger [{}]", getFullName(), fullName);
}
unresolvedLoggers.add(fullName);
if (this.enabled) {
stop(LoggerStopReason.LOGGER_DEPENDENCY, 5000);
}
}
public void removeUnresolvedLogger(String fullName) {
if (this.slog.isDebugEnabled()) {
this.slog.debug("araqne log api: logger [{}] has resolved logger [{}]", getFullName(), fullName);
}
if (!this.unresolvedLoggers.remove(fullName))
return;
boolean transformerResolved = config.get("transformer") == null || transformer != null;
if (!manualStart && status != LoggerStatus.Running && enabled && transformerResolved && unresolvedLoggers.isEmpty()) {
start(LoggerStartReason.DEPENDENCY_RESOLVED, getInterval());
}
}
@Override
public String toString() {
String format = "yyyy-MM-dd HH:mm:ss";
String start = DateFormat.format(format, lastStartDate);
String run = DateFormat.format(format, lastRunDate);
String log = DateFormat.format(format, lastLogDate);
String status = getStatus().toString().toLowerCase();
if (isPassive())
status += " (passive)";
else
status += " (interval=" + interval + "ms)";
return String.format(
"name=%s, factory=%s, status=%s, log count=%d, log volume=%d, last start=%s, last run=%s, last log=%s",
getFullName(), factoryFullName, status, getLogCount(), getLogVolume(), start, run, log);
}
}