DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Frameworks
  4. Logging Spring REST APIs

Logging Spring REST APIs

In this article, we take a look at how Java developers can approach the logging of Spring-based REST APIs. Put on your lumberjack hat!

Ati M. user avatar by
Ati M.
·
Mar. 09, 18 · Tutorial
Like (16)
Save
Tweet
Share
49.80K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, I will try to explain an approach for logging Spring-based Rest APIs. Although Spring provides tracing of request and responses using its Actuator, logging the request and response body is not supported out of the box. This sometimes makes it difficult to find what is coming to and going from the Rest APIs.

There are several approaches for writing a logger for Spring applications. One of them is writing a listener. But when you write a listener, it starts to work for all the APIs. In this demonstration, we will focus on logging only the APIs that we annotate with our own logging annotation. 

Before the logging part, we have to change Spring’s default HttpServletRequestWrapper to our own, because default one doesn’t support caching. With the default wrapper, when Spring initializes the API parameters, it opens the input stream and reads the content. When it finishes, it closes the stream, and reading from the stream from that time is not possible and any attempt results in an exception. We will replace this with ContentCachingRequestWrapper, which allows for the caching of a request. For registering this wrapper, we will extend DispatcherServlet and override the doDispatch method.

You can find the implementation details below.

public class LoggableDispatcherServlet extends DispatcherServlet {
@Override
protected void doDispatch(HttpServletRequest request, 
                              HttpServletResponse response) throws Exception {
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
super.doDispatch(request, response);
}
}

As you can see, this dispatcher replaces Spring’s request wrapper with ContentCachingRequestWrapper, so we will be able to read request parameters as much as we want without any error.

After this, we have to register our own dispatcher servlet to a Spring context like below, this can be achieved in any of the Spring configuration files.

@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new LoggableDispatcherServlet();
}

Now Spring will start using our new dispatcher servlet. After this, we will create a new annotation type for logging.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLogger {
}

This will allow us to annotate our Rest APIs that we want to log. This way we will be able to log only the APIs we want.

And, now, we come to the implementation of logging. The implementation details for the annotation are below.

@Aspect
@Component
public class RequestLoggerAspect {

private Logger logger = LoggerFactory.getLogger(RequestLoggerAspect.class);

@Autowired
private ObjectMapper objectMapper;

@Around("execution(@RequestLogger * *(..)) && @annotation(requestLogger)")
public Object logRequest(ProceedingJoinPoint joinPoint, RequestLogger requestLogger) throws Throwable {

ContentCachingRequestWrapper request = getWrapper(joinPoint);

StringBuilder apiLog = new StringBuilder();

apiLog.append("Rest API: ").append(request.getRequestURL().toString()).append("\n");

apiLog.append("Body:").append(getRequestBody(request)).append("\n");

for (String header : Collections.list(request.getHeaderNames())) {
apiLog.append(header).append(":").append(request.getHeader(header))
.append("\n");
}

logger.debug(apiLog.toString());

Object retVal = joinPoint.proceed();

logger.debug("Response:" + objectMapper.writeValueAsString(retVal));

return retVal;

}

private String getRequestBody(final ContentCachingRequestWrapper wrapper) {
String payload = null;
if (wrapper != null) {

byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
try {
int maxLength = buf.length > 500 ? 500 : buf.length;

payload = new String(buf, 0, maxLength,
wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
logger.error("UnsupportedEncoding.", e);
}
}
}
return payload;
}

private ContentCachingRequestWrapper getWrapper(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();

ContentCachingRequestWrapper request = null;

for (Object arg : args) {
if (arg instanceof ContentCachingRequestWrapper) {
request = (ContentCachingRequestWrapper) arg;
break;
}
}

return request;
}

If you look at line 13, we start with getting the ContentCachingRequestWrapper object from the jointPoint object. To achieve this, HttpServletRequest should be included in the specified Rest API method's parameters. If you look at the other lines, we are able to get an API URL, request body, and headers from the ContentCachingRequestWrapper object.

For getting the body part, please have a look at the getRequestBody method on line 36. In this method, if the length of the body is greater than 500, it is cut to 500. This magic number (500) can be parameterized in a more serious project and fine-tuned according to your performance and storage needs.

If you look at line 28, we call joinPoint.proceed(). This causes the API method to be executed. When the method returns, we have the chance to get the return value and log it, too. At this point, we have finished the logging of a Spring Rest API using annotation. If you want to further investigate, you can find a working sample project on GitHub. I hope you found the article helpful. 

Spring Framework REST Web Protocols

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How to Create a Real-Time Scalable Streaming App Using Apache NiFi, Apache Pulsar, and Apache Flink SQL
  • 7 Awesome Libraries for Java Unit and Integration Testing
  • Differences Between Site Reliability Engineer vs. Software Engineer vs. Cloud Engineer vs. DevOps Engineer
  • Mr. Over, the Engineer [Comic]

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: