/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import freenet.config.EnumerableOptionCallback;
import freenet.config.InvalidConfigValueException;
import freenet.config.NodeNeedRestartException;
import freenet.config.OptionFormatException;
import freenet.config.SubConfig;
import freenet.support.Executor;
import freenet.support.FileLoggerHook;
import freenet.support.Logger;
import freenet.support.LoggerHook;
import freenet.support.LoggerHookChain;
import freenet.support.FileLoggerHook.IntervalParseException;
import freenet.support.Logger.LogLevel;
import freenet.support.LoggerHook.InvalidThresholdException;
import freenet.support.api.BooleanCallback;
import freenet.support.api.IntCallback;
import freenet.support.api.LongCallback;
import freenet.support.api.StringCallback;
public class LoggingConfigHandler {
private static class PriorityCallback extends StringCallback implements EnumerableOptionCallback {
@Override
public String get() {
LoggerHookChain chain = Logger.getChain();
return chain.getThresholdNew().name();
}
@Override
public void set(String val) throws InvalidConfigValueException {
LoggerHookChain chain = Logger.getChain();
try {
chain.setThreshold(val);
} catch (LoggerHook.InvalidThresholdException e) {
throw new OptionFormatException(e.getMessage());
}
}
@Override
public String[] getPossibleValues() {
LogLevel[] priorities = LogLevel.values();
ArrayList<String> values = new ArrayList<String>(priorities.length+1);
for(LogLevel p : priorities)
values.add(p.name());
return values.toArray(new String[values.size()]);
}
}
protected static final String LOG_PREFIX = "freenet";
private final SubConfig config;
private FileLoggerHook fileLoggerHook;
private File logDir;
private long maxZippedLogsSize;
private String logRotateInterval;
private long maxCachedLogBytes;
private int maxCachedLogLines;
private long maxBacklogNotBusy;
private final Executor executor;
public LoggingConfigHandler(SubConfig loggingConfig, Executor executor) throws InvalidConfigValueException {
this.config = loggingConfig;
this.executor = executor;
loggingConfig.register("enabled", true, 1, true, false, "LogConfigHandler.enabled",
"LogConfigHandler.enabledLong",
new BooleanCallback() {
@Override
public Boolean get() {
return fileLoggerHook != null;
}
@Override
public void set(Boolean val) throws InvalidConfigValueException {
if (val == (fileLoggerHook != null)) return;
if (!val) {
disableLogger();
} else {
enableLogger();
}
}
});
boolean loggingEnabled = loggingConfig.getBoolean("enabled");
loggingConfig.register("dirname", "logs", 2, true, false, "LogConfigHandler.dirName",
"LogConfigHandler.dirNameLong",
new StringCallback() {
@Override
public String get() {
return logDir.getPath();
}
@Override
public void set(String val) throws InvalidConfigValueException {
File f = new File(val);
if (f.equals(logDir)) return;
preSetLogDir(f);
// Still here
if(fileLoggerHook == null) {
logDir = f;
} else {
// Discard old data
fileLoggerHook.switchBaseFilename(f.getPath()+File.separator+LOG_PREFIX);
logDir = f;
new Deleter(logDir).start();
}
}
});
logDir = new File(config.getString("dirname"));
if (loggingEnabled) {
preSetLogDir(logDir);
}
// => enableLogger must run preSetLogDir
// max space used by zipped logs
config.register("maxZippedLogsSize", "10M", 3, true, true, "LogConfigHandler.maxZippedLogsSize",
"LogConfigHandler.maxZippedLogsSizeLong",
new LongCallback() {
@Override
public Long get() {
return maxZippedLogsSize;
}
@Override
public void set(Long val) throws InvalidConfigValueException {
if (val < 0)
val = 0L;
maxZippedLogsSize = val;
if (fileLoggerHook != null) {
fileLoggerHook.setMaxOldLogsSize(val);
}
}
}, true);
maxZippedLogsSize = config.getLong("maxZippedLogsSize");
// These two are forced below so we don't need to check them now
// priority
// Node must override this to minor on testnet.
config.register("priority", "warning", 4, false, false, "LogConfigHandler.minLoggingPriority",
"LogConfigHandler.minLoggingPriorityLong",
new PriorityCallback());
// detailed priority
config.register("priorityDetail", "", 5, true, false, "LogConfigHandler.detaildPriorityThreshold",
"LogConfigHandler.detaildPriorityThresholdLong",
new StringCallback() {
@Override
public String get() {
LoggerHookChain chain = Logger.getChain();
return chain.getDetailedThresholds();
}
@Override
public void set(String val) throws InvalidConfigValueException {
LoggerHookChain chain = Logger.getChain();
try {
chain.setDetailedThresholds(val);
} catch (InvalidThresholdException e) {
throw new InvalidConfigValueException(e.getMessage());
}
}
});
// interval
config.register("interval", "1HOUR", 5, true, false, "LogConfigHandler.rotationInterval",
"LogConfigHandler.rotationIntervalLong",
new StringCallback() {
@Override
public String get() {
return logRotateInterval;
}
@Override
public void set(String val) throws InvalidConfigValueException {
if (val.equals(logRotateInterval)) return;
if (fileLoggerHook != null) {
try {
fileLoggerHook.setInterval(val);
} catch (FileLoggerHook.IntervalParseException e) {
throw new OptionFormatException(e.getMessage());
}
}
logRotateInterval = val;
}
});
logRotateInterval = config.getString("interval");
// max cached bytes in RAM
config.register("maxCachedBytes", "1M", 6, true, false, "LogConfigHandler.maxCachedBytes",
"LogConfigHandler.maxCachedBytesLong",
new LongCallback() {
@Override
public Long get() {
return maxCachedLogBytes;
}
@Override
public void set(Long val) throws InvalidConfigValueException {
if (val < 0) val = 0L;
if (val == maxCachedLogBytes) return;
maxCachedLogBytes = val;
if (fileLoggerHook != null) fileLoggerHook.setMaxListBytes(val);
}
}, true);
maxCachedLogBytes = config.getLong("maxCachedBytes");
// max cached lines in RAM
config.register("maxCachedLines", "10k", 7, true, false, "LogConfigHandler.maxCachedLines",
"LogConfigHandler.maxCachedLinesLong",
new IntCallback() {
@Override
public Integer get() {
return maxCachedLogLines;
}
@Override
public void set(Integer val) throws InvalidConfigValueException, NodeNeedRestartException {
if(val < 0) val = 0;
if(val == maxCachedLogLines) return;
maxCachedLogLines = val;
throw new NodeNeedRestartException("logger.maxCachedLogLines");
}
}, false);
maxCachedLogLines = config.getInt("maxCachedLines");
config.register("maxBacklogNotBusy", "60000", 8, true, false, "LogConfigHandler.maxBacklogNotBusy",
"LogConfigHandler.maxBacklogNotBusy",
new LongCallback() {
@Override
public Long get() {
return maxBacklogNotBusy;
}
@Override
public void set(Long val) throws InvalidConfigValueException, NodeNeedRestartException {
if(val < 0) throw new InvalidConfigValueException("Must be >= 0");
if(val == maxBacklogNotBusy) return;
maxBacklogNotBusy = val;
if(fileLoggerHook != null) fileLoggerHook.setMaxBacklogNotBusy(val);
}
}, false);
maxBacklogNotBusy = config.getLong("maxBacklogNotBusy");
if (loggingEnabled) enableLogger();
config.finishedInitialization();
}
private final Object enableLoggerLock = new Object();
/**
* Turn on the logger.
*/
private void enableLogger() {
try {
preSetLogDir(logDir);
} catch (InvalidConfigValueException e3) {
System.err.println("Cannot set log dir: "+logDir+": "+e3);
e3.printStackTrace();
}
synchronized(enableLoggerLock) {
if(fileLoggerHook != null) return;
Logger.setupChain();
try {
config.forceUpdate("priority");
config.forceUpdate("priorityDetail");
} catch (InvalidConfigValueException e2) {
System.err.println("Invalid config value for logger.priority in config file: "+config.getString("priority"));
// Leave it at the default.
} catch (NodeNeedRestartException e) {
// impossible
System.err.println("impossible NodeNeedRestartException for logger.priority in config file: "
+ config.getString("priority"));
}
FileLoggerHook hook;
try {
hook =
new FileLoggerHook(true, new File(logDir, LOG_PREFIX).getAbsolutePath(),
"d (c, t, p): m", "MMM dd, yyyy HH:mm:ss:SSS", logRotateInterval, LogLevel.DEBUG /* filtered by chain */, false, true,
maxZippedLogsSize /* 1GB of old compressed logfiles */, maxCachedLogLines);
} catch (IOException e) {
System.err.println("CANNOT START LOGGER: "+e.getMessage());
return;
} catch (IntervalParseException e) {
System.err.println("INVALID LOGGING INTERVAL: "+e.getMessage());
logRotateInterval = "5MINUTE";
try {
hook =
new FileLoggerHook(true, new File(logDir, LOG_PREFIX).getAbsolutePath(),
"d (c, t, p): m", "MMM dd, yyyy HH:mm:ss:SSS", logRotateInterval, LogLevel.DEBUG /* filtered by chain */, false, true,
maxZippedLogsSize /* 1GB of old compressed logfiles */, maxCachedLogLines);
} catch (IntervalParseException e1) {
System.err.println("CANNOT START LOGGER: IMPOSSIBLE: "+e1.getMessage());
return;
} catch (IOException e1) {
System.err.println("CANNOT START LOGGER: "+e1.getMessage());
return;
}
}
hook.setMaxListBytes(maxCachedLogBytes);
hook.setMaxBacklogNotBusy(maxBacklogNotBusy);
fileLoggerHook = hook;
Logger.globalAddHook(hook);
hook.start();
}
}
protected void disableLogger() {
synchronized(enableLoggerLock) {
if(fileLoggerHook == null) return;
FileLoggerHook hook = fileLoggerHook;
Logger.globalRemoveHook(hook);
hook.close();
fileLoggerHook = null;
Logger.destroyChainIfEmpty();
}
}
protected void preSetLogDir(File f) throws InvalidConfigValueException {
boolean exists = f.exists();
if(exists && !f.isDirectory())
throw new InvalidConfigValueException("Cannot overwrite a file with a log directory");
if(!exists) {
f.mkdir();
exists = f.exists();
if(!exists || !f.isDirectory())
throw new InvalidConfigValueException("Cannot create log directory");
}
}
class Deleter implements Runnable {
File logDir;
public Deleter(File logDir) {
this.logDir = logDir;
}
void start() {
executor.execute(this, "Old log directory "+logDir+" deleter");
}
@Override
public void run() {
freenet.support.Logger.OSThread.logPID(this);
fileLoggerHook.waitForSwitch();
delete(logDir);
}
/** @return true if we can't delete due to presence of non-Freenet files */
private boolean delete(File dir) {
boolean failed = false;
File[] files = dir.listFiles();
for(File f: files) {
String s = f.getName();
if(s.startsWith("freenet-") && (s.indexOf(".log") != -1)) {
if(f.isFile()) {
if(!f.delete()) failed = true;
} else if(f.isDirectory()) {
if(delete(f)) failed = true;
}
} else {
failed = true;
}
}
if(!failed) {
failed = !(dir.delete());
}
return failed;
}
}
public FileLoggerHook getFileLoggerHook() {
return fileLoggerHook;
}
public void forceEnableLogging() {
enableLogger();
}
public long getMaxZippedLogFiles() {
return maxZippedLogsSize;
}
public void setMaxZippedLogFiles(String maxSizeAsString) throws InvalidConfigValueException,
NodeNeedRestartException {
config.set("maxZippedLogsSize", maxSizeAsString);
}
}