From 9dfbcec9981cd0e7eadc10e04892afe0fcc5fc29 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:19:03 +0600 Subject: [PATCH] [FSSDK-11985] feat: add android logger support (#90) * chore: add new classes for logging - Add FlutterLogbackAppender.java for logging events in the Flutter app - Integrate OptimizelyFlutterSdkPlugin with FlutterLogbackAppender - Update Constants.swift, SwiftOptimizelyFlutterSdkPlugin.swift, and related classes * refactor: update log level conversion logic - Adjust method to correctly convert log level strings to integers - Refactor switch statement for better readability and maintainability * refactor: optimize log level conversion - Remove unnecessary default log level value initialization - Refactor switch statement to directly return log levels for each case - Simplify comparison for warning log levels to include both "WARN" and "WARNING" * style: update logback configuration and log levels - Comment out configuration block in logback.xml - Modify log level 'WARNING' to 'WARN' in FlutterLogbackAppender.java * fix: ensure debugging logs are only printed in debug mode - Add check for kDebugMode to control printing of logs * chore: update logback.xml - Comment out old logback configuration - Add note explaining the use of FlutterLogbackAppender for logging --- android/src/main/assets/logback.xml | 19 +----- .../FlutterLogbackAppender.java | 62 +++++++++++++++++++ .../OptimizelyFlutterSdkPlugin.java | 30 +++++++++ ios/Classes/HelperClasses/Constants.swift | 1 - .../SwiftOptimizelyFlutterSdkPlugin.swift | 13 ++-- lib/optimizely_flutter_sdk.dart | 12 +--- lib/src/logger/flutter_logger.dart | 5 +- lib/src/optimizely_client_wrapper.dart | 1 - lib/src/utils/constants.dart | 1 - 9 files changed, 103 insertions(+), 41 deletions(-) create mode 100644 android/src/main/java/com/optimizely/optimizely_flutter_sdk/FlutterLogbackAppender.java diff --git a/android/src/main/assets/logback.xml b/android/src/main/assets/logback.xml index 8e6e0d6..7f531a8 100644 --- a/android/src/main/assets/logback.xml +++ b/android/src/main/assets/logback.xml @@ -1,18 +1 @@ - - - - Optimizely - - - %msg - - - - - - - + diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/FlutterLogbackAppender.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/FlutterLogbackAppender.java new file mode 100644 index 0000000..2252cdd --- /dev/null +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/FlutterLogbackAppender.java @@ -0,0 +1,62 @@ +package com.optimizely.optimizely_flutter_sdk; +import com.optimizely.optimizely_flutter_sdk.helper_classes.Constants; + +import android.os.Handler; +import android.os.Looper; + +import java.util.HashMap; +import java.util.Map; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import io.flutter.plugin.common.MethodChannel; + +public class FlutterLogbackAppender extends AppenderBase { + + public static final String CHANNEL_NAME = "optimizely_flutter_sdk_logger"; + public static MethodChannel channel; + private static final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + + public static void setChannel(MethodChannel channel) { + FlutterLogbackAppender.channel = channel; + } + + @Override + protected void append(ILoggingEvent event) { + if (channel == null) { + return; + } + + String message = event.getFormattedMessage(); + String level = event.getLevel().toString(); + int logLevel = convertLogLevel(level); + Map logData = new HashMap<>(); + logData.put("level", logLevel); + logData.put("message", message); + + mainThreadHandler.post(() -> { + if (channel != null) { + channel.invokeMethod("log", logData); + } + }); + } + + int convertLogLevel(String logLevel) { + if (logLevel == null || logLevel.isEmpty()) { + return 3; + } + + switch (logLevel.toUpperCase()) { + case "ERROR": + return 1; + case "WARN": + return 2; + case "INFO": + return 3; + case "DEBUG": + return 4; + default: + return 3; + } + } +} diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java index 89f787c..5ca2d8e 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java @@ -32,10 +32,19 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; + + /** OptimizelyFlutterSdkPlugin */ public class OptimizelyFlutterSdkPlugin extends OptimizelyFlutterClient implements FlutterPlugin, ActivityAware, MethodCallHandler { public static MethodChannel channel; + private Appender flutterLogbackAppender; @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { @@ -157,11 +166,32 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { channel = new MethodChannel(binding.getBinaryMessenger(), "optimizely_flutter_sdk"); channel.setMethodCallHandler(this); context = binding.getApplicationContext(); + + MethodChannel loggerChannel = new MethodChannel(binding.getBinaryMessenger(), FlutterLogbackAppender.CHANNEL_NAME); + FlutterLogbackAppender.setChannel(loggerChannel); + + // Add appender to logback + flutterLogbackAppender = new FlutterLogbackAppender(); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + flutterLogbackAppender.setContext(lc); + flutterLogbackAppender.start(); + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(ch.qos.logback.classic.Level.ALL); + rootLogger.addAppender(flutterLogbackAppender); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { channel.setMethodCallHandler(null); + // Stop and detach the appender + if (flutterLogbackAppender != null) { + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.detachAppender(flutterLogbackAppender); + flutterLogbackAppender.stop(); + flutterLogbackAppender = null; + } + // Clean up the channel + FlutterLogbackAppender.setChannel(null); } @Override diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index 1b0cbac..a29370a 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -91,7 +91,6 @@ struct RequestParameterKey { static let reasons = "reasons" static let decideOptions = "optimizelyDecideOption" static let defaultLogLevel = "defaultLogLevel" - static let useCustomLogger = "useCustomLogger" static let eventBatchSize = "eventBatchSize" static let eventTimeInterval = "eventTimeInterval" static let eventMaxQueueSize = "eventMaxQueueSize" diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index 75ba8b9..be81576 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -172,15 +172,10 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { notificationIdsTracker.removeValue(forKey: sdkKey) optimizelyClientsTracker.removeValue(forKey: sdkKey) - // Check if custom logger is requested - var logger: OPTLogger? - if let useCustomLogger = parameters[RequestParameterKey.useCustomLogger] as? Bool, useCustomLogger { - // OptimizelyFlutterLogger bridges iOS logs to Flutter via Method Channel - // When useCustomLogger = true: - // iOS SDK log → OptimizelyFlutterLogger → Flutter Method Channel → Flutter console - logger = OptimizelyFlutterLogger() - } - + // OptimizelyFlutterLogger bridges iOS logs to Flutter via Method Channel + // iOS SDK log → OptimizelyFlutterLogger → Flutter Method Channel → Flutter console + var logger: OPTLogger = OptimizelyFlutterLogger() + // Creating new instance let optimizelyInstance = OptimizelyClient( sdkKey:sdkKey, diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index a1ff583..cc7d11d 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -73,11 +73,6 @@ class OptimizelyFlutterSdk { final OptimizelyLogLevel _defaultLogLevel; final SDKSettings _sdkSettings; static OptimizelyLogger? _customLogger; - /// Set a custom logger for the SDK - static void setLogger(OptimizelyLogger logger) { - _customLogger = logger; - LoggerBridge.initialize(logger); - } /// Get the current logger static OptimizelyLogger? get logger { return _customLogger; @@ -97,11 +92,8 @@ class OptimizelyFlutterSdk { _defaultLogLevel = defaultLogLevel, _sdkSettings = sdkSettings { // Set the logger if provided - if (logger != null) { - setLogger(logger); - } else { - logWarning("Logger not provided."); - } + _customLogger = logger ?? DefaultOptimizelyLogger(); + LoggerBridge.initialize(_customLogger); } /// Starts Optimizely SDK (Synchronous) with provided sdkKey. diff --git a/lib/src/logger/flutter_logger.dart b/lib/src/logger/flutter_logger.dart index ad3ec67..f561ed9 100644 --- a/lib/src/logger/flutter_logger.dart +++ b/lib/src/logger/flutter_logger.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; abstract class OptimizelyLogger { @@ -8,7 +9,9 @@ abstract class OptimizelyLogger { class DefaultOptimizelyLogger implements OptimizelyLogger { @override void log(OptimizelyLogLevel level, String message) { - print('[OPTIMIZELY] [${level.name.toUpperCase()}]: $message'); + if (kDebugMode) { + print('[OPTIMIZELY] [${level.name.toUpperCase()}]: $message'); + } } } diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index 8caa22c..a7c092d 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -80,7 +80,6 @@ class OptimizelyClientWrapper { Constants.eventBatchSize: eventOptions.batchSize, Constants.eventTimeInterval: eventOptions.timeInterval, Constants.eventMaxQueueSize: eventOptions.maxQueueSize, - Constants.useCustomLogger: logger != null, }; // Odp Request params diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index 8a1586e..2bb5421 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -86,7 +86,6 @@ class Constants { static const String optimizelyDecideOption = "optimizelyDecideOption"; static const String optimizelySegmentOption = "optimizelySegmentOption"; static const String optimizelySdkSettings = "optimizelySdkSettings"; - static const String useCustomLogger = 'useCustomLogger'; static const String defaultLogLevel = "defaultLogLevel"; static const String payload = "payload"; static const String value = "value";