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:
- 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.
- 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
-
We miss many details about the system and it’s requirements, boundaries,etc…
-
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 for
bucle 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):
- Is File outputFile locked (opened) by any other process? Then IOException will be thrown.
- Home any differents outputFile are? 10,100,1000? Can we hold that much of Loggers in Log4j?
- Are these outputFile going to be moved in runtime? If any is moved/deleted/replaced log4j wont be able to keep logging in it.
- 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