/*
* Copyright 2013-2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.log;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import ccre.channel.EventOutput;
import ccre.cluck.Cluck;
import ccre.cluck.CluckConstants;
import ccre.cluck.CluckNode;
import ccre.cluck.CluckPublisher;
import ccre.cluck.CluckRemoteListener;
import ccre.util.UniqueIds;
/**
* A logging tool that shares all logging between networked cluck systems
* automatically.
*
* @author skeggsc
*/
public final class NetworkAutologger implements LoggingTarget, CluckRemoteListener {
/**
* Whether or not a NetworkAutologger has been registered globally.
*/
private static volatile boolean registered = false;
private static final LoggingTarget localLoggingTarget = new LoggingTarget() {
@Override
public void log(LogLevel level, String message, Throwable throwable) {
Logger.log(level, "[NET] " + message, throwable);
}
@Override
public void log(LogLevel level, String message, String extended) {
Logger.logExt(level, "[NET] " + message, extended);
}
};
/**
* Register a new global NetworkAutologger with the logging manager. This
* only occurs once - after that an warning will be logged again.
*/
public static synchronized void register() {
if (registered) {
Logger.warning("Network autologger registered twice!");
return;
}
registered = true;
NetworkAutologger nlog = new NetworkAutologger(Cluck.getNode());
Logger.addTarget(nlog);
nlog.start();
}
/**
* The current list of remotes to send logging messages to.
*/
private final CopyOnWriteArrayList<String> remotes = new CopyOnWriteArrayList<String>();
/**
* The current cache of subscribed LoggingTargets to send logging messages
* to.
*/
private final HashMap<String, LoggingTarget> targetCache = new HashMap<String, LoggingTarget>();
private final CluckNode node;
private final String localpath, hereID;
/**
* Create a new NetworkAutologger hooked up to the specified node.
*
* @param node The node to attach to.
*/
public NetworkAutologger(final CluckNode node) {
hereID = UniqueIds.global.nextHexId();
localpath = "auto-" + hereID;
this.node = node;
CluckPublisher.publish(node, localpath, localLoggingTarget);
}
// TODO: change from package-private to public
// (it was added in a bugfix release)
String getLocalPath() {
return localpath;
}
/**
* Start the Autologger - it will now start sending out logged messages.
*/
public void start() {
final EventOutput searcher = CluckPublisher.setupSearching(node, this);
searcher.safeEvent();
node.subscribeToStructureNotifications("netwatch-" + hereID, () -> {
Logger.fine("[LOCAL] Rechecking logging...");
searcher.event();
});
}
@Override
public void log(LogLevel level, String message, Throwable throwable) {
if (level == null) {
throw new NullPointerException();
}
// From the network, so don't broadcast.
if (message.contains("[NET]")) {
return;
}
// Local messages should not be sent over the network.
if (message.contains("[LOCAL]")) {
return;
}
for (String cur : remotes) {
LoggingTarget lt = targetCache.get(cur);
if (lt != null) {
lt.log(level, message, throwable);
}
}
}
@Override
public void log(LogLevel level, String message, String extended) {
if (level == null) {
throw new NullPointerException();
}
// From the network, so don't broadcast.
if (message.contains("[NET]")) {
return;
}
// Should not be sent over the network.
if (message.contains("[LOCAL]")) {
return;
}
for (String cur : remotes) {
LoggingTarget lt = targetCache.get(cur);
if (lt != null) {
lt.log(level, message, extended);
}
}
}
@Override
public void handle(String remote, int remoteType) {
if (remoteType != CluckConstants.RMT_LOGTARGET) {
return;
}
if (remote.contains("auto-") && !localpath.equals(remote) && targetCache.get(remote) == null) {
synchronized (targetCache) {
targetCache.put(remote, CluckPublisher.subscribeLT(node, remote));
}
Logger.config("[LOCAL] Loaded logger: " + remote);
}
remotes.addIfAbsent(remote);
}
}