/* * 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.krakenapps.log.api; import java.util.Collections; import java.util.Date; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicLong; import org.krakenapps.api.DateFormat; public abstract class AbstractLogger implements Logger, Runnable { private final org.slf4j.Logger log = 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 boolean isPassive; private volatile LogPipe[] pipes; private Object updateLock = new Object(); private Thread t; private int interval; private Properties config; private volatile LoggerStatus status = LoggerStatus.Stopped; private volatile boolean doStop = false; private volatile boolean stopped = true; private volatile Date lastStartDate; private volatile Date lastRunDate; private volatile Date lastLogDate; private AtomicLong logCounter; private Set<LoggerEventListener> eventListeners; public AbstractLogger(String name, String description, LoggerFactory loggerFactory) { this(name, description, loggerFactory, new Properties()); } public AbstractLogger(String name, String description, LoggerFactory loggerFactory, Properties config) { this("local", name, loggerFactory.getNamespace(), loggerFactory.getName(), description, config); } public AbstractLogger(String namespace, String name, String description, LoggerFactory loggerFactory) { this(namespace, name, description, loggerFactory, new Properties()); } public AbstractLogger(String namespace, String name, String description, LoggerFactory loggerFactory, Properties config) { this(namespace, name, loggerFactory.getNamespace(), loggerFactory.getName(), description, config); } public AbstractLogger(String namespace, String name, String description, LoggerFactory loggerFactory, long logCount, Date lastLogDate, Properties config) { this(namespace, name, loggerFactory.getNamespace(), loggerFactory.getName(), description, logCount, lastLogDate, config); } public AbstractLogger(String namespace, String name, String factoryNamespace, String factoryName, Properties config) { this(namespace, name, factoryNamespace, factoryName, "", config); } public AbstractLogger(String namespace, String name, String factoryNamespace, String factoryName, String description, Properties config) { this(namespace, name, factoryNamespace, factoryName, description, 0, null, config); } public AbstractLogger(String namespace, String name, String factoryNamespace, String factoryName, String description, long logCount, Date lastLogDate, Properties config) { // logger info this.namespace = namespace; this.name = name; this.fullName = namespace + "\\" + name; this.description = description; this.config = config; // logger factory info this.factoryNamespace = factoryNamespace; this.factoryName = factoryName; this.factoryFullName = factoryNamespace + "\\" + factoryName; this.logCounter = new AtomicLong(logCount); this.lastLogDate = lastLogDate; this.pipes = new LogPipe[0]; this.eventListeners = Collections.newSetFromMap(new ConcurrentHashMap<LoggerEventListener, Boolean>()); } @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 isPassive; } @Override public void setPassive(boolean isPassive) { if (!stopped) throw new IllegalStateException("logger is running"); this.isPassive = isPassive; if (isPassive) lastRunDate = null; } @Override public Date getLastStartDate() { return lastStartDate; } @Override public Date getLastRunDate() { return lastRunDate; } @Override public Date getLastLogDate() { return lastLogDate; } @Override public long getLogCount() { return logCounter.get(); } @Override public boolean isRunning() { return !stopped; } @Override public LoggerStatus getStatus() { return status; } @Override public int getInterval() { return interval; } @Override public void start() { // Passive if (!isPassive) throw new IllegalStateException("not passive mode. use start(interval)"); stopped = false; invokeStartCallback(); } @Override public void start(int interval) { // Active if (isPassive) { start(); return; } if (!stopped) throw new IllegalStateException("logger is already running"); status = LoggerStatus.Starting; this.interval = interval; if (getExecutor() == null) { t = new Thread(this, "Logger [" + fullName + "]"); t.start(); } else { stopped = false; getExecutor().execute(this); } invokeStartCallback(); } protected ExecutorService getExecutor() { return null; } private void invokeStartCallback() { lastStartDate = new Date(); status = LoggerStatus.Running; for (LoggerEventListener callback : eventListeners) { try { callback.onStart(this); } catch (Exception e) { log.warn("logger callback should not throw any exception", e); } } } @Override public void stop() { if (isPassive) { stopped = true; status = LoggerStatus.Stopped; invokeStopCallback(); } else stop(INFINITE); } @Override public void stop(int maxWaitTime) { if (isPassive) { stop(); return; } if (t != null) { if (!t.isAlive()) { t = null; return; } t.interrupt(); t = null; } status = LoggerStatus.Stopping; if (getExecutor() == null) { doStop = true; long begin = new Date().getTime(); try { while (true) { if (stopped) break; if (maxWaitTime != 0 && new Date().getTime() - begin > maxWaitTime) break; Thread.sleep(50); } } catch (InterruptedException e) { } } else { status = LoggerStatus.Stopped; stopped = true; try { onStop(); } catch (Exception e) { log.warn("krane log api: [" + fullName + "] stop callback should not throw any exception", e); } } invokeStopCallback(); } private void invokeStopCallback() { for (LoggerEventListener callback : eventListeners) { try { callback.onStop(this); } catch (Exception e) { log.warn("logger callback should not throw any exception", e); } } } protected abstract void runOnce(); protected void onStop() { } @Override public void run() { if (getExecutor() == null) { stopped = false; try { while (true) { try { if (doStop) break; long startedAt = System.currentTimeMillis(); runOnce(); updateConfig(config); long elapsed = System.currentTimeMillis() - startedAt; lastRunDate = new Date(); if (interval - elapsed < 0) continue; Thread.sleep(interval - elapsed); } catch (InterruptedException e) { } } } catch (Exception e) { log.error("kraken log api: logger stopped", e); } finally { status = LoggerStatus.Stopped; stopped = true; doStop = false; try { onStop(); } catch (Exception e) { log.warn("krane log api: [" + fullName + "] stop callback should not throw any exception", e); } } } else { if (!isRunning()) 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(); updateConfig(config); lastRunDate = new Date(); ExecutorService executor = getExecutor(); if (executor != null) executor.execute(this); } } protected void write(Log log) { if (stopped) return; // update last log date lastLogDate = log.getDate(); logCounter.incrementAndGet(); // notify all LogPipe[] capturedPipes = pipes; for (LogPipe pipe : capturedPipes) { try { pipe.onLog(this, log); } catch (Exception e) { if (e.getMessage() != null && e.getMessage().startsWith("invalid time")) this.log.warn("kraken-log-api: log pipe should not throw exception" + e.getMessage()); else this.log.warn("kraken-log-api: log pipe should not throw exception", e); } } } @Override public void updateConfig(Properties config) { for (LoggerEventListener callback : eventListeners) { try { callback.onUpdated(this, config); } catch (Exception e) { log.error("kraken log api: logger event callback should not throw any exception", e); } } } @Override public Properties getConfig() { return config; } @Override public void addLogPipe(LogPipe pipe) { if (pipe == null) throw new IllegalArgumentException("pipe should be not null"); // read-copy-update synchronized (updateLock) { // check if already exists boolean found = false; for (int i = 0; i < pipes.length; i++) if (pipes[i] == pipe) found = true; if (found) return; // copy old items LogPipe[] newPipes = new LogPipe[pipes.length + 1]; for (int i = 0; i < pipes.length; i++) newPipes[i] = pipes[i]; // add new item newPipes[pipes.length] = pipe; pipes = newPipes; } } @Override public void removeLogPipe(LogPipe pipe) { if (pipe == null) throw new IllegalArgumentException("pipe should be not null"); // read-copy-update synchronized (updateLock) { // check if exists boolean found = false; for (int i = 0; i < pipes.length; i++) if (pipes[i] == pipe) found = true; if (!found) return; LogPipe[] newPipes = new LogPipe[pipes.length - 1]; int j = 0; for (int i = 0; i < pipes.length; i++) { if (pipes[i] == pipe) continue; newPipes[j++] = pipes[i]; } pipes = newPipes; } } @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(); } @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, last start=%s, last run=%s, last log=%s", getFullName(), factoryFullName, status, getLogCount(), start, run, log); } }