├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── mock │ │ ├── delete.json │ │ ├── file.json │ │ ├── get.json │ │ ├── patch.json │ │ ├── post.json │ │ ├── put.json │ │ ├── upload.json │ │ └── zip.json │ ├── java │ └── ihsanbal │ │ └── com │ │ └── logginginterceptor │ │ ├── LogApplication.kt │ │ ├── api │ │ └── Api.kt │ │ ├── base │ │ └── BaseCompatActivity.kt │ │ ├── di │ │ ├── NetComponent.kt │ │ └── NetModule.kt │ │ ├── model │ │ └── Body.kt │ │ └── ui │ │ └── MainActivity.kt │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── checkstyle.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── logcat.png ├── screen_shot_1.png ├── screen_shot_2.png ├── screen_shot_4.png └── screen_shot_5.png ├── lib ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ihsanbal │ └── logging │ ├── BufferListener.kt │ ├── I.kt │ ├── Level.kt │ ├── Logger.kt │ ├── LoggingInterceptor.kt │ └── Printer.kt ├── pTML.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .DS_Store 5 | build 6 | captures 7 | .externalNativeBuild 8 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | dist: trusty 3 | 4 | android: 5 | components: 6 | - tools 7 | - platform-tools 8 | - build-tools-29.0.3 9 | - android-29 10 | - extra-android-m2repository 11 | licenses: 12 | - 'android-sdk-preview-license-52d11cd2' 13 | - 'android-sdk-license-.+' 14 | - 'google-gdk-license-.+' 15 | 16 | jdk: 17 | - oraclejdk8 18 | 19 | notifications: 20 | email: false 21 | 22 | before_install: 23 | - yes | sdkmanager "platforms;android-28" 24 | 25 | script: 26 | - ./gradlew assembleDebug 27 | 28 | after_script: 29 | - ./gradlew testReleaseUnitTest 30 | 31 | branches: 32 | only: 33 | - master 34 | - development 35 | - fix 36 | - release/3.0 37 | 38 | cache: 39 | directories: 40 | - $HOME/.m2 41 | - $HOME/.gradle -------------------------------------------------------------------------------- /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 | -------- 3 | 4 | [![Build Status](https://travis-ci.org/ihsanbal/LoggingInterceptor.svg?branch=master)](https://travis-ci.org/ihsanbal/LoggingInterceptor) 5 | [![](https://img.shields.io/badge/AndroidWeekly-%23272-blue.svg?style=flat-square)](http://androidweekly.net/issues/issue-272) 6 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-LoggingInterceptor-green.svg?style=flat-square)](https://android-arsenal.com/details/1/5870) 7 | [![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat-square)](http://www.oracle.com/technetwork/java/javase/downloads/jre7-downloads-1880261.html) 8 | [![](https://jitpack.io/v/ihsanbal/LoggingInterceptor.svg)](https://jitpack.io/#ihsanbal/LoggingInterceptor) 9 | [![SwaggerUI](https://img.shields.io/badge/Swagger-mockable.io-orange.svg?style=flat-square)](https://www.mockable.io/swagger/index.html?url=https%3A%2F%2Fdemo2961085.mockable.io%3Fopenapi#!/demo2961085) 10 | 11 |

12 | 13 |

14 | 15 | Usage 16 | -------- 17 | 18 | ```kotlin 19 | val client = OkHttpClient.Builder() 20 | client.addInterceptor(LoggingInterceptor.Builder() 21 | .setLevel(Level.BASIC) 22 | .log(VERBOSE) 23 | .addHeader("cityCode","53") 24 | .addQueryParam("moonStatus", "crescent") 25 | .build()) 26 | ``` 27 | 28 | Download 29 | -------- 30 | 31 | Gradle: 32 | 33 | Groovy 34 | ```groovy 35 | allprojects { 36 | repositories { 37 | maven { url 'https://jitpack.io' } 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0') { 43 | exclude group: 'org.json', module: 'json' 44 | } 45 | } 46 | ``` 47 | 48 | kotlin DSL 49 | ``` 50 | allprojects { 51 | repositories { 52 | maven { setUrl("https://jitpack.io") } 53 | } 54 | } 55 | 56 | 57 | dependencies { 58 | implementation("com.github.ihsanbal:LoggingInterceptor:3.1.0") { 59 | exclude(group = "org.json", module = "json") 60 | } 61 | } 62 | 63 | ``` 64 | 65 | Maven: 66 | ```xml 67 | 68 | jitpack.io 69 | https://jitpack.io 70 | 71 | 72 | 73 | com.github.ihsanbal 74 | LoggingInterceptor 75 | 3.1.0 76 | 77 | ``` 78 | 79 | 80 | Logger & Mock Support 81 | --------------------- 82 | ```kotlin 83 | LoggingInterceptor.Builder() 84 | //Add logger to print log as plain text 85 | .logger(object : Logger { 86 | override fun log(level: Int, tag: String?, msg: String?) { 87 | Log.e("$tag - $level", "$msg") 88 | } 89 | }) 90 | //Enable mock for develop app with mock data 91 | .enableMock(BuildConfig.MOCK, 1000L, object : BufferListener { 92 | override fun getJsonResponse(request: Request?): String? { 93 | val segment = request?.url?.pathSegments?.getOrNull(0) 94 | return mAssetManager.open(String.format("mock/%s.json", segment)).source().buffer().readUtf8() 95 | } 96 | }) 97 | ``` 98 | 99 | Level 100 | -------- 101 | 102 | ```kotlin 103 | setLevel(Level.BASIC) 104 | .NONE // No logs 105 | .BASIC // Logging url,method,headers and body. 106 | .HEADERS // Logging headers 107 | .BODY // Logging body 108 | ``` 109 | 110 | Platform - [Platform](https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/platform/Platform.java) 111 | -------- 112 | 113 | ```kotlin 114 | log(Platform.WARN) // setting log type 115 | ``` 116 | 117 | Tag 118 | -------- 119 | 120 | ```kotlin 121 | tag("LoggingI") // Request & response each log tag 122 | request("request") // Request log tag 123 | response("response") // Response log tag 124 | 125 | ``` 126 | 127 | Header - [Recipes](https://github.com/square/okhttp/wiki/Recipes) 128 | -------- 129 | 130 | ```kotlin 131 | addHeader("token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ") // Adding to request 132 | ``` 133 | 134 | Notes 135 | -------- 136 | Some tips about log at this blog post: [“The way to get faster on development.”](https://medium.com/@ihsanbal/the-way-to-get-faster-on-development-9d7b23ef8c10) 137 | 138 | Also use the filter & configure logcat header for a better result 139 | 140 |

141 | 142 | 143 |

144 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android-extensions' 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 29 8 | //noinspection GradleDependency 9 | 10 | defaultConfig { 11 | applicationId "ihsanbal.com.logginginterceptor" 12 | minSdkVersion 21 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | buildConfigField("boolean", "MOCK", "false") 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | 25 | lintOptions { 26 | abortOnError false 27 | 28 | disable 'AllowBackup' 29 | disable 'GoogleAppIndexingWarning' 30 | } 31 | 32 | buildTypes { 33 | mock { 34 | debuggable true 35 | buildConfigField("boolean", "MOCK", "true") 36 | } 37 | } 38 | sourceSets { 39 | main.java.srcDirs += 'src/main/kotlin' 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation 'androidx.core:core-ktx:1.2.0' 45 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 46 | 47 | implementation 'androidx.appcompat:appcompat:1.1.0' 48 | implementation 'io.reactivex:rxjava:' + rxjavaVersion 49 | implementation 'io.reactivex:rxandroid:' + rxAndroidVersion 50 | implementation 'com.squareup.retrofit2:retrofit:' + retrofitVersion 51 | implementation 'com.squareup.retrofit2:adapter-rxjava:' + retrofitRxAdapterVersion 52 | implementation 'com.google.dagger:dagger:' + daggerVersion 53 | implementation 'com.squareup.retrofit2:converter-gson:' + gsonConverterVersion 54 | implementation 'com.squareup.okhttp3:okhttp:' + okhttpVersion 55 | implementation 'com.squareup.okio:okio:' + okioVersion 56 | kapt 'com.google.dagger:dagger-compiler:' + daggerVersion 57 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion 58 | releaseApi 'com.squareup.leakcanary:leakcanary-android-no-op:' + leakCanaryVersion 59 | testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:' + leakCanaryVersion 60 | compileOnly 'javax.annotation:jsr250-api:1.0' 61 | implementation(project(':lib')) { 62 | exclude group: 'org.json', module: 'json' 63 | } 64 | // implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0-rc2') { 65 | // exclude group: 'org.json', module: 'json' 66 | // } 67 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 68 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 69 | androidTestImplementation 'androidx.test:rules:1.2.0' 70 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/assets/mock/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/put.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/assets/mock/zip.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "glossary": { 5 | "title": "example glossary", 6 | "GlossDiv": { 7 | "title": "S", 8 | "GlossList": { 9 | "GlossEntry": { 10 | "ID": "SGML", 11 | "SortAs": "SGML", 12 | "GlossTerm": "Standard Generalized Markup Language", 13 | "Acronym": "SGML", 14 | "Abbrev": "ISO 8879:1986", 15 | "GlossDef": { 16 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 17 | "GlossSeeAlso": [ 18 | "GML", 19 | "XML" 20 | ] 21 | }, 22 | "GlossSee": "markup" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | { 29 | "glossary": { 30 | "title": "example glossary", 31 | "GlossDiv": { 32 | "title": "S", 33 | "GlossList": { 34 | "GlossEntry": { 35 | "ID": "SGML", 36 | "SortAs": "SGML", 37 | "GlossTerm": "Standard Generalized Markup Language", 38 | "Acronym": "SGML", 39 | "Abbrev": "ISO 8879:1986", 40 | "GlossDef": { 41 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 42 | "GlossSeeAlso": [ 43 | "GML", 44 | "XML" 45 | ] 46 | }, 47 | "GlossSee": "markup" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "glossary": { 55 | "title": "example glossary", 56 | "GlossDiv": { 57 | "title": "S", 58 | "GlossList": { 59 | "GlossEntry": { 60 | "ID": "SGML", 61 | "SortAs": "SGML", 62 | "GlossTerm": "Standard Generalized Markup Language", 63 | "Acronym": "SGML", 64 | "Abbrev": "ISO 8879:1986", 65 | "GlossDef": { 66 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 67 | "GlossSeeAlso": [ 68 | "GML", 69 | "XML" 70 | ] 71 | }, 72 | "GlossSee": "markup" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/LogApplication.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor 2 | 3 | import android.app.Application 4 | import ihsanbal.com.logginginterceptor.di.DaggerNetComponent 5 | import ihsanbal.com.logginginterceptor.di.NetComponent 6 | import ihsanbal.com.logginginterceptor.di.NetModule 7 | 8 | /** 9 | * @author ihsan on 09/02/2017. 10 | */ 11 | class LogApplication : Application() { 12 | 13 | var appComponent: NetComponent? = null 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | appComponent = DaggerNetComponent.builder() 18 | .netModule(NetModule("http://postman-echo.com/", assets)) 19 | .build() 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/api/Api.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.api 2 | 3 | import ihsanbal.com.logginginterceptor.model.Body 4 | import okhttp3.MultipartBody 5 | import okhttp3.RequestBody 6 | import okhttp3.ResponseBody 7 | import retrofit2.http.* 8 | import rx.Observable 9 | 10 | /** 11 | * @author ihsan on 09/02/2017. 12 | */ 13 | interface Api { 14 | @GET("get?test=123") 15 | fun get(): Observable 16 | 17 | @DELETE("delete") 18 | fun delete(): Observable 19 | 20 | @POST("post?query=q") 21 | @Headers("Cache-Control: Custom-Max-Value=640000") 22 | fun post(@retrofit2.http.Body requestBody: Body?): Observable 23 | 24 | @PATCH("segment/patch") 25 | fun patch(@Query("query") q: String?): Observable 26 | 27 | @PUT("put") 28 | fun put(): Observable 29 | 30 | @Streaming 31 | @GET("http://che.org.il/wp-content/uploads/2016/12/pdf-sample.pdf") 32 | fun pdf(): Observable 33 | 34 | @Multipart 35 | @POST("post") 36 | fun post(@Part("description") description: RequestBody?, @Part file: MultipartBody.Part?): Observable 37 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/base/BaseCompatActivity.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.base 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import ihsanbal.com.logginginterceptor.LogApplication 5 | import ihsanbal.com.logginginterceptor.di.NetComponent 6 | 7 | /** 8 | * @author ihsan on 09/02/2017. 9 | */ 10 | abstract class BaseCompatActivity : AppCompatActivity() { 11 | val injector: NetComponent? 12 | get() = (application as LogApplication).appComponent 13 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/di/NetComponent.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.di 2 | 3 | import dagger.Component 4 | import ihsanbal.com.logginginterceptor.ui.MainActivity 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * @author ihsan on 09/02/2017. 9 | */ 10 | @Singleton 11 | @Component(modules = [NetModule::class]) 12 | interface NetComponent { 13 | fun inject(activity: MainActivity?) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/di/NetModule.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.di 2 | 3 | import android.content.res.AssetManager 4 | import android.util.Log.VERBOSE 5 | import com.ihsanbal.logging.Level 6 | import com.ihsanbal.logging.LoggingInterceptor 7 | import dagger.Module 8 | import dagger.Provides 9 | import ihsanbal.com.logginginterceptor.BuildConfig 10 | import ihsanbal.com.logginginterceptor.api.Api 11 | import okhttp3.OkHttpClient 12 | import retrofit2.Retrofit 13 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import javax.inject.Singleton 16 | 17 | /** 18 | * @author ihsan on 09/02/2017. 19 | */ 20 | @Module 21 | class NetModule(private val mEndPoint: String, private val mAssetManager: AssetManager) { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideOkHttpClient(): OkHttpClient { 26 | val client = OkHttpClient.Builder() 27 | client.addInterceptor(LoggingInterceptor.Builder() 28 | .setLevel(Level.BASIC) 29 | .log(VERBOSE) 30 | .addHeader("version", BuildConfig.VERSION_NAME) 31 | .addQueryParam("query", "0") 32 | // .logger(object : Logger { 33 | // override fun log(level: Int, tag: String?, msg: String?) { 34 | // Log.e("$tag - $level", "$msg") 35 | // } 36 | // }) 37 | // .enableMock(BuildConfig.MOCK, 1000L, object : BufferListener { 38 | // override fun getJsonResponse(request: Request?): String? { 39 | // val segment = request?.url?.pathSegments?.getOrNull(0) 40 | // return mAssetManager.open(String.format("mock/%s.json", segment)).source().buffer().readUtf8() 41 | // } 42 | // }) 43 | .build()) 44 | return client.build() 45 | } 46 | 47 | @Provides 48 | @Singleton 49 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 50 | return Retrofit.Builder() 51 | .addConverterFactory(GsonConverterFactory.create()) 52 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 53 | .baseUrl(mEndPoint) 54 | .client(okHttpClient) 55 | .build() 56 | } 57 | 58 | @Provides 59 | @Singleton 60 | fun provideApi(retrofit: Retrofit): Api { 61 | return retrofit.create(Api::class.java) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/model/Body.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.model 2 | 3 | import androidx.collection.SparseArrayCompat 4 | 5 | /** 6 | * @author ihsan on 09/02/2017. 7 | */ 8 | class Body { 9 | var sparseArray: SparseArrayCompat = SparseArrayCompat(3) 10 | 11 | init { 12 | sparseArray.put(0, 1) 13 | sparseArray.put(1, 2) 14 | sparseArray.put(2, 3) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/ihsanbal/com/logginginterceptor/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package ihsanbal.com.logginginterceptor.ui 2 | 3 | import android.Manifest 4 | import android.content.ActivityNotFoundException 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.net.Uri 8 | import android.os.Bundle 9 | import android.os.Environment 10 | import android.os.StrictMode 11 | import android.util.Log 12 | import android.widget.Toast 13 | import androidx.core.app.ActivityCompat 14 | import androidx.core.content.ContextCompat 15 | import ihsanbal.com.logginginterceptor.R 16 | import ihsanbal.com.logginginterceptor.api.Api 17 | import ihsanbal.com.logginginterceptor.base.BaseCompatActivity 18 | import ihsanbal.com.logginginterceptor.model.Body 19 | import kotlinx.android.synthetic.main.activity_main.* 20 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 21 | import okhttp3.MultipartBody 22 | import okhttp3.MultipartBody.Part.Companion.createFormData 23 | import okhttp3.RequestBody.Companion.asRequestBody 24 | import okhttp3.RequestBody.Companion.toRequestBody 25 | import okhttp3.ResponseBody 26 | import rx.Observable 27 | import rx.Observer 28 | import rx.android.schedulers.AndroidSchedulers 29 | import rx.schedulers.Schedulers 30 | import java.io.* 31 | import javax.inject.Inject 32 | 33 | class MainActivity : BaseCompatActivity() { 34 | 35 | private var outputFile: File? = null 36 | @Inject 37 | lateinit var api: Api 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | injector?.inject(this) 42 | setContentView(R.layout.activity_main) 43 | val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() 44 | StrictMode.setThreadPolicy(policy) 45 | bindListeners() 46 | } 47 | 48 | private fun bindListeners() { 49 | button_post.setOnClickListener { callPost() } 50 | button_zip.setOnClickListener { callZip() } 51 | button_get.setOnClickListener { callGet() } 52 | button_delete.setOnClickListener { callDelete() } 53 | button_patch.setOnClickListener { callPatch() } 54 | button_put.setOnClickListener { callPut() } 55 | button_pdf.setOnClickListener { callPdf() } 56 | button_pdf_upload.setOnClickListener { callUpload() } 57 | } 58 | 59 | private fun callPost() { 60 | api.post(Body()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | .subscribeOn(Schedulers.io()) 63 | .subscribe { log(it) } 64 | } 65 | 66 | private fun callZip() { 67 | val observablePost = api.post(Body()) 68 | .observeOn(AndroidSchedulers.mainThread()) 69 | .subscribeOn(Schedulers.io()) 70 | val observableGet = api.get() 71 | .observeOn(AndroidSchedulers.mainThread()) 72 | .subscribeOn(Schedulers.io()) 73 | Observable.zip(observablePost, observableGet) { o: ResponseBody?, _: ResponseBody? -> o }.subscribe { 74 | try { 75 | log(it) 76 | } catch (e: IOException) { 77 | e.printStackTrace() 78 | } 79 | } 80 | } 81 | 82 | private fun log(it: ResponseBody?) { 83 | Log.w("onNext", "${it?.string()}") 84 | } 85 | 86 | private fun callGet() { 87 | api.get() 88 | .observeOn(AndroidSchedulers.mainThread()) 89 | .subscribeOn(Schedulers.io()) 90 | .subscribe { log(it) } 91 | } 92 | 93 | private fun callDelete() { 94 | api.delete() 95 | .observeOn(AndroidSchedulers.mainThread()) 96 | .subscribeOn(Schedulers.io()) 97 | .subscribe { log(it) } 98 | } 99 | 100 | private fun callPatch() { 101 | api.patch("q2") 102 | .observeOn(AndroidSchedulers.mainThread()) 103 | .subscribeOn(Schedulers.io()) 104 | .subscribe { log(it) } 105 | } 106 | 107 | private fun callPut() { 108 | api.put() 109 | .observeOn(AndroidSchedulers.mainThread()) 110 | .subscribeOn(Schedulers.io()) 111 | .subscribe { log(it) } 112 | } 113 | 114 | private fun callPdf() { 115 | if (checkPermission()) { 116 | api.pdf() 117 | .observeOn(AndroidSchedulers.mainThread()) 118 | .subscribeOn(Schedulers.io()) 119 | .subscribe { downloadFile(it!!) } 120 | } else { 121 | requestPermission() 122 | } 123 | } 124 | 125 | private fun callUpload() { 126 | if (outputFile == null) { 127 | Toast.makeText(this, "Click 'File' for create file", Toast.LENGTH_SHORT).show() 128 | return 129 | } 130 | val requestFile = outputFile?.asRequestBody("application/pdf".toMediaTypeOrNull()) 131 | val body: MultipartBody.Part = createFormData("picture", outputFile!!.name, requestFile!!) 132 | val descriptionString = "hello, this is description speaking" 133 | api.post(descriptionString.toRequestBody(MultipartBody.FORM), body) 134 | .observeOn(AndroidSchedulers.mainThread()) 135 | .subscribeOn(Schedulers.io()) 136 | .subscribe(object : Observer { 137 | override fun onCompleted() {} 138 | override fun onError(e: Throwable) {} 139 | override fun onNext(responseBody: ResponseBody?) {} 140 | }) 141 | } 142 | 143 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 144 | when (requestCode) { 145 | PERMISSION_REQUEST_CODE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 146 | callPdf() 147 | } else { 148 | Toast.makeText(applicationContext, "Permission Denied", Toast.LENGTH_SHORT).show() 149 | } 150 | } 151 | } 152 | 153 | private fun checkPermission(): Boolean { 154 | val result = ContextCompat.checkSelfPermission(this, 155 | Manifest.permission.WRITE_EXTERNAL_STORAGE) 156 | return result == PackageManager.PERMISSION_GRANTED 157 | } 158 | 159 | private fun requestPermission() { 160 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE) 161 | } 162 | 163 | @Suppress("DEPRECATION") 164 | @Throws(IOException::class) 165 | private fun downloadFile(body: ResponseBody) { 166 | var count: Int 167 | val data = ByteArray(1024 * 4) 168 | val bis: InputStream = BufferedInputStream(body.byteStream(), 1024 * 8) 169 | outputFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "file.zip") 170 | val output: OutputStream = FileOutputStream(outputFile!!) 171 | while (bis.read(data).also { count = it } != -1) { 172 | output.write(data, 0, count) 173 | } 174 | output.flush() 175 | output.close() 176 | bis.close() 177 | val target = Intent(Intent.ACTION_VIEW) 178 | target.setDataAndType(Uri.fromFile(outputFile), "application/pdf") 179 | target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY 180 | val intent = Intent.createChooser(target, "Open File") 181 | try { 182 | startActivity(intent) 183 | } catch (e: ActivityNotFoundException) { 184 | e.printStackTrace() 185 | } 186 | } 187 | 188 | companion object { 189 | const val PERMISSION_REQUEST_CODE = 1000 190 | } 191 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 27 | 28 | 33 | 34 | 39 | 40 | 45 | 46 | 51 | 52 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LoggingInterceptor 3 | POST 4 | GET 5 | DELETE 6 | PATCH 7 | PUT 8 | FILE 9 | UPLOAD 10 | ZIP 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | ext.kotlin_version = '1.3.61' 4 | ext.jsonVersion = '20180130' 5 | ext.okioVersion = '2.2.2' 6 | ext.retrofitVersion = '2.6.2' 7 | ext.retrofitRxAdapterVersion = '2.6.2' 8 | ext.gsonConverterVersion = '2.6.2' 9 | ext.okhttpVersion = '4.4.0' 10 | ext.rxjavaVersion = '1.0.10' 11 | ext.rxAndroidVersion = '1.0.1' 12 | ext.daggerVersion = '2.8' 13 | ext.supportVersion = '29.0.3' 14 | ext.leakCanaryVersion = '1.5' 15 | 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | google() 20 | } 21 | dependencies { 22 | //noinspection 23 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 24 | classpath 'com.android.tools.build:gradle:3.6.1' 25 | } 26 | } 27 | 28 | project.ext { 29 | groupId = 'com.github.ihsanbal' 30 | artifactId = 'LoggingInterceptor' 31 | snapshot = '3.1.0-rc2' 32 | } 33 | 34 | allprojects { 35 | repositories { 36 | mavenLocal() 37 | jcenter() 38 | google() 39 | maven { url 'https://jitpack.io' } 40 | } 41 | } 42 | 43 | task clean(type: Delete) { 44 | delete rootProject.buildDir 45 | } 46 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | VERSION_NAME=1.0.4 15 | android.useAndroidX=true 16 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 01 21:51:28 EST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 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/logcat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/images/logcat.png -------------------------------------------------------------------------------- /images/screen_shot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/images/screen_shot_1.png -------------------------------------------------------------------------------- /images/screen_shot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/images/screen_shot_2.png -------------------------------------------------------------------------------- /images/screen_shot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/images/screen_shot_4.png -------------------------------------------------------------------------------- /images/screen_shot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihsanbal/LoggingInterceptor/3cccbc762fd7bbc0dec61bf8c116d124f09415df/images/screen_shot_5.png -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'maven' 3 | apply plugin: 'project-report' 4 | 5 | group = 'com.github.ihsanbal' 6 | 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | implementation group: 'org.json', name: 'json', version: jsonVersion 16 | implementation group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: okhttpVersion 17 | } 18 | 19 | apply from: '../pTML.gradle' -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/BufferListener.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | import okhttp3.Request 4 | import java.io.IOException 5 | 6 | /** 7 | * @author ihsan on 8/12/18. 8 | */ 9 | interface BufferListener { 10 | @Throws(IOException::class) 11 | fun getJsonResponse(request: Request?): String? 12 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/I.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | import okhttp3.internal.platform.Platform.Companion.INFO 4 | import java.util.logging.Level 5 | import java.util.logging.Logger 6 | 7 | /** 8 | * @author ihsan on 10/02/2017. 9 | */ 10 | internal open class I protected constructor() { 11 | companion object { 12 | private val prefix = arrayOf(". ", " .") 13 | private var index = 0 14 | fun log(type: Int, tag: String, msg: String?, isLogHackEnable: Boolean) { 15 | val finalTag = getFinalTag(tag, isLogHackEnable) 16 | val logger = Logger.getLogger(if (isLogHackEnable) finalTag else tag) 17 | when (type) { 18 | INFO -> logger.log(Level.INFO, msg) 19 | else -> logger.log(Level.WARNING, msg) 20 | } 21 | } 22 | 23 | private fun getFinalTag(tag: String, isLogHackEnable: Boolean): String { 24 | return if (isLogHackEnable) { 25 | index = index xor 1 26 | prefix[index] + tag 27 | } else { 28 | tag 29 | } 30 | } 31 | } 32 | 33 | init { 34 | throw UnsupportedOperationException() 35 | } 36 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/Level.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | /** 4 | * @author ihsan on 21/02/2017. 5 | */ 6 | enum class Level { 7 | /** 8 | * No logs. 9 | */ 10 | NONE, 11 | /** 12 | * 13 | * Example: 14 | *
`- URL
15 |      * - Method
16 |      * - Headers
17 |      * - Body
18 |     `
* 19 | */ 20 | BASIC, 21 | /** 22 | * 23 | * Example: 24 | *
`- URL
25 |      * - Method
26 |      * - Headers
27 |     `
* 28 | */ 29 | HEADERS, 30 | /** 31 | * 32 | * Example: 33 | *
`- URL
34 |      * - Method
35 |      * - Body
36 |     `
* 37 | */ 38 | BODY 39 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | import okhttp3.internal.platform.Platform 4 | import okhttp3.internal.platform.Platform.Companion.INFO 5 | 6 | /** 7 | * @author ihsan on 11/07/2017. 8 | */ 9 | interface Logger { 10 | fun log(level: Int = INFO, tag: String?= null, msg: String? = null) 11 | 12 | companion object { 13 | val DEFAULT: Logger = object : Logger { 14 | override fun log(level: Int, tag: String?, msg: String?) { 15 | Platform.get().log("$msg", level, null) 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/LoggingInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | import okhttp3.* 4 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 5 | import okhttp3.ResponseBody.Companion.toResponseBody 6 | import okhttp3.internal.platform.Platform.Companion.INFO 7 | import java.util.* 8 | import java.util.concurrent.Executor 9 | import java.util.concurrent.TimeUnit 10 | 11 | 12 | /** 13 | * @author ihsan on 09/02/2017. 14 | */ 15 | class LoggingInterceptor private constructor(private val builder: Builder) : Interceptor { 16 | 17 | override fun intercept(chain: Interceptor.Chain): Response { 18 | val request = addQueryAndHeaders(chain.request()) 19 | 20 | if (builder.level == Level.NONE) { 21 | return chain.proceed(request) 22 | } 23 | 24 | printlnRequestLog(request) 25 | 26 | val startNs = System.nanoTime() 27 | val response: Response 28 | try { 29 | response = proceedResponse(chain, request) 30 | } catch (e: Exception) { 31 | Printer.printFailed(builder.getTag(false), builder) 32 | throw e 33 | } 34 | val receivedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) 35 | 36 | printlnResponseLog(receivedMs, response, request) 37 | return response 38 | } 39 | 40 | private fun printlnResponseLog(receivedMs: Long, response: Response, request: Request) { 41 | Printer.printJsonResponse( 42 | builder, 43 | receivedMs, 44 | response.isSuccessful, 45 | response.code, 46 | response.headers, 47 | response, 48 | request.url.encodedPathSegments, 49 | response.message, 50 | request.url.toString()) 51 | } 52 | 53 | private fun printlnRequestLog(request: Request) { 54 | Printer.printJsonRequest( 55 | builder, 56 | request.body, 57 | request.url.toUrl().toString(), 58 | request.headers, 59 | request.method) 60 | } 61 | 62 | private fun proceedResponse(chain: Interceptor.Chain, request: Request): Response { 63 | return if (builder.isMockEnabled && builder.listener != null) { 64 | TimeUnit.MILLISECONDS.sleep(builder.sleepMs) 65 | Response.Builder() 66 | .body(builder.listener!!.getJsonResponse(request)?.toResponseBody("application/json".toMediaTypeOrNull())) 67 | .request(chain.request()) 68 | .protocol(Protocol.HTTP_2) 69 | .message("Mock data from LoggingInterceptor") 70 | .code(200) 71 | .build() 72 | } else chain.proceed(request) 73 | } 74 | 75 | private fun addQueryAndHeaders(request: Request): Request { 76 | val requestBuilder = request.newBuilder() 77 | builder.headers.keys.forEach { key -> 78 | builder.headers[key]?.let { 79 | requestBuilder.addHeader(key, it) 80 | } 81 | } 82 | val httpUrlBuilder: HttpUrl.Builder? = request.url.newBuilder(request.url.toString()) 83 | httpUrlBuilder?.let { 84 | builder.httpUrl.keys.forEach { key -> 85 | httpUrlBuilder.addQueryParameter(key, builder.httpUrl[key]) 86 | } 87 | } 88 | return requestBuilder.url(httpUrlBuilder?.build()!!).build() 89 | } 90 | 91 | @Suppress("unused") 92 | class Builder { 93 | val headers: HashMap = HashMap() 94 | val httpUrl: HashMap = HashMap() 95 | var isLogHackEnable = false 96 | private set 97 | var isDebugAble = false 98 | var type: Int = INFO 99 | private set 100 | private var requestTag: String? = null 101 | private var responseTag: String? = null 102 | var level = Level.BASIC 103 | private set 104 | var logger: Logger? = null 105 | private set 106 | var isMockEnabled = false 107 | var sleepMs: Long = 0 108 | var listener: BufferListener? = null 109 | 110 | /** 111 | * @param level set log level 112 | * @return Builder 113 | * @see Level 114 | */ 115 | fun setLevel(level: Level): Builder { 116 | this.level = level 117 | return this 118 | } 119 | 120 | fun getTag(isRequest: Boolean): String { 121 | return when (isRequest) { 122 | true -> if (requestTag.isNullOrEmpty()) TAG else requestTag!! 123 | false -> if (responseTag.isNullOrEmpty()) TAG else responseTag!! 124 | } 125 | } 126 | 127 | /** 128 | * @param name Filed 129 | * @param value Value 130 | * @return Builder 131 | * Add a field with the specified value 132 | */ 133 | fun addHeader(name: String, value: String): Builder { 134 | headers[name] = value 135 | return this 136 | } 137 | 138 | /** 139 | * @param name Filed 140 | * @param value Value 141 | * @return Builder 142 | * Add a field with the specified value 143 | */ 144 | fun addQueryParam(name: String, value: String): Builder { 145 | httpUrl[name] = value 146 | return this 147 | } 148 | 149 | /** 150 | * Set request and response each log tag 151 | * 152 | * @param tag general log tag 153 | * @return Builder 154 | */ 155 | fun tag(tag: String): Builder { 156 | TAG = tag 157 | return this 158 | } 159 | 160 | /** 161 | * Set request log tag 162 | * 163 | * @param tag request log tag 164 | * @return Builder 165 | */ 166 | fun request(tag: String?): Builder { 167 | requestTag = tag 168 | return this 169 | } 170 | 171 | /** 172 | * Set response log tag 173 | * 174 | * @param tag response log tag 175 | * @return Builder 176 | */ 177 | fun response(tag: String?): Builder { 178 | responseTag = tag 179 | return this 180 | } 181 | 182 | /** 183 | * @param isDebug set can sending log output 184 | * @return Builder 185 | */ 186 | @Deprecated(message = "Set level based on your requirement", 187 | replaceWith = ReplaceWith(expression = "setLevel(Level.Basic)"), 188 | level = DeprecationLevel.ERROR) 189 | fun loggable(isDebug: Boolean): Builder { 190 | this.isDebugAble = isDebug 191 | return this 192 | } 193 | 194 | /** 195 | * @param type set sending log output type 196 | * @return Builder 197 | * @see okhttp3.internal.platform.Platform 198 | */ 199 | fun log(type: Int): Builder { 200 | this.type = type 201 | return this 202 | } 203 | 204 | /** 205 | * @param logger manuel logging interface 206 | * @return Builder 207 | * @see Logger 208 | */ 209 | fun logger(logger: Logger?): Builder { 210 | this.logger = logger 211 | return this 212 | } 213 | 214 | /** 215 | * @param executor manual executor for printing 216 | * @return Builder 217 | * @see Logger 218 | */ 219 | @Deprecated(message = "Create your own Logcat filter for best result", level = DeprecationLevel.ERROR) 220 | fun executor(executor: Executor?): Builder { 221 | TODO("Deprecated") 222 | } 223 | 224 | /** 225 | * @param useMock let you use json file from asset 226 | * @param sleep let you see progress dialog when you request 227 | * @return Builder 228 | * @see LoggingInterceptor 229 | */ 230 | fun enableMock(useMock: Boolean, sleep: Long, listener: BufferListener?): Builder { 231 | isMockEnabled = useMock 232 | sleepMs = sleep 233 | this.listener = listener 234 | return this 235 | } 236 | 237 | /** 238 | * Call this if you want to have formatted pretty output in Android Studio logCat. 239 | * By default this 'hack' is not applied. 240 | * 241 | * @param useHack setup builder to use hack for Android Studio v3+ in order to have nice 242 | * output as it was in previous A.S. versions. 243 | * @return Builder 244 | * @see Logger 245 | */ 246 | @Deprecated(message = "Android studio has resolved problem for latest versions", 247 | level = DeprecationLevel.WARNING) 248 | fun enableAndroidStudioV3LogsHack(useHack: Boolean): Builder { 249 | isLogHackEnable = useHack 250 | return this 251 | } 252 | 253 | fun build(): LoggingInterceptor { 254 | return LoggingInterceptor(this) 255 | } 256 | 257 | companion object { 258 | private var TAG = "LoggingI" 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /lib/src/main/java/com/ihsanbal/logging/Printer.kt: -------------------------------------------------------------------------------- 1 | package com.ihsanbal.logging 2 | 3 | import okhttp3.Headers 4 | import okhttp3.RequestBody 5 | import okhttp3.Response 6 | import okhttp3.internal.http.promisesBody 7 | import okio.Buffer 8 | import okio.GzipSource 9 | import org.json.JSONArray 10 | import org.json.JSONException 11 | import org.json.JSONObject 12 | import java.io.EOFException 13 | import java.io.IOException 14 | import java.nio.charset.Charset 15 | import java.nio.charset.StandardCharsets 16 | 17 | /** 18 | * @author ihsan on 09/02/2017. 19 | */ 20 | class Printer private constructor() { 21 | companion object { 22 | private const val JSON_INDENT = 3 23 | private val LINE_SEPARATOR = System.getProperty("line.separator") 24 | private val DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR 25 | private const val N = "\n" 26 | private const val T = "\t" 27 | private const val REQUEST_UP_LINE = "┌────── Request ────────────────────────────────────────────────────────────────────────" 28 | private const val END_LINE = "└───────────────────────────────────────────────────────────────────────────────────────" 29 | private const val RESPONSE_UP_LINE = "┌────── Response ───────────────────────────────────────────────────────────────────────" 30 | private const val BODY_TAG = "Body:" 31 | private const val URL_TAG = "URL: " 32 | private const val METHOD_TAG = "Method: @" 33 | private const val HEADERS_TAG = "Headers:" 34 | private const val STATUS_CODE_TAG = "Status Code: " 35 | private const val RECEIVED_TAG = "Received in: " 36 | private const val DEFAULT_LINE = "│ " 37 | private val OOM_OMITTED = LINE_SEPARATOR + "Output omitted because of Object size." 38 | private fun isEmpty(line: String): Boolean { 39 | return line.isEmpty() || N == line || T == line || line.trim { it <= ' ' }.isEmpty() 40 | } 41 | 42 | fun printJsonRequest(builder: LoggingInterceptor.Builder, body: RequestBody?, url: String, header: Headers, method: String) { 43 | val requestBody = body?.let { 44 | LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyToString(body, header) 45 | } ?: "" 46 | val tag = builder.getTag(true) 47 | if (builder.logger == null) I.log(builder.type, tag, REQUEST_UP_LINE, builder.isLogHackEnable) 48 | logLines(builder.type, tag, arrayOf(URL_TAG + url), builder.logger, false, builder.isLogHackEnable) 49 | logLines(builder.type, tag, getRequest(builder.level, header, method), builder.logger, true, builder.isLogHackEnable) 50 | if (builder.level == Level.BASIC || builder.level == Level.BODY) { 51 | logLines(builder.type, tag, requestBody.split(LINE_SEPARATOR).toTypedArray(), builder.logger, true, builder.isLogHackEnable) 52 | } 53 | if (builder.logger == null) I.log(builder.type, tag, END_LINE, builder.isLogHackEnable) 54 | } 55 | 56 | fun printJsonResponse(builder: LoggingInterceptor.Builder, chainMs: Long, isSuccessful: Boolean, 57 | code: Int, headers: Headers, response: Response, segments: List, message: String, responseUrl: String) { 58 | val responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + getResponseBody(response) 59 | val tag = builder.getTag(false) 60 | val urlLine = arrayOf(URL_TAG + responseUrl, N) 61 | val responseString = getResponse(headers, chainMs, code, isSuccessful, 62 | builder.level, segments, message) 63 | if (builder.logger == null) { 64 | I.log(builder.type, tag, RESPONSE_UP_LINE, builder.isLogHackEnable) 65 | } 66 | logLines(builder.type, tag, urlLine, builder.logger, true, builder.isLogHackEnable) 67 | logLines(builder.type, tag, responseString, builder.logger, true, builder.isLogHackEnable) 68 | if (builder.level == Level.BASIC || builder.level == Level.BODY) { 69 | logLines(builder.type, tag, responseBody.split(LINE_SEPARATOR).toTypedArray(), builder.logger, 70 | true, builder.isLogHackEnable) 71 | } 72 | if (builder.logger == null) { 73 | I.log(builder.type, tag, END_LINE, builder.isLogHackEnable) 74 | } 75 | } 76 | 77 | private fun getResponseBody(response: Response): String { 78 | val responseBody = response.body!! 79 | val headers = response.headers 80 | val contentLength = responseBody.contentLength() 81 | if (!response.promisesBody()) { 82 | return "End request - Promises Body" 83 | } else if (bodyHasUnknownEncoding(response.headers)) { 84 | return "encoded body omitted" 85 | } else { 86 | val source = responseBody.source() 87 | source.request(Long.MAX_VALUE) // Buffer the entire body. 88 | var buffer = source.buffer 89 | 90 | var gzippedLength: Long? = null 91 | if ("gzip".equals(headers["Content-Encoding"], ignoreCase = true)) { 92 | gzippedLength = buffer.size 93 | GzipSource(buffer.clone()).use { gzippedResponseBody -> 94 | buffer = Buffer() 95 | buffer.writeAll(gzippedResponseBody) 96 | } 97 | } 98 | 99 | val contentType = responseBody.contentType() 100 | val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) 101 | ?: StandardCharsets.UTF_8 102 | 103 | if (!buffer.isProbablyUtf8()) { 104 | return "End request - binary ${buffer.size}:byte body omitted" 105 | } 106 | 107 | if (contentLength != 0L) { 108 | return getJsonString(buffer.clone().readString(charset)) 109 | } 110 | 111 | return if (gzippedLength != null) { 112 | "End request - ${buffer.size}:byte, $gzippedLength-gzipped-byte body" 113 | } else { 114 | "End request - ${buffer.size}:byte body" 115 | } 116 | } 117 | } 118 | 119 | private fun getRequest(level: Level, headers: Headers, method: String): Array { 120 | val log: String 121 | val loggableHeader = level == Level.HEADERS || level == Level.BASIC 122 | log = METHOD_TAG + method + DOUBLE_SEPARATOR + 123 | if (isEmpty("$headers")) "" else if (loggableHeader) HEADERS_TAG + LINE_SEPARATOR + dotHeaders(headers) else "" 124 | return log.split(LINE_SEPARATOR).toTypedArray() 125 | } 126 | 127 | private fun getResponse(headers: Headers, tookMs: Long, code: Int, isSuccessful: Boolean, 128 | level: Level, segments: List, message: String): Array { 129 | val log: String 130 | val loggableHeader = level == Level.HEADERS || level == Level.BASIC 131 | val segmentString = slashSegments(segments) 132 | log = ((if (segmentString.isNotEmpty()) "$segmentString - " else "") + "[is success : " 133 | + isSuccessful + "] - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG + 134 | code + " / " + message + DOUBLE_SEPARATOR + when { 135 | isEmpty("$headers") -> "" 136 | loggableHeader -> HEADERS_TAG + LINE_SEPARATOR + 137 | dotHeaders(headers) 138 | else -> "" 139 | }) 140 | return log.split(LINE_SEPARATOR).toTypedArray() 141 | } 142 | 143 | private fun slashSegments(segments: List): String { 144 | val segmentString = StringBuilder() 145 | for (segment in segments) { 146 | segmentString.append("/").append(segment) 147 | } 148 | return segmentString.toString() 149 | } 150 | 151 | private fun dotHeaders(headers: Headers): String { 152 | val builder = StringBuilder() 153 | headers.forEach { pair -> 154 | builder.append("${pair.first}: ${pair.second}").append(N) 155 | } 156 | return builder.dropLast(1).toString() 157 | } 158 | 159 | private fun logLines(type: Int, tag: String, lines: Array, logger: Logger?, 160 | withLineSize: Boolean, useLogHack: Boolean) { 161 | for (line in lines) { 162 | val lineLength = line.length 163 | val maxLogSize = if (withLineSize) 110 else lineLength 164 | for (i in 0..lineLength / maxLogSize) { 165 | val start = i * maxLogSize 166 | var end = (i + 1) * maxLogSize 167 | end = if (end > line.length) line.length else end 168 | if (logger == null) { 169 | I.log(type, tag, DEFAULT_LINE + line.substring(start, end), useLogHack) 170 | } else { 171 | logger.log(type, tag, line.substring(start, end)) 172 | } 173 | } 174 | } 175 | } 176 | 177 | private fun bodyToString(requestBody: RequestBody?, headers: Headers): String { 178 | return requestBody?.let { 179 | return try { 180 | when { 181 | bodyHasUnknownEncoding(headers) -> { 182 | return "encoded body omitted)" 183 | } 184 | requestBody.isDuplex() -> { 185 | return "duplex request body omitted" 186 | } 187 | requestBody.isOneShot() -> { 188 | return "one-shot body omitted" 189 | } 190 | else -> { 191 | val buffer = Buffer() 192 | requestBody.writeTo(buffer) 193 | 194 | val contentType = requestBody.contentType() 195 | val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) 196 | ?: StandardCharsets.UTF_8 197 | 198 | return if (buffer.isProbablyUtf8()) { 199 | getJsonString(buffer.readString(charset)) + LINE_SEPARATOR + "${requestBody.contentLength()}-byte body" 200 | } else { 201 | "binary ${requestBody.contentLength()}-byte body omitted" 202 | } 203 | } 204 | } 205 | } catch (e: IOException) { 206 | "{\"err\": \"" + e.message + "\"}" 207 | } 208 | } ?: "" 209 | } 210 | 211 | private fun bodyHasUnknownEncoding(headers: Headers): Boolean { 212 | val contentEncoding = headers["Content-Encoding"] ?: return false 213 | return !contentEncoding.equals("identity", ignoreCase = true) && 214 | !contentEncoding.equals("gzip", ignoreCase = true) 215 | } 216 | 217 | private fun getJsonString(msg: String): String { 218 | val message: String 219 | message = try { 220 | when { 221 | msg.startsWith("{") -> { 222 | val jsonObject = JSONObject(msg) 223 | jsonObject.toString(JSON_INDENT) 224 | } 225 | msg.startsWith("[") -> { 226 | val jsonArray = JSONArray(msg) 227 | jsonArray.toString(JSON_INDENT) 228 | } 229 | else -> { 230 | msg 231 | } 232 | } 233 | } catch (e: JSONException) { 234 | msg 235 | } catch (e1: OutOfMemoryError) { 236 | OOM_OMITTED 237 | } 238 | return message 239 | } 240 | 241 | fun printFailed(tag: String, builder: LoggingInterceptor.Builder) { 242 | I.log(builder.type, tag, RESPONSE_UP_LINE, builder.isLogHackEnable) 243 | I.log(builder.type, tag, DEFAULT_LINE + "Response failed", builder.isLogHackEnable) 244 | I.log(builder.type, tag, END_LINE, builder.isLogHackEnable) 245 | } 246 | } 247 | 248 | init { 249 | throw UnsupportedOperationException() 250 | } 251 | } 252 | 253 | /** 254 | * @see 'https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/utf8.kt' 255 | * */ 256 | internal fun Buffer.isProbablyUtf8(): Boolean { 257 | try { 258 | val prefix = Buffer() 259 | val byteCount = size.coerceAtMost(64) 260 | copyTo(prefix, 0, byteCount) 261 | for (i in 0 until 16) { 262 | if (prefix.exhausted()) { 263 | break 264 | } 265 | val codePoint = prefix.readUtf8CodePoint() 266 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { 267 | return false 268 | } 269 | } 270 | return true 271 | } catch (_: EOFException) { 272 | return false // Truncated UTF-8 sequence. 273 | } 274 | } -------------------------------------------------------------------------------- /pTML.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | publishing { 4 | publications { 5 | library(MavenPublication) { 6 | 7 | setGroupId(project.groupId) 8 | setArtifactId(project.artifactId) 9 | version project.snapshot 10 | from components.java 11 | 12 | println "publishing: " + groupId 13 | println "publishing: " + artifactId 14 | println "publishing: " + version 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib' 2 | --------------------------------------------------------------------------------