diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt index ce2252b6..e9f12548 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt @@ -199,8 +199,8 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet } companion object { - private const val LOG_TAG = "BaseWebView" - internal const val ECP_LOG_TAG = "CheckoutECP" + private const val LOG_TAG = "base_web_view" + internal const val ECP_LOG_TAG = "ecp" } } @@ -210,7 +210,7 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet internal fun BaseWebView.removeFromParent() { val parent = this.parent if (parent is ViewGroup) { - log.d("BaseWebView", "Existing parent found for WebView, removing.") + log.d("base_web_view", "Existing parent found for WebView, removing.") parent.removeView(this) } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt index d4201afd..c379b4eb 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt @@ -196,6 +196,6 @@ internal class CheckoutDialog( resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES companion object { - private const val LOG_TAG = "CheckoutDialog" + private const val LOG_TAG = "checkout_dialog" } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt index 325ad787..7d05abe3 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/CheckoutWebView.kt @@ -91,6 +91,6 @@ internal class CheckoutWebView(context: Context, attributeSet: AttributeSet? = n } companion object { - private const val LOG_TAG = "CheckoutWebView" + private const val LOG_TAG = "checkout_web_view" } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt index 8fb27754..c3faa4c8 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt @@ -2,27 +2,39 @@ package com.shopify.checkoutkit import android.util.Log +private const val LOG_TAG = "checkout_kit" +private const val DEFAULT_LOG_SCOPE = "sdk" + /** * Wrap Log class static methods to allow testing and/or disabling debug logs */ public class LogWrapper { public fun d(tag: String, msg: String) { if (ShopifyCheckoutKit.configuration.logLevel == LogLevel.DEBUG) { - Log.d(tag, msg) + Log.d(LOG_TAG, formatMessage(tag, msg)) } } public fun w(tag: String, msg: String) { if (listOf(LogLevel.DEBUG, LogLevel.WARN).contains(ShopifyCheckoutKit.configuration.logLevel)) { - Log.w(tag, msg) + Log.w(LOG_TAG, formatMessage(tag, msg)) } } public fun e(tag: String, msg: String) { - Log.e(tag, msg) + Log.e(LOG_TAG, formatMessage(tag, msg)) } public fun e(tag: String, msg: String, throwable: Throwable) { - Log.e(tag, msg, throwable) + Log.e(LOG_TAG, formatMessage(tag, msg), throwable) + } + + private fun formatMessage(tag: String, msg: String): String = "[$LOG_TAG:${tag.toLogScope()}] $msg" + + private fun String.toLogScope(): String = when (this) { + "ShopifyCheckoutKit", "checkout_kit" -> DEFAULT_LOG_SCOPE + "ShopifyAcceleratedCheckouts" -> "accelerated_checkout" + "CheckoutECP" -> "ecp" + else -> this } } diff --git a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt index 60dc7adb..2d4dbc96 100644 --- a/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt +++ b/platforms/android/lib/src/main/java/com/shopify/checkoutkit/ShopifyCheckoutKit.kt @@ -92,22 +92,22 @@ public object ShopifyCheckoutKit { checkoutListener: T, communicationClient: CheckoutCommunicationClient? = null, ): CheckoutKitDialog? { - log.d("ShopifyCheckoutKit", "Present called with checkoutUrl $checkoutUrl.") + log.d("sdk", "Present called with checkoutUrl $checkoutUrl.") if (context.isDestroyed || context.isFinishing) { - log.d("ShopifyCheckoutKit", "Context is destroyed or finishing, returning null.") + log.d("sdk", "Context is destroyed or finishing, returning null.") return null } - log.d("ShopifyCheckoutKit", "Constructing Dialog") + log.d("sdk", "Constructing Dialog") val dialog = CheckoutDialog(checkoutUrl, checkoutListener, context, communicationClient) context.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { - log.d("ShopifyCheckoutKit", "Context is being destroyed, dismissing dialog.") + log.d("sdk", "Context is being destroyed, dismissing dialog.") dialog.dismiss() super.onDestroy(owner) } }) - log.d("ShopifyCheckoutKit", "Starting Dialog.") + log.d("sdk", "Starting Dialog.") dialog.start(context) return CheckoutKitDialog { dialog.dismiss() } } diff --git a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/LogWrapperTest.kt b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/LogWrapperTest.kt index 5fac22f4..d11a0b08 100644 --- a/platforms/android/lib/src/test/java/com/shopify/checkoutkit/LogWrapperTest.kt +++ b/platforms/android/lib/src/test/java/com/shopify/checkoutkit/LogWrapperTest.kt @@ -34,10 +34,10 @@ class LogWrapperTest { it.logLevel = LogLevel.DEBUG } - log.d("TAG", "Debug message") + log.d("tag", "Debug message") assertThat( ShadowLog.getLogs().any { - it.type == Log.DEBUG && it.tag == "TAG" && it.msg == "Debug message" + it.type == Log.DEBUG && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Debug message" } ).isTrue() } @@ -48,10 +48,10 @@ class LogWrapperTest { it.logLevel = LogLevel.WARN } - log.d("TAG", "Debug message") + log.d("tag", "Debug message") assertThat( ShadowLog.getLogs().any { - it.type == Log.DEBUG && it.tag == "TAG" && it.msg == "Debug message" + it.type == Log.DEBUG && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Debug message" } ).isFalse() } @@ -62,10 +62,10 @@ class LogWrapperTest { it.logLevel = LogLevel.WARN } - log.d("TAG", "Debug message") + log.d("tag", "Debug message") assertThat( ShadowLog.getLogs().any { - it.type == Log.DEBUG && it.tag == "TAG" && it.msg == "Debug message" + it.type == Log.DEBUG && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Debug message" } ).isFalse() } @@ -76,10 +76,10 @@ class LogWrapperTest { it.logLevel = LogLevel.DEBUG } - log.w("TAG", "Warn message") + log.w("tag", "Warn message") assertThat( ShadowLog.getLogs().any { - it.type == Log.WARN && it.tag == "TAG" && it.msg == "Warn message" + it.type == Log.WARN && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Warn message" } ).isTrue() } @@ -90,10 +90,10 @@ class LogWrapperTest { it.logLevel = LogLevel.WARN } - log.w("TAG", "Warn message") + log.w("tag", "Warn message") assertThat( ShadowLog.getLogs().any { - it.type == Log.WARN && it.tag == "TAG" && it.msg == "Warn message" + it.type == Log.WARN && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Warn message" } ).isTrue() } @@ -104,10 +104,10 @@ class LogWrapperTest { it.logLevel = LogLevel.ERROR } - log.w("TAG", "Warn message") + log.w("tag", "Warn message") assertThat( ShadowLog.getLogs().any { - it.type == Log.WARN && it.tag == "TAG" && it.msg == "Warn message" + it.type == Log.WARN && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Warn message" } ).isFalse() } @@ -118,10 +118,10 @@ class LogWrapperTest { it.logLevel = LogLevel.DEBUG } - log.e("TAG", "Error message") + log.e("tag", "Error message") assertThat( ShadowLog.getLogs().any { - it.type == Log.ERROR && it.tag == "TAG" && it.msg == "Error message" + it.type == Log.ERROR && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Error message" } ).isTrue() } @@ -132,10 +132,10 @@ class LogWrapperTest { it.logLevel = LogLevel.WARN } - log.e("TAG", "Error message") + log.e("tag", "Error message") assertThat( ShadowLog.getLogs().any { - it.type == Log.ERROR && it.tag == "TAG" && it.msg == "Error message" + it.type == Log.ERROR && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Error message" } ).isTrue() } @@ -146,11 +146,41 @@ class LogWrapperTest { it.logLevel = LogLevel.ERROR } - log.e("TAG", "Error message") + log.e("tag", "Error message") assertThat( ShadowLog.getLogs().any { - it.type == Log.ERROR && it.tag == "TAG" && it.msg == "Error message" + it.type == Log.ERROR && it.tag == "checkout_kit" && it.msg == "[checkout_kit:tag] Error message" } ).isTrue() } + + @Test + fun `should normalize log scopes consistently`() { + ShopifyCheckoutKit.configure { + it.logLevel = LogLevel.ERROR + } + + val cases = mapOf( + "ShopifyCheckoutKit" to "sdk", + "checkout_kit" to "sdk", + "sdk" to "sdk", + "ShopifyAcceleratedCheckouts" to "accelerated_checkout", + "CheckoutECP" to "ecp", + "accelerated_checkout" to "accelerated_checkout", + ) + + cases.forEach { (tag, scope) -> + ShadowLog.clear() + + log.e(tag, "Error message") + + assertThat( + ShadowLog.getLogs().any { + it.type == Log.ERROR && + it.tag == "checkout_kit" && + it.msg == "[checkout_kit:$scope] Error message" + } + ).isTrue() + } + } } diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MainActivity.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MainActivity.kt index 3f229a6d..6fdb828e 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MainActivity.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/MainActivity.kt @@ -15,7 +15,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import timber.log.Timber -import timber.log.Timber.DebugTree class MainActivity : ComponentActivity() { private lateinit var requestPermissionLauncher: ActivityResultLauncher @@ -38,7 +37,7 @@ class MainActivity : ComponentActivity() { // Setup logging in debug build if (BuildConfig.DEBUG) { - Timber.plant(DebugTree()) + Timber.plant(CheckoutKitDebugTree()) } setContent { @@ -100,3 +99,10 @@ class MainActivity : ComponentActivity() { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED } } + +private class CheckoutKitDebugTree : Timber.DebugTree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + val scope = tag ?: "mobile_buy_integration" + super.log(priority, "mobile_buy_integration", "[mobile_buy_integration:$scope] $message", t) + } +} diff --git a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/common/logs/Logger.kt b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/common/logs/Logger.kt index 0ae1b754..6939f642 100644 --- a/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/common/logs/Logger.kt +++ b/platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/common/logs/Logger.kt @@ -17,7 +17,7 @@ class Logger( insert( LogLine( type = LogType.STANDARD, - message = message, + message = formatMessage(message), ) ) } @@ -26,7 +26,7 @@ class Logger( insert( LogLine( type = LogType.CHECKOUT_COMPLETED, - message = "Checkout completed: ${checkout.order?.id ?: "unknown"}", + message = formatMessage("Checkout completed: ${checkout.order?.id ?: "unknown"}"), checkoutCompletedPayload = Json.encodeToString(checkout), ) ) @@ -38,7 +38,7 @@ class Logger( id = UUID.randomUUID(), type = LogType.ERROR, createdAt = Date().time, - message = message, + message = formatMessage(message), errorDetails = ErrorDetails( message = e.message ?: "No message on error", type = "${e::class.java}" @@ -50,4 +50,6 @@ class Logger( private fun insert(logLine: LogLine) = coroutineScope.launch { logDb.logDao().insert(logLine) } + + private fun formatMessage(message: String): String = "[mobile_buy_integration:app] $message" } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/CustomCheckoutListener.java b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/CustomCheckoutListener.java index b4878961..64ecd7c1 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/CustomCheckoutListener.java +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/CustomCheckoutListener.java @@ -14,7 +14,9 @@ import java.util.Map; public class CustomCheckoutListener extends DefaultCheckoutListener { - private static final String TAG = "ShopifyCheckoutKit"; + private static final String TAG = "checkout_kit"; + private static final String LOG_PREFIX = "checkout_kit"; + private static final String LOG_SCOPE = "sdk"; private final ObjectMapper mapper = new ObjectMapper(); @@ -69,7 +71,7 @@ public void onGeolocationPermissionsShowPrompt(@NonNull String origin, // Multi-shot geolocation requests can in principle arrive after a // terminal event or explicit dismiss has released the dispatcher. Log // so the silence is observable rather than mystifying. - Log.w(TAG, "Dropping geolocationRequest — dispatcher already released."); + Log.w(TAG, formatLogMessage("Dropping geolocationRequest — dispatcher already released.")); return; } @@ -81,7 +83,7 @@ public void onGeolocationPermissionsShowPrompt(@NonNull String origin, payload.put("origin", origin); dispatch.invoke(buildEnvelope(DispatchEventTypes.GEOLOCATION_REQUEST, payload)); } catch (IOException e) { - Log.e(TAG, "Error emitting \"geolocationRequest\" event", e); + Log.e(TAG, formatLogMessage("Error emitting \"geolocationRequest\" event"), e); } } @@ -101,7 +103,7 @@ public void onCheckoutFailed(CheckoutException checkoutError) { try { dispatch.invoke(buildEnvelope(DispatchEventTypes.FAIL, populateErrorDetails(checkoutError))); } catch (IOException e) { - Log.e(TAG, "Error processing checkout failed event", e); + Log.e(TAG, formatLogMessage("Error processing checkout failed event"), e); } finally { release(); } @@ -115,7 +117,7 @@ public void onCheckoutCanceled() { try { dispatch.invoke(buildEnvelope(DispatchEventTypes.CLOSE, null)); } catch (IOException e) { - Log.e(TAG, "Error processing checkout canceled event", e); + Log.e(TAG, formatLogMessage("Error processing checkout canceled event"), e); } finally { release(); } @@ -132,6 +134,10 @@ private String buildEnvelope(String type, @Nullable Object payload) throws IOExc return mapper.writeValueAsString(envelope); } + private String formatLogMessage(String message) { + return "[" + LOG_PREFIX + ":" + LOG_SCOPE + "] " + message; + } + private Map populateErrorDetails(CheckoutException checkoutError) { Map errorMap = new HashMap(); errorMap.put("__typename", getErrorTypeName(checkoutError)); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ProtocolRelay.kt b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ProtocolRelay.kt index f136dd86..1507531f 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ProtocolRelay.kt +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ProtocolRelay.kt @@ -6,7 +6,9 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -private const val TAG = "ShopifyCheckoutKit" +private const val TAG = "checkout_kit" +private const val LOG_PREFIX = "checkout_kit" +private const val LOG_SCOPE = "sdk" fun interface DispatchCallback { fun invoke(json: String) @@ -79,7 +81,7 @@ object ProtocolRelay { val envelopeJson = json.encodeToString(DispatchEnvelope(type, payload)) dispatch.invoke(envelopeJson) } catch (e: Exception) { - Log.e(TAG, "Error dispatching protocol event \"$type\"", e) + Log.e(TAG, "[$LOG_PREFIX:$LOG_SCOPE] Error dispatching protocol event \"$type\"", e) } } } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java index f49ca6d0..915cbe36 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java @@ -1,6 +1,7 @@ package com.shopify.reactnative.checkoutkit; import android.app.Activity; +import android.util.Log; import androidx.activity.ComponentActivity; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMethod; @@ -18,6 +19,9 @@ import java.util.Objects; public class ShopifyCheckoutKitModule extends NativeShopifyCheckoutKitSpec { + private static final String TAG = "checkout_kit"; + private static final String LOG_PREFIX = "checkout_kit"; + private static final String LOG_SCOPE = "sdk"; public static Configuration checkoutConfig = new Configuration(); @@ -342,7 +346,7 @@ private Color parseColor(String colorStr) { return new Color.SRGB((int) color); } catch (NumberFormatException e) { - System.out.println("Warning: Invalid color string. Default color will be used."); + Log.w(TAG, "[" + LOG_PREFIX + ":" + LOG_SCOPE + "] Invalid color string. Default color will be used."); return null; } } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ProtocolRelayTest.kt b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ProtocolRelayTest.kt index 4ff53a95..ec4635cb 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ProtocolRelayTest.kt +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ProtocolRelayTest.kt @@ -76,8 +76,8 @@ class ProtocolRelayTest { client.process(ecStartNotificationFixture) shadowOf(Looper.getMainLooper()).runToEndOfTasks() - val logs = ShadowLog.getLogsForTag("ShopifyCheckoutKit") - .filter { it.msg == "Error dispatching protocol event \"ec.start\"" } + val logs = ShadowLog.getLogsForTag("checkout_kit") + .filter { it.msg == "[checkout_kit:sdk] Error dispatching protocol event \"ec.start\"" } assertThat(logs).hasSize(1) assertThat(logs.single().type).isEqualTo(Log.ERROR) assertThat(logs.single().throwable).isSameAs(failure) diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/AcceleratedCheckoutButtons.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/AcceleratedCheckoutButtons.swift index 3dfa0edd..0cff2ae5 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/AcceleratedCheckoutButtons.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/AcceleratedCheckoutButtons.swift @@ -405,7 +405,7 @@ class RCTAcceleratedCheckoutButtonsView: UIView { return try walletStrings.compactMap { walletString in guard let wallet = Wallet(rawValue: walletString), wallet != nil else { let message = "Unknown wallet option: \(String(describing: walletString))" - print("[ShopifyAcceleratedCheckouts] \(message)") + print("[checkout_kit:accelerated_checkout] \(message)") throw NSError(domain: "ShopifyAcceleratedCheckouts", code: 1, userInfo: ["message": message]) } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit+EventSerialization.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit+EventSerialization.swift index 21a02d0b..9d68829a 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit+EventSerialization.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit+EventSerialization.swift @@ -18,7 +18,7 @@ internal enum ShopifyEventSerialization { return jsonObject } } catch { - print("Error encoding to JSON object: \(error)") + print("[checkout_kit:sdk] Error encoding to JSON object: \(error)") } return [:] } @@ -31,7 +31,7 @@ internal enum ShopifyEventSerialization { do { return try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] } catch { - print("Failed to convert string to JSON: \(error)", value ?? "nil") + print("[checkout_kit:sdk] Failed to convert string to JSON: \(error)", value ?? "nil") return [:] } } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift index d2ae7cc7..6b91aa26 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift @@ -258,7 +258,7 @@ class RCTShopifyCheckoutKit: NSObject { return try contactFields.compactMap { guard let field = ShopifyAcceleratedCheckouts.RequiredContactFields(rawValue: $0), field != nil else { let message = "Unknown contactField option: \(String(describing: $0))" - print("[ShopifyCheckoutKit] \(message)") + print("[checkout_kit:sdk] \(message)") throw NSError(domain: "ShopifyCheckoutKit", code: 1, userInfo: ["message": message]) } return field @@ -337,12 +337,12 @@ extension RCTShopifyCheckoutKit { do { let data = try JSONSerialization.data(withJSONObject: envelope, options: []) guard let json = String(data: data, encoding: .utf8) else { - NSLog("[ShopifyCheckoutKit] Failed to encode dispatch envelope for \(type.rawValue): non-UTF8 result") + NSLog("[checkout_kit:sdk] Failed to encode dispatch envelope for \(type.rawValue): non-UTF8 result") return } emitDispatchEvent(json) } catch { - NSLog("[ShopifyCheckoutKit] Failed to serialize dispatch envelope for \(type.rawValue): \(error)") + NSLog("[checkout_kit:sdk] Failed to serialize dispatch envelope for \(type.rawValue): \(error)") } } diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/package.snapshot.json b/platforms/react-native/modules/@shopify/checkout-kit-react-native/package.snapshot.json index 377c2cf4..c6fca3d2 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/package.snapshot.json +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/package.snapshot.json @@ -37,6 +37,8 @@ "lib/commonjs/index.d.js.map", "lib/commonjs/index.js", "lib/commonjs/index.js.map", + "lib/commonjs/logging.js", + "lib/commonjs/logging.js.map", "lib/commonjs/present-dispatcher.js", "lib/commonjs/present-dispatcher.js.map", "lib/commonjs/protocol.js", @@ -61,6 +63,8 @@ "lib/module/index.d.js.map", "lib/module/index.js", "lib/module/index.js.map", + "lib/module/logging.js", + "lib/module/logging.js.map", "lib/module/present-dispatcher.js", "lib/module/present-dispatcher.js.map", "lib/module/protocol.js", @@ -83,6 +87,8 @@ "lib/typescript/src/errors.d.ts.map", "lib/typescript/src/index.d.ts", "lib/typescript/src/index.d.ts.map", + "lib/typescript/src/logging.d.ts", + "lib/typescript/src/logging.d.ts.map", "lib/typescript/src/present-dispatcher.d.ts", "lib/typescript/src/present-dispatcher.d.ts.map", "lib/typescript/src/protocol.d.ts", @@ -111,6 +117,7 @@ "src/errors.ts", "src/index.d.ts", "src/index.ts", + "src/logging.ts", "src/present-dispatcher.ts", "src/protocol.ts", "src/specs/NativeShopifyCheckoutKit.ts", diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/components/AcceleratedCheckoutButtons.tsx b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/components/AcceleratedCheckoutButtons.tsx index 613ea404..c70f7b1c 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/components/AcceleratedCheckoutButtons.tsx +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/components/AcceleratedCheckoutButtons.tsx @@ -7,6 +7,7 @@ import { type CheckoutProtocolPayloads, type ProtocolHandlers, } from '../protocol'; +import {formatLogPrefix} from '../logging'; import RCTAcceleratedCheckoutButtons from '../specs/RCTAcceleratedCheckoutButtonsNativeComponent'; export enum RenderState { @@ -274,7 +275,9 @@ export const AcceleratedCheckoutButtons: React.FC< throw error; } else { // eslint-disable-next-line no-console - console.warn(error.message); + console.warn( + `${formatLogPrefix('accelerated_checkout')} ${error.message}`, + ); return null; } } @@ -313,7 +316,7 @@ function validRenderState(state: string): RenderState { default: // eslint-disable-next-line no-console console.error( - `[ShopifyAcceleratedCheckouts] Invalid render state: ${state}`, + `${formatLogPrefix('accelerated_checkout')} Invalid render state: ${state}`, ); return RenderState.Error; } @@ -468,7 +471,7 @@ function isPlainObject(value: unknown): value is Record { function logUnknownDispatchType(type: string): void { // eslint-disable-next-line no-console console.warn( - `[ShopifyAcceleratedCheckouts] Ignoring protocol dispatch envelope with unknown type "${type}". ` + + `${formatLogPrefix('accelerated_checkout')} Ignoring protocol dispatch envelope with unknown type "${type}". ` + 'Native emitted a Checkout Protocol event this JS package does not know how to handle. ' + 'Confirm native and JS package versions are compatible.', ); @@ -477,14 +480,14 @@ function logUnknownDispatchType(type: string): void { function logProtocolEventParityWarning(detail: string): void { // eslint-disable-next-line no-console console.warn( - '[ShopifyAcceleratedCheckouts] Checkout Protocol event list out of sync between JS ' + + `${formatLogPrefix('accelerated_checkout')} Checkout Protocol event list out of sync between JS ` + 'and native. Rebuild your host app so the bundled native component matches ' + `this version of '@shopify/checkout-kit-react-native'.\n ${detail}`, ); } function logDispatchError(detail: string, raw?: string): void { - const message = `[ShopifyAcceleratedCheckouts] Failed to handle protocol dispatch: ${detail}`; + const message = `${formatLogPrefix('accelerated_checkout')} Failed to handle protocol dispatch: ${detail}`; if (raw == null) { // eslint-disable-next-line no-console console.error(message); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/context.tsx b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/context.tsx index 679ec0e9..af31a259 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/context.tsx +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/context.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useMemo, useRef, useEffect, useState} from 'react'; import type {PropsWithChildren} from 'react'; import {ShopifyCheckout} from './index'; import type {Configuration, Features, PresentCallbacks} from './index.d'; +import {formatLogPrefix} from './logging'; import type {ProtocolHandlers} from './protocol'; type Maybe = T | undefined; @@ -50,7 +51,7 @@ export function ShopifyCheckoutProvider({ if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { // eslint-disable-next-line no-console console.warn( - '[ShopifyCheckoutKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + + `${formatLogPrefix('sdk')} Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.` + 'When the user is authenticated with Customer Accounts, provide accessToken' + 'When the user is otherwise authenticated, provide email/phoneNumber.', ); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/dispatch-events.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/dispatch-events.ts index bf2d97a6..9ba23bea 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/dispatch-events.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/dispatch-events.ts @@ -1,3 +1,5 @@ +import {formatLogPrefix} from './logging'; + /** * Canonical list of SDK lifecycle event types delivered through the * per-`present()` dispatcher. @@ -98,7 +100,7 @@ export function verifyDispatchEventParity( function buildMessage(detail: string): string { return ( - '[ShopifyCheckoutKit] SDK lifecycle event list out of sync between JS ' + + `${formatLogPrefix('sdk')} SDK lifecycle event list out of sync between JS ` + "and native. Rebuild your host app so the bundled native module matches " + "this version of '@shopify/checkout-kit-react-native'.\n " + detail @@ -113,7 +115,9 @@ function buildMessage(detail: string): string { export function __resetDispatchEventParityForTests(): void { if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') { // eslint-disable-next-line no-console - console.warn('[ShopifyCheckoutKit] Test-only function called in production'); + console.warn( + `${formatLogPrefix('sdk')} Test-only function called in production`, + ); return; } parityVerified = false; diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.ts index 26ff0856..ee43b2a9 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.ts @@ -12,6 +12,7 @@ import { createPresentDispatcher, LifecycleEventParseError, } from './present-dispatcher'; +import {formatLogPrefix} from './logging'; import type { AcceleratedCheckoutConfiguration, AndroidAutomaticColors, @@ -204,7 +205,7 @@ class ShopifyCheckout implements ShopifyCheckoutKit { } catch (error) { // eslint-disable-next-line no-console console.error( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + `${formatLogPrefix('sdk')} Failed to configure accelerated checkouts with`, error, ); return false; diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/logging.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/logging.ts new file mode 100644 index 00000000..0a3c0d44 --- /dev/null +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/logging.ts @@ -0,0 +1,20 @@ +const CHECKOUT_KIT_LOG_PREFIX = 'checkout_kit'; +const DEFAULT_LOG_SCOPE = 'sdk'; + +export function formatLogPrefix(scope: string): string { + return `[${CHECKOUT_KIT_LOG_PREFIX}:${toLogScope(scope)}]`; +} + +function toLogScope(scope: string): string { + switch (scope) { + case 'ShopifyCheckoutKit': + case 'checkout_kit': + return DEFAULT_LOG_SCOPE; + case 'ShopifyAcceleratedCheckouts': + return 'accelerated_checkout'; + case 'CheckoutECP': + return 'ecp'; + default: + return scope; + } +} diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/present-dispatcher.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/present-dispatcher.ts index c492bf9d..ab94b7bd 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/present-dispatcher.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/present-dispatcher.ts @@ -5,6 +5,7 @@ import { } from './dispatch-events'; import {parseCheckoutError} from './errors'; import type {CheckoutNativeError} from './errors'; +import {formatLogPrefix} from './logging'; import {decodeProtocolPayload} from './protocol'; import type {CheckoutProtocolPayloads, ProtocolHandlers} from './protocol'; import type {GeolocationRequestEvent, PresentCallbacks} from './index.d'; @@ -159,7 +160,7 @@ function dispatchEnvelope( // registered for it. // eslint-disable-next-line no-console console.warn( - `[ShopifyCheckoutKit] Ignoring dispatch envelope with unknown type "${type}". ` + + `${formatLogPrefix('sdk')} Ignoring dispatch envelope with unknown type "${type}". ` + 'Either the native module emitted an event the JS layer does not know how ' + 'to handle, or no protocol handler was registered for it. Confirm both sides ' + 'are on compatible versions.', @@ -254,7 +255,7 @@ function validateGeolocationRequestPayload( function logParseError(detail: string, raw: string): void { const err = new LifecycleEventParseError( - `Failed to handle present() dispatcher envelope: ${detail}`, + `${formatLogPrefix('sdk')} Failed to handle present() dispatcher envelope: ${detail}`, {cause: detail}, ); // eslint-disable-next-line no-console diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/AcceleratedCheckoutButtons.test.tsx b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/AcceleratedCheckoutButtons.test.tsx index 3fa370cc..ba476964 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/AcceleratedCheckoutButtons.test.tsx +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/AcceleratedCheckoutButtons.test.tsx @@ -410,7 +410,7 @@ describe('AcceleratedCheckoutButtons', () => { ); expect(queryByTestId('accelerated-checkout-buttons')).toBeNull(); expect(warn).toHaveBeenCalledWith( - 'AcceleratedCheckoutButton: Either `cartId` or `variantId` and `quantity` must be provided', + '[checkout_kit:accelerated_checkout] AcceleratedCheckoutButton: Either `cartId` or `variantId` and `quantity` must be provided', ); warn.mockRestore(); @@ -473,7 +473,7 @@ describe('AcceleratedCheckoutButtons', () => { }); expect(mockError).toHaveBeenCalledWith( - '[ShopifyAcceleratedCheckouts] Invalid render state: unexpected', + '[checkout_kit:accelerated_checkout] Invalid render state: unexpected', ); }); }); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts index 9b46fe1a..3a7f4072 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts @@ -844,7 +844,7 @@ describe('ShopifyCheckoutKit', () => { false, ); expect(console.error).toHaveBeenCalledWith( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + '[checkout_kit:sdk] Failed to configure accelerated checkouts with', expectedError, ); }); @@ -862,7 +862,7 @@ describe('ShopifyCheckoutKit', () => { false, ); expect(console.error).toHaveBeenCalledWith( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + '[checkout_kit:sdk] Failed to configure accelerated checkouts with', expectedError, ); }); @@ -887,7 +887,7 @@ describe('ShopifyCheckoutKit', () => { false, ); expect(console.error).toHaveBeenCalledWith( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + '[checkout_kit:sdk] Failed to configure accelerated checkouts with', expectedError, ); }); @@ -912,7 +912,7 @@ describe('ShopifyCheckoutKit', () => { instance.configureAcceleratedCheckouts(invalidConfig as any), ).toBe(false); expect(console.error).toHaveBeenCalledWith( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + '[checkout_kit:sdk] Failed to configure accelerated checkouts with', expectedError, ); }); @@ -951,7 +951,7 @@ describe('ShopifyCheckoutKit', () => { instance.configureAcceleratedCheckouts(invalidConfig as any), ).toBe(false); expect(console.error).toHaveBeenCalledWith( - '[ShopifyCheckoutKit] Failed to configure accelerated checkouts with', + '[checkout_kit:sdk] Failed to configure accelerated checkouts with', expectedError, ); }); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/logging.test.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/logging.test.ts new file mode 100644 index 00000000..1c15146f --- /dev/null +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/logging.test.ts @@ -0,0 +1,14 @@ +import {formatLogPrefix} from '../src/logging'; + +describe('formatLogPrefix', () => { + it.each([ + ['ShopifyCheckoutKit', '[checkout_kit:sdk]'], + ['checkout_kit', '[checkout_kit:sdk]'], + ['sdk', '[checkout_kit:sdk]'], + ['ShopifyAcceleratedCheckouts', '[checkout_kit:accelerated_checkout]'], + ['CheckoutECP', '[checkout_kit:ecp]'], + ['accelerated_checkout', '[checkout_kit:accelerated_checkout]'], + ])('formats %s as %s', (scope, expected) => { + expect(formatLogPrefix(scope)).toBe(expected); + }); +}); diff --git a/platforms/react-native/sample/src/App.tsx b/platforms/react-native/sample/src/App.tsx index 6c7ebe5e..4ed57f4c 100644 --- a/platforms/react-native/sample/src/App.tsx +++ b/platforms/react-native/sample/src/App.tsx @@ -41,10 +41,10 @@ import ProductDetailsScreen from './screens/ProductDetailsScreen'; import type {ProductVariant, ShopifyProduct} from '../@types'; import ErrorBoundary from './ErrorBoundary'; import env from 'react-native-config'; -import {createDebugLogger} from './utils'; +import {createDebugLogger, formatLogPrefix} from './utils'; import {useShopifyEventHandlers} from './hooks/useCheckoutEventHandlers'; -const log = createDebugLogger('ENV'); +const log = createDebugLogger('env'); function configured(value: string | undefined) { return value ? 'configured' : 'missing'; @@ -52,7 +52,7 @@ function configured(value: string | undefined) { const storefrontApiVersion = env.API_VERSION ?? env.STOREFRONT_VERSION; -console.groupCollapsed('ENV'); +console.groupCollapsed(formatLogPrefix('env')); log('STOREFRONT_DOMAIN:', configured(env.STOREFRONT_DOMAIN)); log('STOREFRONT_ACCESS_TOKEN:', configured(env.STOREFRONT_ACCESS_TOKEN)); log('API_VERSION:', configured(storefrontApiVersion)); diff --git a/platforms/react-native/sample/src/ErrorBoundary.tsx b/platforms/react-native/sample/src/ErrorBoundary.tsx index 1e6a5e9f..1d51da72 100644 --- a/platforms/react-native/sample/src/ErrorBoundary.tsx +++ b/platforms/react-native/sample/src/ErrorBoundary.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View, Text} from 'react-native'; +import {formatLogPrefix} from './utils'; interface State { hasError: boolean; @@ -14,7 +15,7 @@ class ErrorBoundary extends React.Component { } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - console.log('[ErrorBoundary]', error, errorInfo); + console.log(formatLogPrefix('error_boundary'), error, errorInfo); } render() { diff --git a/platforms/react-native/sample/src/auth/customerAccountManager.ts b/platforms/react-native/sample/src/auth/customerAccountManager.ts index a8d00dff..76ed5336 100644 --- a/platforms/react-native/sample/src/auth/customerAccountManager.ts +++ b/platforms/react-native/sample/src/auth/customerAccountManager.ts @@ -19,7 +19,7 @@ interface Session { tokenExpiresAt: number | null; } -const log = createDebugLogger('CustomerAccount'); +const log = createDebugLogger('customer_account'); const REFRESH_THRESHOLD_MS = 5 * 60 * 1000; diff --git a/platforms/react-native/sample/src/context/Config.tsx b/platforms/react-native/sample/src/context/Config.tsx index ec78298c..0d605ac2 100644 --- a/platforms/react-native/sample/src/context/Config.tsx +++ b/platforms/react-native/sample/src/context/Config.tsx @@ -10,6 +10,7 @@ import {ColorScheme, ApplePayStyle} from '@shopify/checkout-kit-react-native'; import EncryptedStorage from 'react-native-encrypted-storage'; import {useTheme} from './Theme'; import {BuyerIdentityMode} from '../auth/types'; +import {formatLogPrefix} from '../utils'; export interface AppConfig { colorScheme: ColorScheme; @@ -64,8 +65,8 @@ export const ConfigProvider: React.FC< }, [config, setColorScheme]); const setAppConfig = useCallback((newConfig: AppConfig) => { - console.groupCollapsed('APP CONFIG UPDATE'); - console.log(newConfig); + console.groupCollapsed(formatLogPrefix('app_config')); + console.log(formatLogPrefix('app_config'), newConfig); console.groupEnd(); setInternalAppConfig(newConfig); EncryptedStorage.setItem( diff --git a/platforms/react-native/sample/src/hooks/useCheckoutEventHandlers.ts b/platforms/react-native/sample/src/hooks/useCheckoutEventHandlers.ts index b159906b..3d462106 100644 --- a/platforms/react-native/sample/src/hooks/useCheckoutEventHandlers.ts +++ b/platforms/react-native/sample/src/hooks/useCheckoutEventHandlers.ts @@ -35,16 +35,16 @@ export function useShopifyEventHandlers(name?: string): EventHandlers { const log = createDebugLogger(name ?? ''); return { onFail: error => { - log('onFail', error); + log('on_fail', error); }, onCancel: () => { - log('onCancel'); + log('on_cancel'); }, onRenderStateChange: event => { - log('onRenderStateChange', event); + log('on_render_state_change', event); }, onClickLink: async url => { - log('onClickLink', url); + log('on_click_link', url); if (await Linking.canOpenURL(url)) { await Linking.openURL(url); diff --git a/platforms/react-native/sample/src/utils.ts b/platforms/react-native/sample/src/utils.ts index d061a08b..92a7c4b6 100644 --- a/platforms/react-native/sample/src/utils.ts +++ b/platforms/react-native/sample/src/utils.ts @@ -16,6 +16,12 @@ const { PHONE, } = Config; +const LOG_PREFIX = 'react_native_sample'; + +export function formatLogPrefix(scope = 'sample') { + return `[${LOG_PREFIX}:${scope}]`; +} + export function createBuyerIdentityCartInput( appConfig: AppConfig, customerAccessToken?: string, @@ -72,7 +78,7 @@ export function currency(amount?: string, currency?: string): string { currency: currency, }).format(Number(amount ?? 0)); } catch (error) { - console.error(error); + console.error(formatLogPrefix('utils'), error); const currencyCode = currency ? ` ${currency}` : ''; return `${Number(amount ?? 0).toFixed(2)}` + currencyCode; } @@ -80,11 +86,14 @@ export function currency(amount?: string, currency?: string): string { export function debugLog(message: string, data?: any) { if (__DEV__) { - console.log(message, data || ''); + console.log(`${formatLogPrefix()} ${message}`, data ?? ''); } } export function createDebugLogger(name: string) { - return (message: string, data?: any) => - debugLog(`[${name}] ${message}`, data); + return (message: string, data?: any) => { + if (__DEV__) { + console.log(`${formatLogPrefix(name)} ${message}`, data ?? ''); + } + }; } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/AppDelegate.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/AppDelegate.swift index fd1fefac..998a3e13 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/AppDelegate.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/AppDelegate.swift @@ -29,7 +29,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { $0.logLevel = checkoutKitLogLevel } - print("[MobileBuyIntegration] CheckoutKit Log level set to \(checkoutKitLogLevel)") + print("[mobile_buy_integration:app] CheckoutKit Log level set to \(checkoutKitLogLevel)") UIBarButtonItem.appearance().tintColor = ColorPalette.primaryColor diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/CheckoutCoordinator.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/CheckoutCoordinator.swift index aef79a72..09733f32 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/CheckoutCoordinator.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/CheckoutCoordinator.swift @@ -10,10 +10,10 @@ class CheckoutCoordinator: UIViewController { private let checkoutDelegate = CartResettingCheckoutDelegate() private lazy var client = CheckoutProtocol.Client() .on(CheckoutProtocol.start) { checkout in - OSLogger.shared.debug("[UCP] Checkout started: \(checkout.id)") + OSLogger.shared.debug("UCP Checkout started: \(checkout.id)") } .on(CheckoutProtocol.complete) { [checkoutDelegate] checkout in - OSLogger.shared.debug("[UCP] Checkout completed: \(checkout.order?.id ?? "unknown")") + OSLogger.shared.debug("UCP Checkout completed: \(checkout.order?.id ?? "unknown")") checkoutDelegate.markCompleted() } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/CheckoutProtocolClient.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/CheckoutProtocolClient.swift index 022548bf..389e1e43 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/CheckoutProtocolClient.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/CheckoutProtocolClient.swift @@ -7,25 +7,25 @@ extension CheckoutProtocol.Client { static func with(windowOpen: WindowOpenHandlerOption) -> CheckoutProtocol.Client { let base = CheckoutProtocol.Client() .on(CheckoutProtocol.start) { checkout in - print("[UCP] ec.start: \(checkout.id)") + print("[mobile_buy_integration:ucp] ec.start: \(checkout.id)") } .on(CheckoutProtocol.complete) { checkout in // Do NOT reset the cart here — the cart drives a SwiftUI `if let` in CartView, // and nil-ing it auto-collapses the .sheet, hiding the order confirmation page. // Reset on user dismiss instead (see CartView .onCancel + isCompleted). - print("[UCP] ec.complete: \(checkout.order?.id ?? "unknown")") + print("[mobile_buy_integration:ucp] ec.complete: \(checkout.order?.id ?? "unknown")") } .on(CheckoutProtocol.lineItemsChange) { checkout in - print("[UCP] ec.line_items.change: \(checkout.id)") + print("[mobile_buy_integration:ucp] ec.line_items.change: \(checkout.id)") } .on(CheckoutProtocol.messagesChange) { checkout in - print("[UCP] ec.messages.change: \(checkout.id)") + print("[mobile_buy_integration:ucp] ec.messages.change: \(checkout.id)") } .on(CheckoutProtocol.totalsChange) { checkout in - print("[UCP] ec.totals.change: \(checkout.id)") + print("[mobile_buy_integration:ucp] ec.totals.change: \(checkout.id)") } .on(CheckoutProtocol.error) { error in - print("[UCP] ec.error: \(error.messages.first?.content ?? "(no message)")") + print("[mobile_buy_integration:ucp] ec.error: \(error.messages.first?.content ?? "(no message)")") } switch windowOpen { @@ -35,7 +35,7 @@ extension CheckoutProtocol.Client { return base.on(CheckoutProtocol.windowOpen) { request in let scheme = request.url.scheme?.lowercased() - print("[UCP] ec.window_open (\(scheme ?? ""))") + print("[mobile_buy_integration:ucp] ec.window_open (\(scheme ?? ""))") guard scheme == "http" || scheme == "https" else { return .rejected(reason: "unsupported URL scheme") diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Lib/Logger.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Lib/Logger.swift index baf31052..aca34a46 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Lib/Logger.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Lib/Logger.swift @@ -18,13 +18,13 @@ class FileLogger: Logger { do { fileHandle = try FileHandle(forWritingTo: logFileUrl) } catch let error as NSError { - print("Couldn't open the log file. Error: \(error)") + print("[mobile_buy_integration:app] Couldn't open the log file. Error: \(error)") } } public func log(_ message: String) { guard let fileHandle else { - print("File handle is nil") + print("[mobile_buy_integration:app] File handle is nil") return } @@ -34,7 +34,7 @@ class FileLogger: Logger { dateFormatter.timeStyle = .medium let timeString = dateFormatter.string(from: date) - let logMessage = "\(timeString): \(message)\n" + let logMessage = "\(timeString): [mobile_buy_integration:app] \(message)\n" if let data = logMessage.data(using: .utf8) { fileHandle.seekToEndOfFile() fileHandle.write(data) @@ -45,7 +45,7 @@ class FileLogger: Logger { do { try "".write(toFile: logFileUrl.path, atomically: false, encoding: .utf8) } catch let error as NSError { - print("Couldn't clear the log file. Error: \(error)") + print("[mobile_buy_integration:app] Couldn't clear the log file. Error: \(error)") } } } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift index 65b5e40c..7e36ff95 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift @@ -38,10 +38,10 @@ struct CartView: View { if let cartID = cartManager.cart?.id { AcceleratedCheckoutButtons(cartID: cartID) .onFail { error in - print("[AcceleratedCheckout] Failed: \(error)") + print("[mobile_buy_integration:accelerated_checkout] Failed: \(error)") } .onCancel { - print("[AcceleratedCheckout] Cancelled") + print("[mobile_buy_integration:accelerated_checkout] Cancelled") } .connect(client) .environmentObject( @@ -92,12 +92,12 @@ struct CartView: View { // Set the flag here; defer the cart reset until the user dismisses // the sheet (in .onCancel). Resetting now would nil the cart and // SwiftUI would auto-collapse this sheet, hiding the confirmation page. - print("[UCP] ec.complete: \(checkout.order?.id ?? "unknown")") + print("[mobile_buy_integration:ucp] ec.complete: \(checkout.order?.id ?? "unknown")") isCompleted = true }) .colorScheme(.automatic) .onCancel { - print("[ShopifyCheckoutKit] CANCEL") + print("[mobile_buy_integration:checkout] CANCEL") showCheckoutSheet = false if isCompleted { @@ -107,7 +107,7 @@ struct CartView: View { } .onFail { error in showCheckoutSheet = false - print("[ShopifyCheckoutKit] FAIL - Checkout failed: \(error)") + print("[mobile_buy_integration:checkout] FAIL - Checkout failed: \(error)") } .edgesIgnoringSafeArea(.all) } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartViewController.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartViewController.swift index 28e8d1b0..a3936061 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartViewController.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartViewController.swift @@ -131,10 +131,10 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData private let checkoutDelegate = CartResettingCheckoutDelegate() private lazy var client = CheckoutProtocol.Client() .on(CheckoutProtocol.start) { checkout in - print("[UCP] Checkout started: \(checkout.id)") + print("[mobile_buy_integration:ucp] Checkout started: \(checkout.id)") } .on(CheckoutProtocol.complete) { [checkoutDelegate] checkout in - print("[UCP] Checkout completed: \(checkout.order?.id ?? "unknown")") + print("[mobile_buy_integration:ucp] Checkout completed: \(checkout.order?.id ?? "unknown")") checkoutDelegate.markCompleted() } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/CustomerAccounts/CustomerAccountLoginView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/CustomerAccounts/CustomerAccountLoginView.swift index 7e1bd1ed..fc1e724a 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/CustomerAccounts/CustomerAccountLoginView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/CustomerAccounts/CustomerAccountLoginView.swift @@ -75,14 +75,14 @@ struct CustomerAccountLoginView: UIViewRepresentable { if (error as NSError).code == NSURLErrorCancelled { return } - print("WebView navigation failed: \(error)") + print("[mobile_buy_integration:customer_account] WebView navigation failed: \(error)") } func webView(_: WKWebView, didFailProvisionalNavigation _: WKNavigation!, withError error: Error) { if (error as NSError).code == NSURLErrorCancelled { return } - print("WebView provisional navigation failed: \(error)") + print("[mobile_buy_integration:customer_account] WebView provisional navigation failed: \(error)") } } } @@ -110,7 +110,7 @@ struct LoginSheetView: View { try await accountManager.exchangeCodeForTokens(code: code) dismiss() } catch { - print("Failed to exchange code: \(error)") + print("[mobile_buy_integration:customer_account] Failed to exchange code: \(error)") } } }, diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Logs/LogReader.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Logs/LogReader.swift index 98951071..9f4aa3c6 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Logs/LogReader.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Logs/LogReader.swift @@ -17,7 +17,7 @@ final class LogReader: Sendable { logLines = Array(logLines.suffix(limit)) return logLines.reversed() } catch { - print("Couldn't read the log file") + print("[mobile_buy_integration:app] Couldn't read the log file") return [] } } diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift index bf8edd19..90da5b0e 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift @@ -116,10 +116,10 @@ struct ProductView: View { AcceleratedCheckoutButtons(variantID: variant.id, quantity: 1) .wallets([.applePay]) .onFail { error in - print("[AcceleratedCheckout] Failed: \(error)") + print("[mobile_buy_integration:accelerated_checkout] Failed: \(error)") } .onCancel { - print("[AcceleratedCheckout] Cancelled") + print("[mobile_buy_integration:accelerated_checkout] Cancelled") } .environmentObject( ShopifyAcceleratedCheckouts.Configuration( diff --git a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Network.swift b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Network.swift index 6a5616ce..b82809e0 100644 --- a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Network.swift +++ b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Network.swift @@ -74,7 +74,7 @@ class Network { }() func getProducts() async -> Products? { - print("Network: Starting product fetch from \(EnvironmentVariables.storefrontDomain)") + print("[accelerated_checkouts_app:network] Network: Starting product fetch") let countryCode = GraphQLEnum(Storefront.CountryCode(rawValue: Locale.current.region?.identifier ?? "US") ?? .us) let languageCode = getLanguageCode() @@ -84,13 +84,13 @@ class Network { country: countryCode, language: languageCode )) - print("Network: Successfully fetched products") + print("[accelerated_checkouts_app:network] Network: Successfully fetched products") if let errors = response.errors { - print("Network: GraphQL errors: \(errors)") + print("[accelerated_checkouts_app:network] Network: GraphQL errors: \(errors)") } return response.data?.products } catch { - print("Network: Failed to fetch products - \(error.localizedDescription)") + print("[accelerated_checkouts_app:network] Network: Failed to fetch products - \(error.localizedDescription)") return nil } } @@ -135,7 +135,7 @@ class Network { let response = try await Network.shared.apollo.perform(mutation: mutation) return response.data?.cartCreate?.cart } catch { - print(error) + print("[accelerated_checkouts_app:network] \(error)") return nil } } diff --git a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/CartBuilderView.swift b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/CartBuilderView.swift index 2d657212..10a601fc 100644 --- a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/CartBuilderView.swift +++ b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/CartBuilderView.swift @@ -120,11 +120,11 @@ struct CartBuilderView: View { func onLoad() async { // Prevent multiple simultaneous loads guard !isLoadingProducts else { - print("Already loading products, skipping...") + print("[accelerated_checkouts_app:cart_builder] Already loading products, skipping...") return } - print("Starting to load products...") + print("[accelerated_checkouts_app:cart_builder] Starting to load products...") isLoadingProducts = true // Ensure products load regardless of any configuration issues @@ -134,12 +134,12 @@ struct CartBuilderView: View { let products = await Network.shared.getProducts() if let products { - print("Loaded \(products.nodes.count) products") + print("[accelerated_checkouts_app:cart_builder] Loaded \(products.nodes.count) products") withAnimation { allProducts = products.nodes } } else { - print("Warning: No products returned from API") + print("[accelerated_checkouts_app:cart_builder] Warning: No products returned from API") } } } diff --git a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift index f4461fbd..d2ea773d 100644 --- a/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift +++ b/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift @@ -21,10 +21,10 @@ struct ButtonSet: View { AcceleratedCheckoutButtons(cartID: cartID) .applePayLabel(.plain) .onFail { error in - print("❌ Checkout failed: \(error)") + print("[accelerated_checkouts_app:accelerated_checkout] Checkout failed: \(error)") } .onCancel { - print("🚫 Checkout cancelled") + print("[accelerated_checkouts_app:accelerated_checkout] Checkout cancelled") } .onRenderStateChange { cartRenderState = $0 @@ -48,10 +48,10 @@ struct ButtonSet: View { .cornerRadius(24) .wallets([.applePay, .shopPay]) .onFail { error in - print("❌ Variant checkout failed: \(error)") + print("[accelerated_checkouts_app:accelerated_checkout] Variant checkout failed: \(error)") } .onCancel { - print("🚫 Variant checkout cancelled") + print("[accelerated_checkouts_app:accelerated_checkout] Variant checkout cancelled") } .onRenderStateChange { variantRenderState = $0 diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift index 15ccd698..8d002b57 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts.swift @@ -4,7 +4,7 @@ public enum ShopifyAcceleratedCheckouts { /// Storefront API version used for cart operations internal static let apiVersion = "2026-04" - internal static let name = "ShopifyAcceleratedCheckouts" + internal static let name = "accelerated_checkout" /// The logging level for Accelerated Checkouts operations /// Default: .error - which will emit "error" and "fault" logs diff --git a/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift b/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift index 0ce01e2d..b5b30307 100644 --- a/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift +++ b/platforms/swift/Sources/ShopifyCheckoutKit/Logger.swift @@ -2,6 +2,8 @@ import Foundation import os.log private let subsystem = "com.shopify.checkoutkit" +private let logPrefix = "checkout_kit" +private let defaultLogScope = "sdk" public enum LogLevel: String, CaseIterable { case all @@ -18,7 +20,7 @@ public class OSLogger { public static var shared = OSLogger() public init() { - prefix = "ShopifyCheckoutKit" + prefix = defaultLogScope logLevel = ShopifyCheckoutKit.configuration.logLevel } @@ -30,25 +32,25 @@ public class OSLogger { public func debug(_ message: String) { guard shouldEmit(.debug) else { return } - sendToOSLog("[\(prefix)] (Debug) - \(message)", type: .debug) + sendToOSLog("[\(logPrefix):\(prefix.toLogScope())] (Debug) - \(message)", type: .debug) } public func info(_ message: String) { guard shouldEmit(.debug) else { return } - sendToOSLog("[\(prefix)] (Info) - \(message)", type: .info) + sendToOSLog("[\(logPrefix):\(prefix.toLogScope())] (Info) - \(message)", type: .info) } public func error(_ message: String) { guard shouldEmit(.error) else { return } - sendToOSLog("[\(prefix)] (Error) - \(message)", type: .error) + sendToOSLog("[\(logPrefix):\(prefix.toLogScope())] (Error) - \(message)", type: .error) } public func fault(_ message: String) { guard shouldEmit(.error) else { return } - sendToOSLog("[\(prefix)] (Fault) - \(message)", type: .fault) + sendToOSLog("[\(logPrefix):\(prefix.toLogScope())] (Fault) - \(message)", type: .fault) } /// Capturing `os_log` output is not possible @@ -66,6 +68,21 @@ public class OSLogger { } } +extension String { + fileprivate func toLogScope() -> String { + switch self { + case "ShopifyCheckoutKit", "checkout_kit": + return defaultLogScope + case "ShopifyAcceleratedCheckouts": + return "accelerated_checkout" + case "CheckoutECP": + return "ecp" + default: + return self + } + } +} + public protocol Logger { func log(_ message: String) func clearLogs() diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift index 7df25093..6f691acf 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift @@ -290,8 +290,9 @@ class CheckoutWebViewTests: XCTestCase { } @MainActor - func testAcknowledgeReadyDoesNotInvokeClient() async { - view.client = MockBridgeClient(responseMessage: "client-response") + func testAcknowledgeReadyDoesNotInvokeClient() async throws { + let client = MockBridgeClient(responseMessage: "client-response") + view.client = client let body = #"{"jsonrpc":"2.0","method":"ec.ready","id":"r1","params":{"delegate":[]}}"# let responseSent = expectation(description: "response sent") MockCheckoutBridge.sendResponseExpectation = responseSent @@ -301,8 +302,10 @@ class CheckoutWebViewTests: XCTestCase { await fulfillment(of: [responseSent], timeout: 5.0) - let response = try? XCTUnwrap(MockCheckoutBridge.lastResponseBody) + let response = try XCTUnwrap(MockCheckoutBridge.lastResponseBody) XCTAssertNotEqual(response, "client-response") + let receivedMessages = await client.receivedMessages + XCTAssertTrue(receivedMessages.isEmpty) } func testNonReadyMessageDoesNotTriggerReadyAck() { diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift index fc0590cd..b200d13c 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/LoggerTests.swift @@ -63,19 +63,19 @@ final class OSLoggerTests: XCTestCase { XCTAssertEqual(logger.capturedMessages.count, 4) XCTAssertEqual( - logger.capturedMessages[0].message, "[ShopifyCheckoutKit] (Info) - test info" + logger.capturedMessages[0].message, "[checkout_kit:sdk] (Info) - test info" ) XCTAssertEqual(logger.capturedMessages[0].type, OSLogType.info) XCTAssertEqual( - logger.capturedMessages[1].message, "[ShopifyCheckoutKit] (Debug) - test debug" + logger.capturedMessages[1].message, "[checkout_kit:sdk] (Debug) - test debug" ) XCTAssertEqual(logger.capturedMessages[1].type, OSLogType.debug) XCTAssertEqual( - logger.capturedMessages[2].message, "[ShopifyCheckoutKit] (Error) - test error" + logger.capturedMessages[2].message, "[checkout_kit:sdk] (Error) - test error" ) XCTAssertEqual(logger.capturedMessages[2].type, OSLogType.error) XCTAssertEqual( - logger.capturedMessages[3].message, "[ShopifyCheckoutKit] (Fault) - test fault" + logger.capturedMessages[3].message, "[checkout_kit:sdk] (Fault) - test fault" ) XCTAssertEqual(logger.capturedMessages[3].type, OSLogType.fault) } @@ -90,10 +90,10 @@ final class OSLoggerTests: XCTestCase { XCTAssertEqual(logger.capturedMessages.count, 2) XCTAssertEqual( - logger.capturedMessages[0].message, "[ShopifyCheckoutKit] (Info) - test info" + logger.capturedMessages[0].message, "[checkout_kit:sdk] (Info) - test info" ) XCTAssertEqual( - logger.capturedMessages[1].message, "[ShopifyCheckoutKit] (Debug) - test debug" + logger.capturedMessages[1].message, "[checkout_kit:sdk] (Debug) - test debug" ) } @@ -107,10 +107,10 @@ final class OSLoggerTests: XCTestCase { XCTAssertEqual(logger.capturedMessages.count, 2) XCTAssertEqual( - logger.capturedMessages[0].message, "[ShopifyCheckoutKit] (Error) - test error" + logger.capturedMessages[0].message, "[checkout_kit:sdk] (Error) - test error" ) XCTAssertEqual( - logger.capturedMessages[1].message, "[ShopifyCheckoutKit] (Fault) - test fault" + logger.capturedMessages[1].message, "[checkout_kit:sdk] (Fault) - test fault" ) } @@ -130,24 +130,24 @@ final class OSLoggerTests: XCTestCase { XCTAssertEqual(logger.capturedMessages.count, 4) XCTAssertEqual( logger.capturedMessages[0].message, - "[ShopifyCheckoutKit] (Info) - user action completed" + "[checkout_kit:sdk] (Info) - user action completed" ) XCTAssertEqual( logger.capturedMessages[1].message, - "[ShopifyCheckoutKit] (Debug) - processing checkout data" + "[checkout_kit:sdk] (Debug) - processing checkout data" ) XCTAssertEqual( logger.capturedMessages[2].message, - "[ShopifyCheckoutKit] (Error) - network request failed" + "[checkout_kit:sdk] (Error) - network request failed" ) XCTAssertEqual( logger.capturedMessages[3].message, - "[ShopifyCheckoutKit] (Fault) - critical system error" + "[checkout_kit:sdk] (Fault) - critical system error" ) } func test_customPrefix_withLoggerInitialization_shouldUseCustomPrefix() { - let customLogger = TestableOSLogger(prefix: "CustomModule", logLevel: .all) + let customLogger = TestableOSLogger(prefix: "custom_module", logLevel: .all) customLogger.info("custom module message") customLogger.error("custom error") @@ -155,14 +155,37 @@ final class OSLoggerTests: XCTestCase { XCTAssertEqual(customLogger.capturedMessages.count, 2) XCTAssertEqual( customLogger.capturedMessages[0].message, - "[CustomModule] (Info) - custom module message" + "[checkout_kit:custom_module] (Info) - custom module message" ) XCTAssertEqual( customLogger.capturedMessages[1].message, - "[CustomModule] (Error) - custom error" + "[checkout_kit:custom_module] (Error) - custom error" ) } + func test_prefixNormalization_withMixedCasing_shouldUseConsistentLogScopes() { + let cases = [ + ("ShopifyCheckoutKit", "sdk"), + ("checkout_kit", "sdk"), + ("sdk", "sdk"), + ("ShopifyAcceleratedCheckouts", "accelerated_checkout"), + ("CheckoutECP", "ecp"), + ("accelerated_checkout", "accelerated_checkout") + ] + + for (prefix, scope) in cases { + let logger = TestableOSLogger(prefix: prefix, logLevel: .all) + + logger.info("test message") + + XCTAssertEqual( + logger.capturedMessages.map(\.message), + ["[checkout_kit:\(scope)] (Info) - test message"], + "Expected \(prefix) to normalize to \(scope)" + ) + } + } + func test_logLevelNone_withAllMessageTypes_shouldBlockAllMessagesRegardlessOfType() { let logger = TestableOSLogger(prefix: "Test", logLevel: .none) diff --git a/platforms/swift/Tests/ShopifyCheckoutKitTests/Mocks/MockCheckoutDelegate.swift b/platforms/swift/Tests/ShopifyCheckoutKitTests/Mocks/MockCheckoutDelegate.swift index 6b332aa6..cc139680 100644 --- a/platforms/swift/Tests/ShopifyCheckoutKitTests/Mocks/MockCheckoutDelegate.swift +++ b/platforms/swift/Tests/ShopifyCheckoutKitTests/Mocks/MockCheckoutDelegate.swift @@ -1,11 +1,16 @@ @testable import ShopifyCheckoutKit import XCTest -struct MockBridgeClient: CheckoutCommunicationProtocol { - var responseMessage: String? - var receivedMessages: [String] = [] +actor MockBridgeClient: CheckoutCommunicationProtocol { + let responseMessage: String? + private(set) var receivedMessages: [String] = [] - func process(_: String) async -> String? { + init(responseMessage: String? = nil) { + self.responseMessage = responseMessage + } + + func process(_ message: String) async -> String? { + receivedMessages.append(message) return responseMessage } } diff --git a/platforms/web/src/checkout.test.ts b/platforms/web/src/checkout.test.ts index e392748e..7b287b3a 100644 --- a/platforms/web/src/checkout.test.ts +++ b/platforms/web/src/checkout.test.ts @@ -186,7 +186,7 @@ describe("", () => { checkout.open(); expect(consoleWarnSpy).toHaveBeenCalledWith( - "``: src property is empty or invalid, cannot open checkout", + "[checkout_kit:web] src property is empty or invalid, cannot open checkout", ); expect(windowOpenSpy).not.toHaveBeenCalled(); }); @@ -313,7 +313,7 @@ describe("", () => { checkout.open(); expect(consoleWarnSpy).toHaveBeenCalledWith( - "``: src property is empty or invalid, cannot open checkout", + "[checkout_kit:web] src property is empty or invalid, cannot open checkout", ); expect(windowOpenSpy).not.toHaveBeenCalled(); }); diff --git a/platforms/web/src/checkout.ts b/platforms/web/src/checkout.ts index 4cf1e6ea..4034d8ac 100644 --- a/platforms/web/src/checkout.ts +++ b/platforms/web/src/checkout.ts @@ -22,6 +22,7 @@ export const DEFAULT_POPUP_HEIGHT = 600; export const EMBED_PROTOCOL_VERSION = "2026-04-08"; export const CK_VERSION = "4.0.0"; const EMBED_DELEGATIONS: readonly string[] = ["window.open"]; +const LOG_PREFIX = "[checkout_kit:web]"; const SHADOW_TEMPLATE = createTemplate(html`
@@ -163,7 +164,7 @@ export class ShopifyCheckout #debugWarn(message: string, ...args: unknown[]) { if (this.debug) { // eslint-disable-next-line no-console - console.warn(`: ${message}`, ...args); + console.warn(`${LOG_PREFIX} ${message}`, ...args); } } @@ -253,7 +254,7 @@ export class ShopifyCheckout if (!src) { // eslint-disable-next-line no-console - console.warn("``: src property is empty or invalid, cannot open checkout"); + console.warn(`${LOG_PREFIX} src property is empty or invalid, cannot open checkout`); return; } @@ -613,7 +614,7 @@ export class ShopifyCheckout if (!body || typeof body.url !== "string") { // eslint-disable-next-line no-console console.warn( - ": ec.window.open_request received without a valid url", + `${LOG_PREFIX} ec.window.open_request received without a valid url`, message, ); message.source?.postMessage( @@ -670,7 +671,7 @@ export class ShopifyCheckout default: { // eslint-disable-next-line no-console console.warn( - `: Unknown checkout protocol message received: ${message.name}`, + `${LOG_PREFIX} Unknown checkout protocol message received: ${message.name}`, message, ); break;