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";