Skip to content

Commit

Permalink
Include application, http, and geoserver ows request properties in th…
Browse files Browse the repository at this point in the history
…e logging MDC
  • Loading branch information
groldan committed Aug 8, 2024
1 parent c51c83e commit 4d65b25
Show file tree
Hide file tree
Showing 14 changed files with 686 additions and 1 deletion.
34 changes: 33 additions & 1 deletion src/starters/observability/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,43 @@
</parent>
<artifactId>gs-cloud-starter-observability</artifactId>
<packaging>jar</packaging>
<description>Spring boot starter for application observability (logging, metrics, tracing)</description>
<description>Spring boot starter for application observability (logging,
metrics, tracing)</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-main</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* (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;

import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
import org.geoserver.cloud.observability.logging.ows.MDCDispatcherCallback;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.DispatcherCallback;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link AutoConfiguration @AutoConfiguration} to enable logging MDC (Mapped Diagnostic Context)
* for the GeoSever {@link Dispatcher} events using a {@link DispatcherCallback}
*
* @see MDCDispatcherCallback
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({
Dispatcher.class,
// from spring-webmvc, required by Dispatcher.class
org.springframework.web.servlet.mvc.AbstractController.class
})
@ConditionalOnWebApplication(type = Type.SERVLET)
class GeoServerDispatcherMDCConfiguration {

@Bean
MDCDispatcherCallback mdcDispatcherCallback(MDCConfigProperties config) {
return new MDCDispatcherCallback(config);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* (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;

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.security.GeoServerSecurityFilterChainProxy;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.AutoConfiguration;
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.boot.info.BuildProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;

import java.util.Optional;

/**
* {@link AutoConfiguration @AutoConfiguration} to enable logging MDC (Mapped Diagnostic Context)
* contributions during the request life cycle
*
* @see GeoServerDispatcherMDCConfiguration
*/
@AutoConfiguration
@EnableConfigurationProperties({
MDCConfigProperties.class,
HttpRequestMdcConfigProperties.class,
SpringEnvironmentMdcConfigProperties.class
})
@Import(GeoServerDispatcherMDCConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class LoggingMDCAutoConfiguration {

/**
* @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();
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SpringEnvironmentMdcFilter springEnvironmentMdcFilter(
Environment env,
SpringEnvironmentMdcConfigProperties config,
Optional<BuildProperties> buildProperties) {
return new SpringEnvironmentMdcFilter(env, buildProperties, config);
}

/**
* A servlet registration for {@link MDCAuthenticationFilter}, with {@link
* FilterRegistrationBean#setMatchAfter setMatchAfter(true)} to ensure it runs after {@link
* GeoServerSecurityFilterChainProxy} and hence the {@link SecurityContext} already has the
* {@link Authentication} object.
*/
@Bean
FilterRegistrationBean<MDCAuthenticationFilter> mdcAuthenticationPropertiesServletFilter(
MDCConfigProperties config) {
FilterRegistrationBean<MDCAuthenticationFilter> registration =
new FilterRegistrationBean<>();

var filter = new MDCAuthenticationFilter(config);
registration.setMatchAfter(true);

registration.addUrlPatterns("/*");
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
registration.setFilter(filter);
return registration;
}
}
Original file line number Diff line number Diff line change
@@ -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.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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* (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;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import org.geoserver.cloud.observability.logging.config.MDCConfigProperties;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.DispatcherCallback;
import org.geoserver.ows.Request;
import org.geoserver.platform.Operation;
import org.geoserver.platform.Service;
import org.slf4j.MDC;

@RequiredArgsConstructor
public class MDCDispatcherCallback extends AbstractDispatcherCallback
implements DispatcherCallback {

private final @NonNull MDCConfigProperties 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());
}
}
return super.serviceDispatched(request, service);
}

@Override
public Operation operationDispatched(Request request, Operation operation) {
if (config.isOws()) {
MDC.put("gs.ows.service.operation", operation.getId());
}
return super.operationDispatched(request, operation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* (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 lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.regex.Pattern;

@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(".*");
}
Loading

0 comments on commit 4d65b25

Please sign in to comment.