Access to request parameter in custom logback appender - spring

I have custom logback appender that is used in my project. The project is based on REST controllers.
Is there a possibility to have access to current request parameters in the logback appender that is used by logger in spring controller? I need to write something to logs in basis on the request parameter.
What I can see is that logback is initialized before spring boot beans which is not good for me.

Use the org.slf4j.MDC: You can put values in the MDC at any time. Before anything else.
Example :
#ResponseBody
#RequestMapping(value="/myRequest", method = RequestMethod.GET)
public String doAnyAction(#RequestParam(value="Key", required = true) long key)
{
MDC.put("key", key);
logger.info(key);
}
And change your Logback.xml appender to add the "key" as well :
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<Pattern>%X{key} - %m%n</Pattern>
</layout>
</appender>

Related

How to Log AWSRequestId with Micronaut Lambda Application Native Image

Based on this guide about logging AWSRequestId, I can add %X{AWSRequestId} on my logback.xml to print the request id on my lambda function.
On the example, the method that adds this key on MDC is populateMappingDiagnosticContextValues of class MicronautRequestHandler. But since my lambda function has two endpoints, I created it as an Application type and it says it's using the MicronautLambdaHandler as the handler class.
What I did is I created my custom handler that extends the MicronautLambdaHandler and added the populateMappingDiagnosticContextValues method.
#Override
public AwsProxyResponse handleRequest(AwsProxyRequest input, Context context) {
if (context != null) {
populateMappingDiagnosticContextValues(context);
}
return handler.proxy(input, context);
}
logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>false</withJansi>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1} - %m%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
On the lambda setup I pointed the handler on my custom handler and the app is working.
Except that the logs is not showing the AWSRequestId.
Is this functionality only works on MicronautRequestHandler class and not on MicronautLambdaHandler?
I also tried the aws guide here that uses log4j2 for logging the request id. But log4j doesn't work when building the GraalVM native image, that's why I sticked to logback.
If you copied populateMappingDiagnosticContextValues and mdcput into your Handler (I suppose you did it) it should work without any additional changes.

Logback and JsonLayout: can't pass custom fields

I have this configuration in my logback.xml into a Spring Web Application (NO Spring Boot).
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSX</timestampFormat>
<timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId>
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
<prettyPrint>true</prettyPrint>
</jsonFormatter>
</layout>
<customFields>{"appname":"foobar"}</customFields>
</encoder>
</appender>
<!-- LOG everything at INFO level -->
<root level="INFO">
<appender-ref ref="Console" />
</root>
</configuration>
The JSON layout works fine but custom fields as "appname": "foobar" are not printed:
{
"timestamp" : "2020-06-10T14:55:25.534Z",
"level" : "INFO",
"thread" : "Catalina-utility-1",
"logger" : "org.springframework.web.servlet.DispatcherServlet",
"message" : "FrameworkServlet 'dispatcher': initialization completed in 72 ms",
"context" : "default"
}
What am I doing wrong?
SOLUTION
I was using the wrong libraries for my needs:
logback-jackson
logback-json-classic
Because of the fact that I need to process logs through Logstash I've corrected my configuration like this:
pom.xml
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.4</version>
</dependency>
logback.xml
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"customer":"X", "appname":"Y", "environment":"dev"}</customFields>
</encoder>
</appender>
and now It works fine.
I just stumbled this question because I had the same problem, and I found a solution, with logback-jackson and logback-json-classic.
Option 1: Per-Thread via Mapped Diagnostic Context (MDC)
SLF4j's Mapped Diagnostic Context is a per-thread key-value store that we can use to write custom structured data to the log output.
MDC.put("customKey", "customValue");
Logback's JsonLayout will automatically print this value under a special mdc JSON object without any further configuration.
{ [...], "mdc": {"customKey", "customValue"}}
Note that the MDC is constructed per thread and if it is empty, no mdc field is printed to the log output.
Option 2: Global (for all threads)
If you want custom fields to appear at the JSON output's root, you need to create a custom, but simple Layout class that extends JsonLayout. JsonLayout provides us with a addCustomDataToJsonMap we can override.
package com.mypackage;
import ch.qos.logback.contrib.json.classic.JsonLayout;
public class CustomJsonLayout extends JsonLayout {
#Override
protected void addCustomDataToJsonMap(Map<String, Object> map, ILoggingEvent event) {
map.put("customKey", "customValue");
}
}
Now, you just need to tell Logback to use CustomJsonLayout instead of JsonLayout in your logback.xml file and keep the rest the same.
<layout class="com.mypackage.CustomJsonLayout">
...
</layout>
Now, any log message will have the following output:
{ ..., "customKey": "customValue"}

Logback.xml configuration by environment

