logging-log4j2 icon indicating copy to clipboard operation
logging-log4j2 copied to clipboard

New Feature: Add stacktrace package-name filtering for JsonTemplateLayout like it exists for PatternLayout

Open takanuva15 opened this issue 1 year ago • 10 comments

Hi, in PatternLayout, we are able to filter out certain package names from stacktraces by providing a list of package-names to filter, as documented in the patterns table for pattern layout: %ex{filters("org.apache", "org.spring")}

In JsonTemplateLayout, there are a number of configuration options available for printing stacktraces, but I can't find a filtering option that allows me to filter out packages from stacktraces the same way that we're able to in PatternLayout. I am aware of the "truncation" configuration defined under the "exception" section of the JsonTemplateLayout documentation, but this wouldn't work for my use case because I have interceptors defined in my code that are interspered in the stacktrace with the boilerplate "org.apache" stuff that I don't need to see. If I were to truncate the stacktrace after something like "org.apache", I would lose the knowledge of what interceptors were involved in a particular request before the exception occurred.

Below is an example of one such stacktrace where my own "com.mycompany" stacktrace lines are spread among other package names I would like to filter out:

Click me to expand stacktrace
2023-10-29 17:33:14.904 ERROR 86240 [nio-8080-exec-2][][] c.m.m.c.ExceptionHelper                  : Request: http://localhost:8080/myapi/doc/save raised exception: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('[' (code 91)): was expecting a colon to separate field name and value
at [Source: (String)"{"items" "<rest of my json payload>"[truncated 12434 chars]; line: 1, column: 13]
	at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:659) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipColon2(ReaderBasedJsonParser.java:2309) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipColon(ReaderBasedJsonParser.java:2288) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:737) ~[jackson-core-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597) ~[jackson-databind-2.13.5.jar:2.13.5]
	at com.mycompany.myapi.controller.MyController.saveDoc(MyController.java:159) ~[classes/:?]
	at com.mycompany.myapi.controller.MyController$$FastClassBySpringCGLIB$$d73ec01b.invoke(<generated>) ~[classes/:?]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.28.jar:5.3.28]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.28.jar:5.3.28]
	at com.mycompany.myapi.controller.MyController$$EnhancerBySpringCGLIB$$5aa9ebd4.saveDoc(<generated>) ~[classes/:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:555) ~[tomcat-embed-core-9.0.76.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.28.jar:5.3.28]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) ~[tomcat-embed-core-9.0.76.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at com.mycompany.myapi.config.HttpRequestLoggingFilter.doFilterInternal(HttpRequestLoggingFilter.java:28) ~[classes/:?]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:164) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.9.jar:5.7.9]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.7.13.jar:2.7.13]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.28.jar:5.3.28]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at com.mycompany.myapi.config.StatusInterceptorValve.invoke(StatusInterceptorValve.java:32) ~[classes/:?]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.76.jar:9.0.76]
	at java.lang.Thread.run(Thread.java:829) ~[?:?]

Assuming my understanding is correct that there is no stacktrace-package-filtering option available in JsonTemplateLayout like there is in PatternLayout, then I would like to add such a feature that could look like this:

Currently, to specify truncation, I would specify the following in my template-json file:

  "stackTrace": {
    "stringified": {
      "truncation": {
        "suffix": "... [truncated]",
        "pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
      }
    }
  }

To add stacktrace filtering to this configuration, I was thinking the easiest would be to have a separate block like this:

  "stackTrace": {
    "stringified": {
      "filtered": {
        "packagesToExclude": [
            "org.apache",
            "org.spring",
            "jdk."
        ]
      }
    }
  }

If someone specified both truncation and filtering, we can throw an error so the developer picks one to use for processing their stacktraces. Then we can add in the logic for the "filtered" Resolver to a new Java file like we have for the stacktrace truncation stuff in StackTraceStringResolver.java.

I hope that makes sense. Assuming this could be approved, I can work on making a PR for this in the near future. 👍

takanuva15 avatar Nov 02 '23 13:11 takanuva15

