From bae0f3d2cee84978636d0f589bc7e2f745671baf Mon Sep 17 00:00:00 2001 From: Ian Keith Date: Thu, 29 Apr 2021 11:13:42 -0400 Subject: [PATCH] feat: Unify logging behavior across environments (#4416) --- .../src/main/assets/native-bridge.js | 4 +- .../main/java/com/getcapacitor/Bridge.java | 2 +- .../main/java/com/getcapacitor/CapConfig.java | 45 +++++++++++--- .../main/java/com/getcapacitor/JSExport.java | 4 +- .../main/java/com/getcapacitor/Logger.java | 2 +- .../com/getcapacitor/ConfigBuildingTest.java | 4 +- .../com/getcapacitor/ConfigReadingTest.java | 6 +- cli/src/config.ts | 15 +++++ cli/src/declarations.ts | 40 ++++++++++++ core/native-bridge.ts | 4 +- core/src/definitions.ts | 1 + core/src/runtime.ts | 1 + core/src/tests/runtime.spec.ts | 13 ++++ .../Capacitor.xcodeproj/project.pbxproj | 4 ++ .../Capacitor/CAPBridgeViewController.swift | 4 +- .../Capacitor/CAPInstanceConfiguration.h | 4 +- .../Capacitor/CAPInstanceConfiguration.m | 16 ++++- .../Capacitor/CAPInstanceDescriptor.h | 12 +++- .../Capacitor/CAPInstanceDescriptor.m | 2 +- .../Capacitor/CAPInstanceDescriptor.swift | 30 +++++++-- ios/Capacitor/Capacitor/CapacitorBridge.swift | 19 ++++-- ios/Capacitor/Capacitor/JSExport.swift | 4 +- .../Capacitor/assets/native-bridge.js | 4 +- .../CapacitorTests/CapacitorTests.swift | 2 +- .../CapacitorTests/ConfigurationTests.swift | 61 ++++++++++++++++--- .../TestsHostApp/configurations/bad.json | 2 +- .../TestsHostApp/configurations/flat.json | 2 +- .../configurations/hidinglogs.json | 17 ++++++ .../configurations/hierarchy.json | 2 +- .../TestsHostApp/configurations/server.json | 1 + 30 files changed, 265 insertions(+), 62 deletions(-) create mode 100644 ios/Capacitor/TestsHostApp/configurations/hidinglogs.json diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index 6f4c809bbc..523b552494 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -369,7 +369,7 @@ const nativeBridge = (function (exports) { methodName: methodName, options: options || {}, }; - if (cap.DEBUG && pluginName !== 'Console') { + if (cap.isLoggingEnabled && pluginName !== 'Console') { cap.logToNative(callData); } // post the call data to native @@ -390,7 +390,7 @@ const nativeBridge = (function (exports) { */ cap.fromNative = result => { var _a, _b; - if (cap.DEBUG && result.pluginId !== 'Console') { + if (cap.isLoggingEnabled && result.pluginId !== 'Console') { cap.logFromNative(result); } // get the stored call, if it exists diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index c76772773c..670bfec9f5 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -755,7 +755,7 @@ public ActivityResultLauncher registerForActivityResult( */ private JSInjector getJSInjector() { try { - String globalJS = JSExport.getGlobalJS(context, isDevMode()); + String globalJS = JSExport.getGlobalJS(context, config.isLoggingEnabled(), isDevMode()); String bridgeJS = JSExport.getBridgeJS(context); String pluginJS = JSExport.getPluginJS(plugins.values()); String cordovaJS = JSExport.getCordovaJS(context); diff --git a/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java b/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java index 0eaa1c52da..2e44503342 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java +++ b/android/capacitor/src/main/java/com/getcapacitor/CapConfig.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; @@ -20,6 +21,10 @@ */ public class CapConfig { + private static final String LOG_BEHAVIOR_NONE = "none"; + private static final String LOG_BEHAVIOR_DEBUG = "debug"; + private static final String LOG_BEHAVIOR_PRODUCTION = "production"; + // Server Config private boolean html5mode = true; private String serverUrl; @@ -34,7 +39,7 @@ public class CapConfig { private boolean allowMixedContent = false; private boolean captureInput = false; private boolean webContentsDebuggingEnabled = false; - private boolean hideLogs = false; + private boolean loggingEnabled = true; // Embedded private String startPath; @@ -110,7 +115,7 @@ private CapConfig(Builder builder) { this.allowMixedContent = builder.allowMixedContent; this.captureInput = builder.captureInput; this.webContentsDebuggingEnabled = builder.webContentsDebuggingEnabled; - this.hideLogs = builder.hideLogs; + this.loggingEnabled = builder.loggingEnabled; // Embedded this.startPath = builder.startPath; @@ -137,6 +142,8 @@ private void loadConfig(AssetManager assetManager) { * Deserializes the config from JSON into a Capacitor Configuration object. */ private void deserializeConfig(@Nullable Context context) { + boolean isDebug = context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + // Server html5mode = JSONUtils.getBoolean(configJSON, "server.html5mode", html5mode); serverUrl = JSONUtils.getString(configJSON, "server.url", null); @@ -158,9 +165,27 @@ private void deserializeConfig(@Nullable Context context) { JSONUtils.getBoolean(configJSON, "allowMixedContent", allowMixedContent) ); captureInput = JSONUtils.getBoolean(configJSON, "android.captureInput", captureInput); - hideLogs = JSONUtils.getBoolean(configJSON, "android.hideLogs", JSONUtils.getBoolean(configJSON, "hideLogs", hideLogs)); - webContentsDebuggingEnabled = context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - webContentsDebuggingEnabled = JSONUtils.getBoolean(configJSON, "android.webContentsDebuggingEnabled", webContentsDebuggingEnabled); + webContentsDebuggingEnabled = JSONUtils.getBoolean(configJSON, "android.webContentsDebuggingEnabled", isDebug); + + String logBehavior = JSONUtils.getString( + configJSON, + "android.loggingBehavior", + JSONUtils.getString(configJSON, "loggingBehavior", null) + ); + if (logBehavior == null) { + boolean hideLogs = JSONUtils.getBoolean(configJSON, "android.hideLogs", JSONUtils.getBoolean(configJSON, "hideLogs", false)); + logBehavior = hideLogs ? LOG_BEHAVIOR_NONE : LOG_BEHAVIOR_DEBUG; + } + switch (logBehavior.toLowerCase(Locale.ROOT)) { + case LOG_BEHAVIOR_PRODUCTION: + loggingEnabled = true; + break; + case LOG_BEHAVIOR_NONE: + loggingEnabled = false; + break; + default: // LOG_BEHAVIOR_DEBUG + loggingEnabled = isDebug; + } // Plugins pluginsConfiguration = deserializePluginsConfig(JSONUtils.getObject(configJSON, "plugins")); @@ -214,8 +239,8 @@ public boolean isWebContentsDebuggingEnabled() { return webContentsDebuggingEnabled; } - public boolean isLogsHidden() { - return hideLogs; + public boolean isLoggingEnabled() { + return loggingEnabled; } public PluginConfig getPluginConfiguration(String pluginId) { @@ -372,7 +397,7 @@ public static class Builder { private boolean allowMixedContent = false; private boolean captureInput = false; private Boolean webContentsDebuggingEnabled = null; - private boolean hideLogs = false; + private boolean loggingEnabled = true; // Embedded private String startPath = null; @@ -467,8 +492,8 @@ public Builder setWebContentsDebuggingEnabled(boolean webContentsDebuggingEnable return this; } - public Builder setLogsHidden(boolean hideLogs) { - this.hideLogs = hideLogs; + public Builder setLoggingEnabled(boolean enabled) { + this.loggingEnabled = enabled; return this; } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/JSExport.java b/android/capacitor/src/main/java/com/getcapacitor/JSExport.java index 9a5c98699e..b36adee475 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/JSExport.java +++ b/android/capacitor/src/main/java/com/getcapacitor/JSExport.java @@ -17,8 +17,8 @@ public class JSExport { private static String CATCHALL_OPTIONS_PARAM = "_options"; private static String CALLBACK_PARAM = "_callback"; - public static String getGlobalJS(Context context, boolean isDebug) { - return "window.Capacitor = { DEBUG: " + isDebug + ", Plugins: {} };"; + public static String getGlobalJS(Context context, boolean loggingEnabled, boolean isDebug) { + return "window.Capacitor = { DEBUG: " + isDebug + ", isLoggingEnabled: " + loggingEnabled + ", Plugins: {} };"; } public static String getCordovaJS(Context context) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/Logger.java b/android/capacitor/src/main/java/com/getcapacitor/Logger.java index b90f7bbba1..f528e25796 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Logger.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Logger.java @@ -98,6 +98,6 @@ public static void error(String tag, String message, Throwable e) { } protected static boolean shouldLog() { - return config == null || !config.isLogsHidden(); + return config == null || config.isLoggingEnabled(); } } diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java index d465c36193..2a3892cd87 100644 --- a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java @@ -46,7 +46,7 @@ public void setup() { .setAllowNavigation(new String[] { "http://www.google.com" }) .setAndroidScheme("test") .setCaptureInput(true) - .setLogsHidden(true) + .setLoggingEnabled(true) .setHTML5mode(false) .setOverriddenUserAgentString("test-user-agent") .setAppendedUserAgentString("test-append") @@ -66,7 +66,7 @@ public void getCoreConfigValues() { assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation()); assertEquals("test", config.getAndroidScheme()); assertTrue(config.isInputCaptured()); - assertTrue(config.isLogsHidden()); + assertTrue(config.isLoggingEnabled()); assertFalse(config.isHTML5Mode()); assertEquals("test-user-agent", config.getOverriddenUserAgentString()); assertEquals("test-append", config.getAppendedUserAgentString()); diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java index 5fba0afd80..e1685cf9ac 100644 --- a/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigReadingTest.java @@ -47,7 +47,7 @@ public void bad() { CapConfig config = CapConfig.loadDefault(context); assertEquals("not a real domain", config.getServerUrl()); assertNull(config.getBackgroundColor()); - assertFalse(config.isLogsHidden()); + assertFalse(config.isLoggingEnabled()); } catch (IOException e) { fail(); } @@ -62,7 +62,7 @@ public void flat() { assertEquals("level 1 override", config.getOverriddenUserAgentString()); assertEquals("level 1 append", config.getAppendedUserAgentString()); assertEquals("#ffffff", config.getBackgroundColor()); - assertTrue(config.isLogsHidden()); + assertFalse(config.isLoggingEnabled()); assertEquals(1, config.getPluginConfiguration("SplashScreen").getInt("launchShowDuration", 0)); } catch (IOException e) { fail(); @@ -78,7 +78,7 @@ public void hierarchy() { assertEquals("level 2 override", config.getOverriddenUserAgentString()); assertEquals("level 2 append", config.getAppendedUserAgentString()); assertEquals("#000000", config.getBackgroundColor()); - assertFalse(config.isLogsHidden()); + assertFalse(config.isLoggingEnabled()); } catch (IOException e) { fail(); } diff --git a/cli/src/config.ts b/cli/src/config.ts index 52c8551d47..b97b4757b1 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -62,6 +62,8 @@ export async function loadConfig(): Promise { }, }; + checkExternalConfig(conf); + debug('config: %O', config); return config; @@ -435,3 +437,16 @@ const config: CapacitorConfig = ${formatJSObject(extConfig)}; export default config;\n`; } + +function checkExternalConfig(config: ExtConfigPairs): void { + if ( + typeof config.extConfig.hideLogs !== 'undefined' || + typeof config.extConfig.android?.hideLogs !== 'undefined' || + typeof config.extConfig.ios?.hideLogs !== 'undefined' + ) { + logger.warn( + `The ${c.strong('hideLogs')} configuration option has been deprecated. ` + + `Please update to use ${c.strong('loggingBehavior')} instead.`, + ); + } +} diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 2f99a85718..09ab5a7d2e 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -45,10 +45,28 @@ export interface CapacitorConfig { * Hide or show the native logs for iOS and Android. * * @since 2.1.0 + * @deprecated 3.0.0 * @default false */ hideLogs?: boolean; + /** + * The build configuration (as defined by the native app) under which Capacitor + * will send statements to the log system. This applies to log statements in + * native code as well as statements redirected from JavaScript (`console.debug`, + * `console.error`, etc.). Enabling logging will let statements render in the + * Xcode and Android Studio windows but can leak information on device if enabled + * in released builds. + * + * 'none' = logs are never produced + * 'debug' = logs are produced in debug builds but not production builds + * 'production' = logs are always produced + * + * @since 3.0.0 + * @default debug + */ + loggingBehavior?: 'none' | 'debug' | 'production'; + /** * User agent of Capacitor Web View. * @@ -152,10 +170,21 @@ export interface CapacitorConfig { * Overrides global `hideLogs` option. * * @since 2.1.0 + * @deprecated 3.0.0 * @default false */ hideLogs?: boolean; + /** + * The build configuration under which Capacitor will generate logs on Android. + * + * Overrides global `loggingBehavior` option. + * + * @since 3.0.0 + * @default debug + */ + loggingBehavior?: 'none' | 'debug' | 'production'; + /** * Allowlist of plugins to include during `npx cap sync` for Android. * @@ -269,10 +298,21 @@ export interface CapacitorConfig { * Overrides global `hideLogs` option. * * @since 1.1.0 + * @deprecated 3.0.0 * @default false */ hideLogs?: boolean; + /** + * The build configuration under which Capacitor will generate logs on iOS. + * + * Overrides global `loggingBehavior` option. + * + * @since 3.0.0 + * @default debug + */ + loggingBehavior?: 'none' | 'debug' | 'production'; + /** * Allowlist of plugins to include during `npx cap sync` for iOS. * diff --git a/core/native-bridge.ts b/core/native-bridge.ts index 893ded94c2..8d80c03324 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -437,7 +437,7 @@ const initBridge = (w: any): void => { options: options || {}, }; - if (cap.DEBUG && pluginName !== 'Console') { + if (cap.isLoggingEnabled && pluginName !== 'Console') { cap.logToNative(callData); } @@ -459,7 +459,7 @@ const initBridge = (w: any): void => { * Process a response from the native layer. */ cap.fromNative = result => { - if (cap.DEBUG && result.pluginId !== 'Console') { + if (cap.isLoggingEnabled && result.pluginId !== 'Console') { cap.logFromNative(result); } diff --git a/core/src/definitions.ts b/core/src/definitions.ts index 110bbc111e..1471becabe 100644 --- a/core/src/definitions.ts +++ b/core/src/definitions.ts @@ -52,6 +52,7 @@ export interface CapacitorGlobal { ) => void; DEBUG?: boolean; + isLoggingEnabled?: boolean; // Deprecated in v3, will be removed from v4 diff --git a/core/src/runtime.ts b/core/src/runtime.ts index 0feac167d0..2c5ce112a3 100644 --- a/core/src/runtime.ts +++ b/core/src/runtime.ts @@ -223,6 +223,7 @@ export const createCapacitor = (win: WindowCapacitor): CapacitorInstance => { cap.registerPlugin = registerPlugin; cap.Exception = CapacitorException; cap.DEBUG = !!cap.DEBUG; + cap.isLoggingEnabled = !!cap.isLoggingEnabled; // Deprecated props cap.platform = cap.getPlatform(); diff --git a/core/src/tests/runtime.spec.ts b/core/src/tests/runtime.spec.ts index d3b2d88269..1b8a7a964e 100644 --- a/core/src/tests/runtime.spec.ts +++ b/core/src/tests/runtime.spec.ts @@ -45,6 +45,19 @@ describe('runtime', () => { expect(cap.DEBUG).toBe(true); }); + it('isLoggingEnabled false default', () => { + cap = createCapacitor(win); + expect(cap.isLoggingEnabled).toBe(false); + }); + + it('isLoggingEnabled set from window.Capacitor.isLoggingEnabled', () => { + (win as any).Capacitor = { + isLoggingEnabled: true, + }; + cap = createCapacitor(win); + expect(cap.isLoggingEnabled).toBe(true); + }); + it('cannot reset server url after initializing capacitor', () => { win.WEBVIEW_SERVER_URL = 'whatever://home'; initBridge(win); diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index 170f1395a3..8a4d3a97f6 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 623D691E254C7462002D01D1 /* CAPInstanceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */; }; 625AF1ED258963C700869675 /* WebViewAssetHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625AF1EC258963C700869675 /* WebViewAssetHandler.swift */; }; 6263686025F6EC0100576C1C /* PluginCallAccessorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6263685F25F6EC0100576C1C /* PluginCallAccessorTests.m */; }; + 626D2D992613B61E0046CE81 /* hidinglogs.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 626D2D902613B4BB0046CE81 /* hidinglogs.json */; }; 62959B162524DA7800A3D7F1 /* CAPPluginCall.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959AE22524DA7700A3D7F1 /* CAPPluginCall.h */; settings = {ATTRIBUTES = (Public, ); }; }; 62959B172524DA7800A3D7F1 /* JSExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AE32524DA7700A3D7F1 /* JSExport.swift */; }; 62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959AE52524DA7700A3D7F1 /* CAPBridgedPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -110,6 +111,7 @@ dstPath = configurations; dstSubfolderSpec = 1; files = ( + 626D2D992613B61E0046CE81 /* hidinglogs.json in CopyFiles */, 62A91C3F2553710E00861508 /* nonjson.json in CopyFiles */, 62E0736125535E8700BAAADB /* server.json in CopyFiles */, 62E0736225535E8700BAAADB /* bad.json in CopyFiles */, @@ -157,6 +159,7 @@ 623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CAPInstanceConfiguration.m; sourceTree = ""; }; 625AF1EC258963C700869675 /* WebViewAssetHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewAssetHandler.swift; sourceTree = ""; }; 6263685F25F6EC0100576C1C /* PluginCallAccessorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PluginCallAccessorTests.m; sourceTree = ""; }; + 626D2D902613B4BB0046CE81 /* hidinglogs.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = hidinglogs.json; sourceTree = ""; }; 62959AE22524DA7700A3D7F1 /* CAPPluginCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPPluginCall.h; sourceTree = ""; }; 62959AE32524DA7700A3D7F1 /* JSExport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSExport.swift; sourceTree = ""; }; 62959AE52524DA7700A3D7F1 /* CAPBridgedPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPBridgedPlugin.h; sourceTree = ""; }; @@ -382,6 +385,7 @@ 62E0735225535E6500BAAADB /* server.json */, 62E0735325535E6500BAAADB /* bad.json */, 62E0735425535E6500BAAADB /* flat.json */, + 626D2D902613B4BB0046CE81 /* hidinglogs.json */, 62E0735525535E6500BAAADB /* hierarchy.json */, 62A91C392553710300861508 /* nonjson.json */, ); diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index f98eaaa6b3..ef5d9533ce 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -29,8 +29,8 @@ import Cordova override public final func loadView() { // load the configuration and set the logging flag let configDescriptor = instanceDescriptor() - let configuration = InstanceConfiguration(with: configDescriptor) - CAPLog.enableLogging = configuration.enableLogging + let configuration = InstanceConfiguration(with: configDescriptor, isDebug: CapacitorBridge.isDevEnvironment) + CAPLog.enableLogging = configuration.loggingEnabled logWarnings(for: configDescriptor) if configDescriptor.instanceType == .fixed { diff --git a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h index 1224dd8853..27b46631e2 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h +++ b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h @@ -14,7 +14,7 @@ NS_SWIFT_NAME(InstanceConfiguration) @property (nonatomic, readonly, nonnull) NSURL *localURL; @property (nonatomic, readonly, nonnull) NSURL *serverURL; @property (nonatomic, readonly, nonnull) NSDictionary *pluginConfigurations; -@property (nonatomic, readonly) BOOL enableLogging; +@property (nonatomic, readonly) BOOL loggingEnabled; @property (nonatomic, readonly) BOOL enableScrolling; @property (nonatomic, readonly) BOOL allowLinkPreviews; @property (nonatomic, readonly) BOOL handleApplicationNotifications; @@ -25,7 +25,7 @@ NS_SWIFT_NAME(InstanceConfiguration) @property (nonatomic, readonly, nonnull) NSDictionary *legacyConfig DEPRECATED_MSG_ATTRIBUTE("Use direct properties instead"); -- (instancetype _Nonnull)initWithDescriptor:(CAPInstanceDescriptor* _Nonnull)descriptor NS_SWIFT_NAME(init(with:)); +- (instancetype _Nonnull)initWithDescriptor:(CAPInstanceDescriptor* _Nonnull)descriptor isDebug:(BOOL)debug NS_SWIFT_NAME(init(with:isDebug:)); - (instancetype _Nonnull)updatingAppLocation:(NSURL* _Nonnull)location NS_SWIFT_NAME(updatingAppLocation(_:)); @end diff --git a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m index bce96b2145..6b84ec7cf3 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m +++ b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m @@ -8,7 +8,7 @@ - (instancetype)initWithConfiguration:(CAPInstanceConfiguration*)configuration a @implementation CAPInstanceConfiguration -- (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor { +- (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor isDebug:(BOOL)debug { if (self = [super init]) { // first, give the descriptor a chance to make itself internally consistent [descriptor normalize]; @@ -17,7 +17,17 @@ - (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor { _overridenUserAgentString = descriptor.overridenUserAgentString; _backgroundColor = descriptor.backgroundColor; _allowedNavigationHostnames = descriptor.allowedNavigationHostnames; - _enableLogging = descriptor.enableLogging; + switch (descriptor.loggingBehavior) { + case CAPInstanceLoggingBehaviorProduction: + _loggingEnabled = true; + break; + case CAPInstanceLoggingBehaviorDebug: + _loggingEnabled = debug; + break; + default: + _loggingEnabled = false; + break; + } _enableScrolling = descriptor.enableScrolling; _allowLinkPreviews = descriptor.allowLinkPreviews; _handleApplicationNotifications = descriptor.handleApplicationNotifications; @@ -49,7 +59,7 @@ - (instancetype)initWithConfiguration:(CAPInstanceConfiguration*)configuration a _localURL = [[configuration localURL] copy]; _serverURL = [[configuration serverURL] copy]; _pluginConfigurations = [[configuration pluginConfigurations] copy]; - _enableLogging = configuration.enableLogging; + _loggingEnabled = configuration.loggingEnabled; _enableScrolling = configuration.enableScrolling; _allowLinkPreviews = configuration.allowLinkPreviews; _handleApplicationNotifications = configuration.handleApplicationNotifications; diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h index 3aecbb97a3..69a9d29c9c 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h @@ -17,6 +17,12 @@ typedef NS_OPTIONS(NSUInteger, CAPInstanceWarning) { CAPInstanceWarningInvalidCordovaFile NS_SWIFT_NAME(invalidCordovaFile) = 1 << 4 } NS_SWIFT_NAME(InstanceWarning); +typedef NS_OPTIONS(NSUInteger, CAPInstanceLoggingBehavior) { + CAPInstanceLoggingBehaviorNone NS_SWIFT_NAME(none) = 1 << 0, + CAPInstanceLoggingBehaviorDebug NS_SWIFT_NAME(debug) = 1 << 1, + CAPInstanceLoggingBehaviorProduction NS_SWIFT_NAME(production) = 1 << 2, +} NS_SWIFT_NAME(InstanceLoggingBehavior); + extern NSString * _Nonnull const CAPInstanceDescriptorDefaultScheme NS_SWIFT_UNAVAILABLE("Use InstanceDescriptorDefaults"); extern NSString * _Nonnull const CAPInstanceDescriptorDefaultHostname NS_SWIFT_UNAVAILABLE("Use InstanceDescriptorDefaults"); @@ -63,10 +69,10 @@ NS_SWIFT_NAME(InstanceDescriptor) */ @property (nonatomic, retain, nonnull) NSDictionary *pluginConfigurations; /** - @brief Whether or not logging is turned on. - @discussion Set by @c hideLogs in the configuration file. + @brief The build configurations under which logging should be enabled. + @discussion Defaults to @c debug. Set by @c loggingBehavior in the configuration file but will inherit the deprecated @c hideLogs flag if @c loggingBehavior is absent. */ -@property (nonatomic, assign) BOOL enableLogging; +@property (nonatomic, assign) CAPInstanceLoggingBehavior loggingBehavior; /** @brief Whether or not the web view can scroll. @discussion Set by @c ios.scrollEnabled in the configuration file. Corresponds to @c isScrollEnabled on WKWebView. diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m index fb691e7771..85fba0dec8 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m @@ -35,7 +35,7 @@ - (void)_setDefaultsWithAppLocation:(NSURL*)location { _urlHostname = CAPInstanceDescriptorDefaultHostname; _pluginConfigurations = @{}; _legacyConfig = @{}; - _enableLogging = YES; + _loggingBehavior = CAPInstanceLoggingBehaviorDebug; _enableScrolling = YES; _allowLinkPreviews = YES; _handleApplicationNotifications = YES; diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift index 33c89bd8b8..f173664cd9 100644 --- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift @@ -5,6 +5,21 @@ public enum InstanceDescriptorDefaults { static let hostname = "localhost" } +private extension InstanceLoggingBehavior { + static func behavior(from: String) -> InstanceLoggingBehavior? { + switch from.lowercased() { + case "none": + return InstanceLoggingBehavior.none + case "debug": + return InstanceLoggingBehavior.debug + case "production": + return InstanceLoggingBehavior.production + default: + return nil + } + } +} + /** The purpose of this function is to hide the messy details of parsing the configuration(s) so the complexity is worth it. And the name starts with an underscore to match the convention of @@ -69,9 +84,6 @@ internal extension InstanceDescriptor { let color = UIColor.capacitor.color(fromHex: colorString) { backgroundColor = color } - if let hideLogs = (config[keyPath: "ios.hideLogs"] as? Bool) ?? (config[keyPath: "hideLogs"] as? Bool) { - enableLogging = !hideLogs - } if let allowNav = config[keyPath: "server.allowNavigation"] as? [String] { allowedNavigationHostnames = allowNav } @@ -96,12 +108,20 @@ internal extension InstanceDescriptor { if let allowPreviews = config[keyPath: "ios.allowsLinkPreview"] as? Bool { allowLinkPreviews = allowPreviews } - if let scrollEnabled = config[keyPath: "ios.scrollEnabled"] as? Bool { - enableScrolling = scrollEnabled + if let enabled = config[keyPath: "ios.scrollEnabled"] as? Bool { + enableScrolling = enabled } if let pluginConfig = config[keyPath: "plugins"] as? JSObject { pluginConfigurations = pluginConfig } + // `hideLogs` is deprecated so it's used as a fallback option + if let value = (config[keyPath: "ios.loggingBehavior"] as? String) ?? (config[keyPath: "loggingBehavior"] as? String) { + if let behavior = InstanceLoggingBehavior.behavior(from: value) { + loggingBehavior = behavior + } + } else if let hideLogs = (config[keyPath: "ios.hideLogs"] as? Bool) ?? (config[keyPath: "hideLogs"] as? Bool), hideLogs { + loggingBehavior = .none + } } } // swiftlint:enable cyclomatic_complexity diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index fc85b0de01..d4059819be 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -15,6 +15,16 @@ import Cordova // swiftlint:disable type_body_length internal class CapacitorBridge: NSObject, CAPBridgeProtocol { + // this decision is needed before the bridge is instantiated, + // so we need a class property to avoid duplication + internal static var isDevEnvironment: Bool { + #if DEBUG + return true + #else + return false + #endif + } + // MARK: - CAPBridgeProtocol: Properties public var webView: WKWebView? { @@ -32,11 +42,7 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { } public var isDevEnvironment: Bool { - #if DEBUG - return true - #else - return false - #endif + return CapacitorBridge.isDevEnvironment } public var userInterfaceStyle: UIUserInterfaceStyle { @@ -211,7 +217,8 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { func exportCoreJS(localUrl: String) { do { try JSExport.exportCapacitorGlobalJS(userContentController: webViewDelegationHandler.contentController, - isDebug: isDevMode(), + isDebug: isDevEnvironment, + loggingEnabled: config.loggingEnabled, localUrl: localUrl) try JSExport.exportBridgeJS(userContentController: webViewDelegationHandler.contentController) } catch { diff --git a/ios/Capacitor/Capacitor/JSExport.swift b/ios/Capacitor/Capacitor/JSExport.swift index b07c91c8e3..c9eddd3a07 100644 --- a/ios/Capacitor/Capacitor/JSExport.swift +++ b/ios/Capacitor/Capacitor/JSExport.swift @@ -15,8 +15,8 @@ internal class JSExport { static let catchallOptionsParameter = "_options" static let callbackParameter = "_callback" - static func exportCapacitorGlobalJS(userContentController: WKUserContentController, isDebug: Bool, localUrl: String) throws { - let data = "window.Capacitor = { DEBUG: \(isDebug), Plugins: {} }; window.WEBVIEW_SERVER_URL = '\(localUrl)';" + static func exportCapacitorGlobalJS(userContentController: WKUserContentController, isDebug: Bool, loggingEnabled: Bool, localUrl: String) throws { + let data = "window.Capacitor = { DEBUG: \(isDebug), isLoggingEnabled: \(loggingEnabled), Plugins: {} }; window.WEBVIEW_SERVER_URL = '\(localUrl)';" let userScript = WKUserScript(source: data, injectionTime: .atDocumentStart, forMainFrameOnly: true) userContentController.addUserScript(userScript) } diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index 6f4c809bbc..523b552494 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -369,7 +369,7 @@ const nativeBridge = (function (exports) { methodName: methodName, options: options || {}, }; - if (cap.DEBUG && pluginName !== 'Console') { + if (cap.isLoggingEnabled && pluginName !== 'Console') { cap.logToNative(callData); } // post the call data to native @@ -390,7 +390,7 @@ const nativeBridge = (function (exports) { */ cap.fromNative = result => { var _a, _b; - if (cap.DEBUG && result.pluginId !== 'Console') { + if (cap.isLoggingEnabled && result.pluginId !== 'Console') { cap.logFromNative(result); } // get the stored call, if it exists diff --git a/ios/Capacitor/CapacitorTests/CapacitorTests.swift b/ios/Capacitor/CapacitorTests/CapacitorTests.swift index 1dd83d8793..00131f4390 100644 --- a/ios/Capacitor/CapacitorTests/CapacitorTests.swift +++ b/ios/Capacitor/CapacitorTests/CapacitorTests.swift @@ -23,7 +23,7 @@ class CapacitorTests: XCTestCase { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. let descriptor = InstanceDescriptor.init() - bridge = MockBridge(with: InstanceConfiguration(with: descriptor), delegate: MockBridgeViewController(), cordovaConfiguration: descriptor.cordovaConfiguration, assetHandler: MockAssetHandler(), delegationHandler: MockDelegationHandler()) + bridge = MockBridge(with: InstanceConfiguration(with: descriptor, isDebug: true), delegate: MockBridgeViewController(), cordovaConfiguration: descriptor.cordovaConfiguration, assetHandler: MockAssetHandler(), delegationHandler: MockDelegationHandler()) } override func tearDown() { diff --git a/ios/Capacitor/CapacitorTests/ConfigurationTests.swift b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift index 5d7587dc9a..6b46fedd2b 100644 --- a/ios/Capacitor/CapacitorTests/ConfigurationTests.swift +++ b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift @@ -8,6 +8,7 @@ class ConfigurationTests: XCTestCase { case nested = "hierarchy" case server = "server" case invalid = "bad" + case deprecated = "hidinglogs" case nonparsable = "nonjson" } static var files: [ConfigFile: URL] = [:] @@ -56,18 +57,30 @@ class ConfigurationTests: XCTestCase { XCTAssertEqual(descriptor.urlHostname, "localhost") XCTAssertNil(descriptor.serverURL) XCTAssertTrue(descriptor.enableScrolling) - XCTAssertTrue(descriptor.enableLogging) + XCTAssertEqual(descriptor.loggingBehavior, .debug) XCTAssertTrue(descriptor.allowLinkPreviews) XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .never) } + func testDeprecatedParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.deprecated], cordovaConfiguration: nil) + XCTAssertEqual(descriptor.loggingBehavior, .none) + } + + func testDeprecatedOverrideParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.server], cordovaConfiguration: nil) + XCTAssertEqual(descriptor.loggingBehavior, .production) + } + func testTopLevelParsing() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.flat], cordovaConfiguration: nil) XCTAssertEqual(descriptor.backgroundColor, UIColor(red: 1, green: 1, blue: 1, alpha: 1)) XCTAssertEqual(descriptor.overridenUserAgentString, "level 1 override") XCTAssertEqual(descriptor.appendedUserAgentString, "level 1 append") - XCTAssertFalse(descriptor.enableLogging) + XCTAssertEqual(descriptor.loggingBehavior, .debug) } func testNestedParsing() throws { @@ -76,7 +89,7 @@ class ConfigurationTests: XCTestCase { XCTAssertEqual(descriptor.backgroundColor, UIColor(red: 0, green: 0, blue: 0, alpha: 1)) XCTAssertEqual(descriptor.overridenUserAgentString, "level 2 override") XCTAssertEqual(descriptor.appendedUserAgentString, "level 2 append") - XCTAssertTrue(descriptor.enableLogging) + XCTAssertEqual(descriptor.loggingBehavior, .none) XCTAssertFalse(descriptor.enableScrolling) XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .scrollableAxes) } @@ -93,21 +106,21 @@ class ConfigurationTests: XCTestCase { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.invalid], cordovaConfiguration: nil) XCTAssertNil(descriptor.backgroundColor) - XCTAssertTrue(descriptor.enableLogging) + XCTAssertEqual(descriptor.loggingBehavior, .debug) XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .never) } func testBadDataTransformation() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.invalid], cordovaConfiguration: nil) - let configuration = InstanceConfiguration(with: descriptor) + let configuration = InstanceConfiguration(with: descriptor, isDebug: true) XCTAssertEqual(configuration.serverURL, URL(string: "capacitor://myhost"), "Invalid server.url and invalid ioScheme were not ignored") } func testServerTransformation() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.server], cordovaConfiguration: nil) - let configuration = InstanceConfiguration(with: descriptor) + let configuration = InstanceConfiguration(with: descriptor, isDebug: true) XCTAssertEqual(configuration.serverURL, URL(string: "http://192.168.100.1:2057")) XCTAssertEqual(configuration.localURL, URL(string: "override://myhost")) } @@ -115,7 +128,7 @@ class ConfigurationTests: XCTestCase { func testPluginConfig() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.flat], cordovaConfiguration: nil) - let configuration = InstanceConfiguration(with: descriptor) + let configuration = InstanceConfiguration(with: descriptor, isDebug: true) let value = configuration.getPluginConfigValue("SplashScreen", "launchShowDuration") as? Int XCTAssertNotNil(value) XCTAssertTrue(value == 1) @@ -124,7 +137,7 @@ class ConfigurationTests: XCTestCase { func testLegacyConfig() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.nested], cordovaConfiguration: nil) - let configuration = InstanceConfiguration(with: descriptor) + let configuration = InstanceConfiguration(with: descriptor, isDebug: true) var value = configuration.getValue("overrideUserAgent") as? String XCTAssertEqual(value, "level 1 override") value = configuration.getValue("ios.overrideUserAgent") as? String @@ -134,7 +147,7 @@ class ConfigurationTests: XCTestCase { func testNavigationRules() throws { let url = Bundle.main.url(forResource: "configurations", withExtension: "")! let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.server], cordovaConfiguration: nil) - let configuration = InstanceConfiguration(with: descriptor) + let configuration = InstanceConfiguration(with: descriptor, isDebug: true) XCTAssertTrue(configuration.shouldAllowNavigation(to: "ionic.io")) XCTAssertTrue(configuration.shouldAllowNavigation(to: "ionic.io".uppercased())) XCTAssertTrue(configuration.shouldAllowNavigation(to: "test.capacitorjs.com")) @@ -144,4 +157,34 @@ class ConfigurationTests: XCTestCase { XCTAssertFalse(configuration.shouldAllowNavigation(to: "192.168.0.2")) XCTAssertFalse(configuration.shouldAllowNavigation(to: "ionicframework.com")) } + + func testNoLoggingTransformation() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: nil, cordovaConfiguration: nil) + descriptor.loggingBehavior = .none + var configuration = InstanceConfiguration(with: descriptor, isDebug: false) + XCTAssertFalse(configuration.loggingEnabled) + configuration = InstanceConfiguration(with: descriptor, isDebug: true) + XCTAssertFalse(configuration.loggingEnabled) + } + + func testDebugLoggingTransformation() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: nil, cordovaConfiguration: nil) + descriptor.loggingBehavior = .debug + var configuration = InstanceConfiguration(with: descriptor, isDebug: false) + XCTAssertFalse(configuration.loggingEnabled) + configuration = InstanceConfiguration(with: descriptor, isDebug: true) + XCTAssertTrue(configuration.loggingEnabled) + } + + func testProductionLoggingTransformation() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: nil, cordovaConfiguration: nil) + descriptor.loggingBehavior = .production + var configuration = InstanceConfiguration(with: descriptor, isDebug: false) + XCTAssertTrue(configuration.loggingEnabled) + configuration = InstanceConfiguration(with: descriptor, isDebug: true) + XCTAssertTrue(configuration.loggingEnabled) + } } diff --git a/ios/Capacitor/TestsHostApp/configurations/bad.json b/ios/Capacitor/TestsHostApp/configurations/bad.json index 609d3f0643..569a6c6016 100644 --- a/ios/Capacitor/TestsHostApp/configurations/bad.json +++ b/ios/Capacitor/TestsHostApp/configurations/bad.json @@ -7,7 +7,7 @@ "overrideUserAgent": "level 1 override", "appendUserAgent": "level 1 append", "backgroundColor": "invalid string", - "hideLogs": "yep", + "loggingBehavior": "foo", "ios": { "allowsLinkPreview": false, "scrollEnabled": false, diff --git a/ios/Capacitor/TestsHostApp/configurations/flat.json b/ios/Capacitor/TestsHostApp/configurations/flat.json index 08d8b87b9f..d4dd298287 100644 --- a/ios/Capacitor/TestsHostApp/configurations/flat.json +++ b/ios/Capacitor/TestsHostApp/configurations/flat.json @@ -7,7 +7,7 @@ "overrideUserAgent": "level 1 override", "appendUserAgent": "level 1 append", "backgroundColor": "#ffffff", - "hideLogs": true, + "loggingBehavior": "debug", "plugins": { "SplashScreen": { "launchShowDuration": 1 diff --git a/ios/Capacitor/TestsHostApp/configurations/hidinglogs.json b/ios/Capacitor/TestsHostApp/configurations/hidinglogs.json new file mode 100644 index 0000000000..08d8b87b9f --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/hidinglogs.json @@ -0,0 +1,17 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "plugins": { + "SplashScreen": { + "launchShowDuration": 1 + } + }, + "cordova": {} +} diff --git a/ios/Capacitor/TestsHostApp/configurations/hierarchy.json b/ios/Capacitor/TestsHostApp/configurations/hierarchy.json index 8c68a25702..8b1d5f5682 100644 --- a/ios/Capacitor/TestsHostApp/configurations/hierarchy.json +++ b/ios/Capacitor/TestsHostApp/configurations/hierarchy.json @@ -7,7 +7,7 @@ "overrideUserAgent": "level 1 override", "appendUserAgent": "level 1 append", "backgroundColor": "#ffffff", - "hideLogs": true, + "loggingBehavior": "none", "ios": { "overrideUserAgent": "level 2 override", "appendUserAgent": "level 2 append", diff --git a/ios/Capacitor/TestsHostApp/configurations/server.json b/ios/Capacitor/TestsHostApp/configurations/server.json index fea40e302e..953db184e7 100644 --- a/ios/Capacitor/TestsHostApp/configurations/server.json +++ b/ios/Capacitor/TestsHostApp/configurations/server.json @@ -8,6 +8,7 @@ "appendUserAgent": "level 1 append", "backgroundColor": "#ffffff", "hideLogs": true, + "loggingBehavior": "production", "server": { "iosScheme": "override", "allowNavigation": ["*.capacitorjs.com", "ionic.io", "192.168.0.1", "subdomain.*.ionicframework.com"],