Logging in Spring Boot - REST Controllers and WebClient - java

I'm creating a microservice using Spring Boot and i'm trying to develop my logging solution for it. Has of now i'm using this class for logging request/response.
#Component
#Slf4j
public class RequestAndResponseLoggingFilter implements Filter {
private static void logRequest(ContentCachingRequestWrapper request) {
String queryString = request.getQueryString();
if (queryString == null) {
log.info("{} {}", request.getMethod(), request.getRequestURI());
} else {
log.info("{} {}?{}", request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{}: {}", headerName, headerValue)));
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
logContent(content, request.getContentType(), request.getCharacterEncoding());
}
}
private static void logResponse(ContentCachingResponseWrapper response) {
int status = response.getStatus();
log.info("{} {}", status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{}: {}", headerName, headerValue)));
byte[] content = response.getContentAsByteArray();
if (content.length > 0) {
logContent(content, response.getContentType(), response.getCharacterEncoding());
}
}
private static void logContent(byte[] content, String contentType, String contentEncoding) {
try {
String contentString = new String(content, contentEncoding);
contentString = contentString.replace("\n", "").replace("\t", "");
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}", line));
} catch (UnsupportedEncodingException e) {
log.info("[{} bytes content]", content.length);
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
#Override
public void init(FilterConfig filterConfig) {
log.info("Initializing Request and Response Logging Filter");
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
#Override
public void destroy() {
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} finally {
logRequestResponse(request, response);
response.copyBodyToResponse();
}
}
protected void logRequestResponse(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
logRequest(request);
logResponse(response);
}
}
}
This class works fine for me: it prints the request and the respective response. My microservice makes REST calls to other microservices and for that
i use WebClient:
public testClient(ServiceConfig serviceConfig) {
this.webClient = WebClient.builder()
.filter(new TracingExchangeFilterFunction(GlobalTracer.get(),
Collections.singletonList(new WebClientSpanDecorator.StandardTags())))
.baseUrl(serviceConfig.getMockUri())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
The problem is when i run my microservice the class responsible for the logging does not "catch" the request/response made by this WebClient instance.
1) Can you explain to me why this happens?
2) I also did a bit of search and i found a piece of code that can log the request made by the WebClient and that prints what i want to:
private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
logger.info("Request: {} {}", clientRequest.method(),
clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> logger.info("{}=
{}", name, value)));
return next.exchange(clientRequest);
};
}
Is there a way that i can use my logging class in my WebClient instance or do i need to write separate code for those two cases?

Related

How to set a delay for interceptor in okhttp?

Lets say we need to retry a request in case of exception:
public class TestUpInterceptor implements Interceptor {
#Override public Response intercept(Chain chain) throws IOException {
final Response response = chain.proceed(chain.request());
//TODO: in case of exception retry in 3 sec
return retryResponse;
}
}
how to add a delay to interceptor?
Use SystemClock.sleep(3000); for delay.
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
boolean responseOK = false;
int tryCount = 0;
while (!responseOK && tryCount < 3) {
try {
SystemClock.sleep(3000);
response = chain.proceed(request);
responseOK = response.isSuccessful();
}catch (Exception e){
Log.d("intercept", "Request is not successful - " + tryCount);
}finally{
tryCount++;
}
}
// otherwise just pass the original response on
return response;
}
});

Pass an object from API resource Method to APIFilter PostCheckMethod