@takanuva15, sounds reasonable to me. I think both package exclusions and point matchers can coexist, there is no need to throw an exception when they do. Please take the following implementation notes into account:

  • You need to update StackTraceStringResolver.
  • You need to refactor the current STSR#truncate(TruncatingBufferedPrintWriter,TruncatingBufferedPrintWriter,CharSequencePointer) into two methods: one for point matchers, one for package exclusions.
  • Since point matchers and package exclusions will coexist, you need an order. Please do the truncation using point matchers first.
  • Introduce packageExclusionPrefixes (containing an array of package name prefixes that will be matched against) and packageExclusionRegexes directives
  • Note that stringified stack traces and their truncation operate in totally different ways between PatternLayout and JsonTemplateLayout. JTL first renders the stack trace using Throwable#printStackTrace(PrintWriter) into a mutable CharSequence and then truncates it. This is of crucial importance, since it enables cheap (almost garbage-free?) stack trace rendering. Hence, no allocations, please.
  • Please try to adopt the existing coding style.
  • Don't forget to add final modifiers wherever possible.
  • Don't forget to add a src/changelog/add-package-exclusion-to-jtl.xml.
  • Don't forget to update json-template-layout.adoc
  • Don't forget to update the ExceptionResolver javadoc (this one and AsciiDoc one should match)
  • Add your tests to StackTraceStringResolverTest. (Please adhere to the grouping implemented by comment blocks there.)

You can find me on Slack if you need help. Thanks in advance and good luck! :muscle:

vy avatar Nov 02 '23 14:11 vy

@vy I tried accessing the Slack link but it says I don't have an account. Is there an account request-form somewhere that I need to submit?

Question:

  • Is using String.format(...) considered an allocation?
  • I keep getting java: plug-in not found: ErrorProne when running the junit test even though I'm using the latest IntelliJ?

takanuva15 avatar Nov 23 '23 22:11 takanuva15

@vy I tried accessing the Slack link but it says I don't have an account. Is there an account request-form somewhere that I need to submit?

@takanuva15, I think you're right. I guess that is exclusive to ASF members. :disappointed: Let me know if you any suggestions on a particular medium for chat.

  • Is using String.format(...) considered an allocation?

Yes.

  • I keep getting java: plug-in not found: ErrorProne when running the junit test even though I'm using the latest IntelliJ?

Try removing all "Override compiler parameters per-module" entries in "Settings > Build, Execution, Deployment > Compiler > Java Compiler". Sadly I am the one and only one in the entire Log4j crew who thinks tests should be able to run in an IDE. The rest thinks this is not necessary and they are fine with attaching their IDE to ./mvnw -Dmaven.surefire.debug process. :face_exhaling: If the aforementioned IntelliJ trick doesn't work for you, try the following:

  1. Make sure you installed everything in the 2.x branch: ./mvnw install -DskipTests
  2. After making a change to the JTL code, install your changes: ./mvnw install -pl :log4j-layout-template-json -DskipTests
  3. Run all tests: ./mvnw test -pl :log4j-layout-template-json-test
  4. Run a particular test ./mvnw test -pl :log4j-layout-template-json-test -Dtest=FooBarTest

You can connect your IDE to a ./mvnw test run by

  1. Add -Dmaven.surefire.debug to the ./mvnw test command
  2. Use "Run > Attach to process" in IntelliJ

vy avatar Nov 27 '23 08:11 vy

@takanuva15, I think you're right. I guess that is exclusive to ASF members. 😞 Let me know if you any suggestions on a particular medium for chat.

I worked on a different open-source project a few years ago and the guy I was working with pointed to me a cool open-source chat protocol called matrix. Not sure if you've heard of it before, but it's basically like iMessage, WhatsApp, etc. with 1-1 and group chats, but the chat protocol is open so anyone can develop a client for it as long as it understands the Matrix protocol. I already have an existing account on there as @takanuva15:matrix.org, so if you're inclined to give it a shot, you should be able to download any of the compatible clients available and message my username.

Try removing all "Override compiler parameters per-module" entries in "Settings > Build, Execution, Deployment > Compiler > Java Compiler".

This fix worked in my IntelliJ! Whoooo I can debug my code now!!! Would it be fine to update the BUILDING.md file in my future PR with this tidbit you just shared?

