- preface
- Strengthen the logback
- Web interceptor configuration
- Write javaAgent program
- Making javaagent package
- test
- conclusion
preface
Program running log is very important for troubleshooting system problems, business monitoring, etc. Java logging is mostly realized by logback, Log4J and other frameworks. I packaged a log plug-in package according to the company’s log specification before, and the system needs to integrate toolkit and print logs according to the log printing specification. The operation and maintenance system uses FileBeat to collect logs to ES, and develops ELK (ES, LogSTah, Kibana) to view logs. But some of the very old and big system is not integrated logging toolkit, and for some reason has not, this leads to some problems, for example, on the query log data processing failure is very difficult, because there is no process in the log traceId context, this means that the printing of all is broken, There’s no way to do a link trace. Every troubleshooting problem is simply not too painful; In addition, for some interfaces, it is not possible to visualize the interface execution time in the log. Arthas can be used to record the interface execution time, but it is not appropriate to use Arthas every time you want to check the interface execution time. Is there a way to add traceId and time to the system log without reinventing the system to zero intrusion into the business code? Of course, this article will introduce the implementation of business system code without intrusion enhanced log through JavaAgent technology + bytecode enhanced Logback (logback, LOG4J can also be enhanced) Logger.
Strengthen the logback
First, we define the storage of traceId to realize the ability to add traceId to the log content. We define a class. I named TraceContext and defined it as the context of the link. TraceContext: ThreadLocal is used to store traceids. ThreadLocal is used to store traceids, but ThreadLocal is used for thread isolation.
/ * * *@author liupenghui
* @date 2021/10/8 5:30 下午
*/
public class TraceContext {
/** * Store thread traceId */
private static final ThreadLocal<String> TRANSMITTABLE_THREAD_LOCAL = new ThreadLocal<>();
/** * if a ThreadLocal has a traceId, then a new * is generated@return traceId
*/
public static String getTraceId(a) {
String traceId = TRANSMITTABLE_THREAD_LOCAL.get();
if (StringUtils.isNotBlank(traceId)) {
return traceId;
} else {
// Simple use of UUID, you can use the snowflake algorithm, I am here to simplify, roughly demonstrate the meaning, not industrial applications
traceId = UUID.randomUUID().toString();
TRANSMITTABLE_THREAD_LOCAL.set(traceId);
returntraceId; }}/** * Delete traceId from ThreadLocal
public static void remove(a) { TRANSMITTABLE_THREAD_LOCAL.remove(); }}Copy the code
Then enhance the Logger of logback. Here I use the open source tool TLog to enhance bytecode using Javassit. The code is as follows:
public class AspectLogEnhance {
public static void enhance(a) {
try {
CtClass cc = null;
try {
ClassPool pool = ClassPool.getDefault();
// Import the custom logback enhancement class LogbackBytesEnhance
pool.importPackage("org.agent.enhance.bytes.logback.LogbackBytesEnhance");
/ / enhancement class, ch. Qos. Logback. Classic. The Logger
cc = pool.get("ch.qos.logback.classic.Logger");
if(cc ! =null) {
// The method of enhancement
CtMethod ctMethod = cc.getDeclaredMethod("buildLoggingEventAndAppend");
/ / method body, enhanced LogbackBytesEnhance. Enhance their implementation
ctMethod.setBody("{return LogbackBytesEnhance.enhance($1, $2, $3, $4, $5, $6, this); }");
cc.toClass();
System.out.println("Locakback log enhancement succeeded"); }}catch (Exception e) {
e.printStackTrace();
System.out.println("Locakback log enhancement failed"); }}catch(Throwable t) { t.printStackTrace(); }}}Copy the code
Org. Agent. Enhance the bytes. Logback. LogbackBytesEnhance code is as follows:
public class LogbackBytesEnhance {
/ * * * to enhance ch. Qos. Logback. Classic. Logger buildLoggingEventAndAppend method *@param fqcn
* @param marker
* @param level
* @param msg
* @param params
* @param t
* @param logger
*/
public static void enhance(final String fqcn, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t, Logger logger) {
// Log content
String resultLog;
/ / generated traceId
if (StringUtils.isNotBlank(TraceContext.getTraceId())) {
// Concatenate the traceId with the log content. This is the key point. This is where the trceId is generated and concatenated to the log content
resultLog = StrUtil.format("{} {}", TraceContext.getTraceId(), msg);
} else {
resultLog = msg;
}
/ / enhance buildLoggingEventAndAppend
LoggingEvent loggingEvent = newLoggingEvent(fqcn, logger, level, resultLog, t, params); loggingEvent.setMarker(marker); logger.callAppenders(loggingEvent); }}Copy the code
Web interceptor configuration
My program is based on SpringMVC, so I need to configure the interceptor for Http requests. If the interface is Dubbo, SpringCloud and other RPC frameworks can also use the Filter or interceptor provided by them. I will implement the interceptor configuration of SpringMVC first. The interceptor configuration code is as follows:
SpringWebInterceptorConfiguration
/ * * *@author liupenghui
* @date2021/10/9 5:45pm */
@Configuration
// This configuration is loaded only with WebMvcConfigurer
@ConditionalOnClass(name = {"org.springframework.web.servlet.config.annotation.WebMvcConfigurer"})
public class SpringWebInterceptorConfiguration {
private static final Logger log = LoggerFactory.getLogger(SpringWebInterceptorConfiguration.class);
@Bean
public WebMvcConfiguration addWebInterceptor(a) {
log.info("Define Web interceptors");
return newWebMvcConfiguration(); }}Copy the code
WebMvcConfiguration adds interceptors
The following code implements the WebMvcConfigurer interface and overwrites the addInterceptors method to addInterceptors. Here, two interceptors are added, one to clean up the traceId in TraceContext and the other to record the execution time of the interface.
/ * * *@author liupenghui
* @date 2021/10/11 9:43 上午
*/
public class WebMvcConfiguration implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(WebMvcConfiguration.class);
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptorRegistration;
log.info("Add interceptor");
// Intercepted request path
interceptorRegistration = registry.addInterceptor(new WebInterceptor()).addPathPatterns("/ *");
Springboot 1.5.X does not have an order method
try{
Method method = ReflectUtil.getMethod(InterceptorRegistration.class, "order", Integer.class);
if(ObjectUtil.isNotNull(method)){ method.invoke(interceptorRegistration, Ordered.HIGHEST_PRECEDENCE); }}catch (Exception e){
e.printStackTrace();
log.info("TraceId interceptor load failed");
}
interceptorRegistration = registry.addInterceptor(new TimeInterceptor()).addPathPatterns("/ *");
Springboot 1.5.X does not have an order method
try{
Method method = ReflectUtil.getMethod(InterceptorRegistration.class, "order", Integer.class);
if(ObjectUtil.isNotNull(method)){ method.invoke(interceptorRegistration, Ordered.HIGHEST_PRECEDENCE); }}catch (Exception e){
e.printStackTrace();
log.info("Time-consuming interceptor load failed"); }}}Copy the code
Configure the Springboot autowler and create the spring.factories under the resource/ meta-INF directory as follows:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.agent.config.SpringWebInterceptorConfiguration
Copy the code
TraceId Clears interceptors
/ * * *@author liupenghui
* @date 2021/10/9 6:29 下午
*/
public class WebInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(WebInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("Intercept the request");
response.addHeader("traceId", TraceContext.getTraceId());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("Clean up the traceId"); TraceContext.remove(); }}Copy the code
Interface time interceptor
/ * * *@author liupenghui
* @date 2021/10/8 1:34 下午
*/
public class TimeInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(TimeInterceptor.class);
private static final ThreadLocal<StopWatch> STOP_WATCH_THREAD_LOCAL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
String parameters = JSON.toJSONString(request.getParameterMap());
log.info("Start request URL[{}] with: {}", url, parameters);
StopWatch stopWatch = new StopWatch();
STOP_WATCH_THREAD_LOCAL.set(stopWatch);
stopWatch.start();
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
StopWatch stopWatch = STOP_WATCH_THREAD_LOCAL.get();
stopWatch.stop();
log.info("End the call to URL[{}] in {} ms", request.getRequestURI(), stopWatch.getTime()); STOP_WATCH_THREAD_LOCAL.remove(); }}Copy the code
Write javaAgent program
Logback enhancements and HTTP interface interception capabilities are already in place, so all you need to do is write the JavaAgent, which is as simple as aspectLogenance.enhance () in the premain method; The code is as follows:
/ * * *@author liupenghui
* @date 2021/10/8 11:21 上午
*/
public class PreAgent {
public static void premain(String args, Instrumentation inst) { AspectLogEnhance.enhance(); }}Copy the code
The details of JavaAgent technology are not introduced here, but you can refer to the javaAgent usage guide in the article I reprinted
Making javaagent package
The program is packaged using Maven, and the plug-in package is as follows
<build>
<finalName>java-agent</finalName>
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<promoteTransitiveDependencies>false</promoteTransitiveDependencies>
<createDependencyReducedPom>true</createDependencyReducedPom>
<minimizeJar>false</minimizeJar>
<createSourcesJar>true</createSourcesJar>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>org.agent.PreAgent</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Copy the code
test
Write test program
/ * * *@author liupenghui
* @date 2021/10/8 4:34 下午
*/
@SpringBootApplication
@RestController
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) throws Exception {
new SpringApplication(Application.class).run(args);
}
@GetMapping("/hello")
public String hello(a) {
log.info("Test Log 1");
log.info("Test Log 2");
log.info("Test Log 3");
log.info("Test Log 4");
return "11 + hello"; }}Copy the code
The test results
No logging enhanced by JavaAgent
The test result without log enhancement is shown in the figure below. I called the interface four times, and the log printed by the interface did not contain traceId. As you can imagine, it would be very difficult to find the problem in the business system log.
Enhanced logging effects through JavaAgent
- Program start parameter adds JavaAgent
Method one:
Direct IDEA setup program startup parameters, as shown below:
Method 2:
Java-javaagent: Your path /java-agent.jar -jar Your path /java-agent-test.jar
I adopted method 1 here, and the test results are shown as follows:
As you can see in the red box, a random string is added to the log content. This is the traceId we added, and the interceptor does its job, recording the interface execution time, and clearing the traceId of the current thread after the interface execution.
conclusion
Javaagent and bytecode enhancement techniques have been used to implement invastion-free logging, but there are some limitations to this. The problem I have encountered is that there is no way to implement asynchronous logging, for example, how to do link tracing of asynchronous logs. Because I realize before logging toolkit by com. Alibaba. TTL. TransmittableThreadLocal rewrite MDCAdapter and modify the way a thread pool is can realize asynchronous log, but I now this way is not support for the MDC.