I have logback configuration which is like bellow, as you can see i have an appender that appends all the logs to the standalone logging service.
<appender name="digitalGelfAppender"
class="de.siegmar.logbackgelf.GelfUdpAppender">
<graylogHost>testserver.loggingservice.com</graylogHost>
<graylogPort>1234</graylogPort>
<useCompression>true</useCompression>
<layout class="de.siegmar.logbackgelf.GelfLayout">
<originHost>originHost</originHost>
<includeRawMessage>false</includeRawMessage>
<includeMarker>true</includeMarker>
<includeMdcData>true</includeMdcData>
<includeCallerData>false</includeCallerData>
<includeRootCauseData>false</includeRootCauseData>
<includeLevelName>false</includeLevelName>
<shortPatternLayout class="ch.qos.logback.classic.PatternLayout">
<pattern>${log.pattern}</pattern>
</shortPatternLayout>
<fullPatternLayout class="ch.qos.logback.classic.PatternLayout">
<pattern>${log.pattern}</pattern>
</fullPatternLayout>
<staticField>application:DAP</staticField>
<staticField>environment:UAT</staticField>
</layout>
</appender>
<root level="#logback.loglevel#">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="digitalGelfAppender"/>
</root>
My question is that i need to change
<graylogHost>testserver.loggingservice.com</graylogHost>
<graylogPort>1234</graylogPort>
by environment value. So it will be like bellow on production server.
<graylogHost>prodserver.loggingservice.com</graylogHost>
<graylogPort>prodPort</graylogPort>
How can i pass the environment value to the logback.xml file by environment?
I tried to add these variables and use it like property inside xml. Its not working at all.
<property name="GRAYLOG_SERVER" value="${graylog-hostname}" />
<property name="GRAYLOG_SERVER_PORT" value="{graylog-port}" />
Usage
<graylogHost>${GRAYLOG_SERVER}</graylogHost>
<graylogPort>${GRAYLOG_SERVER_PORT}</graylogPort>
Hi i solve my problem by implementing listener that retrieve data from System parameter of environment;
#Controller
public class LoggerContextListener extends ContextAwareBase implements
LoggerContextListener, LifeCycle {
#Override
public void start() {
Map<String, String> property = System.getenv();
Context context = getContext();
context.putProperty("GRAYLOG_SERVER", property.get("graylog-hostname"));
context.putProperty("GRAYLOG_SERVER_PORT", property.get("graylog-port"));
}
After that i add that listener as a contextListener to the lockback.xml
<contextListener class="com.anadolusigorta.dap.config.DapLoggerContextListener"/>
Then problem solved! Now my xml can use these parameter that set from listener;
<graylogHost>${GRAYLOG_SERVER}</graylogHost>
<graylogPort>${GRAYLOG_SERVER_PORT}</graylogPort>
You don't need to create the listener. It is enough that use the environment variables as follows.
<graylogHost>${graylog-hostname}</graylogHost>
<graylogPort>${graylog-port}</graylogPort>

Debug logging of ResponseBody by Springframework

I have a scenario where I send some sensitive data in the ResponseBody of a spring controller. Spring logs the entire response body in DEBUG mode, where I can see the sensitive data also. Now if I configure my app to print only INFO logs or higher, this won't be displayed, but that is not a fool proof method, since its possible that DEBUG mode could be turned on accidentally.
Is there anyway I can disable certain fields in the response body from being logged by Spring?
Thanks
My suggestion would be that you implement a custom converter for logback (if that is your logging library)
http://logback.qos.ch/manual/layouts.html#customConversionSpecifier
It enables you to convert your logging data and blur out any data that should be removed from the message.
From the documentation it is pretty straight forward
public class MySampleConverter extends ClassicConverter {
long start = System.nanoTime();
#Override
public String convert(ILoggingEvent event) {
long nowInNanos = System.nanoTime();
return Long.toString(nowInNanos-start);
}
}
And the config
<configuration>
<conversionRule conversionWord="nanos"
converterClass="chapters.layouts.MySampleConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-6nanos [%thread] - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
If you're using Log4j you can implement a custom filter which would mask the data per your layout
http://vozis.blogspot.sg/2012/02/log4j-filter-to-mask-payment-card.html
Edit
The final easier solution turned out to be to override toString of the return object. Spring uses toString to log the return message

perf4j #Profiled annotation not working

I have gone thru the below link from perf4J site and have done same to same: http://perf4j.codehaus.org/devguide.html#Using_Spring_AOP_to_Integrate_Timing_Aspects
Added the following in my spring.xml.
<aop:aspectj-autoproxy/>
<bean id="timingAspect" class="org.perf4j.log4j.aop.TimingAspect"/>
<bean id="wscClientBase" class="com.xyz.csa.core.common.WscClientBase"/>
In class WscClientBase I have the following method with #Profiled annotation.
#Profiled(tag = "SOAPCALLTEST")
public Object sendMessage(Object message) {
String msg = message.toString();
if (msg.indexOf(' ') > 1) {
msg = msg.substring(1, msg.indexOf(' '));
}
try {
Object ret = marshalSendAndReceive(message);
return ret;
} catch (RuntimeException ex) {
throw ex;
}
}
I dont see the perf4j TimingLogger statements in the application log. However if I use it obtrusively (without annotation) as below, I see the log statements successfully.
public Object sendMessage(Object message) {
String msg = message.toString();
if (msg.indexOf(' ') > 1) {
msg = msg.substring(1, msg.indexOf(' '));
}
StopWatch stopWatch = new Slf4JStopWatch();
try {
Object ret = marshalSendAndReceive(message);
stopWatch.stop("PERF_SUCCESS_TAG", msg);
return ret;
} catch (RuntimeException ex) {
stopWatch.stop("PERF_FAILURE_TAG", msg);
throw ex;
}
}
Am I missing something?
Perf4j
This is a performance analysis and checking plugin for application. It can be integrated with spring using spring AOP. It creates a log file that is give to a parser to analyse and produce relevant information. It can provide average,mean, std deviation by default.
For more general information please check http://perf4j.codehaus.org/index.html
How to setup Perf4j.
For normal setup you just need to add perf4j jar and create StopWatch instance for every code sniplet you want to monitor.
StopWatch stopWatch= new StopWatch(“snipletTagName”)
…
//{your code sniplet}
…
stopwatch.stop();
This will create perf4j monitor and you will get logg information on the console.
Main purpose of this documentation is to have a setup by setup understanding of integrating perf4j with spring.
1.Add all of the below Jar files.
1.perf4j-0.9.16-slf4jonly.jar
2.aspectjweaver-1.6.12.jar
3.aopalliance-1.0.jar
4.commons-logging-1.1.1.jar
5.logback-classic-1.0.7.jar
6.logback-core-1.0.7.jar
7.slf4j-api-1.7.1.jar
8.perf4j-0.9.16.jar
9.aspectjrt-1.6.1.jar
10.commons-jexl-1.1.jar
11.asm-1.5.3.jar
12.cglib-2.1_3.jar
Make sure you have all these jars in your classpath along with spring libraries.
2.create your own logback.xml that will be used by perf4j implicitly
the content of the logback.xml will be
<configuration>
<appender name="perf4jFileAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>logs/perf4j.log</File>
<encoder>
<Pattern>%date %-5level [%thread] %logger{36} [%file:%line] %msg%n
</Pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>logs/perf4j.%d{yyyy-MM-dd}.log</FileNamePattern>
</rollingPolicy>
</appender>
<appender name="CoalescingStatistics"
class="org.perf4j.logback.AsyncCoalescingStatisticsAppender">
<param name="TimeSlice" value="1" />
<appender-ref ref="perf4jFileAppender" />
</appender>
<appender name="RootConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%date %-5level [%thread] %logger{36} [%file:%line] %msg%n
</pattern>
</layout>
</appender>
<!-- Loggers -->
<!-- The Perf4J logger. Note that org.perf4j.TimingLogger is the value of
the org.perf4j.StopWatch.DEFAULT_LOGGER_NAME constant. Also, note that additivity
is set to false, which is usually what is desired - this means that timing
statements will only be sent to this logger and NOT to upstream loggers. -->
<logger name="org.perf4j.TimingLogger" additivity="false">
<level value="DEBUG" />
<appender-ref ref="CoalescingStatistics" />
<appender-ref ref="perf4jFileAppender" />
<appender-ref ref="RootConsoleAppender" />
</logger>
</configuration>
3.In your spring configuration file you need to add aspectj tag that will enable #Profiled annotation of perf4j.
(Note: What is #Profiled annotation?: you will add this tag to all the
methods in all the classes that are called from spring instance or use
dependency injection. The object basically should be spring context
registered and the method should be invoked by the object that are
registered in spring context. I wasted one day thinking why my method
was not logged then I realized that the object I tested was not part
of spring context.
OK, the code that you need to add to spring configuration xml is
<!-- this is my spring-context.xml -->
<beans>
<aop:aspectj-autoproxy>
<aop:include name="timingAspect" />
</aop:aspectj-autoproxy>
<bean id="timingAspect" class="org.perf4j.slf4j.aop.TimingAspect" />
<!-- this is the class that will be registered with the spring and now we can get this class and call the method that we need to monitor-->
<bean class="com.perf4jexample.Test" />
</beans>
4.Create the Test class that will implement #Profiled annotation.
public class Test {
private String testVal;
public Test() {
// TODO Auto-generated constructor stub
}
#Profiled
public void testing() {
System.out.println("testt" );
}
public String getTestVal() {
return testVal;
}
public void setTestVal(String testVal) {
this.testVal = testVal;
}
}
5.Ok now you have setup every thing just thing remains is test class that will start spring context and with it load perf4j.
public class Test(){
public static void main(){
AbstractApplicationContext context = new ClassPathXmlApplicationContext(
"spring-context.xml");
context.start();
Test bean = context.getBean(Test.class);
bean.testing();
}
I hope by following these setups you should be able to perf4j console appender to display one line on console.
Perf4j Monitoring command on the log:
For Generating Performance statistical information execute on you logger path
java -jar perf4j-0.9.16.jar myLogger.log
For Generating Graphs
java -jar perf4j-0.9.16.jar --graph perfGraphs.out myLogger.log
I hope this tutorial helps you to integrated Spring, perf4j, logback with Profiled annotation.
Try adding <aop:include name="timingAspect"/> inside the <aop:aspectj-autoproxy/>.
Can you also confirm that you are invoking sendMessage on an object that is retrieved from the spring application context (using getBean or injected as a dependency).
Here I have two ways to make perf4j #Profiled work on spring boot project.
Precondition is adding below dependencies
"org.aspectj:aspectjweaver",
"org.perf4j:perf4j:0.9.16",
"commons-jexl:commons-jexl:1.1",
"cglib:cglib:3.2.1",
For normal spring project, probably need to add little more dependencies like spring-aop, aopalliance... those looks included in spring-boot-starter-parent
1. Java Configuration
This is simplest way and mostly works, but I found that somehow not working on spring-data JpaRepository method. It just provides org.perf4j.log4j.aop.TimingAspect bean and do aspectj autoproxing. Same way like xml configuration provided by other people above
#Configuration
#EnableAspectJAutoProxy
public class PerformanceConfig {
#Bean
public TimingAspect timingAspect() {
return new TimingAspect();
}
}
2. Provide Your own Aspect
In this way, the #Profiled annotated spring-data repository interface methods also work fine. But the downside of this is ignoring the tag given in #Profiled(tag='some tag') and using the joinPoint method name as tag.
#Aspect
#Component
public class PerformanceTracker {
#Around(value="execution(#org.perf4j.aop.Profiled * com.mypackage..*(..))")
public Object checkPerformance(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new Log4JStopWatch(pjp.getSignature().toShortString());
Object result = pjp.proceed();
stopWatch.stop();
return result;
}
}
The reason why it could not work is that the profiled method is in the parent class of the Spring-bean. I can tell it by looking at your bean name: WscClientBase. I assume that this is base class from which you have many children classes.
After some research time I found very important note in Spring documentation about #Transactional and #Cacheable.
http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative-annotations. Check “Method visibility and #Transactional” block.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-annotation-enable. Check “Method visibility and cache annotations”
In both cases they say things like this:
“When using proxies, you should apply the <> annotations only to
methods with public visibility. If you do annotate protected, private
or package-visible methods with these annotations, no error is raised,
but the annotated method does not exhibit the configured caching
settings. Consider the use of AspectJ (see below) if you need to
annotate non-public methods as it changes the bytecode itself.”
And below…
“Spring recommends that you only annotate concrete classes (and
methods of concrete classes) with the #Cache* annotation, as opposed
to annotating interfaces. You certainly can place the #Cache*
annotation on an interface (or an interface method), but this works
only as you would expect it to if you are using interface-based
proxies. The fact that Java annotations are not inherited from
interfaces means that if you are using class-based proxies
(proxy-target-class="true") or the weaving-based aspect (
mode="aspectj"), then the caching settings are not recognized by the
proxying and weaving infrastructure, and the object will not be
wrapped in a caching proxy, which would be decidedly bad.”
I assume that #Profiled use similar weaving mechanism therefore you can't put #Profiled on any method in parent classes.
As a matter of fact I had similar issue in my application: I had #Profiled and #Cacheable in parent class and none of those worked: I didn't see any records in my Perf4J log and cache wasn't updated. When I moved #Profiled into methods of children-classes I started seeing the records in the perf4j.log.
put config below in your "servlet-context-config.xml" .have fun!
<aop:aspectj-autoproxy/>
<bean id="timingAspect" class="org.perf4j.log4j.aop.TimingAspect"/>
<bean id="wscClientBase" class="com.xyz.csa.core.common.WscClientBase"/>
For people who have this type of problem they can check that there is not in spring logs (level info), messages like it " is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)".

Resources