Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ local.properties
build
captures
.externalNativeBuild
.idea/
.idea/
.kotlin
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ allprojects {
}

dependencies {
implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0') {
exclude group: 'org.json', module: 'json'
}
implementation('com.github.ihsanbal:LoggingInterceptor:3.1.0') {
exclude group: 'org.json', module: 'json'
}
}
```

Expand All @@ -55,13 +55,51 @@ allprojects {


dependencies {
implementation("com.github.ihsanbal:LoggingInterceptor:3.1.0") {
exclude(group = "org.json", module = "json")
}
implementation("com.github.ihsanbal:LoggingInterceptor:3.1.0") {
exclude(group = "org.json", module = "json")
}
}

```

## Batching and custom sinks (fork feature)

This fork adds a `sink(...)` API so you can batch a whole request/response block before logging
to avoid interleaving in Logcat. Example (using the bundled `BatchingSink`, now public):

```kotlin
val sink = BatchingSink(LogSink { type, tag, message ->
// Logcat truncates ~4k per line; forward to your own chunker if needed
Log.println(type, tag, message)
})

val client = OkHttpClient.Builder()
.addInterceptor(
LoggingInterceptor.Builder()
.setLevel(Level.BODY)
.log(Log.DEBUG)
.sink(sink)
.build()
)
.build()

If you need chunking/queuing (e.g., Logcat 4k limit), wrap the `LogSink` to your own queue before passing to `BatchingSink`, similar to the sample above.
```

If you want the forked artifact via JitPack:

```kotlin
allprojects {
repositories { maven { setUrl("https://jitpack.io") } }
}

dependencies {
implementation("com.github.rtsketo:LoggingInterceptor:3.1.0rt6") {
exclude(group = "org.json", module = "json")
}
}
```