takanuva15 avatar Nov 29 '23 04:11 takanuva15

I worked on a different open-source project a few years ago and the guy I was working with pointed to me a cool open-source chat protocol called matrix. Not sure if you've heard of it before, but it's basically like iMessage, WhatsApp, etc. with 1-1 and group chats, but the chat protocol is open so anyone can develop a client for it as long as it understands the Matrix protocol. I already have an existing account on there as @takanuva15:matrix.org, so if you're inclined to give it a shot, you should be able to download any of the compatible clients available and message my username.

Sent you a message from @yazicivo:matrix.org.

This fix worked in my IntelliJ! Whoooo I can debug my code now!!! Would it be fine to update the BUILDING.md file in my future PR with this tidbit you just shared?

Updated build instructions in a31ea22a75ec4c57ed55249cb13fa4629454f12b.

vy avatar Nov 29 '23 08:11 vy

Hi, a few additional questions:

  1. You mentioned that I should "refactor the current STSR#truncate" method into two methods, where I do truncation first and then package exclusions. In terms of implementation, what I currently have working is:
    • run package truncation on srcWriter into dstWriter
    • copy the truncation output from dstWriter back to srcWriter
    • run package exclusion on srcWriter into dstWriter

  Does this sound like the correct sequence of events for the implementation?

  1. For the StackTraceStringResolverTest, can I define test json-templates in json files under src/test/resources instead of repeating the deeply-nested asMap(...) calls?
  2. For the StackTraceStringResolverTest, can we split the @Nested test classes into separate files and group them all in a subpackage (eg layout.template.json.resolver.stacktracestring)? The current test class is getting close to 1000 lines with my new tests and the class is starting to look like spaghetti code 😸

takanuva15 avatar Dec 22 '23 21:12 takanuva15

  1. You mentioned that I should "refactor the current STSR#truncate" method into two methods, where I do truncation first and then package exclusions. In terms of implementation, what I currently have working is:

    • run package truncation on srcWriter into dstWriter
    • copy the truncation output from dstWriter back to srcWriter
    • run package exclusion on srcWriter into dstWriter

  Does this sound like the correct sequence of events for the implementation?

Yes. Please add necessary branching to avoid the buffer copying if there are no package exclusions.

  1. For the StackTraceStringResolverTest, can I define test json-templates in json files under src/test/resources instead of repeating the deeply-nested asMap(...) calls?

I would not stop your from doing it, but I like self-contained tests. As can be seen in log4j-core-test, placing test resources to src/test/resources is a slippery slope that eventually leads to a waste land of files where it is not possible to trace back who uses what for which purpose. Not to mention, it adds to the cognitive load while troubleshooting too.

If you insist on using src/test/resources, please only place your JSON templates there. That is, don't place a Log4j configuration and use that to bootstrap a fully-fledged LoggerContext. They are expensive, they pollute the JVM and obstruct test parallelization. Please only create a JsonTemplateLayout instance and test its rendering.

  1. For the StackTraceStringResolverTest, can we split the @Nested test classes into separate files and group them all in a subpackage (eg layout.template.json.resolver.stacktracestring)? The current test class is getting close to 1000 lines with my new tests and the class is starting to look like spaghetti code 😸

Okay. Use stacktrace.string please, we will need a stacktrace.object in the future.

vy avatar Dec 29 '23 08:12 vy

Hello, Any ETA on this enhancement? Would really love to have this released

abhijit911turbo avatar Apr 09 '24 16:04 abhijit911turbo

@abhijit911turbo It's still under development on my fork; been distracted with other things that life has thrown on me lol. No definite ETA I can give you at this point

takanuva15 avatar Apr 09 '24 21:04 takanuva15

@abhijit911turbo, @takanuva15, sorry to break it to you, but if you need this feature from Log4j, which I presume is deployed at an organization helping you to pay your bills at the end of the month, you can always contract one of the maintainers. I can understand the motivation behind "waiting until someone magically makes it happen", but, honestly, paying a couple of bucks instead if waiting is a win-win from many perspectives.

vy avatar Apr 10 '24 07:04 vy