diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3163428 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +# General +*.log +*.keystore +.history/ +*.tgz + +# git-secret rules +.secrets/*.* +!.secrets/*.secret +!.secrets/public-keys/ +.gitsecret/keys/random_seed +.gitsecret/keys/pubring.kbx~ + + +.ci/gradle* + +# You can put a script which call `yarn prettier-eslint --write` that prettify the file in-place. +# Keep in mind the hook files are executed by alphabet sequence: so `in_place_prettier_eslint` will be executed before `prettier_eslint` +buildtools/hooks/pre-commit.d/in_place_prettier_eslint + +# Archives / Binaries +*.jar +*.zip +*.tar.gz +*.so +*.xcarchive + +# MacOS +.DS_Store + +# Build Output +build/ +out/ +bin/ +codemr/ + +# JetBrains +.idea/ +*.iml +*.ipr +*.iws +.mvn/ +.project + +# VSCode +.vscode/ +*.code-workspace + +# Eclipse +.settings/ +.classpath + +# Java +*.hprof +.gradle/ +!gradle-wrapper.jar +!gradle-wrapper.properties + + +# Node or NPM or Yarn +npm-debug.log* +yarn-debug.log* +yarn-error.log* +node_modules/ +.eslintcache + +# Jest/Bamboo test results output +jest.json +coverage/ + +# Vagrant +.vagrant/ + +# Xcode +# +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace +**/*.framework/ + + +# VIM +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +version.yml + +#Andorid +local.properties \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..ad3aded --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,59 @@ + +plugins { + `java-library` + `maven-publish` + // Not possible to set the version for a plugin from a variable. + kotlin("plugin.spring") version "1.8.21" + kotlin("jvm") version "1.8.21" + id("io.spring.dependency-management") version "1.1.2" +} + +repositories { + mavenLocal() + maven { + url = uri("https://repo.maven.apache.org/maven2/") + } + mavenCentral() +} + +group = "com.github.firetail-io" +version = "0.0.1.SNAPSHOT" +description = "firetail-java-lib" +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +dependencies { + implementation( + platform("org.springframework.boot:spring-boot-dependencies:2.7.14"), + ) + api("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + api("commons-io:commons-io:2.7") + api("net.logstash.logback:logstash-logback-encoder:7.4") + api("javax.annotation:javax.annotation-api:1.3.2") + // Dependencies are transitively imported from spring-boot-dependencies + api("org.slf4j:slf4j-api") + api("ch.qos.logback:logback-classic") + compileOnly("javax.servlet:javax.servlet-api") + compileOnly("org.springframework.boot:spring-boot-autoconfigure") + compileOnly("org.springframework:spring-context") + compileOnly("org.springframework:spring-web") + compileOnly("org.springframework:spring-web") + compileOnly("org.springframework:spring-webmvc") + testImplementation(kotlin("test")) + testImplementation("javax.servlet:javax.servlet-api") + testImplementation("org.springframework:spring-test") + testImplementation("org.assertj:assertj-core") + testImplementation("org.mockito.kotlin:mockito-kotlin:5.0.0") +} + +publishing { + publications.create("maven") { + from(components["java"]) + } +} +kotlin { + jvmToolchain(8) +} + +tasks.test { // See 5️⃣ + useJUnitPlatform() // JUnitPlatform for tests. See 6️⃣ +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..033e24c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..62f495d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..fcb6fca --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml index 2ac8a70..252c401 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 0.0.1.SNAPSPOT firetail-java-lib Java Library for Firetail - https://github.com/muhammadn/firetail-java-lib + https://github.com/muhammadn/firetail-java-lib @@ -30,7 +30,7 @@ 2.6 4.0.1 5.3 - 5.2.3.RELEASE + 5.2.21.RELEASE 2.1.4.RELEASE 1.7.26 @@ -74,7 +74,7 @@ org.springframework.boot spring-boot-autoconfigure - ${spring.boot.version} + ${spring.boot.version} provided diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..2c8deb4 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") +} + +rootProject.name = "firetail-java-lib" diff --git a/src/main/java/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.java b/src/main/java/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.java deleted file mode 100644 index 5965953..0000000 --- a/src/main/java/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.firetail.logging.client; - -import org.slf4j.MDC; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; - -import java.io.IOException; - -public class RestTemplateSetHeaderInterceptor implements ClientHttpRequestInterceptor { - - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().add("X-Correlation-ID", MDC.get("X-Correlation-ID")); - request.getHeaders().add("X-Request-ID", MDC.get("X-Request-ID")); - return execution.execute(request, body); - } - -} diff --git a/src/main/java/io/firetail/logging/config/SpringLoggerAutoConfiguration.java b/src/main/java/io/firetail/logging/config/SpringLoggerAutoConfiguration.java deleted file mode 100644 index 09d50d8..0000000 --- a/src/main/java/io/firetail/logging/config/SpringLoggerAutoConfiguration.java +++ /dev/null @@ -1,142 +0,0 @@ -package io.firetail.logging.config; - -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.core.net.ssl.KeyStoreFactoryBean; -import ch.qos.logback.core.net.ssl.SSLConfiguration; -//import net.logstash.logback.appender.LogstashTcpSocketAppender; -//import net.logstash.logback.encoder.LogstashEncoder; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.web.client.RestTemplate; - -import io.firetail.logging.client.RestTemplateSetHeaderInterceptor; -import io.firetail.logging.filter.SpringLoggingFilter; -import io.firetail.logging.util.UniqueIDGenerator; - -import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@Configuration -@ConfigurationProperties(prefix = "logging.logstash") -public class SpringLoggingAutoConfiguration { - - private static final String FIRETAIL_APPENDER_NAME = "FIRETAIL"; - - private String url = "localhost:8500"; - private String ignorePatterns; - private boolean logHeaders; - private String trustStoreLocation; - private String trustStorePassword; - @Value("${spring.application.name:-}") - String name; - @Autowired(required = false) - Optional template; - - @Bean - public UniqueIDGenerator generator() { - return new UniqueIDGenerator(); - } - - @Bean - public SpringLoggingFilter loggingFilter() { - return new SpringLoggingFilter(generator(), ignorePatterns, logHeaders); - } - - @Bean - @ConditionalOnMissingBean(RestTemplate.class) - public RestTemplate restTemplate() { - RestTemplate restTemplate = new RestTemplate(); - List interceptorList = new ArrayList(); - interceptorList.add(new RestTemplateSetHeaderInterceptor()); - restTemplate.setInterceptors(interceptorList); - return restTemplate; - } - - /* rewrite this method to send data to firetail backend - @Bean - @ConditionalOnProperty("logging.firetail.enabled") - public FiretailTcpSocketAppender firetailAppender() { - LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); - LogstashTcpSocketAppender logstashTcpSocketAppender = new LogstashTcpSocketAppender(); - logstashTcpSocketAppender.setName(FIRETAIL_APPENDER_NAME); - logstashTcpSocketAppender.setContext(loggerContext); - logstashTcpSocketAppender.addDestination(url); - if (trustStoreLocation != null) { - SSLConfiguration sslConfiguration = new SSLConfiguration(); - KeyStoreFactoryBean factory = new KeyStoreFactoryBean(); - factory.setLocation(trustStoreLocation); - if (trustStorePassword != null) - factory.setPassword(trustStorePassword); - sslConfiguration.setTrustStore(factory); - logstashTcpSocketAppender.setSsl(sslConfiguration); - } - LogstashEncoder encoder = new LogstashEncoder(); - encoder.setContext(loggerContext); - encoder.setIncludeContext(true); - encoder.setCustomFields("{\"appname\":\"" + name + "\"}"); - encoder.start(); - logstashTcpSocketAppender.setEncoder(encoder); - logstashTcpSocketAppender.start(); - loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(logstashTcpSocketAppender); - return logstashTcpSocketAppender; - } */ - - @PostConstruct - public void init() { - template.ifPresent(restTemplate -> { - List interceptorList = new ArrayList(); - interceptorList.add(new RestTemplateSetHeaderInterceptor()); - restTemplate.setInterceptors(interceptorList); - }); - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getTrustStoreLocation() { - return trustStoreLocation; - } - - public void setTrustStoreLocation(String trustStoreLocation) { - this.trustStoreLocation = trustStoreLocation; - } - - public String getTrustStorePassword() { - return trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getIgnorePatterns() { - return ignorePatterns; - } - - public void setIgnorePatterns(String ignorePatterns) { - this.ignorePatterns = ignorePatterns; - } - - public boolean isLogHeaders() { - return logHeaders; - } - - public void setLogHeaders(boolean logHeaders) { - this.logHeaders = logHeaders; - } -} diff --git a/src/main/java/io/firetail/logging/filter/SpringLoggerFilter.java b/src/main/java/io/firetail/logging/filter/SpringLoggerFilter.java deleted file mode 100644 index ce11374..0000000 --- a/src/main/java/io/firetail/logging/filter/SpringLoggerFilter.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.firetail.logging.filter; - -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerExecutionChain; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import io.firetail.logging.util.UniqueIDGenerator; -import io.firetail.logging.wrapper.SpringRequestWrapper; -import io.firetail.logging.wrapper.SpringResponseWrapper; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - -public class SpringLoggingFilter extends OncePerRequestFilter { - - private static final Logger LOGGER = LoggerFactory.getLogger(SpringLoggingFilter.class); - private UniqueIDGenerator generator; - private String ignorePatterns; - private boolean logHeaders; - - @Autowired - ApplicationContext context; - - public SpringLoggingFilter(UniqueIDGenerator generator, String ignorePatterns, boolean logHeaders) { - this.generator = generator; - this.ignorePatterns = ignorePatterns; - this.logHeaders = logHeaders; - } - - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - if (ignorePatterns != null && request.getRequestURI().matches(ignorePatterns)) { - chain.doFilter(request, response); - } else { - generator.generateAndSetMDC(request); - try { - getHandlerMethod(request); - } catch (Exception e) { - LOGGER.trace("Cannot get handler method"); - } - final long startTime = System.currentTimeMillis(); - final SpringRequestWrapper wrappedRequest = new SpringRequestWrapper(request); - if (logHeaders) - LOGGER.info("Request: method={}, uri={}, payload={}, headers={}, audit={}", wrappedRequest.getMethod(), - wrappedRequest.getRequestURI(), IOUtils.toString(wrappedRequest.getInputStream(), - wrappedRequest.getCharacterEncoding()), wrappedRequest.getAllHeaders(), value("audit", true)); - else - LOGGER.info("Request: method={}, uri={}, payload={}, audit={}", wrappedRequest.getMethod(), - wrappedRequest.getRequestURI(), IOUtils.toString(wrappedRequest.getInputStream(), - wrappedRequest.getCharacterEncoding()), value("audit", true)); - final SpringResponseWrapper wrappedResponse = new SpringResponseWrapper(response); - wrappedResponse.setHeader("X-Request-ID", MDC.get("X-Request-ID")); - wrappedResponse.setHeader("X-Correlation-ID", MDC.get("X-Correlation-ID")); - - try { - chain.doFilter(wrappedRequest, wrappedResponse); - } catch (Exception e) { - logResponse(startTime, wrappedResponse, 500); - throw e; - } - logResponse(startTime, wrappedResponse, wrappedResponse.getStatus()); - } - } - - private void logResponse(long startTime, SpringResponseWrapper wrappedResponse, int overriddenStatus) throws IOException { - final long duration = System.currentTimeMillis() - startTime; - wrappedResponse.setCharacterEncoding("UTF-8"); - if (logHeaders) - LOGGER.info("Response({} ms): status={}, payload={}, headers={}, audit={}", value("X-Response-Time", duration), - value("X-Response-Status", overriddenStatus), IOUtils.toString(wrappedResponse.getContentAsByteArray(), - wrappedResponse.getCharacterEncoding()), wrappedResponse.getAllHeaders(), value("audit", true)); - else - LOGGER.info("Response({} ms): status={}, payload={}, audit={}", value("X-Response-Time", duration), - value("X-Response-Status", overriddenStatus), - IOUtils.toString(wrappedResponse.getContentAsByteArray(), wrappedResponse.getCharacterEncoding()), value("audit", true)); - } - - private void getHandlerMethod(HttpServletRequest request) throws Exception { - RequestMappingHandlerMapping mappings1 = (RequestMappingHandlerMapping) context.getBean("requestMappingHandlerMapping"); - Map handlerMethods = mappings1.getHandlerMethods(); - HandlerExecutionChain handler = mappings1.getHandler(request); - if (Objects.nonNull(handler)) { - HandlerMethod handler1 = (HandlerMethod) handler.getHandler(); - MDC.put("X-Operation-Name", handler1.getBeanType().getSimpleName() + "." + handler1.getMethod().getName()); - } - } - -} diff --git a/src/main/java/io/firetail/logging/util/UniqueIDGenerator.java b/src/main/java/io/firetail/logging/util/UniqueIDGenerator.java deleted file mode 100644 index 7d95a69..0000000 --- a/src/main/java/io/firetail/logging/util/UniqueIDGenerator.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.firetail.logging.util; - -import org.slf4j.MDC; - -import javax.servlet.http.HttpServletRequest; -import java.util.UUID; - -public class UniqueIDGenerator { - - private static final String REQUEST_ID_HEADER_NAME = "X-Request-ID"; - private static final String CORRELATION_ID_HEADER_NAME = "X-Correlation-ID"; - - public void generateAndSetMDC(HttpServletRequest request) { - MDC.clear(); - String requestId = request.getHeader(REQUEST_ID_HEADER_NAME); - if (requestId == null) - requestId = UUID.randomUUID().toString(); - MDC.put(REQUEST_ID_HEADER_NAME, requestId); - - String correlationId = request.getHeader(CORRELATION_ID_HEADER_NAME); - if (correlationId == null) - correlationId = UUID.randomUUID().toString(); - MDC.put(CORRELATION_ID_HEADER_NAME, correlationId); - } - -} diff --git a/src/main/java/io/firetail/logging/wrapper/ServletOutputStreamWrapper.java b/src/main/java/io/firetail/logging/wrapper/ServletOutputStreamWrapper.java deleted file mode 100644 index c9964dd..0000000 --- a/src/main/java/io/firetail/logging/wrapper/ServletOutputStreamWrapper.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.firetail.logging.wrapper; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; - -public class ServletOutputStreamWrapper extends ServletOutputStream { - - private OutputStream outputStream; - private ByteArrayOutputStream copy; - - public ServletOutputStreamWrapper(OutputStream outputStream) { - this.outputStream = outputStream; - this.copy = new ByteArrayOutputStream(); - } - - @Override - public void write(int b) throws IOException { - outputStream.write(b); - copy.write(b); - } - - public byte[] getCopy() { - return copy.toByteArray(); - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - - } -} diff --git a/src/main/java/io/firetail/logging/wrapper/SpringRequestWrapper.java b/src/main/java/io/firetail/logging/wrapper/SpringRequestWrapper.java deleted file mode 100644 index 79be944..0000000 --- a/src/main/java/io/firetail/logging/wrapper/SpringRequestWrapper.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.firetail.logging.wrapper; - -import org.apache.commons.io.IOUtils; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public class SpringRequestWrapper extends HttpServletRequestWrapper { - - private byte[] body; - - public SpringRequestWrapper(HttpServletRequest request) { - super(request); - try { - body = IOUtils.toByteArray(request.getInputStream()); - } catch (IOException ex) { - body = new byte[0]; - } - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return new ServletInputStream() { - public boolean isFinished() { - return false; - } - - public boolean isReady() { - return true; - } - - public void setReadListener(ReadListener readListener) { - - } - - ByteArrayInputStream byteArray = new ByteArrayInputStream(body); - - @Override - public int read() throws IOException { - return byteArray.read(); - } - }; - } - - public Map getAllHeaders() { - final Map headers = new HashMap<>(); - Collections.list(getHeaderNames()).forEach(it -> headers.put(it, getHeader(it))); - return headers; - } -} diff --git a/src/main/java/io/firetail/logging/wrapper/SpringResponseWrapper.java b/src/main/java/io/firetail/logging/wrapper/SpringResponseWrapper.java deleted file mode 100644 index b8c191c..0000000 --- a/src/main/java/io/firetail/logging/wrapper/SpringResponseWrapper.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.firetail.logging.wrapper; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -public class SpringResponseWrapper extends HttpServletResponseWrapper { - - private ServletOutputStream outputStream; - private PrintWriter writer; - private ServletOutputStreamWrapper copier; - - public SpringResponseWrapper(HttpServletResponse response) throws IOException { - super(response); - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - if (writer != null) { - throw new IllegalStateException("getWriter() has already been called on this response."); - } - - if (outputStream == null) { - outputStream = getResponse().getOutputStream(); - copier = new ServletOutputStreamWrapper(outputStream); - } - - return copier; - } - - @Override - public PrintWriter getWriter() throws IOException { - if (outputStream != null) { - throw new IllegalStateException("getOutputStream() has already been called on this response."); - } - - if (writer == null) { - copier = new ServletOutputStreamWrapper(getResponse().getOutputStream()); - writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true); - } - - return writer; - } - - @Override - public void flushBuffer() throws IOException { - if (writer != null) { - writer.flush(); - } - else if (outputStream != null) { - copier.flush(); - } - } - - public byte[] getContentAsByteArray() { - if (copier != null) { - return copier.getCopy(); - } - else { - return new byte[0]; - } - } - - public Map getAllHeaders() { - final Map headers = new HashMap<>(); - getHeaderNames().forEach(it -> headers.put(it, getHeader(it))); - return headers; - } - -} - diff --git a/src/main/kotlin/io/firetail/logging/Constants.kt b/src/main/kotlin/io/firetail/logging/Constants.kt new file mode 100644 index 0000000..3af7ab8 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/Constants.kt @@ -0,0 +1,12 @@ +package io.firetail.logging + +class Constants { + companion object { + const val REQUEST_ID = "X-Request-ID" + const val CORRELATION_ID = "X-Correlation-ID" + const val OP_NAME = "X-Operation-Name" + const val RESPONSE_TIME = "X-Response-Time" + const val RESPONSE_STATUS = "X-Response-Status" + val empty = ByteArray(0) + } +} diff --git a/src/main/kotlin/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.kt b/src/main/kotlin/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.kt new file mode 100644 index 0000000..4cf96fd --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/client/RestTemplateSetHeaderInterceptor.kt @@ -0,0 +1,21 @@ +package io.firetail.logging.client + +import io.firetail.logging.Constants.Companion.CORRELATION_ID +import io.firetail.logging.Constants.Companion.REQUEST_ID +import org.slf4j.MDC +import org.springframework.http.HttpRequest +import org.springframework.http.client.ClientHttpRequestExecution +import org.springframework.http.client.ClientHttpRequestInterceptor +import org.springframework.http.client.ClientHttpResponse + +class RestTemplateSetHeaderInterceptor : ClientHttpRequestInterceptor { + override fun intercept( + request: HttpRequest, + body: ByteArray, + execution: ClientHttpRequestExecution, + ): ClientHttpResponse { + request.headers.add(CORRELATION_ID, MDC.get(CORRELATION_ID)) + request.headers.add(REQUEST_ID, MDC.get(REQUEST_ID)) + return execution.execute(request, body) + } +} diff --git a/src/main/kotlin/io/firetail/logging/config/SpringLoggerAutoConfiguration.kt b/src/main/kotlin/io/firetail/logging/config/SpringLoggerAutoConfiguration.kt new file mode 100644 index 0000000..f92d7d5 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/config/SpringLoggerAutoConfiguration.kt @@ -0,0 +1,91 @@ +package io.firetail.logging.config + +import io.firetail.logging.client.RestTemplateSetHeaderInterceptor +import io.firetail.logging.filter.SpringLoggerFilter +import io.firetail.logging.util.UniqueIDGenerator +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.client.ClientHttpRequestInterceptor +import org.springframework.web.client.RestTemplate +import java.util.* +import javax.annotation.PostConstruct + +// import net.logstash.logback.appender.LogstashTcpSocketAppender; +// import net.logstash.logback.encoder.LogstashEncoder; +@Configuration +@ConfigurationProperties(prefix = "logging.logstash") +class SpringLoggerAutoConfiguration { + // private static final String FIRETAIL_APPENDER_NAME = "FIRETAIL"; + var url = "localhost:8500" + var ignorePatterns: String? = null + var isLogHeaders = false + var trustStoreLocation: String? = null + var trustStorePassword: String? = null + + @Value("\${spring.application.name:-}") + lateinit var name: String + + @Autowired(required = false) + var template: Optional? = null + + @Bean + fun generator(): UniqueIDGenerator { + return UniqueIDGenerator() + } + + @Bean + fun loggingFilter(): SpringLoggerFilter { + return SpringLoggerFilter(generator(), ignorePatterns, isLogHeaders) + } + + @Bean + @ConditionalOnMissingBean(RestTemplate::class) + fun restTemplate(): RestTemplate { + val restTemplate = RestTemplate() + val interceptorList: MutableList = ArrayList() + interceptorList.add(RestTemplateSetHeaderInterceptor()) + restTemplate.interceptors = interceptorList + return restTemplate + } + + /* rewrite this method to send data to firetail backend + @Bean + @ConditionalOnProperty("logging.firetail.enabled") + public FiretailTcpSocketAppender firetailAppender() { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + LogstashTcpSocketAppender logstashTcpSocketAppender = new LogstashTcpSocketAppender(); + logstashTcpSocketAppender.setName(FIRETAIL_APPENDER_NAME); + logstashTcpSocketAppender.setContext(loggerContext); + logstashTcpSocketAppender.addDestination(url); + if (trustStoreLocation != null) { + SSLConfiguration sslConfiguration = new SSLConfiguration(); + KeyStoreFactoryBean factory = new KeyStoreFactoryBean(); + factory.setLocation(trustStoreLocation); + if (trustStorePassword != null) + factory.setPassword(trustStorePassword); + sslConfiguration.setTrustStore(factory); + logstashTcpSocketAppender.setSsl(sslConfiguration); + } + LogstashEncoder encoder = new LogstashEncoder(); + encoder.setContext(loggerContext); + encoder.setIncludeContext(true); + encoder.setCustomFields("{\"appname\":\"" + name + "\"}"); + encoder.start(); + logstashTcpSocketAppender.setEncoder(encoder); + logstashTcpSocketAppender.start(); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(logstashTcpSocketAppender); + return logstashTcpSocketAppender; + } */ + @PostConstruct + fun init() { + template!!.ifPresent { restTemplate: RestTemplate -> + val interceptorList: MutableList = ArrayList() + interceptorList.add(RestTemplateSetHeaderInterceptor()) + restTemplate.interceptors = interceptorList + } + } +} diff --git a/src/main/kotlin/io/firetail/logging/filter/SpringLoggerFilter.kt b/src/main/kotlin/io/firetail/logging/filter/SpringLoggerFilter.kt new file mode 100644 index 0000000..b56a10d --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/filter/SpringLoggerFilter.kt @@ -0,0 +1,120 @@ +package io.firetail.logging.filter + +import io.firetail.logging.Constants.Companion.CORRELATION_ID +import io.firetail.logging.Constants.Companion.OP_NAME +import io.firetail.logging.Constants.Companion.REQUEST_ID +import io.firetail.logging.Constants.Companion.RESPONSE_STATUS +import io.firetail.logging.Constants.Companion.RESPONSE_TIME +import io.firetail.logging.util.UniqueIDGenerator +import io.firetail.logging.wrapper.SpringRequestWrapper +import io.firetail.logging.wrapper.SpringResponseWrapper +import net.logstash.logback.argument.StructuredArguments +import org.apache.commons.io.IOUtils +import org.slf4j.LoggerFactory +import org.slf4j.MDC +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.ApplicationContext +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.method.HandlerMethod +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping +import java.util.* +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class SpringLoggerFilter( + private val generator: UniqueIDGenerator, + private val ignorePatterns: String?, + private val logHeaders: Boolean, +) : OncePerRequestFilter() { + @Autowired + lateinit var context: ApplicationContext + + override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { + if (ignorePatterns != null && request.requestURI.matches(ignorePatterns.toRegex())) { + chain.doFilter(request, response) + } else { + generator.generateAndSetMDC(request) + try { + getHandlerMethod(request) + } catch (e: Exception) { + LOGGER.trace("Cannot get handler method") + } + val startTime = System.currentTimeMillis() + val wrappedRequest = SpringRequestWrapper(request) + if (logHeaders) { + LOGGER.info( + "Request: method={}, uri={}, payload={}, headers={}, audit={}", + wrappedRequest.method, + wrappedRequest.requestURI, + IOUtils.toString( + wrappedRequest.inputStream, + wrappedRequest.characterEncoding, + ), + wrappedRequest.allHeaders, + StructuredArguments.value("audit", true), + ) + } else { + LOGGER.info( + "Request: method={}, uri={}, payload={}, audit={}", + wrappedRequest.method, + wrappedRequest.requestURI, + IOUtils.toString( + wrappedRequest.inputStream, + wrappedRequest.characterEncoding, + ), + StructuredArguments.value("audit", true), + ) + } + val wrappedResponse = SpringResponseWrapper(response) + wrappedResponse.setHeader(REQUEST_ID, MDC.get(REQUEST_ID)) + wrappedResponse.setHeader(CORRELATION_ID, MDC.get(CORRELATION_ID)) + try { + chain.doFilter(wrappedRequest, wrappedResponse) + } catch (e: Exception) { + logResponse(startTime, wrappedResponse, 500) + throw e + } + logResponse(startTime, wrappedResponse, wrappedResponse.status) + } + } + + private fun logResponse(startTime: Long, wrappedResponse: SpringResponseWrapper, overriddenStatus: Int) { + val duration = System.currentTimeMillis() - startTime + wrappedResponse.characterEncoding = "UTF-8" + if (logHeaders) { + LOGGER.info( + "Response({} ms): status={}, payload={}, headers={}, audit={}", + StructuredArguments.value(RESPONSE_TIME, duration), + StructuredArguments.value(RESPONSE_STATUS, overriddenStatus), + IOUtils.toString( + wrappedResponse.contentAsByteArray, + wrappedResponse.characterEncoding, + ), + wrappedResponse.allHeaders, + StructuredArguments.value("audit", true), + ) + } else { + LOGGER.info( + "Response({} ms): status={}, payload={}, audit={}", + StructuredArguments.value(RESPONSE_TIME, duration), + StructuredArguments.value(RESPONSE_STATUS, overriddenStatus), + IOUtils.toString(wrappedResponse.contentAsByteArray, wrappedResponse.characterEncoding), + StructuredArguments.value("audit", true), + ) + } + } + + private fun getHandlerMethod(request: HttpServletRequest) { + val mappings = context.getBean("requestMappingHandlerMapping") as RequestMappingHandlerMapping + val nullableHandler = mappings.getHandler(request) + if (Objects.nonNull(nullableHandler)) { + val handler = nullableHandler?.handler as HandlerMethod + MDC.put(OP_NAME, handler.beanType.simpleName + "." + handler.method.name) + } + } + + companion object { + private val LOGGER = LoggerFactory.getLogger(SpringLoggerFilter::class.java) + } +} diff --git a/src/main/kotlin/io/firetail/logging/util/Generator.kt b/src/main/kotlin/io/firetail/logging/util/Generator.kt new file mode 100644 index 0000000..1eaabd0 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/util/Generator.kt @@ -0,0 +1,9 @@ +package io.firetail.logging.util + +import java.util.UUID + +class Generator { + fun generate(): String { + return UUID.randomUUID().toString() + } +} diff --git a/src/main/kotlin/io/firetail/logging/util/UniqueIDGenerator.kt b/src/main/kotlin/io/firetail/logging/util/UniqueIDGenerator.kt new file mode 100644 index 0000000..6788727 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/util/UniqueIDGenerator.kt @@ -0,0 +1,18 @@ +package io.firetail.logging.util + +import io.firetail.logging.Constants.Companion.CORRELATION_ID +import io.firetail.logging.Constants.Companion.REQUEST_ID +import org.slf4j.MDC +import javax.servlet.http.HttpServletRequest + +class UniqueIDGenerator(private val generator: Generator = Generator()) { + fun generateAndSetMDC(request: HttpServletRequest) { + MDC.clear() + var requestId = request.getHeader(REQUEST_ID) + if (requestId == null) requestId = generator.generate() + MDC.put(REQUEST_ID, requestId) + var correlationId = request.getHeader(CORRELATION_ID) + if (correlationId == null) correlationId = generator.generate() + MDC.put(CORRELATION_ID, correlationId) + } +} diff --git a/src/main/kotlin/io/firetail/logging/wrapper/ServletOutputStreamWrapper.kt b/src/main/kotlin/io/firetail/logging/wrapper/ServletOutputStreamWrapper.kt new file mode 100644 index 0000000..32d7e86 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/wrapper/ServletOutputStreamWrapper.kt @@ -0,0 +1,25 @@ +package io.firetail.logging.wrapper + +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import javax.servlet.ServletOutputStream +import javax.servlet.WriteListener + +class ServletOutputStreamWrapper(private val outputStream: OutputStream) : ServletOutputStream() { + private val copy: ByteArrayOutputStream = ByteArrayOutputStream() + + override fun write(b: Int) { + outputStream.write(b) + copy.write(b) + } + + fun getCopy(): ByteArray { + return copy.toByteArray() + } + + override fun isReady(): Boolean { + return true + } + + override fun setWriteListener(writeListener: WriteListener) {} +} diff --git a/src/main/kotlin/io/firetail/logging/wrapper/SpringRequestWrapper.kt b/src/main/kotlin/io/firetail/logging/wrapper/SpringRequestWrapper.kt new file mode 100644 index 0000000..4f95787 --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/wrapper/SpringRequestWrapper.kt @@ -0,0 +1,51 @@ +package io.firetail.logging.wrapper + +import io.firetail.logging.Constants.Companion.empty +import org.apache.commons.io.IOUtils +import java.io.ByteArrayInputStream +import java.io.IOException +import java.util.* +import java.util.function.Consumer +import javax.servlet.ReadListener +import javax.servlet.ServletInputStream +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletRequestWrapper + +class SpringRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) { + private var body: ByteArray + + init { + body = try { + IOUtils.toByteArray(request.inputStream) + } catch (ex: IOException) { + empty + } + } + + override fun getInputStream(): ServletInputStream { + return object : ServletInputStream() { + override fun isFinished(): Boolean { + return false + } + + override fun isReady(): Boolean { + return true + } + + override fun setReadListener(readListener: ReadListener) {} + var byteArray = ByteArrayInputStream(body) + + override fun read(): Int { + return byteArray.read() + } + } + } + + val allHeaders: Map + get() { + val headers: MutableMap = HashMap() + Collections.list(headerNames) + .forEach(Consumer { it: String -> headers[it] = getHeader(it) }) + return headers + } +} diff --git a/src/main/kotlin/io/firetail/logging/wrapper/SpringResponseWrapper.kt b/src/main/kotlin/io/firetail/logging/wrapper/SpringResponseWrapper.kt new file mode 100644 index 0000000..9532a5b --- /dev/null +++ b/src/main/kotlin/io/firetail/logging/wrapper/SpringResponseWrapper.kt @@ -0,0 +1,51 @@ +package io.firetail.logging.wrapper + +import io.firetail.logging.Constants.Companion.empty +import java.io.OutputStreamWriter +import java.io.PrintWriter +import java.util.function.Consumer +import javax.servlet.ServletOutputStream +import javax.servlet.http.HttpServletResponse +import javax.servlet.http.HttpServletResponseWrapper + +class SpringResponseWrapper(response: HttpServletResponse?) : HttpServletResponseWrapper(response) { + private var outputStream: ServletOutputStream? = null + private var writer: PrintWriter? = null + private var copier: ServletOutputStreamWrapper? = null + + 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() + headerNames.forEach(Consumer { it: String -> headers[it] = getHeader(it) }) + return headers + } +} diff --git a/src/test/kotlin/io/firetail/logging/IdGeneratorTests.kt b/src/test/kotlin/io/firetail/logging/IdGeneratorTests.kt new file mode 100644 index 0000000..1aac882 --- /dev/null +++ b/src/test/kotlin/io/firetail/logging/IdGeneratorTests.kt @@ -0,0 +1,45 @@ +package io.firetail.logging + +import io.firetail.logging.Constants.Companion.CORRELATION_ID +import io.firetail.logging.Constants.Companion.REQUEST_ID +import io.firetail.logging.util.Generator +import io.firetail.logging.util.UniqueIDGenerator +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.slf4j.MDC +import org.springframework.mock.web.MockHttpServletRequest + +class IdGeneratorTests { + @Test + fun mdcIsSetFromHeaderValues() { + val idGenerator = UniqueIDGenerator() // test with default generator + val httpRequest = MockHttpServletRequest() + val requestId = "requestId" + val correlationId = "correlationId" + httpRequest.addHeader(REQUEST_ID, requestId) + httpRequest.addHeader(CORRELATION_ID, correlationId) + idGenerator.generateAndSetMDC(httpRequest) + + assertThat(MDC.get(REQUEST_ID)).isEqualTo(requestId) + assertThat(MDC.get(CORRELATION_ID)).isEqualTo(correlationId) + assertThat(httpRequest.getHeader(REQUEST_ID)).isEqualTo(requestId) + assertThat(httpRequest.getHeader(CORRELATION_ID)).isEqualTo(correlationId) + } + + @Test + fun mdcIsSetWhenNoHeaderValues() { + val generator = Mockito.mock(Generator::class.java) + MDC.clear() + assertThat(MDC.get(CORRELATION_ID)).isNull() + assertThat(MDC.get(REQUEST_ID)).isNull() + val httpRequest = MockHttpServletRequest() + val id = "someValue" + Mockito.`when`(generator.generate()).thenReturn(id) + val idGenerator = UniqueIDGenerator(generator) + idGenerator.generateAndSetMDC(httpRequest) + assertThat(httpRequest.headerNames.toList()).isEmpty() + assertThat(MDC.get(REQUEST_ID)).isEqualTo(id) + assertThat(MDC.get(CORRELATION_ID)).isEqualTo(id) + } +}