Add advanced webserver logging + settings

This commit is contained in:
Lukas Rieger (Blue) 2023-06-29 13:37:31 +02:00
parent 8f97b08eb5
commit d570884def
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
10 changed files with 319 additions and 22 deletions

View File

@ -65,7 +65,7 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
this.coreConfig = loadCoreConfig(defaultDataFolder);
this.webappConfig = loadWebappConfig(defaultWebroot);
this.webserverConfig = loadWebserverConfig(webappConfig.getWebroot());
this.webserverConfig = loadWebserverConfig(webappConfig.getWebroot(), coreConfig.getData());
this.pluginConfig = usePluginConf ? loadPluginConfig() : new PluginConfig();
this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs(webappConfig.getWebroot()));
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs());
@ -144,7 +144,7 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
return configManager.loadConfig(configFileRaw, CoreConfig.class);
}
private synchronized WebserverConfig loadWebserverConfig(Path defaultWebroot) throws ConfigurationException {
private synchronized WebserverConfig loadWebserverConfig(Path defaultWebroot, Path dataRoot) throws ConfigurationException {
Path configFileRaw = Path.of("webserver");
Path configFile = configManager.findConfigPath(configFileRaw);
Path configFolder = configFile.getParent();
@ -156,6 +156,8 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
configFolder.resolve("webserver.conf"),
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/webserver.conf")
.setVariable("webroot", formatPath(defaultWebroot))
.setVariable("logfile", formatPath(dataRoot.resolve("logs").resolve("webapp.log")))
.setVariable("logfile-with-time", formatPath(dataRoot.resolve("logs").resolve("webapp_%1$tF_%1$tT.log")))
.build(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
);

View File

@ -38,13 +38,13 @@ import java.nio.file.Path;
public class WebserverConfig {
private boolean enabled = true;
private Path webroot = Path.of("bluemap", "web");
private String ip = "0.0.0.0";
private int port = 8100;
private LogConfig log = new LogConfig();
public boolean isEnabled() {
return enabled;
}
@ -71,4 +71,30 @@ public class WebserverConfig {
return port;
}
public LogConfig getLog() {
return log;
}
@DebugDump
@ConfigSerializable
public static class LogConfig {
private String file = null;
private boolean append = true;
private String format = "%1$s \"%3$s %4$s %5$s\" %6$s %7$s";
public String getFile() {
return file;
}
public boolean isAppend() {
return append;
}
public String getFormat() {
return format;
}
}
}

View File

@ -59,6 +59,9 @@ import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@ -185,8 +188,22 @@ public class Plugin implements ServerEventListener {
);
}
// create web-logger
List<Logger> webLoggerList = new ArrayList<>();
if (webserverConfig.getLog().getFile() != null) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
webLoggerList.add(Logger.file(
Path.of(String.format(webserverConfig.getLog().getFile(), zdt)),
webserverConfig.getLog().isAppend()
));
}
try {
webServer = new HttpServer(routingRequestHandler);
webServer = new HttpServer(new LoggingRequestHandler(
routingRequestHandler,
webserverConfig.getLog().getFormat(),
Logger.combine(webLoggerList)
));
webServer.bind(new InetSocketAddress(
webserverConfig.resolveIp(),
webserverConfig.getPort()

View File

@ -1,50 +1,74 @@
package de.bluecolored.bluemap.common.web;
import de.bluecolored.bluemap.common.web.http.HttpHeader;
import de.bluecolored.bluemap.common.web.http.HttpRequest;
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
import de.bluecolored.bluemap.common.web.http.HttpResponse;
import de.bluecolored.bluemap.common.web.http.*;
import de.bluecolored.bluemap.core.logger.Logger;
public class LoggingRequestHandler implements HttpRequestHandler {
private final HttpRequestHandler delegate;
private final Logger logger;
private final String format;
public LoggingRequestHandler(HttpRequestHandler delegate) {
this(delegate, Logger.global);
}
public LoggingRequestHandler(HttpRequestHandler delegate, Logger logger) {
this(delegate, "", logger);
}
public LoggingRequestHandler(HttpRequestHandler delegate, String format) {
this(delegate, format, Logger.global);
}
public LoggingRequestHandler(HttpRequestHandler delegate, String format, Logger logger) {
this.delegate = delegate;
this.format = format;
this.logger = logger;
}
@Override
public HttpResponse handle(HttpRequest request) {
String source = request.getSource().toString();
// gather format parameters from request
String source = request.getSource().toString();
String xffSource = source;
HttpHeader xffHeader = request.getHeader("X-Forwarded-For");
if (xffHeader != null && !xffHeader.getValues().isEmpty()) {
source = xffHeader.getValues().get(0);
xffSource = xffHeader.getValues().get(0);
}
StringBuilder log = new StringBuilder()
.append(source)
.append(" \"").append(request.getMethod())
.append(" ").append(request.getAddress())
.append(" ").append(request.getVersion())
.append("\" ");
String method = request.getMethod();
String address = request.getAddress();
String version = request.getVersion();
// run request
HttpResponse response = delegate.handle(request);
log.append(response.getStatusCode());
if (response.getStatusCode().getCode() < 400) {
logger.logInfo(log.toString());
// gather format parameters from response
HttpStatusCode status = response.getStatusCode();
int statusCode = status.getCode();
String statusMessage = status.getMessage();
// format log message
String log = String.format(this.format,
source,
xffSource,
method,
address,
version,
statusCode,
statusMessage
);
// do the logging
if (statusCode < 500) {
logger.logInfo(log);
} else {
logger.logWarning(log.toString());
logger.logWarning(log);
}
// return the response
return response;
}

View File

@ -16,3 +16,30 @@ webroot: "${webroot}"
# The port that the webserver listens to.
# Default is 8100
port: 8100
# Config-section for webserver-activity logging
log: {
# The file where all the webserver-activity will be logged to.
# Comment out to disable the logging completely.
# Java String formatting syntax can be used to add time
# Default is no logging
file: "${logfile}"
#file: "${logfile-with-time}"
# Whether the logger should append to an existing file, or overwrite it
# Default is true
append: true
# The format of the webserver-acivity logs.
# The syntax is the java String formatting, see: https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html
# Possible Arguments:
# 1 - the source address (ignoring any xff headers)
# 2 - the source address (using the (leftmost) xff header if provided)
# 3 - the http-method of the request
# 4 - the full request-address
# 5 - the protocol version of the request
# 6 - the status-code of the response
# 7 - the status-message of the response
# Default is "%1$s \"%3$s %4$s %5$s\" %6$s %7$s"
format: "%1$s \"%3$s %4$s %5$s\" %6$s %7$s"
}

View File

@ -0,0 +1,44 @@
package de.bluecolored.bluemap.core.logger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JavaLogger extends AbstractLogger {
private final Logger out;
public JavaLogger(Logger out) {
this.out = out;
}
@Override
public void logError(String message, Throwable throwable) {
out.log(Level.SEVERE, message, throwable);
}
@Override
public void logWarning(String message) {
out.log(Level.WARNING, message);
}
@Override
public void logInfo(String message) {
out.log(Level.INFO, message);
}
@Override
public void logDebug(String message) {
if (out.isLoggable(Level.FINE)) out.log(Level.FINE, message);
}
@Override
public void noFloodDebug(String message) {
if (out.isLoggable(Level.FINE)) super.noFloodDebug(message);
}
@Override
public void noFloodDebug(String key, String message) {
if (out.isLoggable(Level.FINE)) super.noFloodDebug(key, message);
}
}

View File

@ -0,0 +1,58 @@
package de.bluecolored.bluemap.core.logger;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class LogFormatter extends Formatter {
private final String format;
public LogFormatter() {
this("[%1$tF %1$tT][%4$s] %5$s%6$s%n");
}
public LogFormatter(String format) {
this.format = format;
}
/**
* Taken from {@link java.util.logging.SimpleFormatter} to be able to overwrite the format string.
* @param record the log record to be formatted.
* @return the formatted log
*/
public String format(LogRecord record) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(
record.getInstant(), ZoneId.systemDefault());
String source;
if (record.getSourceClassName() != null) {
source = record.getSourceClassName();
if (record.getSourceMethodName() != null) {
source += " " + record.getSourceMethodName();
}
} else {
source = record.getLoggerName();
}
String message = formatMessage(record);
String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
record.getThrown().printStackTrace(pw);
pw.close();
throwable = sw.toString();
}
return String.format(format,
zdt,
source,
record.getLoggerName(),
record.getLevel().getLocalizedName(),
message,
throwable);
}
}

View File

@ -24,6 +24,14 @@
*/
package de.bluecolored.bluemap.core.logger;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.FileHandler;
import java.util.stream.StreamSupport;
public abstract class Logger {
public static Logger global = stdOut();
@ -107,4 +115,41 @@ public abstract class Logger {
return new PrintStreamLogger(System.out, System.err);
}
public static Logger file(Path path) throws IOException {
return file(path, null);
}
public static Logger file(Path path, boolean append) throws IOException {
return file(path, null, append);
}
public static Logger file(Path path, String format) throws IOException {
return file(path, format, true);
}
public static Logger file(Path path, @Nullable String format, boolean append) throws IOException {
Files.createDirectories(path.getParent());
FileHandler fileHandler = new FileHandler(path.toString(), append);
fileHandler.setFormatter(format == null ? new LogFormatter() : new LogFormatter(format));
java.util.logging.Logger javaLogger = java.util.logging.Logger.getAnonymousLogger();
javaLogger.setUseParentHandlers(false);
javaLogger.addHandler(fileHandler);
return new JavaLogger(javaLogger);
}
public static Logger combine(Iterable<Logger> logger) {
return combine(StreamSupport.stream(logger.spliterator(), false)
.toArray(Logger[]::new));
}
public static Logger combine(Logger... logger) {
if (logger.length == 0) return new VoidLogger();
if (logger.length == 1) return logger[0];
return new MultiLogger(logger);
}
}

View File

@ -0,0 +1,36 @@
package de.bluecolored.bluemap.core.logger;
@SuppressWarnings("ForLoopReplaceableByForEach")
public class MultiLogger extends AbstractLogger {
private final Logger[] logger;
public MultiLogger(Logger... logger) {
this.logger = logger;
}
@Override
public void logError(String message, Throwable throwable) {
for (int i = 0; i < logger.length; i++)
logger[i].logError(message, throwable);
}
@Override
public void logWarning(String message) {
for (int i = 0; i < logger.length; i++)
logger[i].logWarning(message);
}
@Override
public void logInfo(String message) {
for (int i = 0; i < logger.length; i++)
logger[i].logInfo(message);
}
@Override
public void logDebug(String message) {
for (int i = 0; i < logger.length; i++)
logger[i].logDebug(message);
}
}

View File

@ -58,6 +58,9 @@ import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@ -203,10 +206,25 @@ public class BlueMapCLI implements ServerInterface {
);
}
List<Logger> webLoggerList = new ArrayList<>();
if (verbose) webLoggerList.add(Logger.global);
if (config.getLog().getFile() != null) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
webLoggerList.add(Logger.file(
Path.of(String.format(config.getLog().getFile(), zdt)),
config.getLog().isAppend()
));
}
HttpRequestHandler handler = new BlueMapResponseModifier(routingRequestHandler);
if (verbose) handler = new LoggingRequestHandler(handler);
handler = new LoggingRequestHandler(
handler,
config.getLog().getFormat(),
Logger.combine(webLoggerList)
);
try {
//noinspection resource
HttpServer webServer = new HttpServer(handler);
webServer.bind(new InetSocketAddress(
config.resolveIp(),