Maven:
```xml
<repository>
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extension is expired so need to be replaced with binding

apply plugin: 'kotlin-kapt'

android {
Expand Down Expand Up @@ -67,4 +67,4 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
}
}
Binary file modified app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ buildscript {
}

project.ext {
groupId = 'com.github.ihsanbal'
artifactId = 'LoggingInterceptor'
snapshot = '3.1.0-rc2'
groupId = project.findProperty("group") ?: 'com.github.ihsanbal'
artifactId = project.findProperty("artifactId") ?: 'LoggingInterceptor'
snapshot = project.findProperty("version") ?: '3.1.0-rc2'
}

allprojects {
Expand Down
Binary file modified images/logcat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/screen_shot_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/screen_shot_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/screen_shot_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/screen_shot_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
.kotlin
2 changes: 1 addition & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ dependencies {
implementation group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: okhttpVersion
}

apply from: '../pTML.gradle'
apply from: '../pTML.gradle'
29 changes: 29 additions & 0 deletions lib/src/main/java/com/ihsanbal/logging/BatchingSink.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ihsanbal.logging

import java.util.concurrent.ConcurrentHashMap

/**
* Batches all lines for a request/response and flushes them as one block.
*/
class BatchingSink(
private val delegate: LogSink
) : LogSink {

private data class BufferKey(val tag: String)

private val buffers = ConcurrentHashMap<BufferKey, StringBuilder>()

override fun log(type: Int, tag: String, message: String) {
val key = BufferKey(tag)
val buffer = buffers.getOrPut(key) { StringBuilder() }
if (buffer.isNotEmpty()) buffer.append('\n')
buffer.append(message)
}

override fun close(type: Int, tag: String) {
val key = BufferKey(tag)
buffers.remove(key)?.let { block ->
delegate.log(type, tag, block.toString())
}
}
}
44 changes: 37 additions & 7 deletions lib/src/main/java/com/ihsanbal/logging/I.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.ihsanbal.logging

import okhttp3.internal.platform.Platform.Companion.INFO
import java.util.logging.Level
import java.util.logging.Logger

Expand All @@ -11,15 +10,46 @@ internal open class I protected constructor() {
companion object {
private val prefix = arrayOf(". ", " .")
private var index = 0
fun log(type: Int, tag: String, msg: String?, isLogHackEnable: Boolean) {

fun log(type: Int, tag: String, msg: String?, isLogHackEnable: Boolean, sink: LogSink? = null) {
if (sink != null) {
sink.log(type, tag, msg ?: "")
return
}

val finalTag = getFinalTag(tag, isLogHackEnable)
val logger = Logger.getLogger(if (isLogHackEnable) finalTag else tag)
when (type) {
INFO -> logger.log(Level.INFO, msg)
else -> logger.log(Level.WARNING, msg)

if (!logWithAndroid(type, finalTag, msg)) {
val logger = Logger.getLogger(if (isLogHackEnable) finalTag else tag)
logger.log(mapJavaLevel(type), msg)
}
}

private fun logWithAndroid(type: Int, tag: String, msg: String?): Boolean {
return try {
val logClass = Class.forName("android.util.Log")
val printlnMethod = logClass.getMethod(
"println",
Int::class.javaPrimitiveType,
String::class.java,
String::class.java
)
printlnMethod.invoke(null, type, tag, msg ?: "")
true
} catch (_: Throwable) {
false
}
}

private fun mapJavaLevel(type: Int): Level =
when (type) {
2, 3 -> Level.FINE // VERBOSE/DEBUG
4 -> Level.INFO
5 -> Level.WARNING
6, 7, 8, 9 -> Level.SEVERE
else -> Level.INFO
}

private fun getFinalTag(tag: String, isLogHackEnable: Boolean): String {
return if (isLogHackEnable) {
index = index xor 1
Expand All @@ -33,4 +63,4 @@ internal open class I protected constructor() {
init {
throw UnsupportedOperationException()
}
}
}
6 changes: 6 additions & 0 deletions lib/src/main/java/com/ihsanbal/logging/LogSink.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ihsanbal.logging

interface LogSink {
fun log(type: Int, tag: String, message: String)
fun close(type: Int, tag: String) {}
}
22 changes: 18 additions & 4 deletions lib/src/main/java/com/ihsanbal/logging/LoggingInterceptor.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.ihsanbal.logging

import okhttp3.*
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.internal.platform.Platform.Companion.INFO
import java.util.*
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -95,14 +98,15 @@ class LoggingInterceptor private constructor(private val builder: Builder) : Int
var isLogHackEnable = false
private set
var isDebugAble = false
var type: Int = INFO
var type: Int = DEFAULT_LOG_TYPE
private set
private var requestTag: String? = null
private var responseTag: String? = null
var level = Level.BASIC
private set
var logger: Logger? = null
private set
var sink: LogSink? = null
var isMockEnabled = false
var sleepMs: Long = 0
var listener: BufferListener? = null
Expand Down Expand Up @@ -250,12 +254,22 @@ class LoggingInterceptor private constructor(private val builder: Builder) : Int
return this
}

/**
* Override the default line logger with a sink. If both logger and sink
* are set, sink takes precedence.
*/
fun sink(sink: LogSink): Builder {
this.sink = sink
return this
}

fun build(): LoggingInterceptor {
return LoggingInterceptor(this)
}

companion object {
private const val DEFAULT_LOG_TYPE = 4 // android.util.Log.INFO compatible
private var TAG = "LoggingI"
}
}
}
}
74 changes: 36 additions & 38 deletions lib/src/main/java/com/ihsanbal/logging/Printer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Printer private constructor() {
private const val URL_TAG = "URL: "
private const val METHOD_TAG = "Method: @"
private const val HEADERS_TAG = "Headers:"
private const val STATUS_CODE_TAG = "Status Code: "
private const val STATUS_LINE_TAG = "Status Code: "
private const val RECEIVED_TAG = "Received in: "
private const val DEFAULT_LINE = "│ "
private val OOM_OMITTED = LINE_SEPARATOR + "Output omitted because of Object size."
Expand All @@ -44,34 +44,32 @@ class Printer private constructor() {
LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyToString(body, header)
} ?: ""
val tag = builder.getTag(true)
if (builder.logger == null) I.log(builder.type, tag, REQUEST_UP_LINE, builder.isLogHackEnable)
logLines(builder.type, tag, arrayOf(URL_TAG + url), builder.logger, false, builder.isLogHackEnable)
logLines(builder.type, tag, getRequest(builder.level, header, method), builder.logger, true, builder.isLogHackEnable)
val sink = builder.sink
emit(builder, tag, REQUEST_UP_LINE)
logLines(builder.type, tag, arrayOf(URL_TAG + url), builder.logger, false, builder.isLogHackEnable, sink)
logLines(builder.type, tag, getRequest(builder.level, header, method), builder.logger, true, builder.isLogHackEnable, sink)
if (builder.level == Level.BASIC || builder.level == Level.BODY) {
logLines(builder.type, tag, requestBody.split(LINE_SEPARATOR).toTypedArray(), builder.logger, true, builder.isLogHackEnable)
logLines(builder.type, tag, requestBody.split(LINE_SEPARATOR).toTypedArray(), builder.logger, true, builder.isLogHackEnable, sink)
}
if (builder.logger == null) I.log(builder.type, tag, END_LINE, builder.isLogHackEnable)
emit(builder, tag, END_LINE)
sink?.close(builder.type, tag)
}

fun printJsonResponse(builder: LoggingInterceptor.Builder, chainMs: Long, isSuccessful: Boolean,
code: Int, headers: Headers, response: Response, segments: List<String>, message: String, responseUrl: String) {
val responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + getResponseBody(response)
val tag = builder.getTag(false)
val urlLine = arrayOf(URL_TAG + responseUrl, N)
val responseString = getResponse(headers, chainMs, code, isSuccessful,
builder.level, segments, message)
if (builder.logger == null) {
I.log(builder.type, tag, RESPONSE_UP_LINE, builder.isLogHackEnable)
}
logLines(builder.type, tag, urlLine, builder.logger, true, builder.isLogHackEnable)
logLines(builder.type, tag, responseString, builder.logger, true, builder.isLogHackEnable)
val statusLine = getStatusLine(chainMs, code, message)
val sink = builder.sink
emit(builder, tag, RESPONSE_UP_LINE)
logLines(builder.type, tag, arrayOf(URL_TAG + responseUrl), builder.logger, false, builder.isLogHackEnable, sink)
logLines(builder.type, tag, statusLine, builder.logger, true, builder.isLogHackEnable, sink)
if (builder.level == Level.BASIC || builder.level == Level.BODY) {
logLines(builder.type, tag, responseBody.split(LINE_SEPARATOR).toTypedArray(), builder.logger,
true, builder.isLogHackEnable)
}
if (builder.logger == null) {
I.log(builder.type, tag, END_LINE, builder.isLogHackEnable)
true, builder.isLogHackEnable, sink)
}
emit(builder, tag, END_LINE)
sink?.close(builder.type, tag)
}

private fun getResponseBody(response: Response): String {
Expand Down Expand Up @@ -124,20 +122,19 @@ class Printer private constructor() {
return log.split(LINE_SEPARATOR).toTypedArray()
}

private fun getResponse(headers: Headers, tookMs: Long, code: Int, isSuccessful: Boolean,
level: Level, segments: List<String>, message: String): Array<String> {
val log: String
val loggableHeader = level == Level.HEADERS || level == Level.BASIC
val segmentString = slashSegments(segments)
log = ((if (segmentString.isNotEmpty()) "$segmentString - " else "") + "[is success : "
+ isSuccessful + "] - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG +
code + " / " + message + DOUBLE_SEPARATOR + when {
isEmpty("$headers") -> ""
loggableHeader -> HEADERS_TAG + LINE_SEPARATOR +
dotHeaders(headers)
else -> ""
})
return log.split(LINE_SEPARATOR).toTypedArray()
private fun getStatusLine(tookMs: Long, code: Int, message: String): Array<String> {
val status = "$STATUS_LINE_TAG$code / $message ($RECEIVED_TAG$tookMs ms)"
return arrayOf(status)
}

private fun emit(builder: LoggingInterceptor.Builder, tag: String, line: String) {
val sink = builder.sink
val logger = builder.logger
when {
sink != null -> sink.log(builder.type, tag, line)
logger == null -> I.log(builder.type, tag, line, builder.isLogHackEnable)
else -> logger.log(builder.type, tag, line)
}
}

private fun slashSegments(segments: List<String>): String {
Expand All @@ -157,18 +154,19 @@ class Printer private constructor() {
}

private fun logLines(type: Int, tag: String, lines: Array<String>, logger: Logger?,
withLineSize: Boolean, useLogHack: Boolean) {
withLineSize: Boolean, useLogHack: Boolean, sink: LogSink? = null) {
for (line in lines) {
val lineLength = line.length
val maxLogSize = if (withLineSize) 110 else lineLength
for (i in 0..lineLength / maxLogSize) {
val start = i * maxLogSize
var end = (i + 1) * maxLogSize
end = if (end > line.length) line.length else end
if (logger == null) {
I.log(type, tag, DEFAULT_LINE + line.substring(start, end), useLogHack)
} else {
logger.log(type, tag, line.substring(start, end))
val chunk = DEFAULT_LINE + line.substring(start, end)
when {
sink != null -> sink.log(type, tag, chunk)
logger == null -> I.log(type, tag, chunk, useLogHack)
else -> logger.log(type, tag, chunk)
}
}
}
Expand Down Expand Up @@ -271,4 +269,4 @@ internal fun Buffer.isProbablyUtf8(): Boolean {
} catch (_: EOFException) {
return false // Truncated UTF-8 sequence.
}
}
}