Logging RestTemplate’s responses

I want to log responses using Spring’s RestTemplate.
At first I thought I should do it using a ClientHttpRequestInterceptor, but since every set of requests should be logged to a different file, I figured out I should create a new RestTemplate with a new ClientHttpRequestInterceptor for every set of requests (though all other configurations are the same).

But I saw that RestTemplate is pretty expensive object to create, when about 70% of the creation time, is creating the MessageConverters.

So – is there a better way to implement this requirement ? Is it possible to share MessageConverters across multiple RestTemplate instances, hence, improve performance significantly ?

EDIT
Here is a sample of my code:

protected MyResponse<MyInterface> getInterfaces(RestTemplate restClient, RestParams inputParams, String cookie, File outputFile) {

    Request devInterfacesRequest = getRequest(RequestType.INTERFACES, inputParams, cookie);

    return restClient.postForObject(inputParams.getUrl(), devInterfacesRequest , new ParameterizedTypeReference<MyResponse<MyInterface>>(){});
}

Where can I add the outputFile ?

8

Well I have been thinking in this question and this’s what I get:

Restrictions

Citing question’s owner:

  1. To implement my own RestTemplate is to heavy

But I saw that RestTemplate is pretty expensive object to create, when about 70% of the creation time, is creating the MessageConverters.

  1. RestTemplate is not thread safe after construction. But it’s not after construction.See more Is RestTemplate thread Safe?

Conceptually, it is very similar to the JdbcTemplate, JmsTemplate, and the various other templates found in the Spring Framework and other portfolio projects. This means, for instance, that the RestTemplate is thread-safe once constructed

This last feature is suggesting to don’t modify a RestTemplate after its construction. So I neither would add any new Interceptor nor remove it

  1. We miss many details about the system and it’s requirements, boundaries,etc…

  2. We don’t know what these files are for. So it compromises the adequacy of the solution I propose.

What do we have?

If I’m right, we have Appache Log4J configured. (@Nati told me by chat).

So I have decided to use Log4j as logger tool. However, please check out Drawbacks section. (hope someone else reading this answer could dispel my doubts)

Why Log4j instead of my own File management? Because I wanted to delegate to Log4j the next features: access to files, usage of patterns, usage of filters (optional), usage of levels, …

Solution

LoggerHelper

I have implemented a helper in order to make the solution portable through any layer with the less coupling possible.

package org.myworkouts.helper;

import java.io.File;
import java.io.IOException;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;


public class LoggerHelper {

    // Helper's own logger
    private static final Logger LOGGER = LogManager
            .getLogger(LoggerHelper.class);
    private static final String DEFAULT_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n";
    private static final String LOGGER_PREFIX = "logger";
    private static final String APPENDER_PREFIX = "appender";

    private static LoggerHelper instance = null;

    // Singleton
    private LoggerHelper() {
        super();
    }

    public static LoggerHelper getInstance() {
        if (instance == null) {
            instance = new LoggerHelper();
        }
        return instance;
    }

    /**
     * Get the logger by its name or create if never was instanciated previously
     * 
     * @throws IOException
     */
    public Logger getCustomFileLogger(String filePath) throws IOException {
        String loggerName = resolveLoggerName(filePath);

        Logger logger = LogManager.exists(loggerName);
        if (logger == null) {
            LOGGER.info("Creating new Logger: ".concat(loggerName));
            logger = LogManager.getLogger(loggerName);
        }
        setLevel(logger, Level.INFO).addFileAppender(logger, filePath);
        return logger;
    }

    private LoggerHelper addFileAppender(Logger logger, String filePath)
            throws IOException {
        if (logger != null) {
            String appenderName = resolveAppenderName(filePath);
            if (logger.getAppender(appenderName) == null) {
                logger.addAppender(new FileAppender(new PatternLayout(
                        DEFAULT_LOG_PATTERN), filePath, Boolean.TRUE));
            }
        }
        return this;
    }

    private LoggerHelper setLevel(Logger logger, Level level) {
        if (logger != null) {
            logger.setLevel(level);
        }
        return this;
    }

    /* Optionl. Generate a custom name for a new Appender */
    private String resolveAppenderName(String filePath) {
        return APPENDER_PREFIX.concat(File.pathSeparator).concat(
                getNameWithoutExtension(filePath));
    }

    /* Optionl. Generate a custom name for a new Logger */
    private String resolveLoggerName(String filePath) {
        return LOGGER_PREFIX.concat(File.pathSeparator).concat(
                getNameWithoutExtension(filePath));
    }

    //Took from com.google.common.io.Files
    //I had it in my workspace but I didn't want to use it at this
    //class
    private String getNameWithoutExtension(String filePath) {
        String fileName = new File(filePath).getName();
        int dotIndex = fileName.lastIndexOf('.');
        return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
    }
}

Implementation

Attaching the solution to owner’s code

protected MyResponse<MyInterface> getInterfaces(RestTemplate restClient, RestParams inputParams, String cookie, File outputFile) {

    Request devInterfacesRequest = getRequest(RequestType.INTERFACES, inputParams, cookie);

     MyResponse<MyInterface> response =  restClient.postForObject(inputParams.getUrl(), devInterfacesRequest , new ParameterizedTypeReference<MyResponse<MyInterface>>(){});
     try{
           Logger customLogger = LoggerHelper.getInstance().getCustomFileLogger(outputFile.getPath());
           //... we can also to parse it to Json, XML, whatever...
           customLogger.info(response.toString());
     }catch(IOException ex){
          //... what do we have to do if logger could not be instanciated?
     }
     return response;
}

Note: Implementation could be also moved to getInterfaces caller who has (I suppouse) a forbucle and also decide the outputFile. Then getCustomFileLogger(...) would be invoked just one time.

Test

public class LoggerHelperTest {

    public static final Logger LOGGER = LogManager.getLogger(LoggerHelperTest.class);

    @Test
    public void getCustomLoggerTest() throws IOException{
        Logger customLogger = LoggerHelper.getInstance().getCustomFileLogger("file.txt");
        Assert.assertNotNull("CustomLogger should not be null",customLogger);
        Assert.assertEquals("CustomLogger's level should be INFO by default",Level.INFO, customLogger.getLevel());
        Assert.assertTrue("CustomLogger's name should contain file's name on its name",customLogger.getName().contains("file"));
        customLogger.info("Trace 1");
        customLogger.info("Trace 2");
        customLogger.info("Trace 3");
    }
}

file.txt

The output I got

2016-05-03 13:38:56 INFO  logger:file:47 - Trace 1
2016-05-03 13:38:56 INFO  logger:file:48 - Trace 2
2016-05-03 13:38:56 INFO  logger:file:49 - Trace 3

Doubts

This’s a suggestion. It’s opened to suggestions or improvements. I also have my own doubts about my solution. (I didn’t stress the solution):

  1. Is File outputFile locked (opened) by any other process? Then IOException will be thrown.
  2. Home any differents outputFile are? 10,100,1000? Can we hold that much of Loggers in Log4j?
  3. Are these outputFile going to be moved in runtime? If any is moved/deleted/replaced log4j wont be able to keep logging in it.
  4. Shall we catch IOException instead of let it go?. I delegate to LoggerHelper user to decice what to do…

I have used Log4j because I have had in mind that the requirement is to keep loggers separated printing everyone on its own file. But I expect they only contain log traces (for further reviews or debugs).

However if file’s content matters, if content is important for us, the solution is not suitable.

If content matters and I don’t want to implement a file management, I would think on storage these logs in noSQL db’s (mongoDB or any other more suitable for our platform) which also is capable to support a high volume of data. Data can be obtained later with proper requests

4

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *