From e7088caca040e9171f8bb92fa678a7e21929a519 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Tue, 30 Apr 2024 15:25:57 +0200 Subject: [PATCH 1/5] Improved logging + added log listener --- library/gradle.properties | 2 +- .../android/powerauth/networking/Api.kt | 12 +- .../android/powerauth/networking/Logger.kt | 167 +++++++++++------- .../powerauth/networking/WPNLogListener.kt | 35 ++++ .../processing/GsonRequestBodyBytes.kt | 2 +- .../processing/GsonResponseBodyConverter.kt | 2 +- 6 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt diff --git a/library/gradle.properties b/library/gradle.properties index 15c9236..ab69bfd 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -14,6 +14,6 @@ # and limitations under the License. # -VERSION_NAME=1.3.1-SNAPSHOT +VERSION_NAME=1.3.2-SNAPSHOT GROUP_ID=com.wultra.android.powerauth ARTIFACT_ID=powerauth-networking diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt b/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt index 6320cf6..8a7e3e7 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt @@ -68,7 +68,7 @@ interface IApiCallResponseListener { */ abstract class Api( @PublishedApi internal val baseUrl: String, - @PublishedApi internal val okHttpClient: OkHttpClient, + okHttpClient: OkHttpClient, @PublishedApi internal val powerAuthSDK: PowerAuthSDK, @PublishedApi internal val gsonBuilder: GsonBuilder, @PublishedApi internal val appContext: Context, @@ -80,8 +80,16 @@ abstract class Api( */ var acceptLanguage = "en" + @PublishedApi internal val okHttpClient: OkHttpClient + @PublishedApi internal val tokenProvider: IPowerAuthTokenProvider = tokenProvider ?: TokenManager(appContext, powerAuthSDK.tokenStore) + init { + val builder = okHttpClient.newBuilder() + Logger.configure(builder) + this.okHttpClient = builder.build() + } + // PUBLIC API inline fun post( @@ -182,12 +190,14 @@ abstract class Api( if (ts.isTimeSynchronized) { completion(Result.success(Unit)) } else { + Logger.i("Time is not synchronized, requesting synchronization first.") ts.synchronizeTime(object: ITimeSynchronizationListener { override fun onTimeSynchronizationSucceeded() { completion(Result.success(Unit)) } override fun onTimeSynchronizationFailed(t: Throwable) { + Logger.e("Time failed to synchronize, stopping whole request: $t") completion(Result.failure(t)) } }) diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt b/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt index 2f27684..eadaff5 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt @@ -18,15 +18,17 @@ package com.wultra.android.powerauth.networking import android.util.Log import okhttp3.Headers +import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import okio.Buffer import java.lang.StringBuilder +import java.net.URL /** * Logger provides simple logging facility. * - * Logs are written with "WMT" tag to standard [android.util.Log] logger. + * Logs are written with "WPN" tag to standard [android.util.Log] logger. */ class Logger { @@ -37,6 +39,8 @@ class Logger { ERROR, /** Errors and warnings will be printed into the log. */ WARNING, + /** Info logs, errors and warnings will be printed into the log. */ + INFO, /** All messages will be printed into the log. */ DEBUG } @@ -47,95 +51,138 @@ class Logger { /** Current verbose level. */ var verboseLevel = VerboseLevel.WARNING - private val tag = "WMT" + /** Listener that can tap into the log stream and process it on it's own. */ + var logListener: WPNLogListener? = null - internal fun d(message: String) { - if (verboseLevel.ordinal >= VerboseLevel.DEBUG.ordinal) { - Log.d(tag, message) + private val tag = "WPN" + + private fun log(valueFn: () -> String, allowedLevel: VerboseLevel, logFn: (String?, String) -> Unit, listenerFn: ((String) -> Unit)?) { + val shouldProcess = verboseLevel.ordinal >= allowedLevel.ordinal + val log = if (shouldProcess || logListener?.followVerboseLevel == false) valueFn() else return + if (shouldProcess) { + logFn(tag, log) } + listenerFn?.invoke(log) + } + + internal fun d(message: String) { + d { message } } internal fun d(fn: () -> String) { - if (verboseLevel.ordinal >= VerboseLevel.DEBUG.ordinal) { - Log.d(tag, fn()) - } + log(fn, VerboseLevel.DEBUG, Log::d, logListener?.let { it::debug }) } internal fun w(message: String) { - if (verboseLevel.ordinal >= VerboseLevel.WARNING.ordinal) { - Log.w(tag, message) - } + w { message } } internal fun w(fn: () -> String) { - if (verboseLevel.ordinal >= VerboseLevel.WARNING.ordinal) { - Log.w(tag, fn()) - } + log(fn, VerboseLevel.WARNING, Log::w, logListener?.let { it::warning }) } - internal fun e(message: String, t: Throwable? = null) { - if (verboseLevel.ordinal >= VerboseLevel.ERROR.ordinal) { - Log.e(tag, message, t) - } + internal fun i(message: String) { + i { message } + } + + internal fun i(fn: () -> String) { + log(fn, VerboseLevel.INFO, Log::i, logListener?.let { it::info }) + } + + internal fun e(message: String) { + e { message } } internal fun e(fn: () -> String) { - if (verboseLevel.ordinal >= VerboseLevel.ERROR.ordinal) { - Log.e(tag, fn()) - } + log(fn, VerboseLevel.ERROR, Log::e, logListener?.let { it::error }) } internal fun configure(builder: OkHttpClient.Builder) { - builder.addInterceptor { chain -> - val request = chain.request() - d { - var body = "" - try { - val buffer = Buffer() - request.newBuilder().build().body()?.writeTo(buffer) - body = buffer.readUtf8() - } catch (e: Throwable) { - e("Failed to parse request body") + builder.addInterceptor( + object: ECIESInterceptor { + override fun encryptedResponseReceived(url: URL, decrypted: ByteArray) { + d { + "- Decrypted response ($url) - ${decrypted.decodeToString()}" + } } - "\n--- WMT REQUEST ---" + - "\n- URL: ${request.method()} - ${request.url()}" + - "\n- Headers: ${request.headers().forLog()}" + - "\n- Body: $body" - } - - - val response: Response - - try { - response = chain.proceed(request) - } catch (e: Throwable) { - d { - "\n--- WMT REQUEST FAILED ---" + - "\n- URL: ${request.method()} - ${request.url()}" + - "\n- Error: $e" + override fun intercept(chain: Interceptor.Chain): Response { + + val request = chain.request() + + i { + "\n<--- WPN REQUEST ---" + + "\n- URL: ${request.method()} - ${request.url()}" + + "\n- Headers: ${request.headers().forLog()}" + } + + try { + d { + val buffer = Buffer() + request.newBuilder().build().body()?.writeTo(buffer) + "- Body: $${buffer.readUtf8()}" + } + } catch (e: Throwable) { + e("- Failed to parse request body: ${e.message}") + } + + val response: Response + + try { + response = chain.proceed(request) + } catch (e: Throwable) { + e { + "\n--- WPN REQUEST FAILED --->" + + "\n- URL: ${request.method()} - ${request.url()}" + + "\n- Error: $e" + } + throw e + } + + i { + "\n--- WPN RESPONSE --->" + + "\n- URL: ${response.request().method()} - ${ + response.request().url() + }" + + "\n- Status code: ${response.code()}" + + "\n- Headers: ${response.headers().forLog()}" + } + + try { + d { + "Body: ${ + response.peekBody(10_000).string() + }" // allow max 10 KB of text + } + } catch (e: Throwable) { + e("- Failed to parse response body: ${e.message}") + } + + return response } - throw e - } - - d { - "\n--- WMT RESPONSE ---" + - "\n- URL: ${response.request().method()} - ${response.request().url()}" + - "\n- Status code: ${response.code()}" + - "\n- Headers: ${response.headers().forLog()}" + - "\n- Body: ${response.peekBody(10_000).string()}" // allow max 10 KB of text } - - response - } + ) } } } + +private val headersToSkp = listOf( + "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", + "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" +) + private fun Headers.forLog(): String { val result = StringBuilder() + var skipped = 0 for (i in 0 until size()) { - result.append("\n - ").append(name(i)).append(": ").append(value(i)) + val name = name(i) + if (!headersToSkp.contains(name.lowercase())) { + result.append("\n - ${name}: ${value(i)}") + } else { + skipped += 1 + } } + result.insert(0, "$skipped filtered out") return result.toString() } \ No newline at end of file diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt b/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt new file mode 100644 index 0000000..a8b5275 --- /dev/null +++ b/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, Wultra s.r.o. (www.wultra.com). + * + * All rights reserved. This source code can be used only for purposes specified + * by the given license contract signed by the rightful deputy of Wultra s.r.o. + * This source code can be used only by the owner of the license. + * + * Any disputes arising in respect of this agreement (license) shall be brought + * before the Municipal Court of Prague. + */ + +package com.wultra.android.powerauth.networking + +/** Log listener receives logs from the library logger for further processing. */ +interface WPNLogListener { + /** + * If the listener should follow selected verbosity level. + * + * When set to true, then when [Logger.VerboseLevel.ERROR] is selected as a [Logger.verboseLevel], only [error] methods will be called. + * When set to false, all methods might be called no matter the selected [Logger.verboseLevel]. + */ + val followVerboseLevel: Boolean + + /** Error log */ + fun error(message: String) + + /** Warning log */ + fun warning(message: String) + + /** Info log */ + fun info(message: String) + + /** Debug log */ + fun debug(message: String) +} diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt index 1023580..81e9d09 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt @@ -42,7 +42,7 @@ internal class GsonRequestBodyBytes(private val gson: Gson, private val adapt } return outputStream.toByteArray() } catch (t: Throwable) { - Logger.e("Failed to process request", t) + Logger.e("Failed to process request: $t") throw t } } diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt index ae57f9f..366e01e 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt @@ -55,7 +55,7 @@ internal class GsonResponseBodyConverter(private val gson: Gson, private val return result } } catch (t: Throwable) { - Logger.e("Failed to process response", t) + Logger.e("Failed to process response: $t") throw t } } From d35805bd9df1e37d17e63b5c1c91d91123d1c300 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Tue, 30 Apr 2024 16:29:15 +0200 Subject: [PATCH 2/5] Renamed Log class --- .../android/powerauth/networking/Api.kt | 7 ++-- .../powerauth/networking/WPNLogListener.kt | 35 ------------------- .../networking/log/WPNLogListener.kt | 24 +++++++++++++ .../{Logger.kt => log/WPNLogger.kt} | 24 +++---------- .../processing/GsonRequestBodyBytes.kt | 4 +-- .../processing/GsonResponseBodyConverter.kt | 4 +-- .../powerauth/networking/utils/AppUtils.kt | 4 +-- 7 files changed, 38 insertions(+), 64 deletions(-) delete mode 100644 library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt create mode 100644 library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt rename library/src/main/java/com/wultra/android/powerauth/networking/{Logger.kt => log/WPNLogger.kt} (89%) diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt b/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt index 8a7e3e7..871866b 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/Api.kt @@ -31,6 +31,7 @@ import com.wultra.android.powerauth.networking.data.StatusResponse import com.wultra.android.powerauth.networking.error.ApiError import com.wultra.android.powerauth.networking.error.ApiHttpException import com.wultra.android.powerauth.networking.error.ErrorResponse +import com.wultra.android.powerauth.networking.log.WPNLogger import com.wultra.android.powerauth.networking.processing.GsonRequestBodyBytes import com.wultra.android.powerauth.networking.processing.GsonResponseBodyConverter import com.wultra.android.powerauth.networking.tokens.IPowerAuthTokenListener @@ -86,7 +87,7 @@ abstract class Api( init { val builder = okHttpClient.newBuilder() - Logger.configure(builder) + WPNLogger.configure(builder) this.okHttpClient = builder.build() } @@ -190,14 +191,14 @@ abstract class Api( if (ts.isTimeSynchronized) { completion(Result.success(Unit)) } else { - Logger.i("Time is not synchronized, requesting synchronization first.") + WPNLogger.i("Time is not synchronized, requesting synchronization first.") ts.synchronizeTime(object: ITimeSynchronizationListener { override fun onTimeSynchronizationSucceeded() { completion(Result.success(Unit)) } override fun onTimeSynchronizationFailed(t: Throwable) { - Logger.e("Time failed to synchronize, stopping whole request: $t") + WPNLogger.e("Time failed to synchronize, stopping whole request: $t") completion(Result.failure(t)) } }) diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt b/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt deleted file mode 100644 index a8b5275..0000000 --- a/library/src/main/java/com/wultra/android/powerauth/networking/WPNLogListener.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024, Wultra s.r.o. (www.wultra.com). - * - * All rights reserved. This source code can be used only for purposes specified - * by the given license contract signed by the rightful deputy of Wultra s.r.o. - * This source code can be used only by the owner of the license. - * - * Any disputes arising in respect of this agreement (license) shall be brought - * before the Municipal Court of Prague. - */ - -package com.wultra.android.powerauth.networking - -/** Log listener receives logs from the library logger for further processing. */ -interface WPNLogListener { - /** - * If the listener should follow selected verbosity level. - * - * When set to true, then when [Logger.VerboseLevel.ERROR] is selected as a [Logger.verboseLevel], only [error] methods will be called. - * When set to false, all methods might be called no matter the selected [Logger.verboseLevel]. - */ - val followVerboseLevel: Boolean - - /** Error log */ - fun error(message: String) - - /** Warning log */ - fun warning(message: String) - - /** Info log */ - fun info(message: String) - - /** Debug log */ - fun debug(message: String) -} diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt new file mode 100644 index 0000000..caa5608 --- /dev/null +++ b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt @@ -0,0 +1,24 @@ +package com.wultra.android.powerauth.networking.log + +/** Log listener receives logs from the library logger for further processing. */ +interface WPNLogListener { + /** + * If the listener should follow selected verbosity level. + * + * When set to true, then when [WPNLogger.VerboseLevel.ERROR] is selected as a [WPNLogger.verboseLevel], only [error] methods will be called. + * When set to false, all methods might be called no matter the selected [WPNLogger.verboseLevel]. + */ + val followVerboseLevel: Boolean + + /** Error log */ + fun error(message: String) + + /** Warning log */ + fun warning(message: String) + + /** Info log */ + fun info(message: String) + + /** Debug log */ + fun debug(message: String) +} \ No newline at end of file diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt similarity index 89% rename from library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt rename to library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt index eadaff5..e568758 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/Logger.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt @@ -1,22 +1,7 @@ -/* - * Copyright 2022 Wultra s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions - * and limitations under the License. - */ - -package com.wultra.android.powerauth.networking +package com.wultra.android.powerauth.networking.log import android.util.Log +import com.wultra.android.powerauth.networking.ECIESInterceptor import okhttp3.Headers import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -30,7 +15,7 @@ import java.net.URL * * Logs are written with "WPN" tag to standard [android.util.Log] logger. */ -class Logger { +class WPNLogger { enum class VerboseLevel { /** Silences all messages. */ @@ -166,7 +151,6 @@ class Logger { } } - private val headersToSkp = listOf( "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" @@ -185,4 +169,4 @@ private fun Headers.forLog(): String { } result.insert(0, "$skipped filtered out") return result.toString() -} \ No newline at end of file +} diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt index 81e9d09..e3ea100 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonRequestBodyBytes.kt @@ -18,7 +18,7 @@ package com.wultra.android.powerauth.networking.processing import com.google.gson.Gson import com.google.gson.TypeAdapter -import com.wultra.android.powerauth.networking.Logger +import com.wultra.android.powerauth.networking.log.WPNLogger import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStreamWriter @@ -42,7 +42,7 @@ internal class GsonRequestBodyBytes(private val gson: Gson, private val adapt } return outputStream.toByteArray() } catch (t: Throwable) { - Logger.e("Failed to process request: $t") + WPNLogger.e("Failed to process request: $t") throw t } } diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt index 366e01e..3f1a94f 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/processing/GsonResponseBodyConverter.kt @@ -20,7 +20,7 @@ import com.google.gson.Gson import com.google.gson.JsonIOException import com.google.gson.TypeAdapter import com.google.gson.stream.JsonToken -import com.wultra.android.powerauth.networking.Logger +import com.wultra.android.powerauth.networking.log.WPNLogger import okhttp3.ResponseBody import java.io.IOException import java.io.Reader @@ -55,7 +55,7 @@ internal class GsonResponseBodyConverter(private val gson: Gson, private val return result } } catch (t: Throwable) { - Logger.e("Failed to process response: $t") + WPNLogger.e("Failed to process response: $t") throw t } } diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/utils/AppUtils.kt b/library/src/main/java/com/wultra/android/powerauth/networking/utils/AppUtils.kt index 371b6d8..6c928db 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/utils/AppUtils.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/utils/AppUtils.kt @@ -22,7 +22,7 @@ import android.content.pm.PackageManager import android.net.* import android.os.Build import androidx.annotation.RequiresApi -import com.wultra.android.powerauth.networking.Logger +import com.wultra.android.powerauth.networking.log.WPNLogger class AppUtils { companion object { @@ -65,7 +65,7 @@ object ConnectionMonitor { } } catch (e: Throwable) { status = Status.UNKNOWN - Logger.d("Failed to create Connectivity Manager with Exception: $e") + WPNLogger.d("Failed to create Connectivity Manager with Exception: $e") } return status.value } From a46ad9e78134479093ee0389d6e2fd7456b7f6a5 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Mon, 6 May 2024 11:15:04 +0200 Subject: [PATCH 3/5] Minor improvements --- .../networking/log/WPNLogListener.kt | 2 +- .../powerauth/networking/log/WPNLogger.kt | 83 +++++++++++++++---- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt index caa5608..f69c47e 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogListener.kt @@ -5,7 +5,7 @@ interface WPNLogListener { /** * If the listener should follow selected verbosity level. * - * When set to true, then when [WPNLogger.VerboseLevel.ERROR] is selected as a [WPNLogger.verboseLevel], only [error] methods will be called. + * When set to true, then (for example) if [WPNLogger.VerboseLevel.ERROR] is selected as a [WPNLogger.verboseLevel], only [error] methods will be called. * When set to false, all methods might be called no matter the selected [WPNLogger.verboseLevel]. */ val followVerboseLevel: Boolean diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt index e568758..40166b2 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt @@ -14,9 +14,11 @@ import java.net.URL * Logger provides simple logging facility. * * Logs are written with "WPN" tag to standard [android.util.Log] logger. + * You can set [logListener] to start listening on the */ class WPNLogger { + /** Level of the log which should be effectively logged. */ enum class VerboseLevel { /** Silences all messages. */ OFF, @@ -32,12 +34,27 @@ class WPNLogger { companion object { - @JvmStatic /** Current verbose level. */ - var verboseLevel = VerboseLevel.WARNING + @JvmStatic var verboseLevel = VerboseLevel.WARNING /** Listener that can tap into the log stream and process it on it's own. */ - var logListener: WPNLogListener? = null + @JvmStatic var logListener: WPNLogListener? = null + + /** If HTTP traffic should be logged. */ + @JvmStatic var logHttpTraffic = true + + /** + * Headers to skip when logging HTTP traffic. + * + * Note that all headers are transformed to lowercase variant when added. + * + * Default headers to skip are: + * ``` + * "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", + * "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" + * ``` + */ + @JvmStatic var httpHeadersToSkip = HeaderBlockList() private val tag = "WPN" @@ -86,19 +103,25 @@ class WPNLogger { builder.addInterceptor( object: ECIESInterceptor { override fun encryptedResponseReceived(url: URL, decrypted: ByteArray) { - d { - "- Decrypted response ($url) - ${decrypted.decodeToString()}" + if (logHttpTraffic) { + d { + "- Decrypted response ($url) - ${decrypted.decodeToString()}" + } } } override fun intercept(chain: Interceptor.Chain): Response { - + val request = chain.request() + if (!logHttpTraffic) { + return chain.proceed(request) + } + i { "\n<--- WPN REQUEST ---" + "\n- URL: ${request.method()} - ${request.url()}" + - "\n- Headers: ${request.headers().forLog()}" + "\n- Headers: ${request.headers().forLog(httpHeadersToSkip.toList())}" } try { @@ -130,7 +153,7 @@ class WPNLogger { response.request().url() }" + "\n- Status code: ${response.code()}" + - "\n- Headers: ${response.headers().forLog()}" + "\n- Headers: ${response.headers().forLog(httpHeadersToSkip.toList())}" } try { @@ -151,17 +174,49 @@ class WPNLogger { } } -private val headersToSkp = listOf( - "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", - "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" -) +/** + * Headers to skip when logging. + * + * Note that all headers are transformed to lowercase variant when added. + * + * Default headers to skip are: + * ``` + * "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", + * "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" + * ``` + */ +class HeaderBlockList { + + private val headersToSkp = mutableListOf( + "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent", + "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary" + ) + + fun add(element: String): Boolean { + return headersToSkp.add(element.lowercase()) + } + + fun addAll(elements: Collection): Boolean { + return headersToSkp.addAll(elements.map { it.lowercase() }) + } + + fun remove(element: String): Boolean { + return headersToSkp.remove(element.lowercase()) + } + + fun removeAll(elements: Collection): Boolean { + return headersToSkp.removeAll(elements.map { it.lowercase() }.toSet()) + } + + fun toList() = headersToSkp.toList() +} -private fun Headers.forLog(): String { +private fun Headers.forLog(skip: List): String { val result = StringBuilder() var skipped = 0 for (i in 0 until size()) { val name = name(i) - if (!headersToSkp.contains(name.lowercase())) { + if (!skip.contains(name.lowercase())) { result.append("\n - ${name}: ${value(i)}") } else { skipped += 1 From c22bb715a7750931ebde6cb09acd1e3c3f6699a0 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Mon, 6 May 2024 11:17:53 +0200 Subject: [PATCH 4/5] Bumped to 1.4.0 --- library/gradle.properties | 2 +- .../com/wultra/android/powerauth/networking/log/WPNLogger.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/gradle.properties b/library/gradle.properties index ab69bfd..498e39e 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -14,6 +14,6 @@ # and limitations under the License. # -VERSION_NAME=1.3.2-SNAPSHOT +VERSION_NAME=1.4.0-SNAPSHOT GROUP_ID=com.wultra.android.powerauth ARTIFACT_ID=powerauth-networking diff --git a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt index 40166b2..2ebca20 100644 --- a/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt +++ b/library/src/main/java/com/wultra/android/powerauth/networking/log/WPNLogger.kt @@ -111,7 +111,7 @@ class WPNLogger { } override fun intercept(chain: Interceptor.Chain): Response { - + val request = chain.request() if (!logHttpTraffic) { From 39820296f7239ed432d984d6ca76db5db06a7a38 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Mon, 6 May 2024 16:21:49 +0200 Subject: [PATCH 5/5] Docs --- README.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 701e995..706deb0 100644 --- a/README.md +++ b/README.md @@ -260,18 +260,30 @@ The default value is always `en`. With other languages, we use values compliant ## Logging -For logging purposes `com.wultra.android.powerauth.networking.Logger` that prints to the console is used. +The library is intensively logging into the console via `WPNLogger`. + + +`WPNLogger` calls internally the `android.util.Log` class. + ### Verbosity Level You can limit the amount of logged information via `verboseLevel` property. -| Level | Description | -| --- | --- | -| `OFF` | Silences all messages. | -| `ERROR` | Only errors will be printed into the log. | +| Level | Description | +|-----------------------|---------------------------------------------------| +| `OFF` | Silences all messages. | +| `ERROR` | Only errors will be printed into the log. | | `WARNING` _(default)_ | Errors and warnings will be printed into the log. | -| `DEBUG` | All messages will be printed into the log. | +| `DEBUG` | All messages will be printed into the log. | + +### Log Listener + +The `WPNLogger` class offers a static `logListener` property. If you provide a listener, all logs will also be passed to it (the library always logs into the Android default log). + + +Log listener comes in handy when you want to log into a file or some online service. + ## Web Documentation