diff --git a/compose/.env b/compose/.env
index d3f530015..422d8fef5 100644
--- a/compose/.env
+++ b/compose/.env
@@ -15,7 +15,7 @@ GEOSERVER_BASE_PATH=/geoserver/cloud
# logging profile, either "default" or "json-logs"
#LOGGING_PROFILE=json-logs
LOGGING_PROFILE=default
-GEOSERVER_DEFAULT_PROFILES="${LOGGING_PROFILE},acl,logging_debug_events"
+GEOSERVER_DEFAULT_PROFILES="${LOGGING_PROFILE},acl"
GATEWAY_DEFAULT_PROFILES=${LOGGING_PROFILE}
DISCOVERY_SERVER_DEFAULT_PROFILES=${LOGGING_PROFILE}
diff --git a/config b/config
index 85a5df9ff..4a727b965 160000
--- a/config
+++ b/config
@@ -1 +1 @@
-Subproject commit 85a5df9ffce34110fa24ca246842a1cca0a28dfd
+Subproject commit 4a727b96541b7cc46d423cebed17423c5737cc2a
diff --git a/src/starters/observability/pom.xml b/src/starters/observability/pom.xml
index 0520df315..7eae7fa7d 100644
--- a/src/starters/observability/pom.xml
+++ b/src/starters/observability/pom.xml
@@ -20,7 +20,6 @@
com.github.f4b6a3
ulid-creator
-
javax.servlet
javax.servlet-api
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/accesslog/AccessLogServletAutoConfiguration.java b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/accesslog/AccessLogServletAutoConfiguration.java
new file mode 100644
index 000000000..359114628
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/accesslog/AccessLogServletAutoConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.autoconfigure.logging.accesslog;
+
+import org.geoserver.cloud.logging.accesslog.AccessLogFilterConfig;
+import org.geoserver.cloud.logging.accesslog.AccessLogServletFilter;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+@AutoConfiguration
+@ConditionalOnProperty(name = AccessLogFilterConfig.ENABLED_KEY, havingValue = "true", matchIfMissing = false)
+@EnableConfigurationProperties(AccessLogFilterConfig.class)
+@ConditionalOnWebApplication(type = Type.SERVLET)
+public class AccessLogServletAutoConfiguration {
+
+ @Bean
+ AccessLogServletFilter accessLogFilter(AccessLogFilterConfig conf) {
+ return new AccessLogServletFilter(conf);
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/GeoServerDispatcherMDCConfiguration.java b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/GeoServerDispatcherMDCConfiguration.java
similarity index 75%
rename from src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/GeoServerDispatcherMDCConfiguration.java
rename to src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/GeoServerDispatcherMDCConfiguration.java
index a46e058a2..669d14e12 100644
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/GeoServerDispatcherMDCConfiguration.java
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/GeoServerDispatcherMDCConfiguration.java
@@ -2,10 +2,10 @@
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
-package org.geoserver.cloud.autoconfigure.observability;
+package org.geoserver.cloud.autoconfigure.logging.mdc;
-import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
-import org.geoserver.cloud.observability.logging.ows.MDCDispatcherCallback;
+import org.geoserver.cloud.logging.mdc.config.MDCConfigProperties;
+import org.geoserver.cloud.logging.mdc.ows.OWSMdcDispatcherCallback;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.DispatcherCallback;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -19,7 +19,7 @@
* {@link AutoConfiguration @AutoConfiguration} to enable logging MDC (Mapped Diagnostic Context)
* for the GeoSever {@link Dispatcher} events using a {@link DispatcherCallback}
*
- * @see MDCDispatcherCallback
+ * @see OWSMdcDispatcherCallback
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({
@@ -31,7 +31,7 @@
class GeoServerDispatcherMDCConfiguration {
@Bean
- MDCDispatcherCallback mdcDispatcherCallback(MDCConfigProperties config) {
- return new MDCDispatcherCallback(config);
+ OWSMdcDispatcherCallback mdcDispatcherCallback(MDCConfigProperties config) {
+ return new OWSMdcDispatcherCallback(config.getGeoserver().getOws());
}
}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/LoggingMDCAutoConfiguration.java b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/LoggingMDCServletAutoConfiguration.java
similarity index 68%
rename from src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/LoggingMDCAutoConfiguration.java
rename to src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/LoggingMDCServletAutoConfiguration.java
index 26c717a21..c107971d4 100644
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/observability/LoggingMDCAutoConfiguration.java
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/autoconfigure/logging/mdc/LoggingMDCServletAutoConfiguration.java
@@ -2,16 +2,14 @@
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
-package org.geoserver.cloud.autoconfigure.observability;
+package org.geoserver.cloud.autoconfigure.logging.mdc;
import java.util.Optional;
-import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
-import org.geoserver.cloud.observability.logging.servlet.HttpRequestMdcConfigProperties;
-import org.geoserver.cloud.observability.logging.servlet.HttpRequestMdcFilter;
-import org.geoserver.cloud.observability.logging.servlet.MDCAuthenticationFilter;
-import org.geoserver.cloud.observability.logging.servlet.MDCCleaningFilter;
-import org.geoserver.cloud.observability.logging.servlet.SpringEnvironmentMdcConfigProperties;
-import org.geoserver.cloud.observability.logging.servlet.SpringEnvironmentMdcFilter;
+import org.geoserver.cloud.logging.mdc.config.MDCConfigProperties;
+import org.geoserver.cloud.logging.mdc.servlet.HttpRequestMdcFilter;
+import org.geoserver.cloud.logging.mdc.servlet.MDCAuthenticationFilter;
+import org.geoserver.cloud.logging.mdc.servlet.MDCCleaningFilter;
+import org.geoserver.cloud.logging.mdc.servlet.SpringEnvironmentMdcFilter;
import org.geoserver.security.GeoServerSecurityFilterChainProxy;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -36,36 +34,31 @@
* @see GeoServerDispatcherMDCConfiguration
*/
@AutoConfiguration
-@EnableConfigurationProperties({
- MDCConfigProperties.class,
- HttpRequestMdcConfigProperties.class,
- SpringEnvironmentMdcConfigProperties.class
-})
+@EnableConfigurationProperties({MDCConfigProperties.class})
@Import(GeoServerDispatcherMDCConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
-public class LoggingMDCAutoConfiguration {
+public class LoggingMDCServletAutoConfiguration {
+
+ @Bean
+ MDCCleaningFilter mdcCleaningServletFilter() {
+ return new MDCCleaningFilter();
+ }
/**
* @return servlet filter to {@link MDC#clear() clear} the MDC after the servlet request is
* executed
*/
@Bean
- @Order(Ordered.HIGHEST_PRECEDENCE)
- HttpRequestMdcFilter httpMdcFilter(HttpRequestMdcConfigProperties config) {
- return new HttpRequestMdcFilter(config);
- }
-
- @Bean
- @Order(Ordered.HIGHEST_PRECEDENCE)
- MDCCleaningFilter mdcCleaningServletFilter() {
- return new MDCCleaningFilter();
+ @Order(Ordered.HIGHEST_PRECEDENCE + 2)
+ HttpRequestMdcFilter httpMdcFilter(MDCConfigProperties config) {
+ return new HttpRequestMdcFilter(config.getHttp());
}
@Bean
- @Order(Ordered.HIGHEST_PRECEDENCE)
+ @Order(Ordered.HIGHEST_PRECEDENCE + 2)
SpringEnvironmentMdcFilter springEnvironmentMdcFilter(
- Environment env, SpringEnvironmentMdcConfigProperties config, Optional buildProperties) {
- return new SpringEnvironmentMdcFilter(env, buildProperties, config);
+ Environment env, MDCConfigProperties config, Optional buildProperties) {
+ return new SpringEnvironmentMdcFilter(env, buildProperties, config.getApplication());
}
/**
@@ -80,7 +73,7 @@ FilterRegistrationBean mdcAuthenticationPropertiesServl
MDCConfigProperties config) {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
- var filter = new MDCAuthenticationFilter(config);
+ var filter = new MDCAuthenticationFilter(config.getUser());
registration.setMatchAfter(true);
registration.addUrlPatterns("/*");
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogFilterConfig.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogFilterConfig.java
new file mode 100644
index 000000000..acb10a6d7
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogFilterConfig.java
@@ -0,0 +1,90 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.accesslog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Configuration to set white/black list over the request URL to determine if
+ * the access log filter will log an entry for it.
+ */
+@Data
+@ConfigurationProperties(prefix = "logging.accesslog")
+@Slf4j(topic = "org.geoserver.cloud.accesslog")
+public class AccessLogFilterConfig {
+
+ public static final String ENABLED_KEY = "logging.accesslog.enabled";
+
+ /**
+ * A list of java regular expressions applied to the request URL for logging at
+ * trace level
+ */
+ List trace = new ArrayList<>();
+
+ /**
+ * A list of java regular expressions applied to the request URL for logging at
+ * debug level
+ */
+ List debug = new ArrayList<>();
+
+ /**
+ * A list of java regular expressions applied to the request URL for logging at
+ * info level
+ */
+ List info = new ArrayList<>();
+
+ private enum Level {
+ OFF {
+ @Override
+ void log(String message, Object... args) {
+ // no-op
+ }
+ },
+ TRACE {
+ @Override
+ void log(String message, Object... args) {
+ log.trace(message, args);
+ }
+ },
+ DEBUG {
+ @Override
+ void log(String message, Object... args) {
+ log.debug(message, args);
+ }
+ },
+ INFO {
+ @Override
+ void log(String message, Object... args) {
+ log.info(message, args);
+ }
+ };
+
+ abstract void log(String message, Object... args);
+ }
+
+ public void log(String method, int statusCode, String uri) {
+ Level level = getLogLevel(uri);
+ level.log("{} {} {} ", method, statusCode, uri);
+ }
+
+ Level getLogLevel(String uri) {
+ if (log.isInfoEnabled() && matches(uri, info)) return Level.INFO;
+ if (log.isDebugEnabled() && matches(uri, debug)) return Level.INFO;
+ if (log.isTraceEnabled() && matches(uri, trace)) return Level.INFO;
+
+ return Level.OFF;
+ }
+
+ private boolean matches(String url, List patterns) {
+ return (patterns == null || patterns.isEmpty())
+ ? false
+ : patterns.stream().anyMatch(pattern -> pattern.matcher(url).matches());
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogServletFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogServletFilter.java
new file mode 100644
index 000000000..4dcdf26b4
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/accesslog/AccessLogServletFilter.java
@@ -0,0 +1,41 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.accesslog;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.NonNull;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.filter.CommonsRequestLoggingFilter;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/** Similar to {@link CommonsRequestLoggingFilter} but uses slf4j */
+@Order(Ordered.HIGHEST_PRECEDENCE + 3)
+public class AccessLogServletFilter extends OncePerRequestFilter {
+
+ private final @NonNull AccessLogFilterConfig config;
+
+ public AccessLogServletFilter(@NonNull AccessLogFilterConfig conf) {
+ this.config = conf;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ try {
+ filterChain.doFilter(request, response);
+ } finally {
+ String uri = request.getRequestURI();
+ String method = request.getMethod();
+ int statusCode = response.getStatus();
+ config.log(method, statusCode, uri);
+ }
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/AuthenticationMdcConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/AuthenticationMdcConfigProperties.java
new file mode 100644
index 000000000..3184c5e59
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/AuthenticationMdcConfigProperties.java
@@ -0,0 +1,19 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.config;
+
+import lombok.Data;
+
+@Data
+public class AuthenticationMdcConfigProperties {
+
+ /** Whether to append the enduser.id MDC property from the Authentication name */
+ private boolean id = false;
+
+ /**
+ * Whether to append the enduser.roles MDC property from the Authentication granted authorities
+ */
+ private boolean roles = false;
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/GeoServerMdcConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/GeoServerMdcConfigProperties.java
new file mode 100644
index 000000000..77e7ab1f9
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/GeoServerMdcConfigProperties.java
@@ -0,0 +1,38 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.config;
+
+import lombok.Data;
+
+@Data
+public class GeoServerMdcConfigProperties {
+
+ private OWSMdcConfigProperties ows = new OWSMdcConfigProperties();
+
+ /** Configuration properties to contribute GeoServer OWS request properties to the MDC */
+ @Data
+ public static class OWSMdcConfigProperties {
+ /**
+ * Whether to append the gs.ows.service.name MDC property from the OWS dispatched request
+ */
+ private boolean serviceName = true;
+
+ /**
+ * Whether to append the gs.ows.service.version MDC property from the OWS dispatched request
+ */
+ private boolean serviceVersion = true;
+
+ /**
+ * Whether to append the gs.ows.service.format MDC property from the OWS dispatched request
+ */
+ private boolean serviceFormat = true;
+
+ /**
+ * Whether to append the gs.ows.service.operation MDC property from the OWS dispatched
+ * request
+ */
+ private boolean operationName = true;
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/HttpRequestMdcConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/HttpRequestMdcConfigProperties.java
new file mode 100644
index 000000000..6137ea9d4
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/HttpRequestMdcConfigProperties.java
@@ -0,0 +1,229 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.config;
+
+import com.github.f4b6a3.ulid.UlidCreator;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import lombok.Data;
+import lombok.NonNull;
+import org.slf4j.MDC;
+import org.springframework.http.HttpCookie;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.MultiValueMap;
+
+/** Contributes HTTP request properties to MDC attributes */
+@Data
+public class HttpRequestMdcConfigProperties {
+
+ public static final String REQUEST_ID_HEADER = "http.request.id";
+
+ /**
+ * Whether to append the http.request.id MDC property. The value is the id provided by the
+ * http.request.id header, or a new monotonically increating UID if no such header is present.
+ */
+ private boolean id = true;
+
+ /**
+ * Whether to append the http.request.remote-addr MDC property, interpreted as the Internet
+ * Protocol (IP) address of the client or last proxy that sent the request. For HTTP servlets,
+ * same as the value of the CGI variable REMOTE_ADDR.
+ */
+ private boolean remoteAddr = false;
+
+ /**
+ * Whether to append the http.request.remote-host MDC property, interpreted as the fully
+ * qualified name of the client or the last proxy that sent the request. If the engine cannot or
+ * chooses not to resolve the hostname (to improve performance), this method returns the
+ * dotted-string form of the IP address. For HTTP servlets, same as the value of the CGI
+ * variable REMOTE_HOST. Defaults to false to avoid the possible overhead in reverse DNS
+ * lookups. remoteAddress should be enough in most cases.
+ */
+ private boolean remoteHost = false;
+
+ /** Whether to append the http.request.method MDC property */
+ private boolean method = true;
+
+ /** Whether to append the http.request.url MDC property, without the query string */
+ private boolean url = true;
+
+ /**
+ * Whether to append one http.request.parameter.[name] MDC property from each request parameter
+ */
+ private boolean parameters = false;
+
+ /**
+ * Whether to append the http.request.query-string MDC property from the HTTP request query
+ * string
+ */
+ private boolean queryString = false;
+
+ /**
+ * Whether to append the http.request.session.is MDC property if there's an HttpSession
+ * associated to the request
+ */
+ private boolean sessionId = false;
+
+ /** Whether to append one http.request.cookie.[name] MDC property from each request cookie */
+ private boolean cookies = false;
+
+ /**
+ * Whether to append one http.request.header.[name] MDC property from each HTTP request header
+ * whose name matches the headers-pattern
+ */
+ private boolean headers = false;
+
+ /**
+ * Java regular expression indicating which request header names to include when
+ * logging.mdc.include.http.headers=true. Defaults to include all headers with the pattern '.*'
+ */
+ private Pattern headersPattern = Pattern.compile(".*");
+
+ public HttpRequestMdcConfigProperties headers(Supplier headers) {
+ if (isHeaders()) {
+ HttpHeaders httpHeaders = headers.get();
+ httpHeaders.forEach(this::putHeader);
+ }
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties cookies(Supplier> cookies) {
+ if (isCookies()) {
+ cookies.get().values().forEach(this::putCookie);
+ }
+ return this;
+ }
+
+ private void putCookie(List cookies) {
+ cookies.forEach(c -> {
+ String key = "http.request.cookie.%s".formatted(c.getName());
+ String value = MDC.get(key);
+ if (value == null) {
+ value = c.getValue();
+ } else {
+ value = "%s;%s".formatted(value, c.getValue());
+ }
+ MDC.put(key, value);
+ });
+ }
+
+ private boolean includeHeader(String headerName) {
+ if ("cookie".equalsIgnoreCase(headerName)) return false;
+ return getHeadersPattern().matcher(headerName).matches();
+ }
+
+ private void putHeader(String name, List values) {
+ if (includeHeader(name)) {
+ put("http.request.header.%s".formatted(name), () -> values.stream().collect(Collectors.joining(",")));
+ }
+ }
+
+ public HttpRequestMdcConfigProperties id(Supplier headers) {
+ put(REQUEST_ID_HEADER, this::isId, () -> findOrCreateRequestId(headers));
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties method(Supplier method) {
+ put("http.request.method", this::isMethod, method);
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties url(Supplier url) {
+ put("http.request.url", this::isUrl, url);
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties queryString(Supplier getQueryString) {
+ put("http.request.query-string", this::isQueryString, getQueryString);
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties parameters(Supplier> parameters) {
+ if (isParameters()) {
+ Map> params = parameters.get();
+ params.forEach((k, v) -> put("http.request.parameter.%s".formatted(k), values(v)));
+ }
+ return this;
+ }
+
+ private Supplier> values(List v) {
+ return () -> null == v ? "" : v.stream().collect(Collectors.joining(","));
+ }
+
+ public HttpRequestMdcConfigProperties sessionId(Supplier sessionId) {
+ put("http.request.session.id", this::isSessionId, sessionId);
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties remoteAddr(InetSocketAddress remoteAddr) {
+ return remoteAddr(remoteAddr::toString);
+ }
+
+ public HttpRequestMdcConfigProperties remoteAddr(Supplier remoteAddr) {
+ put("http.request.remote-addr", this::isRemoteAddr, remoteAddr);
+ return this;
+ }
+
+ public HttpRequestMdcConfigProperties remoteHost(InetSocketAddress remoteHost) {
+ return remoteAddr(remoteHost::toString);
+ }
+
+ public HttpRequestMdcConfigProperties remoteHost(Supplier remoteHost) {
+ put("http.request.remote-host", this::isRemoteAddr, remoteHost);
+ return this;
+ }
+
+ private void put(String key, BooleanSupplier enabled, Supplier> value) {
+ if (enabled.getAsBoolean()) {
+ put(key, value);
+ }
+ }
+
+ private void put(String key, Supplier> value) {
+ Object val = value.get();
+ String svalue = val == null ? null : String.valueOf(val);
+ put(key, svalue);
+ }
+
+ private void put(@NonNull String key, String value) {
+ MDC.put(key, value);
+ }
+
+ /**
+ * @return the id provided by the {@code traceId} header, {@code http.request.id} header, or a
+ * new monotonically increating UID if no such header is present
+ */
+ public static String findOrCreateRequestId(Supplier headers) {
+ return findRequestId(headers).orElseGet(() -> newRequestId());
+ }
+
+ /**
+ * @return a new monotonically increating UID
+ */
+ public static String newRequestId() {
+ return UlidCreator.getMonotonicUlid().toLowerCase();
+ }
+
+ /**
+ * Obtains the request id, if present, fromt the {@code trace-id}, {@code http.request.id}, or
+ * {@code x-request-id} request headers.
+ */
+ public static Optional findRequestId(Supplier headers) {
+ HttpHeaders httpHeaders = headers.get();
+ return header("trace-id", httpHeaders)
+ .or(() -> header(REQUEST_ID_HEADER, httpHeaders))
+ .or(() -> header("X-Request-ID", httpHeaders));
+ }
+
+ private static Optional header(String name, HttpHeaders headers) {
+ return Optional.ofNullable(headers.get(name)).filter(l -> !l.isEmpty()).map(l -> l.get(0));
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/MDCConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/MDCConfigProperties.java
new file mode 100644
index 000000000..be8b4a0f4
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/MDCConfigProperties.java
@@ -0,0 +1,18 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "logging.mdc.include")
+public class MDCConfigProperties {
+
+ private SpringEnvironmentMdcConfigProperties application = new SpringEnvironmentMdcConfigProperties();
+ private HttpRequestMdcConfigProperties http = new HttpRequestMdcConfigProperties();
+ private AuthenticationMdcConfigProperties user = new AuthenticationMdcConfigProperties();
+ private GeoServerMdcConfigProperties geoserver = new GeoServerMdcConfigProperties();
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/SpringEnvironmentMdcConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/SpringEnvironmentMdcConfigProperties.java
new file mode 100644
index 000000000..ab5413280
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/config/SpringEnvironmentMdcConfigProperties.java
@@ -0,0 +1,55 @@
+package org.geoserver.cloud.logging.mdc.config;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import lombok.Data;
+import org.slf4j.MDC;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+
+@Data
+public class SpringEnvironmentMdcConfigProperties {
+
+ private boolean name = true;
+ private boolean version = false;
+ private boolean instanceId = false;
+
+ /**
+ * Application environment property names where to extract the instance-id from. Defaults to
+ * [info.instance-id, spring.application.instance_id]
+ */
+ private List instanceIdProperties = List.of("info.instance-id", "spring.application.instance_id");
+
+ private boolean activeProfiles = false;
+
+ public void addEnvironmentProperties(Environment env, Optional buildProperties) {
+ if (isName()) MDC.put("application.name", env.getProperty("spring.application.name"));
+
+ putVersion(buildProperties);
+ putInstanceId(env);
+
+ if (isActiveProfiles())
+ MDC.put("spring.profiles.active", Stream.of(env.getActiveProfiles()).collect(Collectors.joining(",")));
+ }
+
+ private void putVersion(Optional buildProperties) {
+ if (isVersion()) {
+ buildProperties.map(BuildProperties::getVersion).ifPresent(v -> MDC.put("application.version", v));
+ }
+ }
+
+ private void putInstanceId(Environment env) {
+ if (!isInstanceId() || null == getInstanceIdProperties()) return;
+
+ for (String prop : getInstanceIdProperties()) {
+ String value = env.getProperty(prop);
+ if (StringUtils.hasText(value)) {
+ MDC.put("application.instance.id", value);
+ return;
+ }
+ }
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/ows/MDCDispatcherCallback.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/ows/OWSMdcDispatcherCallback.java
similarity index 56%
rename from src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/ows/MDCDispatcherCallback.java
rename to src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/ows/OWSMdcDispatcherCallback.java
index 182c75f89..bc6a3461b 100644
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/ows/MDCDispatcherCallback.java
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/ows/OWSMdcDispatcherCallback.java
@@ -2,11 +2,11 @@
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
-package org.geoserver.cloud.observability.logging.ows;
+package org.geoserver.cloud.logging.mdc.ows;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
-import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
+import org.geoserver.cloud.logging.mdc.config.GeoServerMdcConfigProperties;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.DispatcherCallback;
import org.geoserver.ows.Request;
@@ -15,25 +15,26 @@
import org.slf4j.MDC;
@RequiredArgsConstructor
-public class MDCDispatcherCallback extends AbstractDispatcherCallback implements DispatcherCallback {
+public class OWSMdcDispatcherCallback extends AbstractDispatcherCallback implements DispatcherCallback {
- private final @NonNull MDCConfigProperties config;
+ private final @NonNull GeoServerMdcConfigProperties.OWSMdcConfigProperties config;
@Override
public Service serviceDispatched(Request request, Service service) {
- if (config.isOws()) {
- MDC.put("gs.ows.service.name", service.getId());
- MDC.put("gs.ows.service.version", String.valueOf(service.getVersion()));
- if (null != request.getOutputFormat()) {
- MDC.put("gs.ows.service.format", request.getOutputFormat());
- }
+ if (config.isServiceName()) MDC.put("gs.ows.service.name", service.getId());
+
+ if (config.isServiceVersion()) MDC.put("gs.ows.service.version", String.valueOf(service.getVersion()));
+
+ if (config.isServiceFormat() && null != request.getOutputFormat()) {
+ MDC.put("gs.ows.service.format", request.getOutputFormat());
}
+
return super.serviceDispatched(request, service);
}
@Override
public Operation operationDispatched(Request request, Operation operation) {
- if (config.isOws()) {
+ if (config.isOperationName()) {
MDC.put("gs.ows.service.operation", operation.getId());
}
return super.operationDispatched(request, operation);
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/HttpRequestMdcFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/HttpRequestMdcFilter.java
new file mode 100644
index 000000000..2228cac81
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/HttpRequestMdcFilter.java
@@ -0,0 +1,106 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.servlet;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.geoserver.cloud.logging.mdc.config.HttpRequestMdcConfigProperties;
+import org.springframework.http.HttpCookie;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@RequiredArgsConstructor
+public class HttpRequestMdcFilter extends OncePerRequestFilter {
+
+ private final @NonNull HttpRequestMdcConfigProperties config;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws ServletException, IOException {
+ try {
+ if (request instanceof HttpServletRequest req) addRequestMdcProperties(req);
+ } finally {
+ chain.doFilter(request, response);
+ }
+ }
+
+ private void addRequestMdcProperties(HttpServletRequest req) {
+ Supplier headers = headers(req);
+ config.id(headers)
+ .remoteAddr(req::getRemoteAddr)
+ .remoteHost(req::getRemoteHost)
+ .method(req::getMethod)
+ .url(req::getRequestURI)
+ .queryString(req::getQueryString)
+ .parameters(parameters(req))
+ .sessionId(sessionId(req))
+ .headers(headers)
+ .cookies(cookies(req));
+ }
+
+ Supplier> parameters(HttpServletRequest req) {
+ return () -> {
+ var map = new LinkedMultiValueMap();
+ Map params = req.getParameterMap();
+ params.forEach((k, v) -> map.put(k, v == null ? null : Arrays.asList(v)));
+ return map;
+ };
+ }
+
+ private Supplier> cookies(HttpServletRequest req) {
+ return () -> {
+ Cookie[] cookies = req.getCookies();
+ var map = new LinkedMultiValueMap();
+ if (null != cookies && cookies.length > 0) {
+ for (Cookie c : cookies) {
+ map.add(c.getName(), new HttpCookie(c.getName(), c.getValue()));
+ }
+ }
+ return map;
+ };
+ }
+
+ private Supplier sessionId(HttpServletRequest req) {
+ return () -> Optional.ofNullable(req.getSession(false))
+ .map(HttpSession::getId)
+ .orElse(null);
+ }
+
+ private Supplier headers(HttpServletRequest req) {
+ return Suppliers.memoize(buildHeaders(req));
+ }
+
+ private com.google.common.base.Supplier buildHeaders(HttpServletRequest req) {
+ return () -> {
+ HttpHeaders headers = new HttpHeaders();
+ Streams.stream(req.getHeaderNames().asIterator())
+ .forEach(name -> headers.put(name, headerValue(name, req)));
+ return headers;
+ };
+ }
+
+ private List headerValue(String name, HttpServletRequest req) {
+ Enumeration values = req.getHeaders(name);
+ if (null == values) return List.of();
+ return Streams.stream(values.asIterator()).toList();
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCAuthenticationFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCAuthenticationFilter.java
similarity index 76%
rename from src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCAuthenticationFilter.java
rename to src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCAuthenticationFilter.java
index a71b589dc..aa09ae8d6 100644
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCAuthenticationFilter.java
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCAuthenticationFilter.java
@@ -2,7 +2,7 @@
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
-package org.geoserver.cloud.observability.logging.servlet;
+package org.geoserver.cloud.logging.mdc.servlet;
import java.io.IOException;
import java.util.stream.Collectors;
@@ -13,8 +13,10 @@
import javax.servlet.ServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
-import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
+import org.geoserver.cloud.logging.mdc.config.AuthenticationMdcConfigProperties;
+import org.geoserver.cloud.logging.mdc.config.MDCConfigProperties;
import org.slf4j.MDC;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -32,7 +34,7 @@
@RequiredArgsConstructor
public class MDCAuthenticationFilter implements Filter {
- private final @NonNull MDCConfigProperties config;
+ private final @NonNull AuthenticationMdcConfigProperties config;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
@@ -46,10 +48,15 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
void addEnduserMdcProperties() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- boolean authenticated = auth != null && auth.isAuthenticated();
+ boolean authenticated;
+ if (auth == null || auth instanceof AnonymousAuthenticationToken) {
+ authenticated = false;
+ } else {
+ authenticated = auth.isAuthenticated();
+ }
MDC.put("enduser.authenticated", String.valueOf(authenticated));
if (authenticated) {
- if (config.isUser()) MDC.put("enduser.id", auth.getName());
+ if (config.isId()) MDC.put("enduser.id", auth.getName());
if (config.isRoles()) MDC.put("enduser.role", roles(auth));
}
}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCCleaningFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCCleaningFilter.java
similarity index 82%
rename from src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCCleaningFilter.java
rename to src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCCleaningFilter.java
index 08799717b..1a980a321 100644
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/MDCCleaningFilter.java
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/MDCCleaningFilter.java
@@ -2,7 +2,7 @@
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
-package org.geoserver.cloud.observability.logging.servlet;
+package org.geoserver.cloud.logging.mdc.servlet;
import java.io.IOException;
import javax.servlet.FilterChain;
@@ -10,8 +10,11 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
+@Order(Ordered.HIGHEST_PRECEDENCE)
public class MDCCleaningFilter extends OncePerRequestFilter {
@Override
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/SpringEnvironmentMdcFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/SpringEnvironmentMdcFilter.java
new file mode 100644
index 000000000..1237c308b
--- /dev/null
+++ b/src/starters/observability/src/main/java/org/geoserver/cloud/logging/mdc/servlet/SpringEnvironmentMdcFilter.java
@@ -0,0 +1,36 @@
+/*
+ * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
+ * GPL 2.0 license, available at the root application directory.
+ */
+package org.geoserver.cloud.logging.mdc.servlet;
+
+import java.io.IOException;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.geoserver.cloud.logging.mdc.config.SpringEnvironmentMdcConfigProperties;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.core.env.Environment;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@RequiredArgsConstructor
+public class SpringEnvironmentMdcFilter extends OncePerRequestFilter {
+
+ private final @NonNull Environment env;
+ private final @NonNull Optional buildProperties;
+ private final @NonNull SpringEnvironmentMdcConfigProperties config;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws ServletException, IOException {
+ try {
+ config.addEnvironmentProperties(env, buildProperties);
+ } finally {
+ chain.doFilter(request, response);
+ }
+ }
+}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/config/MDCConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/config/MDCConfigProperties.java
deleted file mode 100644
index aa37b9e20..000000000
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/config/MDCConfigProperties.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
- * GPL 2.0 license, available at the root application directory.
- */
-package org.geoserver.cloud.observability.logging.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@Data
-@ConfigurationProperties(prefix = "logging.mdc.include")
-public class MDCConfigProperties {
-
- private boolean user = true;
- private boolean roles = true;
- private boolean ows = true;
-}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcConfigProperties.java b/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcConfigProperties.java
deleted file mode 100644
index 273c9b22a..000000000
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcConfigProperties.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
- * GPL 2.0 license, available at the root application directory.
- */
-package org.geoserver.cloud.observability.logging.servlet;
-
-import java.util.regex.Pattern;
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@Data
-@ConfigurationProperties(prefix = "logging.mdc.include.http")
-public class HttpRequestMdcConfigProperties {
-
- private boolean id = true;
-
- /**
- * The Internet Protocol (IP) address of the client or last proxy that sent the request. For
- * HTTP servlets, same as the value of the CGI variable REMOTE_ADDR.
- */
- private boolean remoteAddr = true;
-
- /**
- * The fully qualified name of the client or the last proxy that sent the request. If the engine
- * cannot or chooses not to resolve the hostname (to improve performance), this method returns
- * the dotted-string form of the IP address. For HTTP servlets, same as the value of the CGI
- * variable REMOTE_HOST. Defaults to false to avoid the possible overhead in reverse DNS
- * lookups. remoteAddress should be enough in most cases.
- */
- private boolean remoteHost = true;
-
- private boolean method = true;
- private boolean url = true;
- private boolean parameters = true;
- private boolean queryString = true;
- private boolean sessionId = true;
-
- private boolean cookies = true;
- private boolean headers = true;
- private Pattern headersPattern = Pattern.compile(".*");
-}
diff --git a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcFilter.java b/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcFilter.java
deleted file mode 100644
index cf912efe3..000000000
--- a/src/starters/observability/src/main/java/org/geoserver/cloud/observability/logging/servlet/HttpRequestMdcFilter.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
- * GPL 2.0 license, available at the root application directory.
- */
-package org.geoserver.cloud.observability.logging.servlet;
-
-import com.github.f4b6a3.ulid.UlidCreator;
-import com.google.common.collect.Streams;
-import java.io.IOException;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import org.slf4j.MDC;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-@RequiredArgsConstructor
-public class HttpRequestMdcFilter extends OncePerRequestFilter {
-
- private final @NonNull HttpRequestMdcConfigProperties config;
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
- throws ServletException, IOException {
- try {
- if (request instanceof HttpServletRequest req) addRequestMdcProperties(req);
- } finally {
- chain.doFilter(request, response);
- }
- }
-
- private void addRequestMdcProperties(HttpServletRequest req) {
- HttpSession session = req.getSession(false);
-
- put("http.request.id", config::isId, () -> requestId(req));
- put("http.request.remote-addr", config::isRemoteAddr, req::getRemoteAddr);
- put("http.request.remote-host", config::isRemoteHost, req::getRemoteHost);
-
- put("http.request.method", config::isMethod, req::getMethod);
- put("http.request.url", config::isUrl, req::getRequestURL);
- putRequestParams(req);
- put("http.request.query-string", config::isQueryString, req::getQueryString);
- put("http.request.session.id", config::isSessionId, () -> session == null ? null : session.getId());
- put("http.request.session.started", config::isSessionId, () -> session == null ? null : !session.isNew());
- addHeaders(req);
- addCookies(req);
- }
-
- private void putRequestParams(HttpServletRequest req) {
- if (config.isParameters()) {
- Streams.stream(req.getParameterNames().asIterator())
- .forEach(name -> put("http.request.parameter.%s".formatted(name), requestParam(name, req)));
- }
- }
-
- private String requestParam(String name, HttpServletRequest req) {
- String[] values = req.getParameterValues(name);
- if (null == values) return null;
- if (values.length == 1) return values[0];
- return null;
- }
-
- private void addHeaders(HttpServletRequest req) {
- if (config.isHeaders()) {
- Streams.stream(req.getHeaderNames().asIterator())
- .filter(h -> !"cookie".equalsIgnoreCase(h))
- .filter(this::includeHeader)
- .forEach(name -> putHeader(name, req));
- }
- }
-
- private void putHeader(String name, HttpServletRequest req) {
- put("http.request.header.%s".formatted(name), () -> getHeader(name, req));
- }
-
- private String getHeader(String name, HttpServletRequest req) {
- return Streams.stream(req.getHeaders(name).asIterator()).collect(Collectors.joining(","));
- }
-
- private boolean includeHeader(String headerName) {
- return config.getHeadersPattern().matcher(headerName).matches();
- }
-
- private void addCookies(HttpServletRequest req) {
- if (config.isCookies()) {
- Cookie[] cookies = req.getCookies();
- if (null != cookies) {
- Stream.of(cookies).forEach(this::put);
- }
- }
- }
-
- private void put(Cookie c) {
- String key = "http.request.cookie.%s".formatted(c.getName());
- String value = MDC.get(key);
- if (value == null) {
- value = c.getValue();
- } else {
- value = "%s;%s".formatted(value, c.getValue());
- }
- MDC.put(key, value);
- }
-
- private void put(String key, BooleanSupplier enabled, Supplier