├── .gitignore ├── LICENSE ├── README.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── screenshot1.png ├── screenshot2.png └── screenshot3.png ├── lib ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── dkorobtsov │ │ └── logging │ │ ├── ClientPrintingExecutor.java │ │ ├── DefaultLogger.java │ │ ├── HttpStatusCodes.java │ │ ├── InterceptorVersion.java │ │ ├── Level.java │ │ ├── LogFormatter.java │ │ ├── LogWriter.java │ │ ├── LoggerConfig.java │ │ ├── LoggingInterceptor.java │ │ ├── Printer.java │ │ ├── RequestDetails.java │ │ ├── ResponseDetails.java │ │ ├── TextUtils.java │ │ ├── converters │ │ ├── ToApacheHttpClientConverter.java │ │ ├── ToOkhttp3Converter.java │ │ └── ToOkhttpConverter.java │ │ └── interceptors │ │ ├── ApacheHttpRequestInterceptor.java │ │ ├── ApacheHttpResponseInterceptor.java │ │ ├── Okhttp3LoggingInterceptor.java │ │ └── OkhttpLoggingInterceptor.java │ └── test │ ├── java │ └── com │ │ └── dkorobtsov │ │ └── logging │ │ ├── BaseTest.java │ │ ├── ClientPrintingExecutorNegativeUnitTests.java │ │ ├── FormatterTest.java │ │ ├── InterceptorBodyHandlingTest.java │ │ ├── Log4j2LoggerTest.java │ │ ├── LoggingInterceptorUnitTests.java │ │ ├── LoggingInterceptorsTests.java │ │ ├── MalformedJsonHandlingTest.java │ │ ├── OutputResizingTest.java │ │ ├── RequestDetailsUnitTests.java │ │ ├── StatusCodeTest.java │ │ ├── TestLogger.java │ │ ├── TestUtil.java │ │ └── ToApacheHttpClientConverterUnitTests.java │ └── resources │ └── log4j2.xml ├── settings.gradle └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea/ 4 | lib/out/ 5 | /lib/build/ 6 | /$WERCKER_CACHE_DIR/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ihsan BAL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LoggingInterceptor - Interceptor for [OkHttp3](https://github.com/square/okhttp) with pretty logger 2 | (version for use with Java logging frameworks - jul, log4j, slf4j, logback, log4j2 etc) 3 | -------- 4 | **NB! Library is archived. Please use new library [Plinter](https://github.com/dkorobtsov/plinter) instead** 5 | 6 | Description 7 | ----------- 8 | What is the difference from [original](https://github.com/ihsanbal/LoggingInterceptor) repository? 9 | Personally I find this interceptor very useful for API testing purposes but original implementation 10 | works well only with Android. Had to significantly rewrite original library to use it in pure Java 11 | project with log4j2 based logging. So, comparing to original repository, changes are following: 12 | 13 | - removed all Android specific stuff (tags, references to BuildConfig etc) 14 | - removed included Android app (along with existing tests) 15 | - removed option to add custom headers / queries 16 | - updated to Java level 8 17 | - refactored Interceptor to make it work without any additional configuration (just plug and play) :) 18 | - fixed some bugs (mostly output related) 19 | - added option to customize logger output (when default JUL logger is used) 20 | - removed some useless params (like option to select between JUL info or warning severity, request/response tags etc) 21 | - added new tests package (can be helpful to figure out how Interceptor should work) 22 | - added new DefaultLogger implementation (basically just manually configured JUL logger) 23 | - reworked builder (to support those above mentioned changes) 24 | - interceptor now pretty prints XML/HTML body 25 | - max output length can be modified (can be useful for body pretty printing) 26 | 27 | Basic Usage 28 | ----------- 29 | Interceptor should work as is - without any additional parameters. 30 | By default JUL logger will be used with INFO level and minimal format 31 | displaying message only. 32 | `okhttp3` version: 33 | ```java 34 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 35 | .buildForOkhttp3(); 36 | OkHttpClient okHttpClient = new OkHttpClient.Builder() 37 | .addInterceptor(interceptor) 38 | .build(); 39 | 40 | // Interceptor can be used with retrofit 41 | Retrofit retrofitAdapter = new Retrofit.Builder() 42 | .addConverterFactory(GsonConverterFactory.create()) 43 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 44 | .baseUrl("https://.../") 45 | .client(okHttpClient) 46 | .build(); 47 | ``` 48 | 49 | `okhttp` version: (can be used for clients generated from swagger-codegen using `okhttp-gson` client) 50 | ```java 51 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 52 | .buildForOkhttp(); 53 | OkHttpClient okHttpClient = new OkHttpClient.Builder() 54 | .addInterceptor(interceptor) 55 | .build(); 56 | ``` 57 | 58 | `apache httpclient` version: (can be used for clients generated from swagger-codegen using `okhttp-gson` client) 59 | ```java 60 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 61 | .buildForApacheHttpClientRequest(); 62 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 63 | .builFordApacheHttpClientResponse(); 64 | 65 | return HttpClientBuilder 66 | .create() 67 | .addInterceptorFirst(requestInterceptor) 68 | .addInterceptorFirst(responseInterceptor) 69 | .setMaxConnTotal(MAX_IDLE_CONNECTIONS) 70 | .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) 71 | .build(); 72 | ``` 73 | Example: 74 | 75 |

76 | 77 |

78 | 79 | Format can be changed to one of the defined templates, for example: 80 | ```java 81 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 82 | .loggable(isDebug()) 83 | .level(Level.BASIC) 84 | .format(LogFormatter.JUL_DATE_LEVEL_MESSAGE) 85 | .maxLineLength(160) 86 | .executor(Executors.newSingleThreadExecutor()) 87 | .buildForOkhttp3(); 88 | ``` 89 | 90 | **Tip:** when logger is in "message only" mode, json response can be copied 91 | from console and converted to POJO with [this](http://www.jsonschema2pojo.org/) service in a matter of seconds. 92 | 93 | Advanced Usage 94 | -------------- 95 | Interceptor can be configured to be used with any existing Java logger - 96 | just need to provide own LogWriter implementation. 97 | 98 | Simple configuration for Log4j2: 99 | ```java 100 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 101 | .logger(new LogWriter() { 102 | final Logger log = LogManager.getLogger("OkHttpLogger"); 103 | 104 | @Override 105 | public void log(String msg) { 106 | log.debug(msg); 107 | } 108 | }) 109 | .buildForOkhttp3(); 110 | ``` 111 | 112 | Or more sophisticated approach with custom logging pattern. 113 | ```java 114 | LogWriter log4j2Writer = new LogWriter() { 115 | final String OK_HTTP_LOG_PATTERN = "[OkHTTP] %msg%n"; 116 | final Logger log = LogManager.getLogger("OkHttpLogger"); 117 | 118 | { 119 | final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 120 | final Configuration config = ctx.getConfiguration(); 121 | 122 | LoggerConfig loggerConfig = new LoggerConfig("OkHttpLogger", Level.TRACE, false); 123 | PatternLayout layout = PatternLayout 124 | .newBuilder() 125 | .withPattern(OK_HTTP_LOG_PATTERN) 126 | .build(); 127 | 128 | final Appender appender = ConsoleAppender 129 | .newBuilder() 130 | .withName("OkHttpConsoleAppender") 131 | .withLayout(layout) 132 | .build(); 133 | 134 | appender.start(); 135 | 136 | loggerConfig.addAppender(appender, Level.TRACE, null); 137 | config.addLogger("OkHttpLogger", loggerConfig); 138 | ctx.updateLoggers(); 139 | } 140 | 141 | @Override 142 | public void log(String msg) { 143 | log.debug(msg); 144 | } 145 | }; 146 | 147 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 148 | .logger(log4j2Writer) 149 | .buildForOkhttp3(); 150 | ``` 151 | Example: 152 |

153 | 154 |

155 | 156 | Download 157 | -------- 158 | 159 | Gradle: 160 | ```groovy 161 | allprojects { 162 | repositories { 163 | ... 164 | maven { url 'https://jitpack.io' } 165 | } 166 | } 167 | 168 | dependencies { 169 | compile('com.github.dkorobtsov:LoggingInterceptor:4.5') { 170 | exclude group: 'org.json', module: 'json' 171 | } 172 | } 173 | ``` 174 | 175 | Maven: 176 | ```xml 177 | 178 | jitpack.io 179 | https://jitpack.io 180 | 181 | 182 | 183 | com.github.dkorobtsov 184 | LoggingInterceptor 185 | 4.5 186 | 187 | ``` 188 | 189 | 190 | Executor 191 | -------- 192 | Add executor that allows to perform sequential concurrent print. 193 | 194 | Format 195 | ------ 196 | Predefined JUL logging patterns: 197 | ```java 198 | .format(LogFormatter.JUL_DATE_LEVEL_MESSAGE) 199 | .JUL_FULL // [Date][Thread][Level] Message 200 | .JUL_DATE_LEVEL_MESSAGE // [Date][Level] Message 201 | .JUL_THREAD_MESSAGE // [Thread] Message 202 | .JUL_LEVEL_MESSAGE // [Level] Message 203 | .JUL_DATE_MESSAGE // [Date] Message 204 | .JUL_MESSAGE_ONLY // Message 205 | ``` 206 | Note that given setting works only with default JUL logger. 207 | 208 | Line Length 209 | ----------- 210 | ```java 211 | .maxLineLength(160) // If needed, max output length can be modified. Default value: 110. Valid values: 10-500. 212 | ``` 213 | 214 | 215 | Level 216 | -------- 217 | 218 | ```java 219 | .setLevel(Level.BASIC) 220 | .NONE // No logs 221 | .BASIC // Logging url, method, headers and body. 222 | .HEADERS // Logging url, method and headers 223 | .BODY // Logging url, method and body 224 | ``` 225 | 226 | Loggable 227 | -------- 228 | 229 | ```java 230 | .loggable(true/false) // enable/disable sending logs output. 231 | ``` 232 | 233 | 234 | ## License 235 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fdkorobtsov%2FLoggingInterceptor.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fdkorobtsov%2FLoggingInterceptor?ref=badge_large) 236 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkorobtsov/LoggingInterceptor/b3caf67e7dfee87063aee0b559e32590843c322e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 02 21:28:29 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkorobtsov/LoggingInterceptor/b3caf67e7dfee87063aee0b559e32590843c322e/images/screenshot1.png -------------------------------------------------------------------------------- /images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkorobtsov/LoggingInterceptor/b3caf67e7dfee87063aee0b559e32590843c322e/images/screenshot2.png -------------------------------------------------------------------------------- /images/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkorobtsov/LoggingInterceptor/b3caf67e7dfee87063aee0b559e32590843c322e/images/screenshot3.png -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | import java.nio.charset.StandardCharsets 2 | 3 | plugins { 4 | id 'java-library' 5 | id 'maven' 6 | id 'jacoco' 7 | id 'project-report' 8 | id "org.sonarqube" version "2.6" 9 | } 10 | 11 | group = 'com.github.dkorobtsov' 12 | 13 | //noinspection GroovyUnusedAssignment 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | //noinspection GroovyUnusedAssignment 16 | targetCompatibility = JavaVersion.VERSION_1_8 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | group = 'com.dkorobtsov.logging' 23 | version = '4.5' 24 | 25 | ext { 26 | encoding = StandardCharsets.UTF_8.name() 27 | mockWebserverVersion = '2.7.5' 28 | jsonVersion = '20180130' 29 | okhttp3Version = '3.9.1' 30 | okhttpVersion = '2.7.5' 31 | junit4Version = '4.12' 32 | log4j2Version = '2.11.0' 33 | junitParamsVersion = '1.1.1' 34 | apacheHttpClientVersion = '4.5.1' 35 | apacheHttpAsyncClientVersion = '4.1.4' 36 | apacheHttpMimeTypeVersion = '4.5.6' 37 | assertjVersion = '3.11.1' 38 | } 39 | 40 | tasks.withType(JavaCompile) { 41 | options.encoding = encoding 42 | options.debug = true 43 | } 44 | 45 | test { 46 | useJUnit() 47 | 48 | jacoco { 49 | enabled = true 50 | 51 | reports { 52 | html.enabled = true 53 | } 54 | } 55 | 56 | outputs.upToDateWhen { 57 | false 58 | } 59 | 60 | testLogging { 61 | showStandardStreams = false 62 | } 63 | 64 | } 65 | 66 | dependencies { 67 | implementation "org.json:json:$jsonVersion" 68 | implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version" 69 | implementation "com.squareup.okhttp:logging-interceptor:$okhttpVersion" 70 | implementation "org.apache.httpcomponents:httpclient:$apacheHttpClientVersion" 71 | implementation "org.apache.httpcomponents:httpasyncclient:$apacheHttpAsyncClientVersion" 72 | implementation "org.apache.httpcomponents:httpmime:$apacheHttpMimeTypeVersion" 73 | 74 | testImplementation "com.squareup.okhttp:mockwebserver:$mockWebserverVersion" 75 | testImplementation "org.apache.logging.log4j:log4j-core:$log4j2Version" 76 | testImplementation "junit:junit:$junit4Version" 77 | testImplementation "pl.pragmatists:JUnitParams:$junitParamsVersion" 78 | testImplementation "org.assertj:assertj-core:$assertjVersion" 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/ClientPrintingExecutor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.TimeUnit; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import okhttp3.Request; 8 | 9 | public class ClientPrintingExecutor { 10 | 11 | private static final Logger logger = Logger.getLogger(ClientPrintingExecutor.class.getName()); 12 | 13 | private ClientPrintingExecutor() { 14 | } 15 | 16 | public static void printFileResponse(ResponseDetails responseDetails, 17 | LoggerConfig config) { 18 | final ExecutorService executor = (ExecutorService) config.executor; 19 | if (executor != null) { 20 | executor.execute(createFileResponseRunnable(config, responseDetails)); 21 | handleThreadTermination(executor); 22 | } else { 23 | Printer.printFileResponse(config.logger, config.level, 24 | config.maxLineLength, responseDetails 25 | ); 26 | } 27 | } 28 | 29 | public static void printJsonResponse(ResponseDetails responseDetails, 30 | LoggerConfig config) { 31 | final ExecutorService executor = (ExecutorService) config.executor; 32 | if (executor != null) { 33 | executor.execute(createPrintJsonResponseRunnable(config, responseDetails)); 34 | handleThreadTermination(executor); 35 | } else { 36 | Printer.printJsonResponse(config.logger, config.level, 37 | config.maxLineLength, responseDetails); 38 | } 39 | } 40 | 41 | public static void printFileRequest(Request request, LoggerConfig config) { 42 | final ExecutorService executor = (ExecutorService) config.executor; 43 | if (executor != null) { 44 | executor.execute(createFileRequestRunnable(config, request)); 45 | handleThreadTermination(executor); 46 | } else { 47 | Printer.printFileRequest(config.logger, config.level, 48 | config.maxLineLength, request); 49 | } 50 | } 51 | 52 | public static void printJsonRequest(Request request, LoggerConfig config) { 53 | final ExecutorService executor = (ExecutorService) config.executor; 54 | if (executor != null) { 55 | executor.execute(createPrintJsonRequestRunnable(config, request)); 56 | handleThreadTermination(executor); 57 | } else { 58 | Printer.printJsonRequest(config.logger, config.level, 59 | config.maxLineLength, request); 60 | } 61 | } 62 | 63 | private static Runnable createFileResponseRunnable(final LoggerConfig config, 64 | final ResponseDetails responseDetails) { 65 | return () -> Printer 66 | .printFileResponse(config.logger, config.level, 67 | config.maxLineLength, responseDetails); 68 | } 69 | 70 | private static Runnable createPrintJsonResponseRunnable(final LoggerConfig config, 71 | final ResponseDetails responseDetails) { 72 | return () -> Printer 73 | .printJsonResponse(config.logger, config.level, 74 | config.maxLineLength, responseDetails); 75 | } 76 | 77 | private static Runnable createFileRequestRunnable(final LoggerConfig config, 78 | final Request request) { 79 | return () -> Printer.printFileRequest(config.logger, config.level, 80 | config.maxLineLength, request); 81 | } 82 | 83 | private static Runnable createPrintJsonRequestRunnable(final LoggerConfig config, 84 | final Request request) { 85 | return () -> Printer.printJsonRequest(config.logger, config.level, 86 | config.maxLineLength, request); 87 | } 88 | 89 | private static void handleThreadTermination(ExecutorService executor) { 90 | try { 91 | executor.awaitTermination(5, TimeUnit.MILLISECONDS); 92 | } catch (InterruptedException e) { 93 | logger.log(Level.SEVERE, e.getMessage(), e); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/DefaultLogger.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.logging.ConsoleHandler; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | public class DefaultLogger implements LogWriter { 8 | 9 | private static final Logger logger = Logger.getLogger("DefaultLogger"); 10 | 11 | DefaultLogger(LogFormatter logFormatter) { 12 | logger.setUseParentHandlers(false); 13 | final ConsoleHandler handler = new ConsoleHandler(); 14 | handler.setFormatter(logFormatter.formatter); 15 | logger.addHandler(handler); 16 | } 17 | 18 | @Override 19 | public void log(String msg) { 20 | logger.log(Level.INFO, msg); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/HttpStatusCodes.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.Arrays; 4 | 5 | public enum HttpStatusCodes { 6 | CONTINUE(100, "CONTINUE"), 7 | SWITCHING_PROTOCOLS(101, "SWITCHING_PROTOCOLS"), 8 | PROCESSING(102, "PROCESSING"), 9 | OK(200, "OK"), 10 | CREATED(201, "CREATED"), 11 | ACCEPTED(202, "ACCEPTED"), 12 | NON_AUTHORITATIVE_INFORMATION(203, "NON_AUTHORITATIVE_INFORMATION"), 13 | NO_CONTENT(204, "NO_CONTENT"), 14 | RESET_CONTENT(205, "RESET_CONTENT"), 15 | PARTIAL_CONTENT(206, "PARTIAL_CONTENT"), 16 | MULTI_STATUS(207, "MULTI_STATUS"), 17 | MULTIPLE_CHOICES(300, "MULTIPLE_CHOICES"), 18 | MOVED_PERMANENTLY(301, "MOVED_PERMANENTLY"), 19 | MOVED_TEMPORARILY(302, "MOVED_TEMPORARILY"), 20 | SEE_OTHER(303, "SEE_OTHER"), 21 | NOT_MODIFIED(304, "NOT_MODIFIED"), 22 | USE_PROXY(305, "USE_PROXY"), 23 | TEMPORARY_REDIRECT(307, "TEMPORARY_REDIRECT"), 24 | BAD_REQUEST(400, "BAD_REQUEST"), 25 | UNAUTHORIZED(401, "UNAUTHORIZED"), 26 | PAYMENT_REQUIRED(402, "PAYMENT_REQUIRED"), 27 | FORBIDDEN(403, "FORBIDDEN"), 28 | NOT_FOUND(404, "NOT_FOUND"), 29 | METHOD_NOT_ALLOWED(405, "METHOD_NOT_ALLOWED"), 30 | NOT_ACCEPTABLE(406, "NOT_ACCEPTABLE"), 31 | PROXY_AUTHENTICATION_REQUIRED(407, "PROXY_AUTHENTICATION_REQUIRED"), 32 | REQUEST_TIMEOUT(408, "REQUEST_TIMEOUT"), 33 | CONFLICT(409, "CONFLICT"), 34 | GONE(410, "GONE"), 35 | LENGTH_REQUIRED(411, "LENGTH_REQUIRED"), 36 | PRECONDITION_FAILED(412, "PRECONDITION_FAILED"), 37 | REQUEST_TOO_LONG(413, "REQUEST_TOO_LONG"), 38 | REQUEST_URI_TOO_LONG(414, "REQUEST_URI_TOO_LONG"), 39 | UNSUPPORTED_MEDIA_TYPE(415, "UNSUPPORTED_MEDIA_TYPE"), 40 | REQUESTED_RANGE_NOT_SATISFIABLE(416, "REQUESTED_RANGE_NOT_SATISFIABLE"), 41 | EXPECTATION_FAILED(417, "EXPECTATION_FAILED"), 42 | INSUFFICIENT_SPACE_ON_RESOURCE(419, "INSUFFICIENT_SPACE_ON_RESOURCE"), 43 | METHOD_FAILURE(420, "METHOD_FAILURE"), 44 | UNPROCESSABLE_ENTITY(422, "UNPROCESSABLE_ENTITY"), 45 | LOCKED(423, "LOCKED"), 46 | FAILED_DEPENDENCY(424, "FAILED_DEPENDENCY"), 47 | INTERNAL_SERVER_ERROR(500, "INTERNAL_SERVER_ERROR"), 48 | NOT_IMPLEMENTED(501, "NOT_IMPLEMENTED"), 49 | BAD_GATEWAY(502, "BAD_GATEWAY"), 50 | SERVICE_UNAVAILABLE(503, "SERVICE_UNAVAILABLE"), 51 | GATEWAY_TIMEOUT(504, "GATEWAY_TIMEOUT"), 52 | HTTP_VERSION_NOT_SUPPORTED(505, "HTTP_VERSION_NOT_SUPPORTED"), 53 | INSUFFICIENT_STORAGE(507, "INSUFFICIENT_STORAGE"); 54 | 55 | public int getStatusCode() { 56 | return statusCode; 57 | } 58 | 59 | public String getMessage() { 60 | return message; 61 | } 62 | 63 | private int statusCode; 64 | private String message; 65 | 66 | HttpStatusCodes(int statusCode, String message) { 67 | this.statusCode = statusCode; 68 | this.message = message; 69 | } 70 | 71 | public static String findMessage(int code) { 72 | return Arrays.stream(values()) 73 | .filter(httpStatusCode -> httpStatusCode.getStatusCode() == code) 74 | .findFirst() 75 | .orElseThrow(() -> new IllegalArgumentException( 76 | String.format("Couldn't find %s http status code", code))) 77 | .getMessage(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/InterceptorVersion.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | public enum InterceptorVersion { 4 | OKHTTP("okhttp"), 5 | OKHTTP3("okhttp3"), 6 | APACHE_HTTPCLIENT_REQUEST("apacheHttpclientRequest"); 7 | 8 | private String name; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public static InterceptorVersion parse(String name) { 15 | switch (name) { 16 | case "okhttp": 17 | return OKHTTP; 18 | case "okhttp3": 19 | return OKHTTP3; 20 | case "apacheHttpclientRequest": 21 | return APACHE_HTTPCLIENT_REQUEST; 22 | default: 23 | throw new IllegalArgumentException("Unknown interceptor version: " + name); 24 | } 25 | } 26 | 27 | InterceptorVersion(String name) { 28 | 29 | this.name = name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/Level.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | public enum Level { 4 | /** 5 | * No logs. 6 | */ 7 | NONE, 8 | /** 9 | *

Example: 10 | *

{@code
11 |      *  - URL
12 |      *  - Method
13 |      *  - Headers
14 |      *  - Body
15 |      * }
16 | */ 17 | BASIC, 18 | /** 19 | *

Example: 20 | *

{@code
21 |      *  - URL
22 |      *  - Method
23 |      *  - Headers
24 |      * }
25 | */ 26 | HEADERS, 27 | /** 28 | *

Example: 29 | *

{@code
30 |      *  - URL
31 |      *  - Method
32 |      *  - Body
33 |      * }
34 | */ 35 | BODY 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/LogFormatter.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.Date; 4 | import java.util.logging.LogRecord; 5 | import java.util.logging.SimpleFormatter; 6 | 7 | public enum LogFormatter { 8 | 9 | JUL_FULL(new SimpleFormatter() { 10 | @Override 11 | public synchronized String format(LogRecord lr) { 12 | return String.format("[%1$tF %1$tT][%2$s][%3$-7s] %4$s %n", 13 | new Date(lr.getMillis()), 14 | Thread.currentThread().getName(), 15 | lr.getLevel().getLocalizedName(), 16 | lr.getMessage() 17 | ); 18 | } 19 | }), 20 | 21 | JUL_DATE_MESSAGE(new SimpleFormatter() { 22 | @Override 23 | public synchronized String format(LogRecord lr) { 24 | return String.format("[%1$tF %1$tT] %2$s %n", 25 | new Date(lr.getMillis()), 26 | lr.getMessage() 27 | ); 28 | } 29 | }), 30 | 31 | JUL_DATE_LEVEL_MESSAGE(new SimpleFormatter() { 32 | @Override 33 | public synchronized String format(LogRecord lr) { 34 | return String.format("[%1$tF %1$tT] [%2$-7s] %3$s %n", 35 | new Date(lr.getMillis()), 36 | lr.getLevel().getLocalizedName(), 37 | lr.getMessage() 38 | ); 39 | } 40 | }), 41 | 42 | JUL_LEVEL_MESSAGE(new SimpleFormatter() { 43 | @Override 44 | public synchronized String format(LogRecord lr) { 45 | return String.format("[%1$s] %2$s %n", 46 | lr.getLevel().getLocalizedName(), 47 | lr.getMessage() 48 | ); 49 | } 50 | }), 51 | 52 | JUL_THREAD_MESSAGE(new SimpleFormatter() { 53 | @Override 54 | public synchronized String format(LogRecord lr) { 55 | return String.format("[%1$s] %2$s %n", 56 | Thread.currentThread().getName(), 57 | lr.getMessage() 58 | ); 59 | } 60 | }), 61 | 62 | JUL_MESSAGE_ONLY(new SimpleFormatter() { 63 | @Override 64 | public synchronized String format(LogRecord lr) { 65 | return String.format("%1$s %n", 66 | lr.getMessage() 67 | ); 68 | } 69 | }); 70 | 71 | public final SimpleFormatter formatter; 72 | 73 | LogFormatter(SimpleFormatter simpleFormatter) { 74 | this.formatter = simpleFormatter; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/LogWriter.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | public interface LogWriter { 4 | 5 | void log(String msg); 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/LoggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | public class LoggerConfig { 6 | 7 | public final boolean isDebug; 8 | public final Level level; 9 | public final LogWriter logger; 10 | public final LogFormatter formatter; 11 | public final Executor executor; 12 | public final int maxLineLength; 13 | 14 | LoggerConfig(boolean isDebug, Level level, LogWriter logger, LogFormatter formatter, 15 | Executor executor, int maxLineLength) { 16 | this.isDebug = isDebug; 17 | this.level = level; 18 | this.logger = logger; 19 | this.formatter = formatter; 20 | this.executor = executor; 21 | this.maxLineLength = maxLineLength; 22 | } 23 | 24 | public static LoggerConfigBuilder builder() { 25 | return new LoggerConfigBuilder(); 26 | } 27 | 28 | public static class LoggerConfigBuilder { 29 | 30 | private boolean isDebug; 31 | private Level level; 32 | private LogWriter logger; 33 | private LogFormatter formatter; 34 | private Executor executor; 35 | private int maxLineLength; 36 | 37 | LoggerConfigBuilder() { 38 | } 39 | 40 | public LoggerConfigBuilder loggable(boolean isDebug) { 41 | this.isDebug = isDebug; 42 | return this; 43 | } 44 | 45 | public LoggerConfigBuilder level(Level level) { 46 | this.level = level; 47 | return this; 48 | } 49 | 50 | public LoggerConfigBuilder logger(LogWriter logger) { 51 | this.logger = logger; 52 | return this; 53 | } 54 | 55 | public LoggerConfigBuilder formatter(LogFormatter formatter) { 56 | this.formatter = formatter; 57 | return this; 58 | } 59 | 60 | public LoggerConfigBuilder executor(Executor executor) { 61 | this.executor = executor; 62 | return this; 63 | } 64 | 65 | public LoggerConfigBuilder maxLineLength(int maxLineLength) { 66 | this.maxLineLength = maxLineLength; 67 | return this; 68 | } 69 | 70 | public LoggerConfig build() { 71 | return new LoggerConfig(isDebug, level, logger, formatter, executor, 72 | maxLineLength); 73 | } 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/LoggingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import com.dkorobtsov.logging.interceptors.ApacheHttpRequestInterceptor; 4 | import com.dkorobtsov.logging.interceptors.ApacheHttpResponseInterceptor; 5 | import com.dkorobtsov.logging.interceptors.Okhttp3LoggingInterceptor; 6 | import com.dkorobtsov.logging.interceptors.OkhttpLoggingInterceptor; 7 | import java.util.concurrent.Executor; 8 | 9 | public class LoggingInterceptor { 10 | 11 | private LoggingInterceptor() { 12 | } 13 | 14 | public static Builder builder() { 15 | return new Builder(); 16 | } 17 | 18 | @SuppressWarnings({"unused", "SameParameterValue"}) 19 | public static class Builder { 20 | 21 | private boolean isDebug = true; 22 | private int maxLineLength = 110; 23 | private Level level = Level.BASIC; 24 | private LogWriter logger; 25 | private LogFormatter formatter; 26 | private Executor executor; 27 | 28 | public Builder() { 29 | formatter = LogFormatter.JUL_MESSAGE_ONLY; 30 | } 31 | 32 | /** 33 | * @param logger use this method to provide your logging interface implementation. 34 | * 35 | * Example: 36 | *
 37 |          *         Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder()
 38 |          *         .logger(new LogWriter() {
 39 |          *           final Logger log = LogManager.getLogger("OkHttpLogger");
 40 |          *
 41 |          *           @Override
 42 |          *           public void log(String msg) {
 43 |          *             log.debug(msg);
 44 |          *           }
 45 |          *         })
 46 |          *         .buildForOkhttp3();
 47 |          * 
48 | */ 49 | public Builder logger(LogWriter logger) { 50 | this.logger = logger; 51 | return this; 52 | } 53 | 54 | LogWriter getLogger() { 55 | if (logger == null) { 56 | logger = new DefaultLogger(formatter); 57 | } 58 | return logger; 59 | } 60 | 61 | /** 62 | * @param format set format for default Java Utility Logger 63 | * 64 | * (will be ignored in case custom logger is used) 65 | */ 66 | public Builder format(LogFormatter format) { 67 | this.formatter = format; 68 | return this; 69 | } 70 | 71 | LogFormatter getFormatter() { 72 | return formatter; 73 | } 74 | 75 | /** 76 | * @param isDebug set can sending log output 77 | */ 78 | public Builder loggable(boolean isDebug) { 79 | this.isDebug = isDebug; 80 | return this; 81 | } 82 | 83 | public boolean isDebug() { 84 | return isDebug; 85 | } 86 | 87 | /** 88 | * @param level set log level 89 | */ 90 | public Builder level(Level level) { 91 | this.level = level; 92 | return this; 93 | } 94 | 95 | Level getLevel() { 96 | return level; 97 | } 98 | 99 | /** 100 | * @param length specifies max line length when printing request/response body 101 | * 102 | * Min value: 10, Max value: 500, Default: 110 103 | */ 104 | public Builder maxLineLength(int length) { 105 | if (length < 10 || length > 500) { 106 | throw new IllegalArgumentException( 107 | "Invalid line length. Should be longer then 10 and shorter then 500."); 108 | } else { 109 | this.maxLineLength = length; 110 | } 111 | return this; 112 | } 113 | 114 | int getMaxLineLength() { 115 | return maxLineLength; 116 | } 117 | 118 | /** 119 | * @param executor manual executor override for printing 120 | */ 121 | public Builder executor(Executor executor) { 122 | this.executor = executor; 123 | return this; 124 | } 125 | 126 | Executor getExecutor() { 127 | return executor; 128 | } 129 | 130 | public OkhttpLoggingInterceptor buildForOkhttp() { 131 | return new OkhttpLoggingInterceptor(loggerConfig()); 132 | } 133 | 134 | public Okhttp3LoggingInterceptor buildForOkhttp3() { 135 | return new Okhttp3LoggingInterceptor(loggerConfig()); 136 | } 137 | 138 | public ApacheHttpRequestInterceptor buildForApacheHttpClientRequest() { 139 | return new ApacheHttpRequestInterceptor(loggerConfig()); 140 | } 141 | 142 | public ApacheHttpResponseInterceptor buildFordApacheHttpClientResponse() { 143 | return new ApacheHttpResponseInterceptor(loggerConfig()); 144 | } 145 | 146 | private LoggerConfig loggerConfig() { 147 | return LoggerConfig.builder() 148 | .executor(this.executor) 149 | .formatter(this.formatter) 150 | .logger(this.logger) 151 | .loggable(this.isDebug) 152 | .level(this.level) 153 | .maxLineLength(this.maxLineLength) 154 | .build(); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/Printer.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | 4 | import java.io.IOException; 5 | import java.io.StringReader; 6 | import java.util.List; 7 | import javax.xml.parsers.DocumentBuilderFactory; 8 | import okhttp3.Request; 9 | import okio.Buffer; 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | import org.w3c.dom.Node; 14 | import org.w3c.dom.bootstrap.DOMImplementationRegistry; 15 | import org.w3c.dom.ls.DOMImplementationLS; 16 | import org.w3c.dom.ls.LSSerializer; 17 | import org.xml.sax.InputSource; 18 | 19 | class Printer { 20 | 21 | private static final int JSON_INDENT = 3; 22 | 23 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 24 | private static final String REGEX_LINE_SEPARATOR = "\r?\n"; 25 | private static final String DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR; 26 | 27 | private static final String[] OMITTED_RESPONSE = {"", "Omitted response body"}; 28 | private static final String[] OMITTED_REQUEST = {"", "Omitted request body"}; 29 | 30 | private static final String N = "\n"; 31 | private static final String T = "\t"; 32 | private static final String REQUEST_UP_LINE = "┌────── Request ────────────────────────────────────────────────────────────────────────"; 33 | private static final String END_LINE = "└───────────────────────────────────────────────────────────────────────────────────────"; 34 | private static final String RESPONSE_UP_LINE = "┌────── Response ───────────────────────────────────────────────────────────────────────"; 35 | private static final String BODY_TAG = "Body:"; 36 | private static final String URL_TAG = "URL: "; 37 | private static final String METHOD_TAG = "Method: @"; 38 | private static final String HEADERS_TAG = "Headers:"; 39 | private static final String STATUS_CODE_TAG = "Status Code: "; 40 | private static final String RECEIVED_TAG = "Received in: "; 41 | private static final String CORNER_UP = "┌ "; 42 | private static final String CORNER_BOTTOM = "└ "; 43 | private static final String CENTER_LINE = "├ "; 44 | private static final String DEFAULT_LINE = " "; 45 | 46 | private Printer() { 47 | } 48 | 49 | private static boolean isEmpty(String line) { 50 | return TextUtils.isEmpty(line) || N.equals(line) || T.equals(line) || TextUtils 51 | .isEmpty(line.trim()); 52 | } 53 | 54 | static void printJsonRequest(LogWriter logWriter, Level level, int maxLineLength, 55 | Request request) { 56 | logWriter.log(REQUEST_UP_LINE); 57 | 58 | logLines(new String[]{URL_TAG + request.url()}, logWriter, maxLineLength, false); 59 | logLines(getRequest(request, level), logWriter, maxLineLength, true); 60 | 61 | String requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyToString(request); 62 | if (level == Level.BASIC || level == Level.BODY) { 63 | logLines(requestBody.split(REGEX_LINE_SEPARATOR), logWriter, maxLineLength, true); 64 | } 65 | 66 | logWriter.log(END_LINE); 67 | } 68 | 69 | static void printJsonResponse(LogWriter logWriter, Level level, int maxLineLength, 70 | ResponseDetails responseDetails) { 71 | logWriter.log(RESPONSE_UP_LINE); 72 | 73 | logLines(new String[]{URL_TAG + responseDetails.url}, logWriter, maxLineLength, false); 74 | logLines(getResponse(level, responseDetails), logWriter, maxLineLength, true); 75 | 76 | final String responseBody = 77 | LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + formattedBody( 78 | responseDetails.originalBody); 79 | if (level == Level.BASIC || level == Level.BODY) { 80 | logLines(responseBody.split(REGEX_LINE_SEPARATOR), logWriter, maxLineLength, true); 81 | } 82 | 83 | logWriter.log(END_LINE); 84 | } 85 | 86 | static void printFileRequest(LogWriter logWriter, Level level, int maxLineLength, 87 | Request request) { 88 | logWriter.log(REQUEST_UP_LINE); 89 | logLines(new String[]{URL_TAG + request.url()}, logWriter, maxLineLength, false); 90 | logLines(getRequest(request, level), logWriter, maxLineLength, true); 91 | 92 | if (level == Level.BASIC || level == Level.BODY) { 93 | logLines(OMITTED_REQUEST, logWriter, maxLineLength, true); 94 | } 95 | 96 | logWriter.log(END_LINE); 97 | 98 | } 99 | 100 | static void printFileResponse(LogWriter logWriter, Level level, 101 | int maxLineLength, ResponseDetails responseDetails) { 102 | logWriter.log(RESPONSE_UP_LINE); 103 | logLines(new String[]{URL_TAG + responseDetails.url}, logWriter, maxLineLength, false); 104 | logLines(getResponse(level, responseDetails), logWriter, maxLineLength, true); 105 | 106 | if (level == Level.BASIC || level == Level.BODY) { 107 | logLines(OMITTED_RESPONSE, logWriter, maxLineLength, true); 108 | } 109 | 110 | logWriter.log(END_LINE); 111 | } 112 | 113 | private static String[] getRequest(Request request, Level level) { 114 | String header = request.headers().toString(); 115 | boolean isLoggable = level == Level.HEADERS || level == Level.BASIC; 116 | String log = METHOD_TAG + request.method() 117 | + DOUBLE_SEPARATOR 118 | + printHeaderIfLoggable(header, isLoggable); 119 | return log.split(REGEX_LINE_SEPARATOR); 120 | } 121 | 122 | private static String[] getResponse(Level level, ResponseDetails responseDetails) { 123 | final boolean isLoggable = level == Level.HEADERS || level == Level.BASIC; 124 | final String segmentString = slashSegments(responseDetails.segmentList); 125 | final String receivedTags = responseDetails.chainMs == 0 126 | ? "" 127 | : " - " + RECEIVED_TAG + responseDetails.chainMs + "ms"; 128 | final String log = (!TextUtils.isEmpty(segmentString) 129 | ? segmentString + " - " 130 | : "") + "is success : " 131 | + responseDetails.isSuccessful + receivedTags 132 | + DOUBLE_SEPARATOR 133 | + STATUS_CODE_TAG + responseDetails.code + " / " + responseDetails.message 134 | + DOUBLE_SEPARATOR 135 | + printHeaderIfLoggable(responseDetails.header, isLoggable); 136 | return log.split(REGEX_LINE_SEPARATOR); 137 | } 138 | 139 | private static String printHeaderIfLoggable(String header, boolean loggable) { 140 | return !isEmpty(header) && loggable ? HEADERS_TAG + LINE_SEPARATOR + dotHeaders(header) 141 | : ""; 142 | } 143 | 144 | private static String slashSegments(List segments) { 145 | StringBuilder segmentString = new StringBuilder(); 146 | for (String segment : segments) { 147 | segmentString.append("/").append(segment); 148 | } 149 | return segmentString.toString(); 150 | } 151 | 152 | private static String dotHeaders(String header) { 153 | String[] headers = header.split(REGEX_LINE_SEPARATOR); 154 | StringBuilder builder = new StringBuilder(); 155 | String tag = "─ "; 156 | if (headers.length > 1) { 157 | for (int i = 0; i < headers.length; i++) { 158 | if (i == 0) { 159 | tag = CORNER_UP; 160 | } else if (i == headers.length - 1) { 161 | tag = CORNER_BOTTOM; 162 | } else { 163 | tag = CENTER_LINE; 164 | } 165 | builder.append(tag).append(headers[i]).append("\n"); 166 | } 167 | } else { 168 | for (String item : headers) { 169 | builder.append(tag).append(item).append("\n"); 170 | } 171 | } 172 | return builder.toString(); 173 | } 174 | 175 | private static void logLines(String[] lines, LogWriter logger, 176 | int maxLineLength, boolean withLineSize) { 177 | 178 | for (String line : lines) { 179 | if (line.isEmpty()) { 180 | logger.log(line); 181 | } else { 182 | int lineLength = line.length(); 183 | int maxLongSize = withLineSize ? maxLineLength : lineLength; 184 | for (int i = 0; i <= lineLength / maxLongSize; i++) { 185 | int start = i * maxLongSize; 186 | int end = (i + 1) * maxLongSize; 187 | end = end > line.length() ? line.length() : end; 188 | logger.log(DEFAULT_LINE + line.substring(start, end)); 189 | } 190 | } 191 | } 192 | } 193 | 194 | private static String bodyToString(final Request request) { 195 | final Request copy = request.newBuilder().build(); 196 | try (final Buffer buffer = new Buffer()) { 197 | if (copy.body() == null) { 198 | return ""; 199 | } 200 | copy.body().writeTo(buffer); 201 | return formattedBody(buffer.readUtf8()); 202 | } catch (final IOException e) { 203 | return "{\"err\": \"" + e.getMessage() + "\"}"; 204 | } 205 | } 206 | 207 | static String formattedBody(final String msg) { 208 | String message; 209 | try { 210 | if (msg.trim().startsWith("{")) { 211 | message = formatAsJsonObject(msg); 212 | } else if (msg.trim().startsWith("[")) { 213 | message = formatAsJsonArray(msg); 214 | } else if (msg.trim().startsWith("<")) { 215 | message = formatAsXml(msg); 216 | } else { 217 | message = msg; 218 | } 219 | } catch (JSONException e) { 220 | message = msg; 221 | } 222 | return message; 223 | } 224 | 225 | private static String formatAsJsonObject(String msg) { 226 | return new JSONObject(msg).toString(JSON_INDENT); 227 | } 228 | 229 | private static String formatAsJsonArray(String msg) { 230 | return new JSONArray(msg).toString(JSON_INDENT); 231 | } 232 | 233 | private static String formatAsXml(String msg) { 234 | try { 235 | final InputSource src = new InputSource(new StringReader(msg)); 236 | final Node document = DocumentBuilderFactory.newInstance() 237 | .newDocumentBuilder().parse(src).getDocumentElement(); 238 | final boolean keepDeclaration = msg.startsWith(" 1) { 42 | throw new IllegalArgumentException( 43 | "You can only initialize one client in the builder. " + 44 | "No more then 1 'from' method per builder invocation allowed"); 45 | } else if (okhttpRequest != null) { 46 | return buildFromOkhttp(); 47 | } else { 48 | return buildFromApacheHttpRequest(); 49 | } 50 | } 51 | 52 | private Request buildFromOkhttp() { 53 | final okhttp3.Request.Builder builder = new okhttp3.Request.Builder(); 54 | builder.url(this.okhttpRequest.url()); 55 | final Map> headersMap = this.okhttpRequest.headers().toMultimap(); 56 | headersMap.forEach((String name, List values) 57 | -> builder.addHeader(name, String.join(";", values))); 58 | 59 | if (permitsRequestBody(this.okhttpRequest.method())) { 60 | builder 61 | .method(this.okhttpRequest.method(), 62 | convertOkhttpRequestBodyTo3(this.okhttpRequest)); 63 | } else { 64 | builder.method(this.okhttpRequest.method(), null); 65 | } 66 | builder.tag(this.okhttpRequest.tag()); 67 | builder.cacheControl(convertOkhttpCacheControlTo3(this.okhttpRequest.cacheControl())); 68 | return builder.build(); 69 | } 70 | 71 | private Request buildFromApacheHttpRequest() { 72 | final okhttp3.Request.Builder builder = new okhttp3.Request 73 | .Builder(); 74 | builder.url(buildUrlFromApacheHttpRequest(this.apacheHttpRequest)); 75 | final Header[] headersMap = this.apacheHttpRequest.getAllHeaders(); 76 | Arrays.stream(headersMap) 77 | .forEach(header -> builder.addHeader(header.getName(), header.getValue())); 78 | final String method = this.apacheHttpRequest.getRequestLine().getMethod(); 79 | if (permitsRequestBody(method)) { 80 | builder.method(method, convertApacheHttpRequestBodyTo3(this.apacheHttpRequest)); 81 | } else { 82 | builder.method(method, null); 83 | } 84 | return builder.build(); 85 | } 86 | 87 | private static String buildUrlFromApacheHttpRequest(HttpRequest request) { 88 | final HttpHost target = ((HttpRequestWrapper) request).getTarget(); 89 | final String portString = target.getPort() == -1 ? "" : ":" + target.getPort(); 90 | return String 91 | .format("%s://%s%s%s", target.getSchemeName(), target.getHostName(), portString, 92 | ((HttpRequestWrapper) request).getURI()); 93 | } 94 | 95 | } 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/ResponseDetails.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import com.dkorobtsov.logging.converters.ToOkhttp3Converter; 4 | import java.io.IOException; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.stream.Collectors; 10 | import okhttp3.MediaType; 11 | import okhttp3.Request; 12 | import okhttp3.Response; 13 | import okhttp3.ResponseBody; 14 | import org.apache.http.HttpResponse; 15 | 16 | public final class ResponseDetails { 17 | 18 | final List segmentList; 19 | final String header; 20 | final int code; 21 | final boolean isSuccessful; 22 | final String message; 23 | public final MediaType contentType; 24 | public final String url; 25 | public final String originalBody; 26 | final long chainMs; 27 | 28 | ResponseDetails(List segmentList, String header, int code, boolean isSuccessful, 29 | String message, MediaType contentType, String url, 30 | String originalBody, long chainMs) { 31 | this.segmentList = segmentList; 32 | this.header = header; 33 | this.code = code; 34 | this.isSuccessful = isSuccessful; 35 | this.message = message; 36 | this.contentType = contentType; 37 | this.url = url; 38 | this.originalBody = originalBody; 39 | this.chainMs = chainMs; 40 | } 41 | 42 | public static ResponseDetails from(Request request, Response response, long chainMs, 43 | boolean isFileRequest) throws IOException { 44 | final List segmentList = request.url().encodedPathSegments(); 45 | final String header = response.headers().toString(); 46 | final int code = response.code(); 47 | final boolean isSuccessful = response.isSuccessful(); 48 | final String message = response.message(); 49 | final ResponseBody responseBody = response.body(); 50 | final MediaType contentType = Objects.requireNonNull(responseBody).contentType(); 51 | final String url = response.request().url().toString(); 52 | final String originalBody = 53 | isFileRequest ? null : responseBody.string(); 54 | 55 | return ResponseDetails 56 | .builder() 57 | .segmentList(segmentList) 58 | .header(header) 59 | .code(code) 60 | .isSuccessful(isSuccessful) 61 | .message(message) 62 | .originalBody(originalBody) 63 | .contentType(contentType) 64 | .url(url) 65 | .chainMs(chainMs) 66 | .build(); 67 | } 68 | 69 | public static ResponseDetails from(HttpResponse response, boolean isFileRequest) 70 | throws IOException { 71 | final List stringifiedHeaders = Arrays.stream(response 72 | .getAllHeaders()) 73 | .map(headerElement -> String 74 | .format("%s=%s", headerElement.getName(), headerElement.getValue())) 75 | .collect(Collectors.toList()); 76 | final String header = String.join(";", stringifiedHeaders); 77 | final int code = response.getStatusLine().getStatusCode(); 78 | final boolean isSuccessful = code >= 200 && code <= 300; 79 | final String message = HttpStatusCodes.findMessage(code); 80 | final ResponseBody responseBody = ToOkhttp3Converter 81 | .convertApacheHttpResponseBodyTo3(response); 82 | final MediaType contentType = responseBody.contentType(); 83 | final String url = ""; 84 | final String originalBody = 85 | isFileRequest ? null : responseBody.string(); 86 | 87 | return ResponseDetails 88 | .builder() 89 | .segmentList(Collections.emptyList()) 90 | .header(header) 91 | .code(code) 92 | .isSuccessful(isSuccessful) 93 | .message(message) 94 | .originalBody(originalBody) 95 | .contentType(contentType) 96 | .url(url) 97 | .build(); 98 | } 99 | 100 | public static ResponseDetailsBuilder builder() { 101 | return new ResponseDetailsBuilder(); 102 | } 103 | 104 | public static class ResponseDetailsBuilder { 105 | 106 | private List segmentList; 107 | private String header; 108 | private int code; 109 | private boolean isSuccessful; 110 | private String message; 111 | private MediaType contentType; 112 | private String url; 113 | private String originalBody; 114 | private long chainMs; 115 | 116 | ResponseDetailsBuilder() { 117 | } 118 | 119 | ResponseDetailsBuilder segmentList(List segmentList) { 120 | this.segmentList = segmentList; 121 | return this; 122 | } 123 | 124 | ResponseDetailsBuilder header(String header) { 125 | this.header = header; 126 | return this; 127 | } 128 | 129 | ResponseDetailsBuilder code(int code) { 130 | this.code = code; 131 | return this; 132 | } 133 | 134 | ResponseDetailsBuilder isSuccessful(boolean isSuccessful) { 135 | this.isSuccessful = isSuccessful; 136 | return this; 137 | } 138 | 139 | ResponseDetailsBuilder message(String message) { 140 | this.message = message; 141 | return this; 142 | } 143 | 144 | ResponseDetailsBuilder contentType(MediaType contentType) { 145 | this.contentType = contentType; 146 | return this; 147 | } 148 | 149 | public ResponseDetailsBuilder url(String url) { 150 | this.url = url; 151 | return this; 152 | } 153 | 154 | ResponseDetailsBuilder chainMs(long chainMs) { 155 | this.chainMs = chainMs; 156 | return this; 157 | } 158 | 159 | ResponseDetailsBuilder originalBody(String originalBody) { 160 | this.originalBody = originalBody; 161 | return this; 162 | } 163 | 164 | public ResponseDetails build() { 165 | return new ResponseDetails(segmentList, header, code, isSuccessful, message, 166 | contentType, url, originalBody, chainMs); 167 | } 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/TextUtils.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | public class TextUtils { 4 | 5 | private TextUtils() { 6 | } 7 | 8 | static boolean isEmpty(CharSequence str) { 9 | return str == null || str.length() == 0; 10 | } 11 | 12 | public static boolean isFileRequest(final String subtype) { 13 | return !(subtype != null && (subtype.contains("json") 14 | || subtype.contains("xml") 15 | || subtype.contains("plain") 16 | || subtype.contains("html"))); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/converters/ToApacheHttpClientConverter.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.converters; 2 | 3 | import java.io.IOException; 4 | import okhttp3.RequestBody; 5 | import okio.Buffer; 6 | import org.apache.http.HttpEntity; 7 | import org.apache.http.entity.ContentType; 8 | import org.apache.http.entity.StringEntity; 9 | 10 | public class ToApacheHttpClientConverter { 11 | 12 | private ToApacheHttpClientConverter() { 13 | } 14 | 15 | public static HttpEntity okhttp3RequestBodyToStringEntity(RequestBody requestBody, 16 | ContentType contentType) throws IOException { 17 | 18 | if (requestBody == null) { 19 | return new StringEntity(""); 20 | } 21 | 22 | final String responseString; 23 | try (final Buffer buffer = new Buffer()) { 24 | requestBody.writeTo(buffer); 25 | responseString = buffer.readUtf8(); 26 | } 27 | 28 | return new StringEntity(responseString, contentType); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/converters/ToOkhttp3Converter.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.converters; 2 | 3 | import static com.squareup.okhttp.internal.http.HttpMethod.permitsRequestBody; 4 | 5 | import com.squareup.okhttp.CacheControl; 6 | import com.squareup.okhttp.MediaType; 7 | import com.squareup.okhttp.Protocol; 8 | import com.squareup.okhttp.Request; 9 | import com.squareup.okhttp.Response; 10 | import java.io.BufferedReader; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.io.Reader; 15 | import java.nio.charset.Charset; 16 | import java.nio.charset.StandardCharsets; 17 | import java.security.cert.Certificate; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | import okhttp3.Headers; 25 | import okhttp3.TlsVersion; 26 | import okio.Buffer; 27 | import org.apache.http.Header; 28 | import org.apache.http.HttpEntity; 29 | import org.apache.http.HttpRequest; 30 | import org.apache.http.HttpResponse; 31 | import org.apache.http.client.entity.EntityBuilder; 32 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 33 | import org.apache.http.client.methods.HttpRequestWrapper; 34 | import org.apache.http.entity.ContentType; 35 | import org.apache.http.message.BasicHeader; 36 | 37 | public class ToOkhttp3Converter { 38 | 39 | private static final Logger logger = Logger.getLogger(ToOkhttp3Converter.class.getName()); 40 | 41 | private static final String APPLICATION_JSON = "application/json"; 42 | 43 | private ToOkhttp3Converter() { 44 | } 45 | 46 | private static okhttp3.MediaType convertOkhttpMediaTypeTo3(MediaType okhttpMediaType) { 47 | return okhttpMediaType == null ? okhttp3.MediaType.parse("") 48 | : okhttp3.MediaType.parse(okhttpMediaType.toString()); 49 | } 50 | 51 | public static okhttp3.RequestBody convertOkhttpRequestBodyTo3(Request request) { 52 | final okhttp3.MediaType contentType = request.body() == null ? okhttp3.MediaType.parse("") 53 | : convertOkhttpMediaTypeTo3(request.body().contentType()); 54 | try { 55 | final Request requestCopy = request.newBuilder().build(); 56 | String requestBodyString = ""; 57 | if (requestCopy.body() != null) { 58 | final Buffer buffer = new Buffer(); 59 | requestCopy.body().writeTo(buffer); 60 | requestBodyString = buffer.readUtf8(); 61 | } 62 | return okhttp3.RequestBody.create(contentType, requestBodyString); 63 | } catch (final IOException e) { 64 | return okhttp3.RequestBody 65 | .create(contentType, "[LoggingInterceptorError] : could not parse request body"); 66 | } 67 | } 68 | 69 | public static okhttp3.RequestBody convertApacheHttpRequestBodyTo3(HttpRequest request) { 70 | if (request instanceof HttpRequestWrapper) { 71 | final HttpRequest original = ((HttpRequestWrapper) request).getOriginal(); 72 | if (original instanceof HttpEntityEnclosingRequestBase) { 73 | final HttpEntity entity = ((HttpEntityEnclosingRequestBase) original).getEntity(); 74 | if (entity != null) { 75 | String requestBodyString; 76 | try { 77 | requestBodyString = readApacheHttpEntity(entity); 78 | } catch (IOException e) { 79 | logger.log(Level.SEVERE, e.getMessage(), e); 80 | return okhttp3.RequestBody 81 | .create(okhttp3.MediaType.parse(APPLICATION_JSON), 82 | "[LoggingInterceptorError] : could not parse request body"); 83 | } 84 | 85 | final HttpEntity newEntity = recreateHttpEntityFromString(requestBodyString, 86 | entity); 87 | ((HttpEntityEnclosingRequestBase) ((HttpRequestWrapper) request).getOriginal()) 88 | .setEntity(newEntity); 89 | 90 | final Header contentTypeHeader = Arrays 91 | .stream(((HttpRequestWrapper) request).getOriginal().getAllHeaders()) 92 | .filter(header -> header.getName().equals("Content-Type")) 93 | .findFirst() 94 | .orElse(new BasicHeader("Content-Type", APPLICATION_JSON)); 95 | 96 | return okhttp3.RequestBody 97 | .create(okhttp3.MediaType.parse(contentTypeHeader.getValue()), 98 | requestBodyString); 99 | } 100 | } 101 | } 102 | return okhttp3.RequestBody.create(okhttp3.MediaType.parse(APPLICATION_JSON), ""); 103 | } 104 | 105 | public static okhttp3.ResponseBody convertApacheHttpResponseBodyTo3(HttpResponse response) { 106 | final HttpEntity entity = response.getEntity(); 107 | if (entity != null) { 108 | String requestBodyString; 109 | try { 110 | requestBodyString = readApacheHttpEntity(entity); 111 | } catch (IOException e) { 112 | logger.log(Level.SEVERE, e.getMessage(), e); 113 | return okhttp3.ResponseBody.create(okhttp3.MediaType.parse(APPLICATION_JSON), 114 | "[LoggingInterceptorError] : could not parse response body"); 115 | } 116 | final Header contentType = response.getEntity().getContentType(); 117 | final String contentTypeString = contentType == null ? "" 118 | : String.format("%s, %s", contentType.getName(), contentType.getValue()); 119 | final HttpEntity newEntity = recreateHttpEntityFromString(requestBodyString, entity); 120 | response.setEntity(newEntity); 121 | return okhttp3.ResponseBody 122 | .create(okhttp3.MediaType.parse(contentTypeString), requestBodyString); 123 | } 124 | return okhttp3.ResponseBody.create(okhttp3.MediaType.parse(APPLICATION_JSON), ""); 125 | } 126 | 127 | private static String readApacheHttpEntity(HttpEntity entity) throws IOException { 128 | if (entity != null) { 129 | StringBuilder textBuilder = new StringBuilder(); 130 | try (Reader reader = new BufferedReader(new InputStreamReader 131 | (entity.getContent(), Charset.forName(StandardCharsets.UTF_8.name())))) { 132 | int c; 133 | while ((c = reader.read()) != -1) { 134 | textBuilder.append((char) c); 135 | } 136 | return textBuilder.toString(); 137 | } 138 | } else { 139 | return ""; 140 | } 141 | } 142 | 143 | private static HttpEntity recreateHttpEntityFromString(String httpEntityContent, 144 | HttpEntity entity) { 145 | final Header contentType = entity.getContentType(); 146 | final String contentTypeString = contentType == null ? "Content-Type=" + APPLICATION_JSON 147 | : String.format("%s=%s", contentType.getName(), contentType.getValue()); 148 | final Header contentEncodingHeader = entity.getContentEncoding(); 149 | final EntityBuilder entityBuilder = EntityBuilder 150 | .create() 151 | .setContentType(ContentType.parse(contentTypeString)) 152 | .setStream(new ByteArrayInputStream(httpEntityContent.getBytes())); 153 | if (contentEncodingHeader != null) { 154 | return entityBuilder 155 | .setContentEncoding(String 156 | .format("%s/%s", contentEncodingHeader.getName(), 157 | contentEncodingHeader.getValue())) 158 | .build(); 159 | } 160 | return entityBuilder.build(); 161 | } 162 | 163 | public static okhttp3.CacheControl convertOkhttpCacheControlTo3(CacheControl cacheControl) { 164 | return new okhttp3.CacheControl 165 | .Builder() 166 | .maxAge(cacheControl.maxAgeSeconds() == -1 ? 0 : cacheControl.maxAgeSeconds(), 167 | TimeUnit.SECONDS) 168 | .maxStale(cacheControl.maxStaleSeconds() == -1 ? 0 : cacheControl.maxStaleSeconds(), 169 | TimeUnit.SECONDS) 170 | .minFresh(cacheControl.minFreshSeconds() == -1 ? 9 : cacheControl.minFreshSeconds(), 171 | TimeUnit.SECONDS) 172 | .build(); 173 | } 174 | 175 | private static okhttp3.Request convertOkhttpRequestTo3(Request request) { 176 | final okhttp3.Request.Builder builder = new okhttp3.Request 177 | .Builder(); 178 | builder.url(request.url()); 179 | final Map> headersMap = request.headers().toMultimap(); 180 | headersMap.forEach( 181 | (String name, List values) -> builder 182 | .addHeader(name, String.join(";", values))); 183 | if (permitsRequestBody(request.method())) { 184 | builder.method(request.method(), convertOkhttpRequestBodyTo3(request)); 185 | } else { 186 | builder.method(request.method(), null); 187 | } 188 | builder.tag(request.tag()); 189 | builder.cacheControl(convertOkhttpCacheControlTo3(request.cacheControl())); 190 | return builder.build(); 191 | } 192 | 193 | private static okhttp3.Handshake convertOkhttpHandshakeTo3( 194 | com.squareup.okhttp.Handshake handshake) { 195 | if (handshake == null) { 196 | return null; 197 | } else { 198 | final String cipherSuite = handshake.cipherSuite(); 199 | final List peerCertificates = handshake.peerCertificates(); 200 | final List localCertificates = handshake.localCertificates(); 201 | return okhttp3.Handshake.get(TlsVersion.SSL_3_0, 202 | okhttp3.CipherSuite.forJavaName(cipherSuite), 203 | peerCertificates, 204 | localCertificates 205 | ); 206 | } 207 | } 208 | 209 | private static okhttp3.Headers convertOkhttpHeadersTo3(com.squareup.okhttp.Headers headers) { 210 | final Headers.Builder headersBuilder = new Headers.Builder(); 211 | headers.names().forEach(name -> headersBuilder.add(name, headers.get(name))); 212 | return headersBuilder.build(); 213 | } 214 | 215 | private static okhttp3.ResponseBody convertOkhttpResponseBodyTo3( 216 | com.squareup.okhttp.ResponseBody responseBody) { 217 | if (responseBody == null) { 218 | return null; 219 | } else { 220 | final MediaType mediaType = responseBody.contentType(); 221 | String responseBodyString = ""; 222 | try { 223 | responseBodyString = new String(responseBody.bytes(), Charset.defaultCharset()); 224 | } catch (IOException e) { 225 | logger.log(Level.SEVERE, e.getMessage(), e); 226 | } 227 | return okhttp3.ResponseBody 228 | .create(convertOkhttpMediaTypeTo3(mediaType), responseBodyString); 229 | } 230 | } 231 | 232 | private static okhttp3.Protocol convertOkhttpProtocolTo3(Protocol protocol) { 233 | switch (protocol) { 234 | case HTTP_1_0: 235 | return okhttp3.Protocol.HTTP_1_0; 236 | case HTTP_1_1: 237 | return okhttp3.Protocol.HTTP_1_1; 238 | case HTTP_2: 239 | return okhttp3.Protocol.HTTP_2; 240 | default: 241 | return okhttp3.Protocol.HTTP_1_1; 242 | } 243 | } 244 | 245 | private static okhttp3.Response convertBaseOkhttpResponseTo3(Response response) { 246 | if (response == null) { 247 | return null; 248 | } else { 249 | return new okhttp3.Response 250 | .Builder() 251 | .request(convertOkhttpRequestTo3(response.request())) 252 | .protocol(convertOkhttpProtocolTo3(response.protocol())) 253 | .code(response.code()) 254 | .message(response.message()) 255 | .handshake(convertOkhttpHandshakeTo3(response.handshake())) 256 | .headers(convertOkhttpHeadersTo3(response.headers())) 257 | .body(convertOkhttpResponseBodyTo3(response.body())) 258 | .build(); 259 | } 260 | } 261 | 262 | public static okhttp3.Response convertOkhttpResponseTo3(Response response) { 263 | if (response == null) { 264 | return null; 265 | } else { 266 | final okhttp3.Response okhttp3Response = convertBaseOkhttpResponseTo3(response); 267 | return okhttp3Response 268 | .newBuilder() 269 | .cacheResponse(convertBaseOkhttpResponseTo3(response.cacheResponse())) 270 | .networkResponse(convertBaseOkhttpResponseTo3(response.networkResponse())) 271 | .build(); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/converters/ToOkhttpConverter.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.converters; 2 | 3 | import com.squareup.okhttp.MediaType; 4 | import com.squareup.okhttp.RequestBody; 5 | import java.io.IOException; 6 | import okio.Buffer; 7 | 8 | public class ToOkhttpConverter { 9 | 10 | private ToOkhttpConverter() { 11 | } 12 | 13 | public static MediaType convertOkhttp3MediaType(okhttp3.MediaType okhttp3MediaType) { 14 | return okhttp3MediaType == null ? MediaType.parse("") 15 | : MediaType.parse(okhttp3MediaType.toString()); 16 | } 17 | 18 | public static RequestBody convertOkhtt3pRequestBody(okhttp3.Request request) { 19 | final MediaType contentType = request.body() == null ? MediaType.parse("") 20 | : convertOkhttp3MediaType(request.body().contentType()); 21 | try { 22 | final okhttp3.Request requestCopy = request.newBuilder().build(); 23 | 24 | String requestBodyString = ""; 25 | if (requestCopy.body() != null) { 26 | final Buffer buffer = new Buffer(); 27 | requestCopy.body().writeTo(buffer); 28 | requestBodyString = buffer.readUtf8(); 29 | } 30 | return RequestBody.create(contentType, requestBodyString); 31 | } catch (final IOException e) { 32 | return RequestBody 33 | .create(contentType, "[LoggingInterceptorError] : could not parse request body"); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/interceptors/ApacheHttpRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.interceptors; 2 | 3 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileRequest; 4 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonRequest; 5 | import static com.dkorobtsov.logging.TextUtils.isFileRequest; 6 | 7 | import com.dkorobtsov.logging.Level; 8 | import com.dkorobtsov.logging.LoggerConfig; 9 | import com.dkorobtsov.logging.RequestDetails; 10 | import java.util.Objects; 11 | import okhttp3.Request; 12 | import okhttp3.RequestBody; 13 | import org.apache.http.HttpRequest; 14 | import org.apache.http.HttpRequestInterceptor; 15 | import org.apache.http.protocol.HttpContext; 16 | 17 | public class ApacheHttpRequestInterceptor implements HttpRequestInterceptor { 18 | 19 | private final boolean isDebug; 20 | private final LoggerConfig loggerConfig; 21 | 22 | public ApacheHttpRequestInterceptor(LoggerConfig loggerConfig) { 23 | this.loggerConfig = loggerConfig; 24 | this.isDebug = loggerConfig.isDebug; 25 | } 26 | 27 | @Override 28 | public void process(HttpRequest request, HttpContext context) { 29 | if (isDebug && loggerConfig.level != Level.NONE) { 30 | final Request requestDetails = new RequestDetails.Builder().from(request).build(); 31 | final RequestBody requestBody = requestDetails.body(); 32 | 33 | String requestSubtype = null; 34 | 35 | if (requestBody != null && requestBody.contentType() != null) { 36 | requestSubtype = Objects.requireNonNull(requestBody.contentType()).subtype(); 37 | } 38 | 39 | if (isFileRequest(requestSubtype)) { 40 | printFileRequest(requestDetails, loggerConfig); 41 | } else { 42 | printJsonRequest(requestDetails, loggerConfig); 43 | } 44 | } 45 | } 46 | 47 | public LoggerConfig loggerConfig() { 48 | return this.loggerConfig; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/interceptors/ApacheHttpResponseInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.interceptors; 2 | 3 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileResponse; 4 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonResponse; 5 | import static com.dkorobtsov.logging.TextUtils.isFileRequest; 6 | 7 | import com.dkorobtsov.logging.Level; 8 | import com.dkorobtsov.logging.LoggerConfig; 9 | import com.dkorobtsov.logging.ResponseDetails; 10 | import java.io.IOException; 11 | import java.util.Objects; 12 | 13 | import org.apache.http.HttpEntity; 14 | import org.apache.http.HttpResponse; 15 | import org.apache.http.HttpResponseInterceptor; 16 | import org.apache.http.protocol.HttpContext; 17 | 18 | public class ApacheHttpResponseInterceptor implements HttpResponseInterceptor { 19 | 20 | private final boolean isDebug; 21 | private final LoggerConfig loggerConfig; 22 | 23 | public ApacheHttpResponseInterceptor(LoggerConfig loggerConfig) { 24 | this.loggerConfig = loggerConfig; 25 | this.isDebug = loggerConfig.isDebug; 26 | } 27 | 28 | @Override 29 | public void process(HttpResponse response, HttpContext context) throws IOException { 30 | if (isDebug && loggerConfig.level != Level.NONE) { 31 | String subtype = null; 32 | final HttpEntity entity = response.getEntity(); 33 | if (entity != null) { 34 | if (entity.getContentType() != null) { 35 | subtype = Objects.requireNonNull(entity.getContentType()).getValue(); 36 | } 37 | } 38 | 39 | ResponseDetails responseDetails = ResponseDetails 40 | .from(response, isFileRequest(subtype)); 41 | 42 | if (isFileRequest(subtype)) { 43 | printFileResponse(responseDetails, loggerConfig); 44 | } else { 45 | printJsonResponse(responseDetails, loggerConfig); 46 | } 47 | } 48 | } 49 | 50 | public LoggerConfig loggerConfig() { 51 | return this.loggerConfig; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/interceptors/Okhttp3LoggingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.interceptors; 2 | 3 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileRequest; 4 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileResponse; 5 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonRequest; 6 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonResponse; 7 | import static com.dkorobtsov.logging.TextUtils.isFileRequest; 8 | 9 | import com.dkorobtsov.logging.Level; 10 | import com.dkorobtsov.logging.LoggerConfig; 11 | import com.dkorobtsov.logging.ResponseDetails; 12 | import java.io.IOException; 13 | import java.util.Objects; 14 | import java.util.concurrent.TimeUnit; 15 | import okhttp3.Interceptor; 16 | import okhttp3.Request; 17 | import okhttp3.RequestBody; 18 | import okhttp3.Response; 19 | import okhttp3.ResponseBody; 20 | 21 | public class Okhttp3LoggingInterceptor implements Interceptor { 22 | 23 | private final boolean isDebug; 24 | private final LoggerConfig loggerConfig; 25 | 26 | public Okhttp3LoggingInterceptor(LoggerConfig loggerConfig) { 27 | this.loggerConfig = loggerConfig; 28 | this.isDebug = loggerConfig.isDebug; 29 | } 30 | 31 | @Override 32 | public Response intercept(Chain chain) throws IOException { 33 | Request request = chain.request(); 34 | 35 | if (!isDebug || loggerConfig.level == Level.NONE) { 36 | return chain.proceed(request); 37 | } 38 | 39 | final RequestBody requestBody = request.body(); 40 | 41 | String requestSubtype = null; 42 | if (requestBody != null && requestBody.contentType() != null) { 43 | requestSubtype = Objects.requireNonNull(requestBody.contentType()).subtype(); 44 | } 45 | 46 | if (isFileRequest(requestSubtype)) { 47 | printFileRequest(request, loggerConfig); 48 | } else { 49 | printJsonRequest(request, loggerConfig); 50 | } 51 | 52 | final long startTime = System.nanoTime(); 53 | final Response response = chain.proceed(request); 54 | final long responseTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); 55 | 56 | String subtype = null; 57 | final ResponseBody body; 58 | 59 | if (Objects.requireNonNull(response.body()).contentType() != null) { 60 | subtype = Objects.requireNonNull(response.body().contentType()).subtype(); 61 | } 62 | 63 | ResponseDetails responseDetails = ResponseDetails 64 | .from(request, response, responseTime, isFileRequest(subtype)); 65 | 66 | if (isFileRequest(subtype)) { 67 | printFileResponse(responseDetails, loggerConfig); 68 | return response; 69 | } else { 70 | printJsonResponse(responseDetails, loggerConfig); 71 | body = ResponseBody.create(responseDetails.contentType, responseDetails.originalBody); 72 | } 73 | 74 | return response.newBuilder() 75 | .body(body) 76 | .build(); 77 | } 78 | 79 | public LoggerConfig loggerConfig() { 80 | return this.loggerConfig; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/main/java/com/dkorobtsov/logging/interceptors/OkhttpLoggingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging.interceptors; 2 | 3 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileRequest; 4 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printFileResponse; 5 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonRequest; 6 | import static com.dkorobtsov.logging.ClientPrintingExecutor.printJsonResponse; 7 | import static com.dkorobtsov.logging.TextUtils.isFileRequest; 8 | import static com.dkorobtsov.logging.converters.ToOkhttp3Converter.convertOkhttpResponseTo3; 9 | import static com.dkorobtsov.logging.converters.ToOkhttpConverter.convertOkhttp3MediaType; 10 | 11 | import com.dkorobtsov.logging.Level; 12 | import com.dkorobtsov.logging.LoggerConfig; 13 | import com.dkorobtsov.logging.RequestDetails; 14 | import com.dkorobtsov.logging.ResponseDetails; 15 | import com.squareup.okhttp.Interceptor; 16 | import com.squareup.okhttp.MediaType; 17 | import com.squareup.okhttp.Request; 18 | import com.squareup.okhttp.Response; 19 | import com.squareup.okhttp.ResponseBody; 20 | import java.io.IOException; 21 | import java.util.Objects; 22 | import java.util.concurrent.TimeUnit; 23 | import okhttp3.RequestBody; 24 | 25 | public class OkhttpLoggingInterceptor implements Interceptor { 26 | 27 | private final boolean isDebug; 28 | private final LoggerConfig loggerConfig; 29 | 30 | public OkhttpLoggingInterceptor(LoggerConfig loggerConfig) { 31 | this.loggerConfig = loggerConfig; 32 | this.isDebug = loggerConfig.isDebug; 33 | } 34 | 35 | @Override 36 | public Response intercept(Chain chain) throws IOException { 37 | Request request = chain.request(); 38 | final okhttp3.Request requestDetails = new RequestDetails.Builder().from(request).build(); 39 | 40 | if (!isDebug || loggerConfig.level == Level.NONE) { 41 | return chain.proceed(request); 42 | } 43 | 44 | final RequestBody requestBody = requestDetails.body(); 45 | 46 | String requestSubtype = null; 47 | if (requestBody != null && requestBody.contentType() != null) { 48 | requestSubtype = Objects.requireNonNull(requestBody.contentType()).subtype(); 49 | } 50 | 51 | if (isFileRequest(requestSubtype)) { 52 | printFileRequest(requestDetails, loggerConfig); 53 | } else { 54 | printJsonRequest(requestDetails, loggerConfig); 55 | } 56 | 57 | final long requestStartTime = System.nanoTime(); 58 | final Response response = chain.proceed(request); 59 | final long responseTime = TimeUnit.NANOSECONDS 60 | .toMillis(System.nanoTime() - requestStartTime); 61 | 62 | String subtype = null; 63 | final ResponseBody body; 64 | 65 | if (Objects.requireNonNull(response.body()).contentType() != null) { 66 | subtype = Objects.requireNonNull(response.body().contentType()).subtype(); 67 | } 68 | 69 | ResponseDetails responseDetails = ResponseDetails 70 | .from(requestDetails, convertOkhttpResponseTo3(response), responseTime, 71 | isFileRequest(subtype)); 72 | 73 | if (isFileRequest(subtype)) { 74 | printFileResponse(responseDetails, loggerConfig); 75 | return response; 76 | } else { 77 | printJsonResponse(responseDetails, loggerConfig); 78 | final okhttp3.MediaType okhttp3MediaType = responseDetails.contentType; 79 | final MediaType mediaType = convertOkhttp3MediaType(okhttp3MediaType); 80 | body = ResponseBody.create(mediaType, responseDetails.originalBody); 81 | } 82 | 83 | return response.newBuilder() 84 | .body(body) 85 | .build(); 86 | } 87 | 88 | public LoggerConfig loggerConfig() { 89 | return this.loggerConfig; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static com.dkorobtsov.logging.converters.ToOkhttpConverter.convertOkhtt3pRequestBody; 4 | import static org.junit.Assert.fail; 5 | 6 | import com.dkorobtsov.logging.converters.ToApacheHttpClientConverter; 7 | import com.dkorobtsov.logging.interceptors.ApacheHttpRequestInterceptor; 8 | import com.dkorobtsov.logging.interceptors.ApacheHttpResponseInterceptor; 9 | import com.dkorobtsov.logging.interceptors.Okhttp3LoggingInterceptor; 10 | import com.dkorobtsov.logging.interceptors.OkhttpLoggingInterceptor; 11 | import com.squareup.okhttp.mockwebserver.MockResponse; 12 | import com.squareup.okhttp.mockwebserver.MockWebServer; 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Objects; 17 | import java.util.concurrent.LinkedBlockingQueue; 18 | import java.util.concurrent.ThreadPoolExecutor; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.logging.LogManager; 21 | import java.util.logging.Logger; 22 | import okhttp3.ConnectionPool; 23 | import okhttp3.Dispatcher; 24 | import okhttp3.Interceptor; 25 | import okhttp3.MediaType; 26 | import okhttp3.OkHttpClient; 27 | import okhttp3.Request; 28 | import okhttp3.RequestBody; 29 | import org.apache.http.HttpEntity; 30 | import org.apache.http.client.HttpClient; 31 | import org.apache.http.client.methods.HttpGet; 32 | import org.apache.http.client.methods.HttpPut; 33 | import org.apache.http.client.methods.HttpUriRequest; 34 | import org.apache.http.entity.ContentType; 35 | import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; 36 | import org.apache.http.impl.client.HttpClientBuilder; 37 | import org.apache.http.message.BasicHeader; 38 | import org.junit.BeforeClass; 39 | import org.junit.Rule; 40 | 41 | public abstract class BaseTest { 42 | 43 | private static final ConnectionPool connectionPool = new ConnectionPool(); 44 | private static final Dispatcher dispatcher = new Dispatcher(); 45 | private static final int MAX_IDLE_CONNECTIONS = 10; 46 | private static final int KEEP_ALIVE_DURATION_MS = 60 * 1000; 47 | private final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager 48 | .getLogger(Log4j2LoggerTest.class); 49 | 50 | @Rule 51 | public MockWebServer server = new MockWebServer(); 52 | 53 | @BeforeClass 54 | public static void cleanAnyExistingJavaUtilityLoggingConfigurations() { 55 | LogManager.getLogManager().reset(); 56 | Logger globalLogger = Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME); 57 | globalLogger.setLevel(java.util.logging.Level.OFF); 58 | } 59 | 60 | /** 61 | * Returns default OkHttp request for use in tests. 62 | */ 63 | Request defaultOkhttp3Request() { 64 | return new Request.Builder() 65 | .url(String.valueOf(server.url("/"))) 66 | .build(); 67 | } 68 | 69 | HttpUriRequest defaultApacheHttpRequest() { 70 | return new HttpGet(server.url("/").uri()); 71 | } 72 | 73 | com.squareup.okhttp.Request defaultOkhttpRequest() { 74 | return new com.squareup.okhttp.Request.Builder() 75 | .url(String.valueOf(server.url("/"))) 76 | .build(); 77 | } 78 | 79 | /** 80 | * Returns OkHttpClient for all interceptor tests to use as a starting point. 81 | * 82 | *

The shared instance allows all tests to share a single connection pool, which prevents 83 | * idle connections from consuming unnecessary resources while connections wait to be evicted. 84 | */ 85 | OkHttpClient defaultOkhttp3ClientWithInterceptor(Interceptor interceptor) { 86 | return new OkHttpClient.Builder() 87 | .connectionPool(connectionPool) 88 | .dispatcher(dispatcher) 89 | .addNetworkInterceptor(interceptor) 90 | .build(); 91 | } 92 | 93 | HttpClient defaultApacheClientWithInterceptors(ApacheHttpRequestInterceptor requestInterceptor, 94 | ApacheHttpResponseInterceptor responseInterceptor) { 95 | return HttpClientBuilder 96 | .create() 97 | .addInterceptorFirst(requestInterceptor) 98 | .addInterceptorFirst(responseInterceptor) 99 | .setMaxConnTotal(MAX_IDLE_CONNECTIONS) 100 | .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) 101 | .build(); 102 | } 103 | 104 | com.squareup.okhttp.OkHttpClient defaultOkhttpClientWithInterceptor( 105 | com.squareup.okhttp.Interceptor interceptor) { 106 | final com.squareup.okhttp.OkHttpClient okHttpClient = new com.squareup.okhttp.OkHttpClient() 107 | .setConnectionPool( 108 | new com.squareup.okhttp.ConnectionPool(MAX_IDLE_CONNECTIONS, 109 | KEEP_ALIVE_DURATION_MS)) 110 | .setDispatcher(new com.squareup.okhttp.Dispatcher()); 111 | okHttpClient.interceptors().add(interceptor); 112 | return okHttpClient; 113 | } 114 | 115 | void attachLoggerToInterceptorWithDefaultRequest(String interceptorVersion, 116 | LogWriter log4j2Writer) throws IOException { 117 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 118 | attachLoggerToOkttp3InterceptorWithDefaultRequest(log4j2Writer); 119 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 120 | attachLoggerToOkhttpInterceptorWithDefaultRequest(log4j2Writer); 121 | } else if (interceptorVersion 122 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 123 | attachLoggerToApacheRequestInterceptorWithDefaultRequest(log4j2Writer); 124 | } else { 125 | fail(String.format( 126 | "I couldn't recognize %s interceptor version. I currently support okhttp and okhttp3 versions", 127 | interceptorVersion)); 128 | } 129 | } 130 | 131 | void attachLoggerToInterceptor(String interceptorVersion, LogWriter log4j2Writer, 132 | Request okhttp3Request, com.squareup.okhttp.Request okhttpRequest, 133 | HttpUriRequest apacheHttpRequest) throws IOException { 134 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 135 | attachLoggerToOkhttp3Interceptor(log4j2Writer, okhttp3Request); 136 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 137 | attachLoggerToOkHttpInterceptor(log4j2Writer, okhttpRequest); 138 | } else if (interceptorVersion 139 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 140 | attachLoggerToApacheRequestInterceptor(log4j2Writer, apacheHttpRequest); 141 | } else { 142 | fail(String.format( 143 | "I couldn't recognize %s interceptor version. I currently support okhttp and okhttp3 versions", 144 | interceptorVersion)); 145 | } 146 | } 147 | 148 | private void attachLoggerToOkhttpInterceptorWithDefaultRequest(LogWriter logWriter) 149 | throws IOException { 150 | attachLoggerToOkHttpInterceptor(logWriter, defaultOkhttpRequest()); 151 | } 152 | 153 | private void attachLoggerToOkHttpInterceptor(LogWriter logWriter, 154 | com.squareup.okhttp.Request request) throws IOException { 155 | OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 156 | .logger(logWriter) 157 | .buildForOkhttp(); 158 | defaultOkhttpClientWithInterceptor(interceptor) 159 | .newCall(request) 160 | .execute(); 161 | } 162 | 163 | private void attachLoggerToOkttp3InterceptorWithDefaultRequest(LogWriter logWriter) 164 | throws IOException { 165 | attachLoggerToOkhttp3Interceptor(logWriter, defaultOkhttp3Request()); 166 | } 167 | 168 | private void attachLoggerToOkhttp3Interceptor(LogWriter logWriter, Request request) 169 | throws IOException { 170 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 171 | .logger(logWriter) 172 | .buildForOkhttp3(); 173 | 174 | log.debug("Sending request."); 175 | defaultOkhttp3ClientWithInterceptor(interceptor) 176 | .newCall(request) 177 | .execute(); 178 | } 179 | 180 | private void attachLoggerToApacheRequestInterceptorWithDefaultRequest(LogWriter logWriter) 181 | throws IOException { 182 | attachLoggerToApacheRequestInterceptor(logWriter, defaultApacheHttpRequest()); 183 | } 184 | 185 | private void attachLoggerToApacheRequestInterceptor(LogWriter logWriter, HttpUriRequest request) 186 | throws IOException { 187 | ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 188 | .logger(logWriter) 189 | .buildForApacheHttpClientRequest(); 190 | 191 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 192 | .logger(logWriter) 193 | .buildFordApacheHttpClientResponse(); 194 | log.debug("Sending request."); 195 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 196 | .execute(request); 197 | } 198 | 199 | List interceptedRequest(RequestBody body, String loggerVersion, 200 | boolean provideExecutor, boolean preserveTrailingSpaces) throws IOException { 201 | return interceptedRequest(body, null, loggerVersion, provideExecutor, 202 | preserveTrailingSpaces); 203 | } 204 | 205 | List interceptedRequest(RequestBody body, Integer maxLineLength, String loggerVersion, 206 | boolean provideExecutor, boolean preserveTrailingSpaces) throws IOException { 207 | server.enqueue(new MockResponse().setResponseCode(200)); 208 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 209 | 210 | Request okhttp3Request = new Request.Builder() 211 | .url(String.valueOf(server.url("/"))) 212 | .put(body) 213 | .build(); 214 | 215 | final LoggingInterceptor.Builder builder = new LoggingInterceptor.Builder() 216 | .logger(testLogger); 217 | 218 | if (Objects.nonNull(maxLineLength)) { 219 | builder.maxLineLength(maxLineLength); 220 | } 221 | 222 | if (provideExecutor) { 223 | builder.executor(new ThreadPoolExecutor(1, 1, 224 | 50L, TimeUnit.MILLISECONDS, 225 | new LinkedBlockingQueue<>())); 226 | } 227 | 228 | InterceptorVersion interceptorVersion = InterceptorVersion.parse(loggerVersion); 229 | switch (interceptorVersion) { 230 | case OKHTTP: 231 | OkhttpLoggingInterceptor okhttpLoggingInterceptor = builder 232 | .buildForOkhttp(); 233 | 234 | final com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder() 235 | .url(String.valueOf(server.url("/"))) 236 | .put(convertOkhtt3pRequestBody(okhttp3Request)) 237 | .build(); 238 | 239 | defaultOkhttpClientWithInterceptor(okhttpLoggingInterceptor) 240 | .newCall(request) 241 | .execute(); 242 | 243 | return testLogger.loggerOutput(preserveTrailingSpaces); 244 | 245 | case OKHTTP3: 246 | Okhttp3LoggingInterceptor okhttp3LoggingInterceptor = builder 247 | .buildForOkhttp3(); 248 | 249 | defaultOkhttp3ClientWithInterceptor(okhttp3LoggingInterceptor) 250 | .newCall(okhttp3Request) 251 | .execute(); 252 | 253 | return testLogger.loggerOutput(preserveTrailingSpaces); 254 | 255 | case APACHE_HTTPCLIENT_REQUEST: 256 | final ApacheHttpRequestInterceptor requestInterceptor = builder 257 | .buildForApacheHttpClientRequest(); 258 | 259 | final ApacheHttpResponseInterceptor responseInterceptor = builder 260 | .buildFordApacheHttpClientResponse(); 261 | 262 | final HttpPut httpPut = new HttpPut(server.url("/").uri()); 263 | final MediaType mediaType = 264 | body.contentType() == null ? MediaType 265 | .parse(ContentType.APPLICATION_JSON.toString()) 266 | : body.contentType(); 267 | 268 | ContentType contentType = ContentType.create( 269 | String.format("%s/%s", Objects.requireNonNull(mediaType).type(), 270 | mediaType.subtype())); 271 | 272 | final HttpEntity entity = ToApacheHttpClientConverter 273 | .okhttp3RequestBodyToStringEntity(body, contentType); 274 | 275 | httpPut.setEntity(entity); 276 | httpPut.setHeader(new BasicHeader("Content-Type", mediaType.toString())); 277 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 278 | .execute(httpPut); 279 | 280 | return testLogger.loggerOutput(preserveTrailingSpaces); 281 | 282 | default: 283 | fail(String 284 | .format( 285 | "I didn't recognize %s version. I support 'okhttp' and 'okhttp3' versions", 286 | loggerVersion)); 287 | return Arrays.asList(new String[1]); 288 | } 289 | } 290 | 291 | List interceptedResponse(String contentType, String body, String loggerVersion, 292 | boolean provideExecutors, boolean preserveTrailingSpaces) throws IOException { 293 | return interceptedResponse(contentType, body, null, loggerVersion, 294 | provideExecutors, preserveTrailingSpaces); 295 | } 296 | 297 | List interceptedResponse(String contentType, String body, Integer maxLineLength, 298 | String loggerVersion, 299 | boolean provideExecutors, boolean preserveTrailingSpaces) 300 | throws IOException { 301 | 302 | server.enqueue(new MockResponse() 303 | .setResponseCode(200) 304 | .setHeader("Content-Type", contentType) 305 | .setBody(body)); 306 | 307 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 308 | final LoggingInterceptor.Builder builder = new LoggingInterceptor.Builder() 309 | .logger(testLogger); 310 | 311 | if (provideExecutors) { 312 | builder.executor(new ThreadPoolExecutor(1, 1, 313 | 50L, TimeUnit.MILLISECONDS, 314 | new LinkedBlockingQueue<>())); 315 | } 316 | 317 | if (Objects.nonNull(maxLineLength)) { 318 | builder.maxLineLength(maxLineLength); 319 | } 320 | 321 | InterceptorVersion interceptorVersion = InterceptorVersion.parse(loggerVersion); 322 | switch (interceptorVersion) { 323 | case OKHTTP: 324 | OkhttpLoggingInterceptor okhttpLoggingInterceptor = builder 325 | .buildForOkhttp(); 326 | defaultOkhttpClientWithInterceptor(okhttpLoggingInterceptor) 327 | .newCall(defaultOkhttpRequest()) 328 | .execute(); 329 | break; 330 | 331 | case OKHTTP3: 332 | Okhttp3LoggingInterceptor okhttp3LoggingInterceptor = builder 333 | .buildForOkhttp3(); 334 | 335 | defaultOkhttp3ClientWithInterceptor(okhttp3LoggingInterceptor) 336 | .newCall(defaultOkhttp3Request()) 337 | .execute(); 338 | break; 339 | 340 | case APACHE_HTTPCLIENT_REQUEST: 341 | final ApacheHttpRequestInterceptor requestInterceptor = builder 342 | .buildForApacheHttpClientRequest(); 343 | 344 | final ApacheHttpResponseInterceptor responseInterceptor = builder 345 | .buildFordApacheHttpClientResponse(); 346 | 347 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 348 | .execute(defaultApacheHttpRequest()); 349 | break; 350 | 351 | default: 352 | fail(String.format( 353 | "I couldn't recognize %s interceptor version. I only support okhttp and okhttp3 versions at the moment", 354 | loggerVersion)); 355 | return Arrays.asList(new String[1]); 356 | 357 | } 358 | 359 | return testLogger.loggerOutput(preserveTrailingSpaces); 360 | } 361 | 362 | } 363 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/ClientPrintingExecutorNegativeUnitTests.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.util.concurrent.Executors; 4 | import okhttp3.Request; 5 | import org.junit.Test; 6 | 7 | public class ClientPrintingExecutorNegativeUnitTests { 8 | 9 | @Test 10 | public void testInterruptingPrintingJsonRequestDoesntCrashProcess() { 11 | final Request request = new Request.Builder() 12 | .get() 13 | .url("http://google.com") 14 | .build(); 15 | 16 | final LoggerConfig loggerConfig = LoggerConfig.builder() 17 | .executor(Executors.newCachedThreadPool()).build(); 18 | 19 | Thread.currentThread().interrupt(); 20 | ClientPrintingExecutor.printJsonRequest(request, loggerConfig); 21 | } 22 | 23 | @Test 24 | public void testInterruptingPrintingFileRequestDoesntCrashProcess() { 25 | final Request request = new Request.Builder() 26 | .get() 27 | .url("http://google.com") 28 | .build(); 29 | 30 | final LoggerConfig loggerConfig = LoggerConfig.builder() 31 | .executor(Executors.newCachedThreadPool()).build(); 32 | 33 | Thread.currentThread().interrupt(); 34 | ClientPrintingExecutor.printFileRequest(request, loggerConfig); 35 | } 36 | 37 | @Test 38 | public void testInterruptingPrintingJsonResponseDoesntCrashProcess() { 39 | final ResponseDetails responseDetails = ResponseDetails.builder().build(); 40 | final LoggerConfig loggerConfig = LoggerConfig.builder() 41 | .executor(Executors.newCachedThreadPool()).build(); 42 | 43 | Thread.currentThread().interrupt(); 44 | ClientPrintingExecutor.printFileResponse(responseDetails, loggerConfig); 45 | } 46 | 47 | @Test 48 | public void testInterruptingPrintingFileResponseDoesntCrashProcess() { 49 | final ResponseDetails responseDetails = ResponseDetails.builder().build(); 50 | final LoggerConfig loggerConfig = LoggerConfig.builder() 51 | .executor(Executors.newCachedThreadPool()).build(); 52 | 53 | Thread.currentThread().interrupt(); 54 | ClientPrintingExecutor.printFileResponse(responseDetails, loggerConfig); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/FormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class FormatterTest { 7 | 8 | private final static String TEST_MESSAGE = "Test"; 9 | 10 | @Test 11 | public void formatterTest_messageOnly_containsOneElement() { 12 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 13 | 14 | testLogger.log(TEST_MESSAGE); 15 | 16 | TestUtil.assertLogEntryElementsCount( 17 | testLogger.lastFormattedEvent(true), 1); 18 | } 19 | 20 | @Test 21 | public void formatterTest_messageOnly_containsOnlyMessage() { 22 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 23 | 24 | testLogger.log(TEST_MESSAGE); 25 | 26 | Assert.assertEquals("Logger output should contain message only", 27 | TEST_MESSAGE, testLogger.lastFormattedEvent(false)); 28 | } 29 | 30 | @Test 31 | public void formatterTest_full_containsFourElements() { 32 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_FULL); 33 | 34 | testLogger.log(TEST_MESSAGE); 35 | 36 | TestUtil.assertLogEntryElementsCount( 37 | testLogger.lastFormattedEvent(true), 4); 38 | } 39 | 40 | @Test 41 | public void formatterTest_full_includesThreadName() { 42 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_FULL); 43 | 44 | testLogger.log(TEST_MESSAGE); 45 | 46 | Assert.assertTrue("Logger output should include thread name.", 47 | testLogger.lastFormattedEvent(true) 48 | .contains(Thread.currentThread().getName())); 49 | } 50 | 51 | @Test 52 | public void formatterTest_full_includesMessage() { 53 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_FULL); 54 | 55 | testLogger.log(TEST_MESSAGE); 56 | 57 | Assert.assertTrue("Logger output should include message text.", 58 | testLogger.lastFormattedEvent(true) 59 | .contains(TEST_MESSAGE)); 60 | } 61 | 62 | @Test 63 | public void formatterTest_full_includesLevel() { 64 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_FULL); 65 | 66 | testLogger.log(TEST_MESSAGE); 67 | 68 | Assert.assertTrue("Logger output should include logging level.", 69 | testLogger.lastFormattedEvent(true) 70 | .contains("INFO")); 71 | } 72 | 73 | @Test 74 | public void formatterTest_full_startsWithDate() { 75 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_FULL); 76 | 77 | testLogger.log(TEST_MESSAGE); 78 | 79 | TestUtil.assertEntryStartsWithParsableDate( 80 | testLogger.lastFormattedEvent(true)); 81 | } 82 | 83 | 84 | @Test 85 | public void formatterTest_dateMessage_containsTwoElements() { 86 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_MESSAGE); 87 | 88 | testLogger.log(TEST_MESSAGE); 89 | 90 | TestUtil.assertLogEntryElementsCount( 91 | testLogger.lastFormattedEvent(true), 2); 92 | } 93 | 94 | @Test 95 | public void formatterTest_dateMessage_includesMessage() { 96 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_MESSAGE); 97 | 98 | testLogger.log(TEST_MESSAGE); 99 | 100 | Assert.assertTrue("Logger output should include message text.", 101 | testLogger.lastFormattedEvent(true) 102 | .contains(TEST_MESSAGE)); 103 | } 104 | 105 | @Test 106 | public void formatterTest_dateMessage_startsWithDate() { 107 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_MESSAGE); 108 | 109 | testLogger.log(TEST_MESSAGE); 110 | 111 | TestUtil.assertEntryStartsWithParsableDate( 112 | testLogger.lastFormattedEvent(true)); 113 | } 114 | 115 | @Test 116 | public void formatterTest_dateLevelMessage_containsThreeElements() { 117 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_LEVEL_MESSAGE); 118 | 119 | testLogger.log(TEST_MESSAGE); 120 | 121 | TestUtil.assertLogEntryElementsCount(testLogger 122 | .lastFormattedEvent(true), 3); 123 | } 124 | 125 | @Test 126 | public void formatterTest_dateLevelMessage_includesMessage() { 127 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_LEVEL_MESSAGE); 128 | 129 | testLogger.log(TEST_MESSAGE); 130 | 131 | Assert.assertTrue("Logger output should include message text.", 132 | testLogger.lastFormattedEvent(true) 133 | .contains(TEST_MESSAGE)); 134 | } 135 | 136 | @Test 137 | public void formatterTest_dateLevelMessage_includesLevel() { 138 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_LEVEL_MESSAGE); 139 | 140 | testLogger.log(TEST_MESSAGE); 141 | 142 | Assert.assertTrue("Logger output should include logging level.", 143 | testLogger.lastFormattedEvent(true) 144 | .contains("INFO")); 145 | } 146 | 147 | @Test 148 | public void formatterTest_dateLevelMessage_startsWithDate() { 149 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_LEVEL_MESSAGE); 150 | 151 | testLogger.log(TEST_MESSAGE); 152 | 153 | TestUtil.assertEntryStartsWithParsableDate( 154 | testLogger.lastFormattedEvent(true)); 155 | } 156 | 157 | @Test 158 | public void formatterTest_levelMessage_containsTwoElements() { 159 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_LEVEL_MESSAGE); 160 | 161 | testLogger.log(TEST_MESSAGE); 162 | 163 | TestUtil.assertLogEntryElementsCount( 164 | testLogger.lastFormattedEvent(true), 2); 165 | } 166 | 167 | @Test 168 | public void formatterTest_levelMessage_includesMessage() { 169 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_LEVEL_MESSAGE); 170 | 171 | testLogger.log(TEST_MESSAGE); 172 | 173 | Assert.assertTrue("Logger output should include message text.", 174 | testLogger.lastFormattedEvent(true) 175 | .contains(TEST_MESSAGE)); 176 | } 177 | 178 | @Test 179 | public void formatterTest_levelMessage_containsLevel() { 180 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_LEVEL_MESSAGE); 181 | 182 | testLogger.log(TEST_MESSAGE); 183 | 184 | Assert.assertTrue("Logger output should include logging level.", 185 | testLogger.lastFormattedEvent(true) 186 | .contains("INFO")); 187 | } 188 | 189 | @Test 190 | public void formatterTest_threadMessage_containsTwoElements() { 191 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_LEVEL_MESSAGE); 192 | 193 | testLogger.log(TEST_MESSAGE); 194 | 195 | TestUtil.assertLogEntryElementsCount( 196 | testLogger.lastFormattedEvent(true), 2); 197 | } 198 | 199 | @Test 200 | public void formatterTest_threadMessage_includesMessage() { 201 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 202 | 203 | testLogger.log(TEST_MESSAGE); 204 | 205 | Assert.assertTrue("Logger output should include message text.", 206 | testLogger.lastFormattedEvent(true) 207 | .contains(TEST_MESSAGE)); 208 | } 209 | 210 | @Test 211 | public void formatterTest_threadMessage_containsThreadName() { 212 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 213 | 214 | testLogger.log(TEST_MESSAGE); 215 | 216 | Assert.assertTrue("Logger output should include thread name.", 217 | testLogger.lastFormattedEvent(true) 218 | .contains(Thread.currentThread().getName())); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/InterceptorBodyHandlingTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.List; 10 | import junitparams.JUnitParamsRunner; 11 | import junitparams.Parameters; 12 | import okhttp3.MediaType; 13 | import okhttp3.RequestBody; 14 | import okio.BufferedSink; 15 | import okio.Okio; 16 | import okio.Source; 17 | import org.junit.Rule; 18 | import org.junit.Test; 19 | import org.junit.rules.TemporaryFolder; 20 | import org.junit.runner.RunWith; 21 | 22 | @RunWith(JUnitParamsRunner.class) 23 | public class InterceptorBodyHandlingTest extends BaseTest { 24 | 25 | private static final String SIMPLE_JSON = "{name: \"John\", age: 31, city: \"New York\"}"; 26 | 27 | private static final String PREFORMATTED_JSON_BODY = "" 28 | + " {\n" 29 | + " \"id\": 431169,\n" 30 | + " \"category\": {\n" 31 | + " \"id\": 0,\n" 32 | + " \"name\": \"string\"\n" 33 | + " },\n" 34 | + " \"name\": \"doggie\",\n" 35 | + " \"photoUrls\": [\n" 36 | + " \"string\"\n" 37 | + " ],\n" 38 | + " \"tags\": [\n" 39 | + " {\n" 40 | + " \"id\": 0,\n" 41 | + " \"name\": \"string\"\n" 42 | + " }\n" 43 | + " ],\n" 44 | + " \"status\": \"available\"\n" 45 | + " }"; 46 | 47 | private static final String MALFORMED_JSON_BODY = "" 48 | + " {\n" 49 | + " \"id\": 431169,\n" 50 | + " \"category\": {\n" 51 | + " \"id\": 0,\n" 52 | + " \"name\": \"string\"\n" 53 | + " \"name\": \"doggie\",\n" 54 | + " \"photoUrls\": [\n" 55 | + " \"string\"\n" 56 | + " \"tags\": [\n" 57 | + " {\n" 58 | + " \"id\": 0,\n" 59 | + " \"name\": \"string\"\n" 60 | + " }\n" 61 | + " ],\n" 62 | + " \"status\": \"available\"\n" 63 | + " }"; 64 | 65 | private static final String XML_BODY = 66 | "" 67 | + "" 68 | + "Goat" 69 | + "Leopard" 70 | + "Zebra " 71 | + ""; 72 | 73 | private static final String MALFORMED_XML_BODY = 74 | "" 75 | + "" 76 | + "Goat" 77 | + "animal id=\"1\" species=\"Panthera pardus\">Leopard" 78 | + "Zebra " 79 | + ""; 80 | 81 | private static final String HTML_BODY = "" 82 | + "" 83 | + "Error 404 Not Found" 84 | + "

HTTP ERROR 404

" 85 | + "
Not Found
"; 86 | 87 | private static final String MALFORMED_HTML_BODY = "" 88 | + "" 89 | + "Error 404 Not Found" 90 | + "

HTTP ERROR 404

" 91 | + "
Not Found
"; 92 | 93 | @Rule 94 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 95 | 96 | @Test 97 | @Parameters({ 98 | "okhttp, true", "okhttp, false", 99 | "okhttp3, true", "okhttp3, false", 100 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 101 | }) 102 | public void interceptorAbleToHandleBody_simpleJsonRequest(String loggerVersion, 103 | boolean provideExecutor) 104 | throws IOException { 105 | final List loggerOutput = interceptedRequest(RequestBody 106 | .create(MediaType.parse("application/json"), SIMPLE_JSON), 107 | loggerVersion, provideExecutor, true); 108 | 109 | assertThat(loggerOutput).contains(" \"city\": \"New York\", "); 110 | } 111 | 112 | @Test 113 | @Parameters({ 114 | "okhttp, true", "okhttp, false", 115 | "okhttp3, true", "okhttp3, false", 116 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 117 | }) 118 | public void interceptorAbleToHandleBody_simpleJsonResponse(String loggerVersion, 119 | boolean provideExecutor) throws IOException { 120 | final List loggerOutput = interceptedResponse("application/json", SIMPLE_JSON, 121 | loggerVersion, provideExecutor, true); 122 | 123 | assertThat(loggerOutput).contains(" \"city\": \"New York\", "); 124 | } 125 | 126 | @Test 127 | @Parameters({ 128 | "okhttp, true", "okhttp, false", 129 | "okhttp3, true", "okhttp3, false", 130 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 131 | }) 132 | public void interceptorAbleToHandleBody_preformattedJsonRequest(String loggerVersion, 133 | boolean provideExecutor) 134 | throws IOException { 135 | final List loggerOutput = interceptedRequest(RequestBody 136 | .create(MediaType.parse("application/json"), PREFORMATTED_JSON_BODY), 137 | loggerVersion, provideExecutor, true); 138 | 139 | assertThat(loggerOutput).containsSequence(" \"name\": \"doggie\", "); 140 | } 141 | 142 | @Test 143 | @Parameters({ 144 | "okhttp, true", "okhttp, false", 145 | "okhttp3, true", "okhttp3, false", 146 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 147 | }) 148 | public void interceptorAbleToHandleBody_preformattedJsonResponse(String loggerVersion, 149 | boolean provideExecutor) throws IOException { 150 | final List loggerOutput = interceptedResponse("application/json", 151 | PREFORMATTED_JSON_BODY, 152 | loggerVersion, provideExecutor, true); 153 | 154 | assertThat(loggerOutput).containsSequence(" \"name\": \"doggie\", "); 155 | } 156 | 157 | @Test 158 | @Parameters({ 159 | "okhttp, true", "okhttp, false", 160 | "okhttp3, true", "okhttp3, false", 161 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 162 | }) 163 | public void interceptorAbleToHandleBody_malformedJsonRequest(String loggerVersion, 164 | boolean provideExecutor) throws IOException { 165 | final List loggerOutput = interceptedRequest(RequestBody 166 | .create(MediaType.parse("application/json"), MALFORMED_JSON_BODY), loggerVersion, 167 | provideExecutor, false); 168 | 169 | loggerOutput 170 | .stream() 171 | .filter(it -> 172 | it.startsWith("\"status\": \"available\"")) 173 | .findFirst() 174 | .orElseThrow(() -> 175 | new AssertionError("Interceptor should be able to handle xml response body.")); 176 | } 177 | 178 | @Test 179 | @Parameters({ 180 | "okhttp, true", "okhttp, false", 181 | "okhttp3, true", "okhttp3, false", 182 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 183 | }) 184 | public void interceptorAbleToHandleBody_malformedJsonResponse(String loggerVersion, 185 | boolean provideExecutor) throws IOException { 186 | final List loggerOutput = interceptedResponse( 187 | "application/json", MALFORMED_JSON_BODY, loggerVersion, provideExecutor, false); 188 | 189 | loggerOutput 190 | .stream() 191 | .filter(it -> 192 | it.startsWith("\"status\": \"available\"")) 193 | .findFirst() 194 | .orElseThrow(() -> 195 | new AssertionError("Interceptor should be able to handle xml response body.")); 196 | } 197 | 198 | @Test 199 | @Parameters({ 200 | "okhttp, true", "okhttp, false", 201 | "okhttp3, true", "okhttp3, false", 202 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 203 | }) 204 | public void interceptorAbleToHandleBody_htmlRequest(String loggerVersion, 205 | boolean provideExecutor) 206 | throws IOException { 207 | final List loggerOutput = interceptedRequest(RequestBody 208 | .create(MediaType.parse("text/html"), HTML_BODY), 209 | loggerVersion, provideExecutor, false); 210 | 211 | assertThat(loggerOutput).contains("Error 404 Not Found"); 212 | } 213 | 214 | @Test 215 | @Parameters({ 216 | "okhttp, true", "okhttp, false", 217 | "okhttp3, true", "okhttp3, false", 218 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 219 | }) 220 | public void interceptorAbleToHandleBody_htmlResponse(String loggerVersion, 221 | boolean provideExecutor) 222 | throws IOException { 223 | final List loggerOutput = interceptedResponse( 224 | "text/html", HTML_BODY, loggerVersion, provideExecutor, false); 225 | 226 | assertThat(loggerOutput).contains("Error 404 Not Found"); 227 | } 228 | 229 | @Test 230 | @Parameters({ 231 | "okhttp, true", "okhttp, false", 232 | "okhttp3, true", "okhttp3, false", 233 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 234 | }) 235 | public void interceptorAbleToHandleBody_malformedHtmlRequest(String loggerVersion, 236 | boolean provideExecutor) throws IOException { 237 | final List loggerOutput = interceptedRequest(RequestBody 238 | .create(MediaType.parse("text/html"), MALFORMED_HTML_BODY), loggerVersion, 239 | provideExecutor, false); 240 | 241 | loggerOutput 242 | .stream() 243 | .filter(it -> 244 | it.startsWith( 245 | "")) 246 | .findFirst() 247 | .orElseThrow(() -> 248 | new AssertionError("Interceptor should be able to handle html request body.")); 249 | } 250 | 251 | @Test 252 | @Parameters({ 253 | "okhttp, true", "okhttp, false", 254 | "okhttp3, true", "okhttp3, false", 255 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 256 | }) 257 | public void interceptorAbleToHandleBody_malformedHtmlResponse(String loggerVersion, 258 | boolean provideExecutor) throws IOException { 259 | final List loggerOutput = interceptedResponse( 260 | "text/html", MALFORMED_HTML_BODY, loggerVersion, provideExecutor, false); 261 | 262 | loggerOutput 263 | .stream() 264 | .filter(it -> 265 | it.startsWith( 266 | "")) 267 | .findFirst() 268 | .orElseThrow(() -> 269 | new AssertionError("Interceptor should be able to handle html response body.")); 270 | } 271 | 272 | @Test 273 | @Parameters({ 274 | "okhttp, true", "okhttp, false", 275 | "okhttp3, true", "okhttp3, false", 276 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 277 | }) 278 | public void interceptorAbleToHandleBody_xmlRequest(String loggerVersion, 279 | boolean provideExecutor) 280 | throws IOException { 281 | final List loggerOutput = interceptedRequest(RequestBody 282 | .create(MediaType.parse("application/xml"), XML_BODY), 283 | loggerVersion, provideExecutor, false); 284 | 285 | assertThat(loggerOutput).contains(""); 286 | assertThat(loggerOutput).contains(""); 287 | } 288 | 289 | @Test 290 | @Parameters({ 291 | "okhttp, true", "okhttp, false", 292 | "okhttp3, true", "okhttp3, false", 293 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 294 | }) 295 | public void interceptorAbleToHandleBody_xmlResponse(String loggerVersion, 296 | boolean provideExecutor) 297 | throws IOException { 298 | final List loggerOutput = interceptedResponse("application/xml", XML_BODY, 299 | loggerVersion, 300 | provideExecutor, false); 301 | 302 | assertThat(loggerOutput).contains(""); 303 | assertThat(loggerOutput).contains(""); 304 | } 305 | 306 | @Test 307 | @Parameters({ 308 | "okhttp, true", "okhttp, false", 309 | "okhttp3, true", "okhttp3, false", 310 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 311 | }) 312 | public void interceptorAbleToHandleBody_malformedXmlRequest(String loggerVersion, 313 | boolean provideExecutor) throws IOException { 314 | final List loggerOutput = interceptedRequest(RequestBody 315 | .create(MediaType.parse("application/xml"), MALFORMED_XML_BODY), loggerVersion, 316 | provideExecutor, false); 317 | 318 | loggerOutput 319 | .stream() 320 | .filter(it -> 321 | it.startsWith( 322 | "")) 323 | .findFirst() 324 | .orElseThrow(() -> 325 | new AssertionError("Interceptor should be able to handle xml request body.")); 326 | } 327 | 328 | @Test 329 | @Parameters({ 330 | "okhttp, true", "okhttp, false", 331 | "okhttp3, true", "okhttp3, false", 332 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 333 | }) 334 | public void interceptorAbleToHandleBody_malformedXmlResponse(String loggerVersion, 335 | boolean provideExecutor) throws IOException { 336 | final List loggerOutput = interceptedResponse("application/xml", MALFORMED_XML_BODY, 337 | loggerVersion, provideExecutor, false); 338 | 339 | loggerOutput 340 | .stream() 341 | .filter(it -> 342 | it.startsWith( 343 | "")) 344 | .findFirst() 345 | .orElseThrow(() -> 346 | new AssertionError("Interceptor should be able to handle xml response body.")); 347 | } 348 | 349 | @Test 350 | @Parameters({ 351 | "okhttp, true", "okhttp, false", 352 | "okhttp3, true", "okhttp3, false", 353 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 354 | }) 355 | public void interceptorAbleToHandleBody_fileRequest(String loggerVersion, 356 | boolean provideExecutor) 357 | throws IOException { 358 | RequestBody body = RequestBody.create(MediaType.parse("application/zip"), 359 | createFileFromString(PREFORMATTED_JSON_BODY)); 360 | 361 | final List loggerOutput = interceptedRequest(body, loggerVersion, provideExecutor, 362 | true); 363 | 364 | assertThat(loggerOutput).contains(" Omitted response body "); 365 | } 366 | 367 | @Test 368 | @Parameters({ 369 | "okhttp, true", "okhttp, false", 370 | "okhttp3, true", "okhttp3, false", 371 | "apacheHttpclientRequest, true", "apacheHttpclientRequest, false" 372 | }) 373 | public void interceptorAbleToHandleBody_fileResponse(String loggerVersion, 374 | boolean provideExecutor) throws IOException { 375 | final List loggerOutput = interceptedResponse("application/zip", 376 | String.valueOf(createFileFromString(PREFORMATTED_JSON_BODY)), loggerVersion, 377 | provideExecutor, true); 378 | 379 | assertThat(loggerOutput).contains(" Omitted response body "); 380 | } 381 | 382 | @SuppressWarnings("SameParameterValue") 383 | private File createFileFromString(String val) throws IOException { 384 | File file = temporaryFolder.newFile(); 385 | byte[] bytes = val.getBytes(StandardCharsets.UTF_8); 386 | Source source = Okio.source(new ByteArrayInputStream(bytes)); 387 | try (BufferedSink b = Okio.buffer(Okio.sink(file))) { 388 | b.writeAll(source); 389 | } 390 | return file; 391 | } 392 | 393 | 394 | } 395 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/Log4j2LoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import com.squareup.okhttp.mockwebserver.MockResponse; 7 | import java.io.IOException; 8 | import java.io.StringWriter; 9 | import java.io.Writer; 10 | import junitparams.JUnitParamsRunner; 11 | import junitparams.Parameters; 12 | import org.apache.logging.log4j.Level; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | import org.apache.logging.log4j.core.Appender; 16 | import org.apache.logging.log4j.core.Filter; 17 | import org.apache.logging.log4j.core.LoggerContext; 18 | import org.apache.logging.log4j.core.appender.ConsoleAppender; 19 | import org.apache.logging.log4j.core.appender.WriterAppender; 20 | import org.apache.logging.log4j.core.config.Configuration; 21 | import org.apache.logging.log4j.core.config.Configurator; 22 | import org.apache.logging.log4j.core.config.LoggerConfig; 23 | import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; 24 | import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 25 | import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; 26 | import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; 27 | import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; 28 | import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 29 | import org.apache.logging.log4j.core.layout.PatternLayout; 30 | import org.junit.BeforeClass; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | @RunWith(JUnitParamsRunner.class) 35 | public class Log4j2LoggerTest extends BaseTest { 36 | 37 | private final Logger log = LogManager.getLogger(Log4j2LoggerTest.class); 38 | 39 | private static final String ROOT_LOG_PATTERN = "%d{HH:mm:ss.SSS} [%t] %-5level %c{0}:%L - %msg%n"; 40 | private static final StringWriter logWriter = new StringWriter(); 41 | 42 | @BeforeClass 43 | public static void configureLogger() throws IOException { 44 | initializeBaseLog4j2Configuration(); 45 | } 46 | 47 | @Test 48 | @Parameters({ 49 | "okhttp", "okhttp3" 50 | }) 51 | public void interceptorCanBeConfiguredToPrintLogWithLog4j2(String interceptorVersion) 52 | throws IOException { 53 | server.enqueue(new MockResponse().setResponseCode(200)); 54 | final String OK_HTTP_LOG_PATTERN = "[OkHTTP] %msg%n"; 55 | 56 | log.debug("Configuring custom Log4j2 logger for intercepted OkHttp traffic."); 57 | LogWriter log4j2Writer = new LogWriter() { 58 | 59 | final Logger log = LogManager.getLogger("OkHttpLogger"); 60 | 61 | { 62 | final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 63 | final Configuration config = ctx.getConfiguration(); 64 | 65 | LoggerConfig loggerConfig = new LoggerConfig("OkHttpLogger", Level.TRACE, false); 66 | PatternLayout layout = PatternLayout 67 | .newBuilder() 68 | .withPattern(OK_HTTP_LOG_PATTERN) 69 | .build(); 70 | 71 | final Appender appender = ConsoleAppender 72 | .newBuilder() 73 | .withName("OkHttpConsoleAppender") 74 | .withLayout(layout) 75 | .build(); 76 | 77 | appender.start(); 78 | 79 | loggerConfig.addAppender(appender, Level.TRACE, null); 80 | config.addLogger("OkHttpLogger", loggerConfig); 81 | ctx.updateLoggers(); 82 | } 83 | 84 | @Override 85 | public void log(String msg) { 86 | log.debug(msg); 87 | } 88 | }; 89 | 90 | log.debug("Adding test double appender for output validation."); 91 | addAppender(logWriter, "TestWriter", OK_HTTP_LOG_PATTERN); 92 | 93 | log.debug("Attaching custom logger to interceptor."); 94 | attachLoggerToInterceptorWithDefaultRequest(interceptorVersion, log4j2Writer); 95 | 96 | log.debug("Retrieving logger output for validation."); 97 | final String logOutput = logWriter.toString(); 98 | 99 | assertFalse("Severity tag should not be present in custom OkHTTP logger output.", 100 | logOutput.contains("DEBUG")); 101 | 102 | assertTrue("Logger name should be present as defined by logging pattern.", 103 | logOutput.contains("OkHTTP")); 104 | 105 | assertTrue("Request section should be present in logger output.", 106 | logOutput.contains("Request")); 107 | 108 | assertTrue("Response section should be present in logger output.", 109 | logOutput.contains("Response")); 110 | 111 | } 112 | 113 | private static void initializeBaseLog4j2Configuration() throws IOException { 114 | ConfigurationBuilder builder 115 | = ConfigurationBuilderFactory.newConfigurationBuilder(); 116 | 117 | AppenderComponentBuilder console = builder.newAppender("stdout", "Console"); 118 | LayoutComponentBuilder layout = builder.newLayout("PatternLayout"); 119 | layout.addAttribute("pattern", ROOT_LOG_PATTERN); 120 | console.add(layout); 121 | builder.add(console); 122 | 123 | RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.DEBUG); 124 | rootLogger.add(builder.newAppenderRef("stdout")); 125 | builder.add(rootLogger); 126 | 127 | builder.writeXmlConfiguration(System.out); 128 | Configurator.initialize(builder.build()); 129 | } 130 | 131 | private static void addAppender(final Writer writer, final String writerName, String pattern) { 132 | final LoggerContext context = LoggerContext.getContext(false); 133 | final Configuration config = context.getConfiguration(); 134 | 135 | PatternLayout layout = PatternLayout.newBuilder().withPattern(pattern).build(); 136 | 137 | final Appender appender = WriterAppender 138 | .createAppender(layout, null, writer, writerName, false, true); 139 | appender.start(); 140 | config.addAppender(appender); 141 | updateLoggers(appender, config); 142 | } 143 | 144 | private static void updateLoggers(final Appender appender, final Configuration config) { 145 | final Level level = null; 146 | final Filter filter = null; 147 | for (final LoggerConfig loggerConfig : config.getLoggers().values()) { 148 | loggerConfig.addAppender(appender, level, filter); 149 | } 150 | config.getRootLogger().addAppender(appender, level, filter); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/LoggingInterceptorUnitTests.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class LoggingInterceptorUnitTests { 7 | 8 | @Test 9 | public void testDefaultLogFormatterIsMessageOnly() { 10 | final LoggingInterceptor.Builder builder = new LoggingInterceptor.Builder(); 11 | Assert 12 | .assertEquals("Default logger", LogFormatter.JUL_MESSAGE_ONLY, builder.getFormatter()); 13 | } 14 | 15 | @Test 16 | public void testSettingFormatForLoggingInterceptor() { 17 | final LoggingInterceptor.Builder builder = new LoggingInterceptor 18 | .Builder(); 19 | final LogFormatter format = LogFormatter.JUL_DATE_LEVEL_MESSAGE; 20 | builder.format(format); 21 | Assert.assertEquals("Log Formatter", format, builder.getFormatter()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/LoggingInterceptorsTests.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.junit.Assert.fail; 7 | 8 | import com.dkorobtsov.logging.interceptors.ApacheHttpRequestInterceptor; 9 | import com.dkorobtsov.logging.interceptors.ApacheHttpResponseInterceptor; 10 | import com.dkorobtsov.logging.interceptors.Okhttp3LoggingInterceptor; 11 | import com.dkorobtsov.logging.interceptors.OkhttpLoggingInterceptor; 12 | import com.squareup.okhttp.mockwebserver.MockResponse; 13 | import java.io.IOException; 14 | import java.util.concurrent.Executors; 15 | import junitparams.JUnitParamsRunner; 16 | import junitparams.Parameters; 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | 20 | @RunWith(JUnitParamsRunner.class) 21 | public class LoggingInterceptorsTests extends BaseTest { 22 | 23 | @Test 24 | @Parameters({ 25 | "okhttp", "okhttp3", "apacheHttpclientRequest" 26 | }) 27 | public void loggerShouldWorkWithoutAnyAdditionalConfiguration(String interceptorVersion) 28 | throws IOException { 29 | server.enqueue(new MockResponse().setResponseCode(200)); 30 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 31 | intercepWithSimpleInterceptor(interceptorVersion, testLogger); 32 | 33 | assertTrue("Logger should publish events using only default configuration", 34 | testLogger.firstFormattedEvent(true) 35 | .contains("Request")); 36 | } 37 | 38 | @Test 39 | @Parameters({ 40 | "okhttp", "okhttp3", "apacheHttpclientRequest" 41 | }) 42 | public void loggerWithDefaultFormatterShouldPrintMessageOnly(String interceptorVersion) 43 | throws IOException { 44 | server.enqueue(new MockResponse().setResponseCode(200)); 45 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 46 | intercepWithSimpleInterceptor(interceptorVersion, testLogger); 47 | 48 | //Comparing message by length since on Gradle runner characters may be different 49 | //unless GradleVM executes with -Dfile.encoding=utf-8 option 50 | assertEquals("Logger with default formatter should publish message only", 51 | testLogger.firstRawEvent().length(), 52 | testLogger.firstFormattedEvent(false).length()); 53 | } 54 | 55 | private void intercepWithSimpleInterceptor(String interceptorVersion, TestLogger testLogger) 56 | throws IOException { 57 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 58 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 59 | .logger(testLogger) 60 | .buildForOkhttp3(); 61 | 62 | defaultOkhttp3ClientWithInterceptor(interceptor) 63 | .newCall(defaultOkhttp3Request()) 64 | .execute(); 65 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 66 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 67 | .logger(testLogger) 68 | .buildForOkhttp(); 69 | defaultOkhttpClientWithInterceptor(interceptor) 70 | .newCall(defaultOkhttpRequest()) 71 | .execute(); 72 | } else if (interceptorVersion 73 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 74 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 75 | .logger(testLogger) 76 | .buildForApacheHttpClientRequest(); 77 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 78 | .logger(testLogger) 79 | .buildFordApacheHttpClientResponse(); 80 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 81 | .execute(defaultApacheHttpRequest()); 82 | } else { 83 | fail("Only okhttp and okhttp3 versions are supported"); 84 | } 85 | } 86 | 87 | @Test 88 | @Parameters({ 89 | "okhttp", "okhttp3", "apacheHttpclientRequest" 90 | }) 91 | public void loggerShouldBeDisabledWhenDebugModeSetToFalse(String interceptorVersion) 92 | throws IOException { 93 | server.enqueue(new MockResponse().setResponseCode(200)); 94 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 95 | interceptWithSimpleLoggableInterceptor(interceptorVersion, testLogger, false); 96 | 97 | assertTrue("Logger output should be empty if debug mode is off.", 98 | testLogger.formattedOutput().isEmpty()); 99 | } 100 | 101 | @Test 102 | @Parameters({ 103 | "okhttp", "okhttp3", "apacheHttpclientRequest" 104 | }) 105 | public void loggerShouldBeEnabledWhenDebugModeSetToTrue(String interceptorVersion) 106 | throws IOException { 107 | server.enqueue(new MockResponse().setResponseCode(200)); 108 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 109 | interceptWithSimpleLoggableInterceptor(interceptorVersion, testLogger, true); 110 | 111 | assertTrue("Logger should publish intercepted events if debug mode is on.", 112 | testLogger.firstFormattedEvent(true) 113 | .contains("Request")); 114 | } 115 | 116 | @Test 117 | @Parameters({ 118 | "okhttp", "okhttp3", "apacheHttpclientRequest" 119 | }) 120 | public void defaultLoggerFormatCanBeModified(String interceptorVersion) throws IOException { 121 | server.enqueue(new MockResponse().setResponseCode(200)); 122 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_DATE_LEVEL_MESSAGE); 123 | interceptWithSimpleLoggableInterceptor(interceptorVersion, testLogger, true); 124 | 125 | String logEntry = testLogger.lastFormattedEvent(true); 126 | 127 | TestUtil.assertLogEntryElementsCount(logEntry, 3); 128 | TestUtil.assertEntryStartsWithParsableDate(logEntry); 129 | } 130 | 131 | private void interceptWithSimpleLoggableInterceptor(String interceptorVersion, 132 | TestLogger testLogger, boolean loggable) throws IOException { 133 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 134 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 135 | .loggable(loggable) 136 | .logger(testLogger) 137 | .buildForOkhttp3(); 138 | 139 | defaultOkhttp3ClientWithInterceptor(interceptor) 140 | .newCall(defaultOkhttp3Request()) 141 | .execute(); 142 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 143 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 144 | .logger(testLogger) 145 | .loggable(loggable) 146 | .buildForOkhttp(); 147 | defaultOkhttpClientWithInterceptor(interceptor) 148 | .newCall(defaultOkhttpRequest()) 149 | .execute(); 150 | } else if (interceptorVersion 151 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 152 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 153 | .logger(testLogger) 154 | .loggable(loggable) 155 | .buildForApacheHttpClientRequest(); 156 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 157 | .logger(testLogger) 158 | .loggable(loggable) 159 | .buildFordApacheHttpClientResponse(); 160 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 161 | .execute(defaultApacheHttpRequest()); 162 | } else { 163 | fail("Only okhttp and okhttp3 versions are supported"); 164 | } 165 | } 166 | 167 | @Test 168 | @Parameters({ 169 | "okhttp", "okhttp3", "apacheHttpclientRequest" 170 | }) 171 | public void loggerShouldBeDisabledWhenLevelSetToNone(String interceptorVersion) 172 | throws IOException { 173 | server.enqueue(new MockResponse().setResponseCode(200)); 174 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 175 | interceptWithLogLevelInterceptor(interceptorVersion, testLogger, Level.NONE); 176 | 177 | assertTrue("Logger output should be empty if debug mode is off.", 178 | testLogger.formattedOutput().isEmpty()); 179 | } 180 | 181 | @Test 182 | @Parameters({ 183 | "okhttp", "okhttp3", "apacheHttpclientRequest" 184 | }) 185 | public void headersShouldNotBeLoggedWhenLevelSetToBody(String interceptorVersion) 186 | throws IOException { 187 | server.enqueue(new MockResponse().setResponseCode(200)); 188 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 189 | interceptWithLogLevelInterceptor(interceptorVersion, testLogger, Level.BODY); 190 | 191 | assertFalse("Headers should not be logged when level set to Body.", 192 | testLogger.formattedOutput().contains("Headers")); 193 | } 194 | 195 | @Test 196 | @Parameters({ 197 | "okhttp", "okhttp3", "apacheHttpclientRequest" 198 | }) 199 | public void bodyShouldNotBeLoggedWhenLevelSetToHeaders(String interceptorVersion) 200 | throws IOException { 201 | server.enqueue(new MockResponse().setResponseCode(200)); 202 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 203 | interceptWithLogLevelInterceptor(interceptorVersion, testLogger, Level.HEADERS); 204 | 205 | assertFalse("Body should not be logged when level set to Headers.", 206 | testLogger.formattedOutput().contains("body")); 207 | } 208 | 209 | @Test 210 | @Parameters({ 211 | "okhttp", "okhttp3", "apacheHttpclientRequest" 212 | }) 213 | public void allDetailsShouldBePrintedIfLevelSetToBasic(String interceptorVersion) 214 | throws IOException { 215 | server.enqueue(new MockResponse().setResponseCode(200)); 216 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 217 | interceptWithLogLevelInterceptor(interceptorVersion, testLogger, Level.BASIC); 218 | 219 | assertTrue("Request section should be present in logger output.", 220 | testLogger.formattedOutput().contains("Request")); 221 | 222 | assertTrue("Response section should be present in logger output.", 223 | testLogger.formattedOutput().contains("Response")); 224 | 225 | assertTrue("Url should be logged when level set to Basic.", 226 | testLogger.formattedOutput().contains("URL")); 227 | 228 | assertTrue("Method should be logged when level set to Basic.", 229 | testLogger.formattedOutput().contains("Method")); 230 | 231 | assertTrue("Headers should be logged when level set to Basic.", 232 | testLogger.formattedOutput().contains("Headers")); 233 | 234 | assertTrue("Status code should be logged when level set to Basic.", 235 | testLogger.formattedOutput().contains("Status Code:")); 236 | 237 | assertTrue("Body should be logged when level set to Basic.", 238 | testLogger.formattedOutput().contains("body")); 239 | } 240 | 241 | private void interceptWithLogLevelInterceptor(String interceptorVersion, TestLogger testLogger, 242 | Level level) throws IOException { 243 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 244 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 245 | .level(level) 246 | .logger(testLogger) 247 | .buildForOkhttp3(); 248 | 249 | defaultOkhttp3ClientWithInterceptor(interceptor) 250 | .newCall(defaultOkhttp3Request()) 251 | .execute(); 252 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 253 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 254 | .logger(testLogger) 255 | .level(level) 256 | .buildForOkhttp(); 257 | defaultOkhttpClientWithInterceptor(interceptor) 258 | .newCall(defaultOkhttpRequest()) 259 | .execute(); 260 | } else if (interceptorVersion 261 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 262 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 263 | .logger(testLogger) 264 | .level(level) 265 | .buildForApacheHttpClientRequest(); 266 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 267 | .logger(testLogger) 268 | .level(level) 269 | .buildFordApacheHttpClientResponse(); 270 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 271 | .execute(defaultApacheHttpRequest()); 272 | } else { 273 | fail("Only okhttp and okhttp3 versions are supported"); 274 | } 275 | } 276 | 277 | @Test 278 | @Parameters({ 279 | "okhttp", "okhttp3", "apacheHttpclientRequest" 280 | }) 281 | public void userShouldBeAbleToSupplyExecutor(String interceptorVersion) throws IOException { 282 | server.enqueue(new MockResponse().setResponseCode(200)); 283 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_THREAD_MESSAGE); 284 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 285 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 286 | .level(Level.BASIC) 287 | .executor(Executors.newSingleThreadExecutor()) 288 | .logger(testLogger) 289 | .buildForOkhttp3(); 290 | 291 | defaultOkhttp3ClientWithInterceptor(interceptor) 292 | .newCall(defaultOkhttp3Request()) 293 | .execute(); 294 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 295 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 296 | .logger(testLogger) 297 | .level(Level.BASIC) 298 | .executor(Executors.newSingleThreadExecutor()) 299 | .buildForOkhttp(); 300 | defaultOkhttpClientWithInterceptor(interceptor) 301 | .newCall(defaultOkhttpRequest()) 302 | .execute(); 303 | } else if (interceptorVersion 304 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 305 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 306 | .logger(testLogger) 307 | .level(Level.BASIC) 308 | .executor(Executors.newSingleThreadExecutor()) 309 | .buildForApacheHttpClientRequest(); 310 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 311 | .logger(testLogger) 312 | .level(Level.BASIC) 313 | .executor(Executors.newSingleThreadExecutor()) 314 | .buildFordApacheHttpClientResponse(); 315 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 316 | .execute(defaultApacheHttpRequest()); 317 | } else { 318 | fail("Only okhttp and okhttp3 versions are supported"); 319 | } 320 | 321 | assertTrue("User should be able to supply executor.", 322 | testLogger.formattedOutput().contains("thread")); 323 | } 324 | 325 | @Test 326 | @Parameters({ 327 | "okhttp", "okhttp3", "apacheHttpclientRequest" 328 | }) 329 | public void userShouldBeAbleToUseDefaultLogger(String interceptorVersion) throws IOException { 330 | server.enqueue(new MockResponse().setResponseCode(200)); 331 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 332 | Okhttp3LoggingInterceptor interceptor = new LoggingInterceptor.Builder() 333 | .level(Level.BASIC) 334 | .executor(Executors.newSingleThreadExecutor()) 335 | .buildForOkhttp3(); 336 | 337 | defaultOkhttp3ClientWithInterceptor(interceptor) 338 | .newCall(defaultOkhttp3Request()) 339 | .execute(); 340 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 341 | final OkhttpLoggingInterceptor interceptor = new LoggingInterceptor.Builder() 342 | .level(Level.BASIC) 343 | .executor(Executors.newSingleThreadExecutor()) 344 | .buildForOkhttp(); 345 | defaultOkhttpClientWithInterceptor(interceptor) 346 | .newCall(defaultOkhttpRequest()) 347 | .execute(); 348 | } else if (interceptorVersion 349 | .equals(InterceptorVersion.APACHE_HTTPCLIENT_REQUEST.getName())) { 350 | final ApacheHttpRequestInterceptor requestInterceptor = new LoggingInterceptor.Builder() 351 | .level(Level.BASIC) 352 | .executor(Executors.newSingleThreadExecutor()) 353 | .buildForApacheHttpClientRequest(); 354 | final ApacheHttpResponseInterceptor responseInterceptor = new LoggingInterceptor.Builder() 355 | .level(Level.BASIC) 356 | .executor(Executors.newSingleThreadExecutor()) 357 | .buildFordApacheHttpClientResponse(); 358 | defaultApacheClientWithInterceptors(requestInterceptor, responseInterceptor) 359 | .execute(defaultApacheHttpRequest()); 360 | } else { 361 | fail("Only okhttp and okhttp3 versions are supported"); 362 | } 363 | } 364 | 365 | 366 | } 367 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/MalformedJsonHandlingTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import com.squareup.okhttp.mockwebserver.MockResponse; 6 | import java.io.IOException; 7 | import junitparams.JUnitParamsRunner; 8 | import junitparams.Parameters; 9 | import okhttp3.MediaType; 10 | import okhttp3.Request; 11 | import okhttp3.RequestBody; 12 | import org.apache.http.client.methods.HttpPut; 13 | import org.apache.http.entity.StringEntity; 14 | import org.apache.http.message.BasicHeader; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | @RunWith(JUnitParamsRunner.class) 19 | public class MalformedJsonHandlingTest extends BaseTest { 20 | 21 | @Test 22 | @Parameters({ 23 | "okhttp", "okhttp3", "apacheHttpclientRequest" 24 | }) 25 | public void interceptorAbleToHandleBody_malformedJsonResponse(String interceptorVersion) 26 | throws IOException { 27 | server.enqueue(new MockResponse() 28 | .setResponseCode(200) 29 | .setHeader("Content-Type", "application/json") 30 | .setBody("? \"test\" : \"test1\"}")); 31 | 32 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 33 | attachLoggerToInterceptorWithDefaultRequest(interceptorVersion, testLogger); 34 | 35 | assertTrue("Interceptor should be able to log malformed json body.", 36 | testLogger.formattedOutput().contains("? \"test\" : \"test1\"}")); 37 | } 38 | 39 | @Test 40 | @Parameters({ 41 | "okhttp", "okhttp3", "apacheHttpclientRequest" 42 | }) 43 | public void interceptorAbleToHandleBody_malformedJsonRequest(String interceptorVersion) 44 | throws IOException { 45 | server.enqueue(new MockResponse().setResponseCode(200)); 46 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 47 | final String content = "? \"test\" : \"test1\"}"; 48 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 49 | Request okhttp3Request = new Request.Builder() 50 | .url(String.valueOf(server.url("/"))) 51 | .put(RequestBody.create(MediaType.parse("application/json"), 52 | content)) 53 | .build(); 54 | attachLoggerToInterceptor(interceptorVersion, testLogger, okhttp3Request, null, null); 55 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 56 | com.squareup.okhttp.Request okhttpRequest = new com.squareup.okhttp.Request.Builder() 57 | .url(String.valueOf(server.url("/"))) 58 | .put(com.squareup.okhttp.RequestBody 59 | .create(com.squareup.okhttp.MediaType.parse("application/json"), content)) 60 | .build(); 61 | attachLoggerToInterceptor(interceptorVersion, testLogger, null, okhttpRequest, null); 62 | } else { 63 | final HttpPut httpPut = new HttpPut(server.url("/").uri()); 64 | httpPut.setEntity(new StringEntity(content)); 65 | httpPut.setHeader(new BasicHeader("Content-Type", "application/json")); 66 | attachLoggerToInterceptor(interceptorVersion, testLogger, null, null, httpPut); 67 | } 68 | assertTrue("Interceptor should be able to log malformed json body.", 69 | testLogger.formattedOutput().contains(content)); 70 | } 71 | 72 | @Test 73 | @Parameters({ 74 | "okhttp", "okhttp3", "apacheHttpclientRequest" 75 | }) 76 | public void interceptorAbleToHandleBody_jsonArrayResponse(String interceptorVersion) 77 | throws IOException { 78 | server.enqueue(new MockResponse() 79 | .setResponseCode(200) 80 | .setHeader("Content-Type", "application/json") 81 | .setBody("[{\"test1\": \"test1\"}, {\"test2\": \"test2\"}]")); 82 | 83 | TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 84 | attachLoggerToInterceptorWithDefaultRequest(interceptorVersion, testLogger); 85 | 86 | assertTrue("Interceptor should be able to log malformed json body.", 87 | testLogger.formattedOutput() 88 | .contains("{\"test1\": \"test1\"},")); 89 | assertTrue("Interceptor should be able to log malformed json body.", 90 | testLogger.formattedOutput() 91 | .contains("{\"test2\": \"test2\"}")); 92 | } 93 | 94 | @Test 95 | @Parameters({ 96 | "okhttp", "okhttp3", "apacheHttpclientRequest" 97 | }) 98 | public void interceptorAbleToHandleBody_jsonArrayRequest(String interceptorVersion) 99 | throws IOException { 100 | server.enqueue(new MockResponse().setResponseCode(200)); 101 | final TestLogger testLogger = new TestLogger(LogFormatter.JUL_MESSAGE_ONLY); 102 | 103 | final String content = "[{\"test1\": \"test1\"}, {\"test2\": \"test2\"}]"; 104 | if (interceptorVersion.equals(InterceptorVersion.OKHTTP3.getName())) { 105 | Request okhttp3Request = new Request.Builder() 106 | .url(String.valueOf(server.url("/"))) 107 | .put(RequestBody.create(MediaType.parse("application/json"), 108 | content)) 109 | .build(); 110 | attachLoggerToInterceptor(interceptorVersion, testLogger, okhttp3Request, null, null); 111 | } else if (interceptorVersion.equals(InterceptorVersion.OKHTTP.getName())) { 112 | com.squareup.okhttp.Request okhttpRequest = new com.squareup.okhttp.Request.Builder() 113 | .url(String.valueOf(server.url("/"))) 114 | .put(com.squareup.okhttp.RequestBody 115 | .create(com.squareup.okhttp.MediaType.parse("application/json"), 116 | content)) 117 | .build(); 118 | attachLoggerToInterceptor(interceptorVersion, testLogger, null, okhttpRequest, null); 119 | } else { 120 | final HttpPut httpPut = new HttpPut(server.url("/").uri()); 121 | httpPut.setEntity(new StringEntity(content)); 122 | httpPut.setHeader(new BasicHeader("Contety-Tyoe", "application/json")); 123 | attachLoggerToInterceptor(interceptorVersion, testLogger, null, null, httpPut); 124 | } 125 | 126 | assertTrue("Interceptor should be able to log malformed json body.", 127 | testLogger.formattedOutput() 128 | .contains("{\"test1\": \"test1\"},")); 129 | assertTrue("Interceptor should be able to log malformed json body.", 130 | testLogger.formattedOutput() 131 | .contains("{\"test2\": \"test2\"}")); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/OutputResizingTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import com.dkorobtsov.logging.interceptors.Okhttp3LoggingInterceptor; 6 | import java.io.IOException; 7 | import java.util.List; 8 | import junitparams.JUnitParamsRunner; 9 | import junitparams.Parameters; 10 | import okhttp3.MediaType; 11 | import okhttp3.RequestBody; 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | @RunWith(JUnitParamsRunner.class) 17 | public class OutputResizingTest extends BaseTest { 18 | 19 | private static final String TEST_JSON = "{name: \"John\", age: 31, city: \"New York\"}"; 20 | 21 | @Test 22 | @Parameters({ 23 | "okhttp, false", 24 | "okhttp3, false", 25 | "apacheHttpclientRequest, false" 26 | }) 27 | public void printerOutputResizingValidation(String loggerVersion, 28 | boolean provideExecutor) throws IOException { 29 | final List loggerOutput = interceptedRequest(RequestBody 30 | .create(MediaType.parse("application/json"), TEST_JSON), 31 | 10, loggerVersion, provideExecutor, false); 32 | 33 | assertTrue("Interceptor should be able to log simple json body.", 34 | loggerOutput.contains("Method: @P")); 35 | } 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | @Parameters({ 39 | "9", "501" 40 | }) 41 | public void invalidOutputLengthHandling(String maxLineLength) { 42 | LoggingInterceptor.builder() 43 | .maxLineLength(Integer.parseInt(maxLineLength)) 44 | .buildForOkhttp3(); 45 | } 46 | 47 | @Test 48 | @Parameters({ 49 | "10", "500" 50 | }) 51 | public void validOutputLengthHandling(String maxLineLength) { 52 | Okhttp3LoggingInterceptor interceptor = LoggingInterceptor.builder() 53 | .maxLineLength(Integer.parseInt(maxLineLength)) 54 | .buildForOkhttp3(); 55 | 56 | Assert.assertEquals(Integer.parseInt(maxLineLength), 57 | interceptor.loggerConfig().maxLineLength); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/RequestDetailsUnitTests.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import com.squareup.okhttp.Request; 4 | import org.apache.http.client.methods.HttpGet; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class RequestDetailsUnitTests { 9 | 10 | @Test(expected = IllegalArgumentException.class) 11 | public void testBuildingWith0ClientsSpecified() { 12 | new RequestDetails.Builder().build(); 13 | } 14 | 15 | @Test(expected = IllegalArgumentException.class) 16 | public void testBuildingWithMoreThenOneClientsSpecified() { 17 | new RequestDetails.Builder() 18 | .from(new com.squareup.okhttp.Request.Builder().url("https://google.com").build()) 19 | .from(new HttpGet("https://google.com")) 20 | .build(); 21 | } 22 | 23 | @Test 24 | public void testBuildingOkhttpClientWithHeaders() { 25 | final String contentType = "application/json"; 26 | final String authorizationHeader = "Bearer bla"; 27 | final Request request = new Request 28 | .Builder() 29 | .url("https://google.com") 30 | .header("Content-Type", contentType) 31 | .header("Authorization", authorizationHeader) 32 | .build(); 33 | final okhttp3.Request builtRequest = new RequestDetails.Builder() 34 | .from(request) 35 | .build(); 36 | Assert.assertEquals("Content-Type header", request.headers().get("Content-Type"), 37 | builtRequest.headers().get("Content-Type")); 38 | Assert.assertEquals("Authorization header", request.headers().get("Authorization"), 39 | builtRequest.headers().get("Authorization")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/StatusCodeTest.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import org.junit.Test; 4 | 5 | public class StatusCodeTest { 6 | 7 | @Test(expected = IllegalArgumentException.class) 8 | public void unknownStatusCodeThrowsIllegalArgumentException() { 9 | HttpStatusCodes.findMessage(999); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/TestLogger.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.OutputStream; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.logging.ConsoleHandler; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | import java.util.logging.StreamHandler; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * DefaultLogger double with additional methods for testing purposes. All published events are 17 | * registered and can be retrieved for validation. 18 | */ 19 | public class TestLogger implements LogWriter { 20 | 21 | private static final Logger logger = Logger.getLogger(TestLogger.class.getName()); 22 | 23 | private final List events = new ArrayList<>(Collections.emptyList()); 24 | private StreamHandler logOutputHandler; 25 | private OutputStream logOut; 26 | private Logger testLogger = Logger.getLogger("TestLogger"); 27 | 28 | TestLogger(LogFormatter logFormatter) { 29 | testLogger.setUseParentHandlers(false); 30 | 31 | // Removing existing handlers for new instance 32 | Arrays.stream(testLogger.getHandlers()).forEach(testLogger::removeHandler); 33 | 34 | // Configuring output to console 35 | ConsoleHandler consoleHandler = new ConsoleHandler(); 36 | consoleHandler.setFormatter(logFormatter.formatter); 37 | testLogger.addHandler(consoleHandler); 38 | 39 | // Configuring output to stream 40 | logOut = new ByteArrayOutputStream(); 41 | logOutputHandler = new StreamHandler(logOut, logFormatter.formatter); 42 | 43 | testLogger.addHandler(logOutputHandler); 44 | } 45 | 46 | @Override 47 | public void log(String msg) { 48 | testLogger.log(Level.INFO, msg); 49 | events.add(msg); 50 | } 51 | 52 | /** 53 | * @return Returns raw messages (in case we want to check content only and don't care about 54 | * format) 55 | */ 56 | private List rawMessages() { 57 | return events; 58 | } 59 | 60 | /** 61 | * @return Returns first formatted event published by current logger 62 | */ 63 | String firstRawEvent() { 64 | return rawMessages().get(0).trim(); 65 | } 66 | 67 | /** 68 | * @return Returns last formatted event published by current logger 69 | */ 70 | String lastRawEvent() { 71 | return rawMessages().get(rawMessages().size() - 1).trim(); 72 | } 73 | 74 | /** 75 | * @return Returns all formatted events published by current logger as String 76 | */ 77 | String formattedOutput() { 78 | try { 79 | // Don't like this solution, but without this wait tests verifying 80 | // logger output with manually added executor are randomly failing 81 | // (part of output is missing). Suppose root cause is that we are 82 | // flashing output before all lines get in buffer 83 | // NB: random failures occur when value < 16 84 | Thread.sleep(20); 85 | } catch (InterruptedException e) { 86 | logger.log(Level.SEVERE, e.getMessage(), e); 87 | } 88 | logOutputHandler.flush(); 89 | return logOut.toString(); 90 | } 91 | 92 | /** 93 | * @return Returns all formatted events published by current logger as String array 94 | */ 95 | List loggerOutput(boolean preserveTrailingSpaces) { 96 | if (preserveTrailingSpaces) { 97 | return Arrays.asList(formattedOutput().split("\r?\n")); 98 | } 99 | return Arrays.stream(formattedOutput() 100 | .split("\r?\n")) 101 | .map(String::trim).collect(Collectors.toList()); 102 | } 103 | 104 | /** 105 | * @return Returns first formatted event published by current logger 106 | */ 107 | String firstFormattedEvent(boolean preserveTrailingSpaces) { 108 | return loggerOutput(preserveTrailingSpaces).get(0); 109 | } 110 | 111 | /** 112 | * @return Returns last formatted event published by current logger 113 | */ 114 | String lastFormattedEvent(boolean preserveTrailingSpaces) { 115 | return loggerOutput(preserveTrailingSpaces) 116 | .get(loggerOutput(preserveTrailingSpaces).size() - 1); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/TestUtil.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import java.text.ParseException; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Arrays; 8 | import org.junit.Assert; 9 | 10 | final class TestUtil { 11 | 12 | static void assertEntryStartsWithParsableDate(String rawEntry) { 13 | String[] entryElements = TestUtil 14 | .extractTextFromLogEntrySeparatedByBrackets(rawEntry); 15 | 16 | try { 17 | new SimpleDateFormat("yyyy-MM-ddd kk:mm:ss").parse(entryElements[0]); 18 | } catch (ParseException e) { 19 | fail("Log entry expected to start with parsable date stamp. But was: \n" + rawEntry); 20 | } 21 | } 22 | 23 | @SuppressWarnings({"RegExpRedundantEscape", "RegExpSingleCharAlternation"}) 24 | static String[] extractTextFromLogEntrySeparatedByBrackets(String logEntry) { 25 | return Arrays 26 | .stream(logEntry.split("\\[|\\]")) 27 | .filter(s -> s.trim().length() > 0) 28 | .map(String::trim) 29 | .toArray(String[]::new); 30 | } 31 | 32 | static void assertLogEntryElementsCount(String entrySeparatedByBrackets, int expectedCount) { 33 | String[] entryElements = TestUtil 34 | .extractTextFromLogEntrySeparatedByBrackets(entrySeparatedByBrackets); 35 | 36 | Assert.assertEquals( 37 | "Log event expected to contain " + expectedCount + " of elements. But was: \n" 38 | + entryElements.length, expectedCount, entryElements.length); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/test/java/com/dkorobtsov/logging/ToApacheHttpClientConverterUnitTests.java: -------------------------------------------------------------------------------- 1 | package com.dkorobtsov.logging; 2 | 3 | import com.dkorobtsov.logging.converters.ToApacheHttpClientConverter; 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.nio.charset.Charset; 9 | import org.apache.http.entity.ContentType; 10 | import org.apache.http.entity.StringEntity; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | 14 | public class ToApacheHttpClientConverterUnitTests { 15 | 16 | @Test 17 | public void testOkhtt3WithEmptyBodyConversion() throws IOException { 18 | final StringEntity httpEntity = (StringEntity) ToApacheHttpClientConverter 19 | .okhttp3RequestBodyToStringEntity(null, ContentType.APPLICATION_JSON); 20 | final InputStream content = httpEntity.getContent(); 21 | StringBuilder stringBuilder = new StringBuilder(); 22 | String line; 23 | 24 | try (BufferedReader bufferedReader = new BufferedReader( 25 | new InputStreamReader(content, Charset.defaultCharset()))) { 26 | while ((line = bufferedReader.readLine()) != null) { 27 | stringBuilder.append(line); 28 | } 29 | } 30 | Assert.assertEquals("body", "", stringBuilder.toString()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%t] %-5level %c{0}:%L - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':lib' 2 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | # docker box definition 2 | box: 3 | id: openjdk:8 4 | 5 | # defining the dev pipeline 6 | dev: 7 | steps: 8 | - script: 9 | name: gradle check 10 | code: | 11 | ./gradlew check 12 | 13 | - internal/watch: 14 | code: | 15 | ./gradlew check 16 | reload: true 17 | 18 | build: 19 | steps: 20 | - script: 21 | name: quality check 22 | code: | 23 | ./gradlew sonarqube --full-stacktrace --project-cache-dir=$WERCKER_CACHE_DIR -Dsonar.organization=dkorobtsov-github -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONARCUBE_TOKEN 24 | 25 | deploy: 26 | steps: 27 | - handijk/github-merge@1.0.3: 28 | token: $GITHUB_TOKEN 29 | base: master 30 | --------------------------------------------------------------------------------