diff --git a/android/build.gradle b/android/build.gradle index df5b447..03427ea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -75,10 +75,16 @@ android { dependencies { implementation 'androidx.multidex:multidex:2.0.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.25' + + //"logback-android" required for programmatic control of global sl4j log level. + // - default log configuration in /assets/logback.xml + // - [ref] https://github.com/tony19/logback-android + implementation 'com.github.tony19:logback-android:3.0.0' + implementation 'org.slf4j:slf4j-api:2.0.7' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' } diff --git a/android/src/main/assets/logback.xml b/android/src/main/assets/logback.xml new file mode 100644 index 0000000..8e6e0d6 --- /dev/null +++ b/android/src/main/assets/logback.xml @@ -0,0 +1,18 @@ + + + + Optimizely + + + %msg + + + + + + + diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java index 8d0ca28..82e3384 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java @@ -97,6 +97,8 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N maxQueueSize = argumentsParser.getEventMaxQueueSize(); } + Utils.setDefaultLogLevel(argumentsParser.getDefaultLogLevel()); + DefaultEventHandler eventHandler = DefaultEventHandler.getInstance(context); eventHandler.setDispatchInterval(-1L); NotificationCenter notificationCenter = new NotificationCenter(); diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java index 605b0aa..88087c7 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java @@ -72,6 +72,10 @@ public List getDecideOptions() { return Utils.getDecideOptions((List) arguments.get(Constants.RequestParameterKey.DECIDE_OPTIONS)); } + public String getDefaultLogLevel() { + return (String) arguments.get(Constants.RequestParameterKey.DEFAULT_LOG_LEVEL); + } + public String getFlagKey() { return (String) arguments.get(Constants.RequestParameterKey.FLAG_KEY); } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java index 2f200db..453b1d6 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java @@ -67,6 +67,7 @@ public static class RequestParameterKey { public static final String ATTRIBUTES = "attributes"; public static final String DECIDE_KEYS = "keys"; public static final String DECIDE_OPTIONS = "optimizelyDecideOption"; + public static final String DEFAULT_LOG_LEVEL = "defaultLogLevel"; public static final String EVENT_BATCH_SIZE = "eventBatchSize"; public static final String EVENT_TIME_INTERVAL = "eventTimeInterval"; public static final String EVENT_MAX_QUEUE_SIZE = "eventMaxQueueSize"; @@ -153,4 +154,11 @@ public static class SegmentOption { public static final String IGNORE_CACHE = "ignoreCache"; public static final String RESET_CACHE = "resetCache"; } + + public static class LogLevel { + public static final String ERROR = "error"; + public static final String WARNING = "warning"; + public static final String INFO = "info"; + public static final String DEBUG = "debug"; + } } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java index 76ec8c8..a4353b5 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import androidx.annotation.Nullable; import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.SOURCE_INFO; @@ -31,6 +32,9 @@ import com.optimizely.ab.notification.UpdateConfigNotification; import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; +import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; public class Utils { @@ -104,4 +108,33 @@ public static Class getNotificationListenerType(String notificationType) { } return listenerClass; } + + // SLF4J log level control: + // - logback logger (ch.qos.logback) is the only option available that supports global log level control programmatically (not only via configuration file) + // - "logback-android" logger (com.github.tony19:logback-android) is integrated in build.gradle. + // - log-level control is not integrated into the native android-sdk core since this solution depends on logback logger. + + public static void setDefaultLogLevel(@Nullable String logLevel) { + Level defaultLogLevel = Utils.mapLogLevel(logLevel); + Logger rootLogger = (Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(defaultLogLevel); + } + + public static Level mapLogLevel(@Nullable String logLevel) { + Level level = Level.INFO; + + if (logLevel == null || logLevel.isEmpty()) { + return level; + } + + switch (logLevel) { + case Constants.LogLevel.ERROR: level = Level.ERROR; break; + case Constants.LogLevel.WARNING: level = Level.WARN; break; + case Constants.LogLevel.INFO: level = Level.INFO; break; + case Constants.LogLevel.DEBUG: level = Level.DEBUG; break; + default: {} + } + return level; + } + } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ceec9a6..89fc349 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 32 ndkVersion flutter.ndkVersion compileOptions { @@ -40,7 +40,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 21 - targetSdkVersion flutter.targetSdkVersion + targetSdkVersion 32 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/ios/Podfile b/example/ios/Podfile index 9411102..313ea4a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/lib/main.dart b/example/lib/main.dart index a717223..e7db8fa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -32,6 +32,7 @@ class _MyAppState extends State { datafilePeriodicDownloadInterval: 10 * 60, eventOptions: const EventOptions( batchSize: 1, timeInterval: 60, maxQueueSize: 10000), + defaultLogLevel: OptimizelyLogLevel.debug, defaultDecideOptions: defaultOptions); var response = await flutterSDK.initializeClient(); diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 154f82d..9f89128 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 90B9B075FD9D5B075E83BE96 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -52,9 +53,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 019D04A8C81D7599D2E638FA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* optimizely_flutter_sdk_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "optimizely_flutter_sdk_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* optimizely_flutter_sdk_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = optimizely_flutter_sdk_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,7 +68,10 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7EEA78DE428A25C4C9195EC6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 8D05B77804EBF39FD6D6D079 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -75,6 +80,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 90B9B075FD9D5B075E83BE96 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,6 +105,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + F74646C1D5F338EF32BE10E1 /* Pods */, ); sourceTree = ""; }; @@ -148,10 +155,22 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; + F74646C1D5F338EF32BE10E1 /* Pods */ = { + isa = PBXGroup; + children = ( + 7EEA78DE428A25C4C9195EC6 /* Pods-Runner.debug.xcconfig */, + 8D05B77804EBF39FD6D6D079 /* Pods-Runner.release.xcconfig */, + 019D04A8C81D7599D2E638FA /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -159,11 +178,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 834CC0346DE4E8E368DFDFD4 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + F96AA93D7120056FD027BD50 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -270,6 +291,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 834CC0346DE4E8E368DFDFD4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F96AA93D7120056FD027BD50 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index c674389..e5be618 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -90,6 +90,7 @@ struct RequestParameterKey { static let eventTags = "eventTags" static let reasons = "reasons" static let decideOptions = "optimizelyDecideOption" + static let defaultLogLevel = "defaultLogLevel" static let eventBatchSize = "eventBatchSize" static let eventTimeInterval = "eventTimeInterval" static let eventMaxQueueSize = "eventMaxQueueSize" diff --git a/ios/Classes/HelperClasses/Utils.swift b/ios/Classes/HelperClasses/Utils.swift index 82f858a..6cac144 100644 --- a/ios/Classes/HelperClasses/Utils.swift +++ b/ios/Classes/HelperClasses/Utils.swift @@ -202,4 +202,17 @@ public class Utils: NSObject { return nil } } + + static func getDefaultLogLevel(_ logLevel: String) -> OptimizelyLogLevel { + var defaultLogLevel: OptimizelyLogLevel + switch logLevel { + case "error": defaultLogLevel = OptimizelyLogLevel.error + case "warning": defaultLogLevel = OptimizelyLogLevel.warning + case "info": defaultLogLevel = OptimizelyLogLevel.info + case "debug": defaultLogLevel = OptimizelyLogLevel.debug + default: defaultLogLevel = OptimizelyLogLevel.info + } + return defaultLogLevel; + } + } diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index cdc1549..287b056 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -106,7 +106,12 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { decideOptions = options } let defaultDecideOptions = Utils.getDecideOptions(options: decideOptions) - + + var defaultLogLevel = OptimizelyLogLevel.info + if let logLevel = parameters[RequestParameterKey.defaultLogLevel] as? String { + defaultLogLevel = Utils.getDefaultLogLevel(logLevel) + } + // SDK Settings Default Values var segmentsCacheSize: Int = 100 var segmentsCacheTimeoutInSecs: Int = 600 @@ -152,7 +157,14 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { optimizelyClientsTracker.removeValue(forKey: sdkKey) // Creating new instance - let optimizelyInstance = OptimizelyClient(sdkKey:sdkKey, eventDispatcher: eventDispatcher, datafileHandler: datafileHandler, periodicDownloadInterval: datafilePeriodicDownloadInterval, defaultDecideOptions: defaultDecideOptions, settings: optimizelySdkSettings) + let optimizelyInstance = OptimizelyClient( + sdkKey:sdkKey, + eventDispatcher: eventDispatcher, + datafileHandler: datafileHandler, + periodicDownloadInterval: datafilePeriodicDownloadInterval, + defaultLogLevel: defaultLogLevel, + defaultDecideOptions: defaultDecideOptions, + settings: optimizelySdkSettings) optimizelyInstance.start{ [weak self] res in switch res { diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index 775130f..51dc9af 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -27,6 +27,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.d import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'; import 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; export 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart' show ClientPlatform, ListenerType; @@ -50,6 +51,8 @@ export 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart' show SDKSettings; export 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart' show DatafileHostOptions; +export 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart' + show OptimizelyLogLevel; /// The main client class for the Optimizely Flutter SDK. /// @@ -63,6 +66,7 @@ class OptimizelyFlutterSdk { final int _datafilePeriodicDownloadInterval; final Map _datafileHostOptions; final Set _defaultDecideOptions; + final OptimizelyLogLevel _defaultLogLevel; final SDKSettings _sdkSettings; OptimizelyFlutterSdk(this._sdkKey, {EventOptions eventOptions = const EventOptions(), @@ -70,11 +74,13 @@ class OptimizelyFlutterSdk { 10 * 60, // Default time interval in seconds Map datafileHostOptions = const {}, Set defaultDecideOptions = const {}, + OptimizelyLogLevel defaultLogLevel = OptimizelyLogLevel.info, SDKSettings sdkSettings = const SDKSettings()}) : _eventOptions = eventOptions, _datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval, _datafileHostOptions = datafileHostOptions, _defaultDecideOptions = defaultDecideOptions, + _defaultLogLevel = defaultLogLevel, _sdkSettings = sdkSettings; /// Starts Optimizely SDK (Synchronous) with provided sdkKey. @@ -85,6 +91,7 @@ class OptimizelyFlutterSdk { _datafilePeriodicDownloadInterval, _datafileHostOptions, _defaultDecideOptions, + _defaultLogLevel, _sdkSettings); } diff --git a/lib/src/data_objects/log_level.dart b/lib/src/data_objects/log_level.dart new file mode 100644 index 0000000..c2eacd3 --- /dev/null +++ b/lib/src/data_objects/log_level.dart @@ -0,0 +1,22 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +enum OptimizelyLogLevel { + error, + warning, + info, + debug +} diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index e26b74f..bb2c4c0 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -27,6 +27,8 @@ import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_respon import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; +import 'data_objects/log_level.dart'; + enum ListenerType { activate, track, decision, logEvent, projectConfigUpdate } enum ClientPlatform { iOS, android } @@ -61,14 +63,17 @@ class OptimizelyClientWrapper { int datafilePeriodicDownloadInterval, Map datafileHostOptions, Set defaultDecideOptions, + OptimizelyLogLevel defaultLogLevel, SDKSettings sdkSettings) async { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); + final convertedLogLevel = Utils.convertLogLevel(defaultLogLevel); Map requestDict = { Constants.sdkKey: sdkKey, Constants.datafilePeriodicDownloadInterval: datafilePeriodicDownloadInterval, Constants.optimizelyDecideOption: convertedOptions, + Constants.defaultLogLevel: convertedLogLevel, Constants.eventBatchSize: eventOptions.batchSize, Constants.eventTimeInterval: eventOptions.timeInterval, Constants.eventMaxQueueSize: eventOptions.maxQueueSize, diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index f6a61d5..d5bc3ff 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -83,6 +83,7 @@ class Constants { static const String optimizelyDecideOption = "optimizelyDecideOption"; static const String optimizelySegmentOption = "optimizelySegmentOption"; static const String optimizelySdkSettings = "optimizelySdkSettings"; + static const String defaultLogLevel = "defaultLogLevel"; static const String payload = "payload"; static const String value = "value"; static const String type = "type"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index da36841..782178e 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -18,6 +18,7 @@ import 'dart:io' show Platform; import 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; class Utils { static Map decideOptions = { @@ -94,4 +95,10 @@ class Utils { Set options) { return options.map((option) => Utils.segmentOptions[option]!).toList(); } + + static String convertLogLevel(OptimizelyLogLevel logLevel) { + // OptimizelyLogLevel.error -> "error" + // OptimizelyLogLevel.debug -> "debug" + return logLevel.toString().split('.').last; + } } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index fa81adb..debf769 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -56,6 +56,7 @@ void main() { DatafileHostOptions datafileHostOptions = const DatafileHostOptions("", ""); SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; + String defaultLogLevel = "error"; const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); dynamic mockOptimizelyConfig; @@ -83,6 +84,9 @@ void main() { case Constants.initializeMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], isNull); + + defaultLogLevel = methodCall.arguments[Constants.defaultLogLevel]; + // To Check if eventOptions were received eventOptions = EventOptions( batchSize: methodCall.arguments[Constants.eventBatchSize], @@ -542,6 +546,33 @@ void main() { }); }); + group("log level configuration", () { + test("with no defaultLogLevel, log level should be info level", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + + var response = await sdk.initializeClient(); + + expect(response.success, isTrue); + expect(defaultLogLevel, equals("info")); + }); + + test("with a valid defaultLogLevel parameter", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey, defaultLogLevel: OptimizelyLogLevel.debug); + + var response = await sdk.initializeClient(); + + expect(response.success, isTrue); + expect(defaultLogLevel, equals("debug")); + }); + + test("should convert OptimizelyLogLevel to string", () async { + expect(Utils.convertLogLevel(OptimizelyLogLevel.error), "error"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.warning), "warning"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.info), "info"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.debug), "debug"); + }); + }); + group("close()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey);