Okhttp3.0ログブロッキングHttpLoggingInterceptorの大ファイルアップロード時OutOfMemoryおよび修復

9286 ワード

プロジェクトにはokhttp+logging-interceptorを使用してローカルに保存されているlogファイルがアップロードされています.
コードクリップ:
var logLevel = HttpLoggingInterceptor.Level.BODY //  BODY  

val builder = OkHttpClient.Builder()
//... 
.addInterceptor(
    HttpLoggingInterceptor().setLevel(logLevel)


そして最初は1つのlogファイルしかなかったため、日がたつにつれてログファイルが大きく、80 M+の(これは複数のファイルに最適化されています)があり、
soが最初にアップロードした時HttpLoggingInterceptorの中で間違って報告して、ロゴを調べるのはOutOfMemoryで、
つまり次のようなコードフラグメントが間違っていて、HttpLoggingInterceptorで80 M+の内容を直接読み込みます.
    @Override 
    public Response intercept(Chain chain) throws IOException {

        //... 
        if (isPlaintext(buffer)) {
          logger.log(buffer.readString(charset)); //       ,        ,   
          logger.log("--> END " + request.method()
              + " (" + requestBody.contentLength() + "-byte body)");
        } else {
          logger.log("--> END " + request.method() + " (binary "
              + requestBody.contentLength() + "-byte body omitted)");
        }  
    }

ここでは判断条件が付けられておらず、サイズ制限もなく、表示に問題があります.
そこで考え方はログ印刷ブロックを書き直し、主にこの場所を読み取り、印刷することです.
後で公式issueを調べてみると、以前にもこの問題に遭遇した人がいて、彼らの案を参考にして、2つのグローバル変数requestBodyLogMaxとresponseBodyLogMaxを追加して、最大印刷のサイズを設定して、一部の内容だけを切り取って印刷することを超えました.
臨界値の設定
val builder = OkHttpClient.Builder()

builder
                    .addInterceptor(
                        CommonHttpLoggingInterceptor().setLevel(logLevel).setRequestBodyLogMax(2000).setResponseBodyLogMax(
                            2000
                        )
                    )
CommonHttpLoggingInterceptor.java    :
class CommonHttpLoggingInterceptor(val logger: Logger = Logger.DEFAULT) : Interceptor {

    @Volatile
    private var level = Level.NONE

    @Volatile
    private var requestBodyLogMax = LOG_LIMITATION_NONE
    @Volatile
    private var responseBodyLogMax = LOG_LIMITATION_NONE

    enum class Level {
        /** No logs.  */
        NONE,
        /**
         * Logs request and response lines.
         *
         *
         * Example:
         * 
`--> POST /greeting http/1.1 (3-byte body)
         *
         *  *
         */
        BASIC,
        /**
         * Logs request and response lines and their respective headers.
         *
         *
         * Example:
         * 
`--> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         * --> END POST
         *
         *  *
         */
        HEADERS,
        /**
         * Logs request and response lines and their respective headers and bodies (if present).
         *
         *
         * Example:
         * 
`--> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         *
         * Hi?
         * --> END POST
         *
         *  *
         */
        BODY,
        /**
         * Logs request and response lines.
         *
         *
         * Example:
         * 
`--> POST /greeting http/1.1 (3-byte body)
         *  result...(part)
         *  *
         */
        SIMPLE
    }

    interface Logger {
        fun log(message: String)

        companion object {

            /** A [Logger] defaults output appropriate for the current platform.  */
            val DEFAULT: Logger = object : Logger {
                override fun log(message: String) {
                    Platform.get().log(INFO, message, null)
                }
            }
        }
    }

    /** Change the level at which this interceptor logs.  */
    fun setLevel(level: Level?): CommonHttpLoggingInterceptor {
        if (level == null) throw NullPointerException("level == null. Use Level.NONE instead.")
        this.level = level
        return this
    }

    fun getLevel(): Level {
        return level
    }

    /** Change the limitation of request body logs size.  */
    fun setRequestBodyLogMax(requestBodyLogMax: Long): CommonHttpLoggingInterceptor {
        if (requestBodyLogMax < 0) {
            this.requestBodyLogMax = LOG_LIMITATION_NONE
        } else {
            this.requestBodyLogMax = requestBodyLogMax
        }
        return this
    }

    /** Change the limitation of response body logs size.  */
    fun setResponseBodyLogMax(responseBodyLogMax: Long): CommonHttpLoggingInterceptor {
        if (responseBodyLogMax < 0) {
            this.responseBodyLogMax = LOG_LIMITATION_NONE
        } else {
            this.responseBodyLogMax = responseBodyLogMax
        }
        return this
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val level = this.level

        val request = chain.request()
        if (level == Level.NONE) {
            return chain.proceed(request)
        }

        val logBody = level == Level.BODY
        val logHeaders = logBody || level == Level.HEADERS
        val logPartBody = level == Level.SIMPLE

        val requestBody = request.body()
        val hasRequestBody = requestBody != null

        val connection = chain.connection()
        var requestStartMessage = ("--> "
                + request.method()
                + ' '.toString() + request.url()
                + (connection?.protocol()?:""))
        if (!logHeaders && hasRequestBody) {
            requestStartMessage += " (${requestBody!!.contentLength()}-byte body)"
        }
        logger.log(requestStartMessage)

        if (logHeaders) {
            if (requestBody != null) {
                // Request body headers are only present when installed as a network interceptor. Force
                // them to be included (when available) so there values are known.
                if (requestBody.contentType() != null) {
                    logger.log("Content-Type: ${requestBody.contentType()}")
                }
                if (requestBody.contentLength() != -1L) {
                    logger.log("Content-Length: ${requestBody.contentLength()}")
                }
            }

            val headers = request.headers()
            var i = 0
            val count = headers.size()
            while (i < count) {
                val name = headers.name(i)
                // Skip headers from the request body as they are explicitly logged above.
                if (!"Content-Type".equals(name, ignoreCase = true) && !"Content-Length".equals(
                        name,
                        ignoreCase = true
                    )
                ) {
                    logger.log(name + ": " + headers.value(i))
                }
                i++
            }
        }

        if (!(logBody || logPartBody) || !hasRequestBody) {
            logger.log("--> END " + request.method())
        } else if (bodyHasUnknownEncoding(request.headers())) {
            logger.log("--> END " + request.method() + " (encoded body omitted)")
        } else {
            val buffer = Buffer()
            requestBody!!.writeTo(buffer)

            var charset: Charset? = UTF8
            val contentType = requestBody.contentType()
            if (contentType != null) {
                charset = contentType.charset(UTF8)
            }

            logger.log("")
            if (isPlaintext(buffer)) {

                if (buffer.size() <= requestBodyLogMax || requestBodyLogMax < 0) {
                    var reqBodyContent = buffer.readString(charset!!)
                    if (logPartBody && reqBodyContent.length > 200) {
                        reqBodyContent = reqBodyContent.shrink(200, '.')
                    }
                    logger.log(reqBodyContent)
                } else {
                    logger.log(
                        "Too large to output logs. "
                                + "Current limitation is $requestBodyLogMax"
                    )
                }
                logger.log("--> END ${request.method()} (${requestBody.contentLength()}-byte body)")
            } else {
                logger.log(
                    ("--> END ${request.method()} (binary ${requestBody.contentLength()}-byte body omitted)")
                )
            }
        }

        val startNs = System.nanoTime()
        val response: Response
        try {
            response = chain.proceed(request)
        } catch (e: Exception) {
            logger.log(" 200) {
                        resBodyContent = resBodyContent.shrink(200, '.')
                    }
                    logger.log(resBodyContent)
                } else {
                    logger.log(
                        ("Too large to output logs. "
                                + "Current limitation is $responseBodyLogMax")
                    )
                }
            }

            if (gzippedLength != null) {
                logger.log(
                    ("