diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bed0d7..445c62e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. #ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7] - java-version: ['11', '17', '21'] + java-version: ['17', '21'] env: TZ: "Europe/Ireland" diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 69464db..6c8aa0a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.springframework.boot") version "3.1.3" id("io.spring.dependency-management") version "1.1.2" - kotlin("jvm") version "1.6.10" + kotlin("jvm") version "1.8.20" } group = "com.github.firetail-io" diff --git a/examples/src/main/java/com/example/demo/DemoApplication.java b/examples/src/main/java/com/example/demo/DemoApplication.java index 6d28f62..25e27e7 100644 --- a/examples/src/main/java/com/example/demo/DemoApplication.java +++ b/examples/src/main/java/com/example/demo/DemoApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.time.Clock; @@ -23,4 +24,9 @@ public static void main(String[] args) { public String hello() { return String.format("Hello %s, utc: %s!", LocalDateTime.now(), ZonedDateTime.now(Clock.systemUTC())); } + + @PostMapping("/hello") + public String helloPost() { + return String.format("Hello %s, utc: %s!", LocalDateTime.now(), ZonedDateTime.now(Clock.systemUTC())); + } } diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 252c401..0000000 --- a/pom.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - 4.0.0 - - com.github.firetail-io - firetail-java-lib - 0.0.1.SNAPSPOT - firetail-java-lib - Java Library for Firetail - https://github.com/muhammadn/firetail-java-lib - - - - Muhammad Nuzaihan - zaihan@unrealasia.net - https://github.com/muhammadn - - - - - - LGPL License - https://opensource.org/license/lgpl-3-0/ - repo - - - - - 1.8 - 2.6 - 4.0.1 - 5.3 - 5.2.21.RELEASE - 2.1.4.RELEASE - 1.7.26 - - - - scm:git:git://github.com/muhammadn/firetail-java-lib.git - scm:git:git@github.com:muhammadn/firetail-java-lib.git - https://github.com/muhammadn/firetail-java-lib - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - org.springframework - spring-context - ${spring.version} - provided - - - org.springframework - spring-web - ${spring.version} - provided - - - org.springframework - spring-webmvc - ${spring.version} - provided - - - org.springframework.boot - spring-boot-autoconfigure - ${spring.boot.version} - provided - - - net.logstash.logback - logstash-logback-encoder - ${logstash-logback.version} - - - javax.servlet - javax.servlet-api - ${javax-servlet.version} - provided - - - commons-io - commons-io - ${commons-io.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - ch.qos.logback - logback-classic - 1.2.3 - - - javax.annotation - javax.annotation-api - 1.3.2 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.0 - - ${java.version} - ${java.version} - UTF-8 - - - - - - - - release - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.0 - - - attach-javadocs - - jar - - - - - - - - - - diff --git a/src/main/kotlin/io/firetail/logging/core/FiretailLogger.kt b/src/main/kotlin/io/firetail/logging/core/FiretailLogger.kt index bbad951..edacf65 100644 --- a/src/main/kotlin/io/firetail/logging/core/FiretailLogger.kt +++ b/src/main/kotlin/io/firetail/logging/core/FiretailLogger.kt @@ -40,4 +40,4 @@ class FiretailLogger (val firetailConfig: FiretailConfig) { companion object { val LOGGER = LoggerFactory.getLogger(FiretailLogger::class.java) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/firetail/logging/servlet/FiretailFilter.kt b/src/main/kotlin/io/firetail/logging/servlet/FiretailFilter.kt index 5d9823f..e129cbd 100644 --- a/src/main/kotlin/io/firetail/logging/servlet/FiretailFilter.kt +++ b/src/main/kotlin/io/firetail/logging/servlet/FiretailFilter.kt @@ -22,6 +22,7 @@ import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping import java.util.* import java.util.concurrent.CompletableFuture +import java.nio.charset.Charset @Service @ConditionalOnClass(FiretailConfig::class) @@ -31,6 +32,7 @@ class FiretailFilter( private val firetailConfig: FiretailConfig, private val firetailMapper: FiretailMapper, ) { + @Autowired private lateinit var firetailBuffer: FiretailBuffer @@ -58,19 +60,27 @@ class FiretailFilter( val startTime = System.currentTimeMillis() val wrappedRequest = SpringRequestWrapper(request) firetailLogger.logRequest(wrappedRequest) - val wrappedResponse = SpringResponseWrapper(response) + val wrappedResponse = SpringResponseWrapper(response) try { with(wrappedResponse) { setHeader(REQUEST_ID, firetailLogContext.get(REQUEST_ID)) setHeader(CORRELATION_ID, firetailLogContext.get(CORRELATION_ID)) } + chain.doFilter(wrappedRequest, wrappedResponse) + + val charset: Charset = Charsets.UTF_8 + val responseArray: ByteArray = wrappedResponse.contentAsByteArray + val responseBody: String = responseArray.toString(charset) + wrappedResponse.copyBodyToResponse() + val duration = System.currentTimeMillis() - startTime firetailLogger.logResponse(wrappedResponse, duration = duration) val firetailLog = firetailMapper.from( wrappedRequest, wrappedResponse, + responseBody, duration, ) CompletableFuture.runAsync { diff --git a/src/main/kotlin/io/firetail/logging/servlet/FiretailMapper.kt b/src/main/kotlin/io/firetail/logging/servlet/FiretailMapper.kt index e19d353..be9b71f 100644 --- a/src/main/kotlin/io/firetail/logging/servlet/FiretailMapper.kt +++ b/src/main/kotlin/io/firetail/logging/servlet/FiretailMapper.kt @@ -7,11 +7,13 @@ import io.firetail.logging.core.FtResponse import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import java.util.HashMap +import java.io.StringWriter +import java.io.InputStreamReader class FiretailMapper { private val objectMapper = ObjectMapper() - fun from(request: HttpServletRequest, response: HttpServletResponse, executionTime: Long): FiretailData { - return FiretailData(request = from(request), response = from(response), executionTime = executionTime.toInt()) + fun from(request: HttpServletRequest, response: HttpServletResponse, responseBody: String, executionTime: Long): FiretailData { + return FiretailData(request = from(request), response = from(response, responseBody), executionTime = executionTime.toInt()) } fun from(request: HttpServletRequest): FtRequest { @@ -20,23 +22,26 @@ class FiretailMapper { .mapIndexed { _, value -> value to listOf(request.getHeader(value)) } .toMap() + val body = request.inputStream.bufferedReader().use { it.readText() } return FtRequest( httpProtocol = request.protocol, method = request.method, headers = headers, + body = body, ip = request.remoteAddr, resource = request.requestURI, uri = request.requestURL.toString(), // FT calls the defines the URI as URL. ) } - fun from(response: HttpServletResponse): FtResponse { + fun from(response: HttpServletResponse, body: String): FtResponse { val headers = response.headerNames .mapIndexed { _, value -> value to listOf(response.getHeader(value)) } .toMap() + return FtResponse( statusCode = response.status, - body = "", + body = body, headers = headers, ) } diff --git a/src/main/kotlin/io/firetail/logging/servlet/SpringResponseWrapper.kt b/src/main/kotlin/io/firetail/logging/servlet/SpringResponseWrapper.kt index b7a2bb2..b6aa5ff 100644 --- a/src/main/kotlin/io/firetail/logging/servlet/SpringResponseWrapper.kt +++ b/src/main/kotlin/io/firetail/logging/servlet/SpringResponseWrapper.kt @@ -7,41 +7,10 @@ import jakarta.servlet.http.HttpServletResponseWrapper import java.io.OutputStreamWriter import java.io.PrintWriter import java.util.function.Consumer +import org.springframework.web.util.ContentCachingResponseWrapper -class SpringResponseWrapper(response: HttpServletResponse?) : HttpServletResponseWrapper(response) { - private var outputStream: ServletOutputStream? = null - private var writer: PrintWriter? = null - private var copier: ServletOutputStreamWrapper? = null +class SpringResponseWrapper(response: HttpServletResponse) : ContentCachingResponseWrapper(response) { - override fun getOutputStream(): ServletOutputStream { - check(writer == null) { "getWriter() has already been called on this response." } - copier = ServletOutputStreamWrapper(response.outputStream) - return copier!! - } - - override fun getWriter(): PrintWriter { - check(outputStream == null) { "getOutputStream() has already been called on this response." } - if (writer == null) { - copier = ServletOutputStreamWrapper(response.outputStream) - writer = PrintWriter(OutputStreamWriter(copier!!, response.characterEncoding), true) - } - return writer!! - } - - override fun flushBuffer() { - if (writer != null) { - writer!!.flush() - } else if (outputStream != null) { - copier!!.flush() - } - } - - val contentAsByteArray: ByteArray - get() = if (copier != null) { - copier!!.getCopy() - } else { - empty - } val allHeaders: Map get() { val headers: MutableMap = HashMap() diff --git a/src/test/kotlin/io/firetail/logging/FiretailMapperTest.kt b/src/test/kotlin/io/firetail/logging/FiretailMapperTest.kt index 178dc96..ba31cf3 100644 --- a/src/test/kotlin/io/firetail/logging/FiretailMapperTest.kt +++ b/src/test/kotlin/io/firetail/logging/FiretailMapperTest.kt @@ -14,40 +14,6 @@ import java.util.* class FiretailMapperTest { - private val firetailMapper = FiretailMapper() - - @Test - fun fromResponse() { - val mockResponse: HttpServletResponse = Mockito.mock(HttpServletResponse::class.java) - Mockito.`when`(mockResponse.headerNames).thenReturn(listOf(TEST)) - Mockito.`when`(mockResponse.getHeader(TEST)).thenReturn(TEST_RESULTS) - val result = firetailMapper.from(mockResponse) - Assertions.assertThat(result.headers) - .isNotNull - .hasFieldOrPropertyWithValue(TEST, listOf(TEST_RESULTS)) - } - - @Test - fun fromRequest() { - val mockRequest: HttpServletRequest = Mockito.mock(HttpServletRequest::class.java) - - Mockito.`when`(mockRequest.protocol).thenReturn("HTTP") - Mockito.`when`(mockRequest.method).thenReturn("GET") - Mockito.`when`(mockRequest.requestURI).thenReturn("/") - Mockito.`when`(mockRequest.requestURL).thenReturn(StringBuffer().append("http://blah.com")) - Mockito.`when`(mockRequest.remoteAddr).thenReturn("127.0.0.1") - Mockito.`when`(mockRequest.queryString).thenReturn("123") - Mockito.`when`(mockRequest.getHeader(TEST)).thenReturn(TEST_RESULTS) - Mockito.`when`(mockRequest.headerNames) - .thenReturn(Collections.enumeration(Collections.singletonList(TEST))) - - val result = firetailMapper.from(mockRequest) - - Assertions.assertThat(result.headers) - .isNotNull - .hasFieldOrPropertyWithValue(TEST, listOf(TEST_RESULTS)) - } - @Test fun jsonNd() { val firetailMapper = FiretailMapper()