Adding MDC logging via RequestIdFilter and MDCFilter
This ensures that we can send header values and those will be printed in the logs. In the class MDCFilter, we are adding the logContext to the reactive logger along with the normal MDC logger as well.
This commit is contained in:
parent
e8b7b234b3
commit
5a1f9ab132
|
|
@ -5,19 +5,16 @@ import com.mobtools.server.dtos.ResponseDto;
|
|||
import com.mobtools.server.exceptions.MobtoolsException;
|
||||
import com.mobtools.server.services.CrudService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public abstract class BaseController<S extends CrudService, T extends BaseDomain, ID> {
|
||||
|
||||
protected final S service;
|
||||
|
|
@ -25,24 +22,28 @@ public abstract class BaseController<S extends CrudService, T extends BaseDomain
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDto<T>> create(@Valid @RequestBody T resource) throws MobtoolsException {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return service.create(resource)
|
||||
.map(created -> new ResponseDto<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
@GetMapping("")
|
||||
public Flux<ResponseDto<T>> getAll() {
|
||||
log.debug("Going to get all resources");
|
||||
return service.get()
|
||||
.map(resources -> new ResponseDto<>(HttpStatus.OK.value(), resources, null));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Mono<ResponseDto<T>> getById(@PathVariable ID id) {
|
||||
log.debug("Going to get resource for id: {}", id);
|
||||
return service.getById(id)
|
||||
.map(resources -> new ResponseDto<>(HttpStatus.OK.value(), resources, null));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public Mono<ResponseDto<T>> update(@PathVariable ID id, @RequestBody T resource) throws Exception {
|
||||
log.debug("Going to update resource with id: {}", id);
|
||||
return service.update(id, resource)
|
||||
.map(updatedResource -> new ResponseDto<>(HttpStatus.OK.value(), updatedResource, null));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import com.mobtools.server.dtos.ResponseDto;
|
|||
import com.mobtools.server.services.PluginService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,7 @@ import com.mobtools.server.domains.Query;
|
|||
import com.mobtools.server.dtos.CommandQueryParams;
|
||||
import com.mobtools.server.services.QueryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@RestController
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ package com.mobtools.server.domains;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.*;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
|
||||
import java.util.Date;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package com.mobtools.server.dtos;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package com.mobtools.server.dtos;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.mobtools.server.filters;
|
||||
|
||||
import com.mobtools.server.helpers.LogHelper;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
/**
|
||||
* This class parses all headers that start with X-MDC-* and set them in the logger MDC.
|
||||
* These MDC parameters are also set in the response object before being sent to the user.
|
||||
*/
|
||||
@Component
|
||||
public class MDCFilter implements WebFilter {
|
||||
|
||||
private static final String MDC_HEADER_PREFIX = "X-MDC-";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
try {
|
||||
// Using beforeCommit here ensures that the function `addContextToHttpResponse` isn't run immediately
|
||||
// It is only run when the response object is being created
|
||||
exchange.getResponse().beforeCommit(() -> addContextToHttpResponse(exchange.getResponse()));
|
||||
return chain.filter(exchange).subscriberContext(ctx -> addRequestHeadersToContext(exchange.getRequest(), ctx));
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private Context addRequestHeadersToContext(final ServerHttpRequest request, final Context context) {
|
||||
final Map<String, String> contextMap = request.getHeaders().toSingleValueMap().entrySet()
|
||||
.stream()
|
||||
.filter(x -> x.getKey().startsWith(MDC_HEADER_PREFIX))
|
||||
.collect(toMap(v -> v.getKey().substring((MDC_HEADER_PREFIX.length())), Map.Entry::getValue));
|
||||
|
||||
// Set the MDC context here for regular non-reactive logs
|
||||
MDC.setContextMap(contextMap);
|
||||
|
||||
// Setting the context map to the reactive context. This will be used in the reactive logger to print the MDC
|
||||
return context.put(LogHelper.CONTEXT_MAP, contextMap);
|
||||
}
|
||||
|
||||
private Mono<Void> addContextToHttpResponse(final ServerHttpResponse response) {
|
||||
return Mono.subscriberContext().doOnNext(ctx -> {
|
||||
if(!ctx.hasKey(LogHelper.CONTEXT_MAP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final HttpHeaders httpHeaders = response.getHeaders();
|
||||
// Add all the request MDC keys to the response object
|
||||
ctx.<Map<String, String>>get(LogHelper.CONTEXT_MAP)
|
||||
.forEach((key, value) -> httpHeaders.add(MDC_HEADER_PREFIX + key, value));
|
||||
}).then();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.mobtools.server.filters;
|
||||
|
||||
import com.mobtools.server.helpers.LogHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class specifically parses the requestId key from the headers and sets it in the logger MDC
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RequestIdFilter implements WebFilter {
|
||||
|
||||
private static final String REQUEST_ID_HEADER = "X-REQUEST-ID";
|
||||
private static final String REQUEST_ID_LOG = "requestId";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
|
||||
if (!exchange.getRequest().getHeaders().containsKey(REQUEST_ID_HEADER)) {
|
||||
exchange.getRequest().mutate().header(REQUEST_ID_HEADER, UUID.randomUUID().toString()).build();
|
||||
}
|
||||
|
||||
String header = exchange.getRequest().getHeaders().get(REQUEST_ID_HEADER).get(0);
|
||||
log.debug("Setting the requestId header to {}", header);
|
||||
return chain.filter(exchange).subscriberContext(LogHelper.putLogContext(REQUEST_ID_LOG, header));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.mobtools.server.helpers;
|
||||
|
||||
import org.slf4j.MDC;
|
||||
import reactor.core.publisher.Signal;
|
||||
import reactor.core.publisher.SignalType;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class LogHelper {
|
||||
|
||||
public static final String CONTEXT_MAP = "context-map";
|
||||
|
||||
public static Function<Context, Context> putLogContext(String key, String value) {
|
||||
return ctx -> {
|
||||
Optional<Map<String, String>> optionalContextMap = ctx.getOrEmpty(CONTEXT_MAP);
|
||||
|
||||
if (optionalContextMap.isPresent()) {
|
||||
optionalContextMap.get().put(key, value);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
Map<String, String> ctxMap = new HashMap<>();
|
||||
ctxMap.put(key, value);
|
||||
|
||||
return ctx.put(CONTEXT_MAP, ctxMap);
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Consumer<Signal<T>> logOnNext(Consumer<T> log) {
|
||||
return signal -> {
|
||||
if (signal.getType() != SignalType.ON_NEXT) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Map<String, String>> maybeContextMap = signal.getContext().getOrEmpty(CONTEXT_MAP);
|
||||
|
||||
if (!maybeContextMap.isPresent()) {
|
||||
log.accept(signal.get());
|
||||
return;
|
||||
}
|
||||
|
||||
MDC.setContextMap(maybeContextMap.get());
|
||||
try {
|
||||
log.accept(signal.get());
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Consumer<Signal<T>> logOnError(Consumer<Throwable> log) {
|
||||
return signal -> {
|
||||
if (!signal.isOnError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Map<String, String>> maybeContextMap
|
||||
= signal.getContext().getOrEmpty(CONTEXT_MAP);
|
||||
|
||||
if (!maybeContextMap.isPresent()) {
|
||||
log.accept(signal.getThrowable());
|
||||
return;
|
||||
}
|
||||
|
||||
MDC.setContextMap(maybeContextMap.get());
|
||||
try {
|
||||
log.accept(signal.getThrowable());
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,12 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ public abstract class BaseService<R extends BaseRepository, T extends BaseDomain
|
|||
|
||||
@Override
|
||||
public Mono<T> getById(ID id) {
|
||||
return repository.findById(id);
|
||||
return repository.findById(id)
|
||||
.switchIfEmpty(Mono.error(new MobtoolsException("Unable to find resource with id: " + id.toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public class TenantServiceImpl extends BaseService<TenantRepository, Tenant, Str
|
|||
@Override
|
||||
public Mono<Tenant> create(Tenant tenant) {
|
||||
|
||||
log.debug("Going to create the tenant");
|
||||
return Mono.just(tenant)
|
||||
//transform the tenant data to embed setting object in each object in tenantSetting list.
|
||||
.flatMap(this::enhanceTenantSettingList)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ spring.data.mongodb.port=27017
|
|||
#spring.data.mongodb.username=
|
||||
#spring.data.mongodb.password=
|
||||
|
||||
logging.level.root=info
|
||||
logging.level.com.mobtools=debug
|
||||
logging.pattern.console=%X - %m%n
|
||||
|
||||
# JDBC Postgres properties
|
||||
jdbc.postgres.driver=org.postgresql.Driver
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ spring.data.mongodb.database=mobtools
|
|||
spring.data.mongodb.uri=mongodb+srv://admin:Y9PuxM52gcP3Dgfo@mobtools-test-cluster-swrsq.mongodb.net/mobtools?retryWrites=true
|
||||
spring.data.mongodb.authentication-database=admin
|
||||
|
||||
logging.level.root=info
|
||||
logging.level.com.mobtools=debug
|
||||
logging.pattern.console=%X - %m%n
|
||||
|
||||
# JDBC Postgres properties
|
||||
jdbc.postgres.driver=org.postgresql.Driver
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user