I have a use case in that for all PUT/Delete/Update call i want to send some object to PostCheck of ApiFilter for some processing.
I am trying like this :
#Context
HttpServletRequest request;
#Context
ContainerResponseContext responseContext ;
#PUT
#Path("/snmp")
#Produces(MediaType.APPLICATION_JSON)
#Timed
public Response<List<SNMPSettingsFactory.ExternalSNMPSettings>> updateSNMPTargetSettingsList(
#Nonnull SNMPSettingsFactory.ExternalSNMPSettings externalSNMPSettings)
{
request.setAttribute("old",oldSettingObject);
request.setAttribute("new",currentSettingObject);
}
in the ApiFilter class, I am trying to get this but it is coming as NULL.
#Provider
public class ApiFilter implements ContainerRequestFilter,
ContainerResponseFilter {
private final ApiNonFunctionalHandler handler;
private final Logger logger = LoggerFactory.getLogger(ApiFilter.class);
public ApiFilter(ApiNonFunctionalHandler handler) {
this.handler = handler;
}
#Context
HttpServletRequest request;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
try {
String urlPath = requestContext.getUriInfo().getRequestUri().getPath();
if (!urlPath.startsWith(ApiConstants.BASE_URL)) {
return;
}
UserInfo userInfo;
String authorization = NullOrEmpty.isFalse(requestContext.getHeaderString(CspClient.AUTH_TOKEN_HEADER))?
requestContext.getHeaderString(CspClient.AUTH_TOKEN_HEADER) : requestContext.getHeaderString("Authorization");
userInfo = handler.preCheck(ApiUtils.getApiCategory(urlPath),
authorization, isAuthRequired(urlPath, requestContext.getMethod()));
requestContext.setSecurityContext(new CustomSecurityContext(userInfo,""));
} catch (RuntimeException e) {
throw Throwables.propagate(e);
} catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws
IOException {
String urlPath = requestContext.getUriInfo().getRequestUri().getPath();
String callerIpAddress = null;
if (request != null) {
callerIpAddress = request.getRemoteAddr();
}
String callerUserName = null;
if (request != null) {
callerUserName = request.getRemoteUser();
}
Object oldObject = request.getAttribute("old");
Object currentObject = request.getAttribute("new");
Please help me how can I pass objects for all API call to postfilter to do some common processing like finding diff and log it.

how to redirect user for accessing to a page from URL in java?

I am coming to a problem where I have loginFilter where I dont want the user to access a page when they are logged in with the URL. So, all I want to do is redirect the user to the index page. Here is my code below. Thank you.
public class LoginFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
String username = req.getHeader("username");
String password = req.getHeader("password");
if(username == null) {
chain.doFilter(request, response);
return;
}
try {
req.login(username, password);
chain.doFilter(request, response);
} catch (ServletException e) {
((HttpServletResponse)response).setStatus(StatusCodes.UNAUTHORIZED);
}
}
#Override
public void destroy() {
}
}
Change your method as :
HttpServletResponse res = (HttpServletResponse)response;
try {
req.login(username, password);
res.sendRedirect("/index");
} catch (ServletException e) {
// you can use SC_UNAUTHORIZED(401) from HttpServletResponse class as well
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

Java filter - conditionally change response body based on request

I have a servlet filter that intercepts requests and checks for a custom "encrypted" header:
public class EncryptionFilter extends GenericFilterBean{
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
if (httpRequest.getHeader("EncryptedCommunication") != null){
decryptedRequest = /*decrypt request body and forward to next filter*/
encryptedResponse = /*encrypt request body and forward to next filter*/
filterChain.doFilter(decryptedRequest, encryptedResponse);
}
else { /* communication not encrypted */
filterChain.doFilter(request, response);
}
}
}
When the header exist I should decrypt the request body and also encrypt the response body.
Otherwise, should leave the request/response body unchanged.
How can I change the response only when needed?
You need to use a HttpServletResponseWrapper example is :
filterChain.doFilter(servletRequest,
new HttpServletResponseWrapper((HttpServletResponse) servletResponse) {
#Override
public void setHeader(String name, String value) {
if (!HTTPCacheHeader.ETAG.getName().equalsIgnoreCase(name)) {
super.setHeader(name, value);
}
}
});
See http://www.programcreek.com/java-api-examples/javax.servlet.http.HttpServletResponseWrapper
This is an example of how the body can be set :
public class ReadTwiceHttpServletRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
public ReadTwiceHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
try {
IOUtils.copy(request.getInputStream(), outputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray())));
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
return new ServletInputStream() {
#Override
public int readLine(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
#Override
public boolean isFinished() {
return inputStream.available() > 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
}
#Override
public int read() throws IOException {
return inputStream.read();
}
};
}
public void setBody(String body) {
outputStream = new ByteArrayOutputStream();
try {
outputStream.write(body.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getBody() {
return new String(outputStream.toByteArray());
}
See How to get the XML from POST request and modify it in Servlet Filter?

Servlet filter wrapper - trouble changing content type

I have RESTful web service which is consumed by javascript. This service returns a content type of "application/json". However, for IE the content type must be "text/html". So I written a filter and wrapper to change the content type when IE is detected as the client. My logic seems to have no effect on the content type. What am I doing wrong?
The filter:
public class IE8Filter implements Filter {
private Logger logger = LoggerHelper.getLogger();
#Override
public void destroy() {}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String userAgent = request.getHeader("User-Agent");
logger.debugf("User Agent = '%s'", userAgent);
IE8FilterResponseWrapper wrapper = new IE8FilterResponseWrapper(response);
chain.doFilter(req, wrapper);
if (userAgent.contains("MSIE 8") || userAgent.contains("MSIE 7")) {
wrapper.setContentType("text/html");
logger.debugf("Content Type = '%s'", wrapper.getContentType());
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {}
}
The wrapper:
public class IE8FilterResponseWrapper extends HttpServletResponseWrapper {
private String contentType;
public IE8FilterResponseWrapper(HttpServletResponse response) {
super(response);
}
public void setContentType(String type) {
this.contentType = type;
super.setContentType(type);
}
public String getContentType() {
return contentType;
}
}
I found an answer. The trick was to prevent my web service from setting the content-type using my wrapper:
public class IE8FilterResponseWrapper extends HttpServletResponseWrapper {
public IE8FilterResponseWrapper(HttpServletResponse response) {
super(response);
}
public void forceContentType(String type) {
super.setContentType(type);
}
public void setContentType(String type) {
}
public void setHeader(String name, String value) {
if (!name.equals("Content-Type")) {
super.setHeader(name, value);
}
}
public void addHeader(String name, String value) {
if (!name.equals("Content-Type")) {
super.addHeader(name, value);
}
}
public String getContentType() {
return super.getContentType();
}
}
And my filter now looks like:
public class IE8Filter implements Filter {
private Logger logger = LoggerHelper.getLogger();
#Override
public void destroy() {}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String userAgent = request.getHeader("User-Agent");
logger.debugf("User Agent = '%s'", userAgent);
IE8FilterResponseWrapper wrapper = new IE8FilterResponseWrapper(response);
if (userAgent.contains("MSIE 8") || userAgent.contains("MSIE 7")) {
wrapper.forceContentType("text/html");
chain.doFilter(req, wrapper);
}
else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {}
}
I'm not sure if this was how wrappers were intended to be used but heck it works.

Categories