Skip to content

Commit c158fe5

Browse files
committed
refactor: Simplify classes and introduce clearer defaults
feat: Add FiretailConfig fix: Add in HTTP filters chore: Upgrade spring
1 parent cd5044f commit c158fe5

16 files changed

+371
-173
lines changed

build.gradle.kts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
plugins {
32
`java-library`
43
`maven-publish`
@@ -8,6 +7,14 @@ plugins {
87
id("io.spring.dependency-management") version "1.1.2"
98
}
109

10+
buildscript {
11+
val kotlinVersion = "1.8.21"
12+
dependencies {
13+
classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion")
14+
classpath("org.jmailen.gradle:kotlinter-gradle:3.14.0")
15+
}
16+
}
17+
1118
repositories {
1219
mavenLocal()
1320
maven {
@@ -19,11 +26,11 @@ repositories {
1926
group = "com.github.firetail-io"
2027
version = "0.0.1.SNAPSHOT"
2128
description = "firetail-java-lib"
22-
java.sourceCompatibility = JavaVersion.VERSION_1_8
29+
// java.sourceCompatibility = JavaVersion.VERSION_1_8
2330

2431
dependencies {
2532
implementation(
26-
platform("org.springframework.boot:spring-boot-dependencies:2.7.14"),
33+
platform("org.springframework.boot:spring-boot-dependencies:2.7.15"),
2734
)
2835
api("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
2936
api("commons-io:commons-io:2.7")
@@ -40,7 +47,8 @@ dependencies {
4047
compileOnly("org.springframework:spring-webmvc")
4148
testImplementation(kotlin("test"))
4249
testImplementation("javax.servlet:javax.servlet-api")
43-
testImplementation("org.springframework:spring-test")
50+
testImplementation("org.springframework.boot:spring-boot-starter-test")
51+
testImplementation("org.springframework:spring-webmvc")
4452
testImplementation("org.assertj:assertj-core")
4553
testImplementation("org.mockito.kotlin:mockito-kotlin:5.0.0")
4654
}
@@ -51,7 +59,7 @@ publishing {
5159
}
5260
}
5361
kotlin {
54-
jvmToolchain(8)
62+
jvmToolchain(17)
5563
}
5664

5765
tasks.test { // See 5️⃣
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.firetail.logging
1+
package io.firetail.logging.config
22

33
class Constants {
44
companion object {
@@ -7,6 +7,7 @@ class Constants {
77
const val OP_NAME = "X-Operation-Name"
88
const val RESPONSE_TIME = "X-Response-Time"
99
const val RESPONSE_STATUS = "X-Response-Status"
10+
const val AUDIT = "audit"
1011
val empty = ByteArray(0)
1112
}
1213
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.firetail.logging.config
2+
3+
import io.firetail.logging.filter.FiretailFilter
4+
import io.firetail.logging.filter.FiretailLogger
5+
import io.firetail.logging.util.LogContext
6+
import io.firetail.logging.util.StringUtils
7+
import org.springframework.beans.factory.annotation.Autowired
8+
import org.springframework.beans.factory.annotation.Value
9+
import org.springframework.context.ApplicationContext
10+
import org.springframework.context.annotation.Configuration
11+
import org.springframework.context.annotation.Import
12+
13+
@Configuration
14+
@Import(
15+
FiretailLogger::class,
16+
StringUtils::class,
17+
LogContext::class,
18+
FiretailFilter::class,
19+
)
20+
class FiretailConfig @Autowired constructor(
21+
@Value("\${firetail.ignorePatterns:#null}")
22+
val ignorePatterns: String?,
23+
@Value("\${firetail.logHeaders:false}")
24+
val logHeaders: Boolean = false,
25+
) {
26+
@Autowired
27+
lateinit var context: ApplicationContext
28+
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
package io.firetail.logging.client
1+
package io.firetail.logging.config
22

3-
import io.firetail.logging.Constants.Companion.CORRELATION_ID
4-
import io.firetail.logging.Constants.Companion.REQUEST_ID
3+
import io.firetail.logging.config.Constants.Companion.CORRELATION_ID
4+
import io.firetail.logging.config.Constants.Companion.REQUEST_ID
55
import org.slf4j.MDC
66
import org.springframework.http.HttpRequest
77
import org.springframework.http.client.ClientHttpRequestExecution
88
import org.springframework.http.client.ClientHttpRequestInterceptor
99
import org.springframework.http.client.ClientHttpResponse
1010

11-
class RestTemplateSetHeaderInterceptor : ClientHttpRequestInterceptor {
11+
class FiretailHeaderInterceptor : ClientHttpRequestInterceptor {
1212
override fun intercept(
1313
request: HttpRequest,
1414
body: ByteArray,
1515
execution: ClientHttpRequestExecution,
1616
): ClientHttpResponse {
17-
request.headers.add(CORRELATION_ID, MDC.get(CORRELATION_ID))
18-
request.headers.add(REQUEST_ID, MDC.get(REQUEST_ID))
17+
with(request) {
18+
headers.add(CORRELATION_ID, MDC.get(CORRELATION_ID))
19+
headers.add(REQUEST_ID, MDC.get(REQUEST_ID))
20+
}
1921
return execution.execute(request, body)
2022
}
2123
}

src/main/kotlin/io/firetail/logging/config/SpringLoggerAutoConfiguration.kt

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
package io.firetail.logging.config
22

3-
import io.firetail.logging.client.RestTemplateSetHeaderInterceptor
4-
import io.firetail.logging.filter.SpringLoggerFilter
5-
import io.firetail.logging.util.UniqueIDGenerator
63
import org.springframework.beans.factory.annotation.Autowired
74
import org.springframework.beans.factory.annotation.Value
85
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
96
import org.springframework.boot.context.properties.ConfigurationProperties
107
import org.springframework.context.annotation.Bean
118
import org.springframework.context.annotation.Configuration
9+
import org.springframework.context.annotation.Import
1210
import org.springframework.http.client.ClientHttpRequestInterceptor
1311
import org.springframework.web.client.RestTemplate
1412
import java.util.*
1513
import javax.annotation.PostConstruct
1614

15+
1716
// import net.logstash.logback.appender.LogstashTcpSocketAppender;
1817
// import net.logstash.logback.encoder.LogstashEncoder;
1918
@Configuration
2019
@ConfigurationProperties(prefix = "logging.logstash")
20+
@Import(FiretailConfig::class)
2121
class SpringLoggerAutoConfiguration {
2222
// private static final String FIRETAIL_APPENDER_NAME = "FIRETAIL";
2323
var url = "localhost:8500"
@@ -32,22 +32,12 @@ class SpringLoggerAutoConfiguration {
3232
@Autowired(required = false)
3333
var template: Optional<RestTemplate>? = null
3434

35-
@Bean
36-
fun generator(): UniqueIDGenerator {
37-
return UniqueIDGenerator()
38-
}
39-
40-
@Bean
41-
fun loggingFilter(): SpringLoggerFilter {
42-
return SpringLoggerFilter(generator(), ignorePatterns, isLogHeaders)
43-
}
44-
4535
@Bean
4636
@ConditionalOnMissingBean(RestTemplate::class)
4737
fun restTemplate(): RestTemplate {
4838
val restTemplate = RestTemplate()
4939
val interceptorList: MutableList<ClientHttpRequestInterceptor> = ArrayList()
50-
interceptorList.add(RestTemplateSetHeaderInterceptor())
40+
interceptorList.add(FiretailHeaderInterceptor())
5141
restTemplate.interceptors = interceptorList
5242
return restTemplate
5343
}
@@ -84,7 +74,7 @@ class SpringLoggerAutoConfiguration {
8474
fun init() {
8575
template!!.ifPresent { restTemplate: RestTemplate ->
8676
val interceptorList: MutableList<ClientHttpRequestInterceptor> = ArrayList()
87-
interceptorList.add(RestTemplateSetHeaderInterceptor())
77+
interceptorList.add(FiretailHeaderInterceptor())
8878
restTemplate.interceptors = interceptorList
8979
}
9080
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.firetail.logging.filter
2+
3+
import io.firetail.logging.config.Constants.Companion.CORRELATION_ID
4+
import io.firetail.logging.config.Constants.Companion.OP_NAME
5+
import io.firetail.logging.config.Constants.Companion.REQUEST_ID
6+
import io.firetail.logging.config.FiretailConfig
7+
import io.firetail.logging.util.LogContext
8+
import io.firetail.logging.util.StringUtils
9+
import io.firetail.logging.wrapper.SpringRequestWrapper
10+
import io.firetail.logging.wrapper.SpringResponseWrapper
11+
import org.slf4j.LoggerFactory
12+
import org.slf4j.MDC
13+
import org.springframework.context.annotation.Bean
14+
import org.springframework.web.filter.OncePerRequestFilter
15+
import org.springframework.web.method.HandlerMethod
16+
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
17+
import java.io.IOException
18+
import java.util.*
19+
import javax.servlet.FilterChain
20+
import javax.servlet.ServletException
21+
import javax.servlet.http.HttpServletRequest
22+
import javax.servlet.http.HttpServletResponse
23+
24+
class FiretailFilter(
25+
private val logContext: LogContext,
26+
private val firetailConfig: FiretailConfig,
27+
) {
28+
private val stringUtils = StringUtils() // UTF-8
29+
private val firetailLogger = FiretailLogger(stringUtils, firetailConfig.logHeaders)
30+
31+
@Bean
32+
fun firetailRequestFilter(): OncePerRequestFilter {
33+
return object : OncePerRequestFilter() {
34+
@Throws(ServletException::class, IOException::class)
35+
override fun doFilterInternal(
36+
request: HttpServletRequest,
37+
response: HttpServletResponse,
38+
chain: FilterChain,
39+
) {
40+
if (firetailConfig.ignorePatterns != null && request.requestURI.matches(firetailConfig.ignorePatterns!!.toRegex())) {
41+
chain.doFilter(request, response)
42+
} else {
43+
logContext.generateAndSetMDC(request)
44+
try {
45+
getHandlerMethod(request)
46+
} catch (e: Exception) {
47+
LOGGER.trace("Cannot get handler method")
48+
}
49+
val startTime = System.currentTimeMillis()
50+
val wrappedRequest = SpringRequestWrapper(request)
51+
firetailLogger.logRequest(wrappedRequest)
52+
val wrappedResponse = SpringResponseWrapper(response)
53+
try {
54+
with(wrappedResponse) {
55+
setHeader(REQUEST_ID, MDC.get(REQUEST_ID))
56+
setHeader(CORRELATION_ID, MDC.get(CORRELATION_ID))
57+
}
58+
chain.doFilter(wrappedRequest, wrappedResponse)
59+
firetailLogger.logResponse(startTime, wrappedResponse)
60+
} catch (e: Exception) {
61+
firetailLogger.logResponse(startTime, wrappedResponse, 500)
62+
throw e
63+
}
64+
}
65+
}
66+
}
67+
}
68+
69+
private fun getHandlerMethod(request: HttpServletRequest) {
70+
val mappings = firetailConfig.context.getBean("requestMappingHandlerMapping")
71+
as RequestMappingHandlerMapping
72+
val nullableHandler = mappings.getHandler(request)
73+
if (Objects.nonNull(nullableHandler)) {
74+
val handler = nullableHandler?.handler as HandlerMethod
75+
MDC.put(OP_NAME, handler.beanType.simpleName + "." + handler.method.name)
76+
}
77+
}
78+
79+
companion object {
80+
private val LOGGER = LoggerFactory.getLogger(FiretailLogger::class.java)
81+
}
82+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.firetail.logging.filter
2+
3+
import io.firetail.logging.config.Constants
4+
import io.firetail.logging.util.StringUtils
5+
import io.firetail.logging.wrapper.SpringRequestWrapper
6+
import io.firetail.logging.wrapper.SpringResponseWrapper
7+
import net.logstash.logback.argument.StructuredArguments
8+
import org.slf4j.LoggerFactory
9+
10+
class FiretailLogger(private val stringUtils: StringUtils = StringUtils(), private val logHeaders: Boolean = false) {
11+
fun logRequest(wrappedRequest: SpringRequestWrapper) {
12+
if (logHeaders) {
13+
logWithHeaders(wrappedRequest)
14+
} else {
15+
logNoHeaders(wrappedRequest)
16+
}
17+
}
18+
19+
private fun logNoHeaders(wrappedRequest: SpringRequestWrapper) {
20+
LOGGER.info(
21+
"Request: method={}, uri={}, payload={}, audit={}",
22+
wrappedRequest.method,
23+
wrappedRequest.requestURI,
24+
stringUtils.toString(wrappedRequest.inputStream.readAllBytes(), wrappedRequest.characterEncoding),
25+
StructuredArguments.value(Constants.AUDIT, true),
26+
)
27+
}
28+
29+
private fun logWithHeaders(wrappedRequest: SpringRequestWrapper) {
30+
LOGGER.info(
31+
"Request: method={}, uri={}, payload={}, headers={}, audit={}",
32+
wrappedRequest.method,
33+
wrappedRequest.requestURI,
34+
stringUtils.toString(wrappedRequest.inputStream.readAllBytes(), wrappedRequest.characterEncoding),
35+
wrappedRequest.allHeaders,
36+
StructuredArguments.value(Constants.AUDIT, true),
37+
)
38+
}
39+
40+
fun logResponse(
41+
startTime: Long,
42+
wrappedResponse: SpringResponseWrapper,
43+
status: Int = wrappedResponse.status,
44+
) {
45+
val duration = System.currentTimeMillis() - startTime
46+
wrappedResponse.characterEncoding = stringUtils.charSet()
47+
if (logHeaders) {
48+
logWithHeaders(duration, status, wrappedResponse)
49+
} else {
50+
logNoHeaders(duration, status, wrappedResponse)
51+
}
52+
}
53+
54+
private fun logNoHeaders(
55+
duration: Long,
56+
status: Int,
57+
wrappedResponse: SpringResponseWrapper,
58+
) {
59+
LOGGER.info(
60+
"Response({} ms): status={}, payload={}, audit={}",
61+
StructuredArguments.value(Constants.RESPONSE_TIME, duration),
62+
StructuredArguments.value(Constants.RESPONSE_STATUS, status),
63+
stringUtils.toString(wrappedResponse.contentAsByteArray),
64+
StructuredArguments.value(Constants.AUDIT, true),
65+
)
66+
}
67+
68+
private fun logWithHeaders(
69+
duration: Long,
70+
status: Int,
71+
wrappedResponse: SpringResponseWrapper,
72+
) {
73+
LOGGER.info(
74+
"Response({} ms): status={}, payload={}, headers={}, audit={}",
75+
StructuredArguments.value(Constants.RESPONSE_TIME, duration),
76+
StructuredArguments.value(Constants.RESPONSE_STATUS, status),
77+
stringUtils.toString(wrappedResponse.contentAsByteArray),
78+
wrappedResponse.allHeaders,
79+
StructuredArguments.value(Constants.AUDIT, true),
80+
)
81+
}
82+
83+
companion object {
84+
private val LOGGER = LoggerFactory.getLogger(this::class.java)
85+
}
86+
}

0 commit comments

Comments
 (0)