From 91e2ac270b1958a12d52b3d0f1a6d50f37ad43b5 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Fri, 23 Jun 2023 16:50:53 +0200
Subject: [PATCH] ENH: make response logging format configurable

---
 conf/core/server.conf                         | 16 +++++-
 .../java/org/caosdb/server/CaosDBServer.java  |  7 +++
 .../org/caosdb/server/ServerProperties.java   |  2 +
 .../server/grpc/LoggingInterceptor.java       | 57 ++++++++++++++++++-
 4 files changed, 79 insertions(+), 3 deletions(-)

diff --git a/conf/core/server.conf b/conf/core/server.conf
index c030d6eb..eed73c13 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -100,6 +100,20 @@ GRPC_SERVER_PORT_HTTPS=8443
 # HTTP port of the grpc end-point
 GRPC_SERVER_PORT_HTTP=
 
+# --------------------------------------------------
+# Response Log formatting (this cannot be configured by the logging frame work
+# and thus has to be configured here).
+# --------------------------------------------------
+
+# Logging format of the GRPC API.
+# Known keys: user-agent, local-address, remote-address, method.
+# 'OFF' turns off the logging.
+GRPC_RESPONSE_LOG_FORMAT={method} {local-address} {remote-address} {user-agent}
+# Logging format of the REST API.
+# Known keys: see column "Variable name" at https://javadocs.restlet.talend.com/2.4/jse/api/index.html
+# 'OFF' turns off the logging.
+REST_RESPONSE_LOG_FORMAT=
+
 # --------------------------------------------------
 # HTTPS options
 # --------------------------------------------------
@@ -229,4 +243,4 @@ ENTITY_VERSIONING_ENABLED=true
 
 
 # Enabling the state machine extension
-# EXT_STATE_ENTITY=ENABLE
\ No newline at end of file
+# EXT_STATE_ENTITY=ENABLE
diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java
index 9daabaed..05fb9485 100644
--- a/src/main/java/org/caosdb/server/CaosDBServer.java
+++ b/src/main/java/org/caosdb/server/CaosDBServer.java
@@ -894,6 +894,13 @@ class CaosDBComponent extends Component {
 
   public CaosDBComponent() {
     super();
+    String responseLogFormat =
+        CaosDBServer.getServerProperty(ServerProperties.KEY_REST_RESPONSE_LOG_FORMAT);
+    if ("OFF".equalsIgnoreCase(responseLogFormat)) {
+      getLogService().setEnabled(false);
+    } else if (responseLogFormat != null && !responseLogFormat.isEmpty()) {
+      getLogService().setResponseLogFormat(responseLogFormat);
+    }
     setName(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_NAME));
     setOwner(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_OWNER));
   }
diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java
index d5825024..93a0c747 100644
--- a/src/main/java/org/caosdb/server/ServerProperties.java
+++ b/src/main/java/org/caosdb/server/ServerProperties.java
@@ -150,6 +150,8 @@ public class ServerProperties extends Properties implements Observable {
 
   public static final String KEY_PASSWORD_STRENGTH_REGEX = "PASSWORD_VALID_REGEX";
   public static final String KEY_PASSWORD_WEAK_MESSAGE = "PASSWORD_INVALID_MESSAGE";
+  public static final String KEY_REST_RESPONSE_LOG_FORMAT = "REST_RESPONSE_LOG_FORMAT";
+  public static final String KEY_GRPC_RESPONSE_LOG_FORMAT = "GRPC_RESPONSE_LOG_FORMAT";
 
   /**
    * Read the config files and initialize the server properties.
diff --git a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
index 9edd3509..4e52485d 100644
--- a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
+++ b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java
@@ -1,5 +1,6 @@
 package org.caosdb.server.grpc;
 
+import io.grpc.Attributes.Key;
 import io.grpc.Context;
 import io.grpc.Contexts;
 import io.grpc.Metadata;
@@ -7,18 +8,70 @@ import io.grpc.ServerCall;
 import io.grpc.ServerCall.Listener;
 import io.grpc.ServerCallHandler;
 import io.grpc.ServerInterceptor;
+import java.net.SocketAddress;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.ServerProperties;
+import org.restlet.routing.Template;
+import org.restlet.util.Resolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+class CallResolver<ReqT, RespT> extends Resolver<String> {
+
+  private ServerCall<ReqT, RespT> call;
+  private Metadata headers;
+
+  public static final Metadata.Key<String> KEY_USER_AGENT =
+      Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
+  public static final Key<SocketAddress> KEY_LOCAL_ADDRESS = io.grpc.Grpc.TRANSPORT_ATTR_LOCAL_ADDR;
+  public static final Key<SocketAddress> KEY_REMOTE_ADDRESS =
+      io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
+
+  public CallResolver(
+      ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+    this.call = call;
+    this.headers = headers;
+  }
+
+  @Override
+  public String resolve(String name) {
+    switch (name) {
+      case "user-agent":
+        return headers.get(KEY_USER_AGENT);
+      case "remote-address":
+        return call.getAttributes().get(KEY_REMOTE_ADDRESS).toString();
+      case "local-address":
+        return call.getAttributes().get(KEY_LOCAL_ADDRESS).toString();
+      case "method":
+        return call.getMethodDescriptor().getFullMethodName();
+      default:
+        break;
+    }
+    return null;
+  }
+}
+
 public class LoggingInterceptor implements ServerInterceptor {
 
+  private Template template;
+
+  public LoggingInterceptor() {
+    String format = CaosDBServer.getServerProperty(ServerProperties.KEY_GRPC_RESPONSE_LOG_FORMAT);
+    if ("OFF".equalsIgnoreCase(format)) {
+      this.template = null;
+    } else if (format != null) {
+      this.template = new Template(format);
+    }
+  }
+
   private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class.getName());
 
   @Override
   public <ReqT, RespT> Listener<ReqT> interceptCall(
       ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
-
-    logger.info(call.getMethodDescriptor().getFullMethodName() + " - " + call.getAttributes());
+    if (template != null) {
+      logger.info(template.format(new CallResolver<ReqT, RespT>(call, headers, next)));
+    }
     return Contexts.interceptCall(Context.current(), call, headers, next);
   }
 }
-- 
GitLab