Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
be95ef5
build: add Optimizely SDK logger classes
muzahidul-opti Sep 19, 2025
9b4c6fe
fix: resolve logging inconsistencies
muzahidul-opti Sep 19, 2025
2f88840
refactor: update logger imports
muzahidul-opti Sep 19, 2025
6336b0c
feat: add custom logger implementation
muzahidul-opti Sep 19, 2025
40cfdaa
refactor: rename logger classes in Android and iOS
muzahidul-opti Sep 19, 2025
8b401ca
feat: update logging behavior for Optimizely SDK
muzahidul-opti Sep 23, 2025
bde2659
feat: add methods and tests for logger state management
muzahidul-opti Sep 24, 2025
6993bfc
feat: add separate logger channel for outgoing log calls
muzahidul-opti Sep 24, 2025
9b26c39
refactor: improve main thread dispatch for Flutter method channel calls
muzahidul-opti Sep 24, 2025
a0a9ca9
chore: clean up logger implementation
muzahidul-opti Sep 24, 2025
8edc431
style: update comment in sendLogToFlutter method
muzahidul-opti Sep 24, 2025
49ed2dc
chore: remove unused import statement
muzahidul-opti Sep 25, 2025
7ae86de
chore: update log messages and method channel handling
muzahidul-opti Sep 25, 2025
e6404aa
refactor: enhance logging functionalities
muzahidul-opti Sep 25, 2025
c2f1507
test: add global logging functions test cases
muzahidul-opti Sep 25, 2025
4b54235
Merge branch 'master' into muzahid/custom-looger
muzahidul-opti Oct 9, 2025
83a4cc3
chore: update Android configuration and logging setup
muzahidul-opti Oct 27, 2025
ed10620
chore: update android build.gradle
muzahidul-opti Oct 27, 2025
55bfb25
refactor: remove logging statements
muzahidul-opti Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ dependencies {
implementation 'com.github.tony19:logback-android:3.0.0'
implementation 'org.slf4j:slf4j-api:2.0.7'

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.0"
implementation "com.optimizely.ab:android-sdk:5.0.1"
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
implementation ('com.google.guava:guava:19.0') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.optimizely.optimizely_flutter_sdk;

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<ILoggingEvent> {

public static final String CHANNEL_NAME = "optimizely_flutter_sdk/logs";
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();
int level = event.getLevel().toInt();

Map<String, Object> logData = new HashMap<>();
logData.put("level", level);
logData.put("message", message);

mainThreadHandler.post(() -> {
if (channel != null) {
channel.invokeMethod("log", logData);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import com.optimizely.ab.UnknownEventTypeException;
import com.optimizely.ab.android.event_handler.DefaultEventHandler;
import com.optimizely.ab.android.sdk.OptimizelyClient;

import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -187,6 +187,15 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N
if (enableVuid) {
optimizelyManagerBuilder.withVuidEnabled();
}

// Check if custom logger is requested
// Boolean useCustomLogger = argumentsParser.getCustomLogger();
// Logger customLogger = null;
// if (useCustomLogger != null && useCustomLogger) {
// customLogger = new OptimizelyFlutterLogger("OptimizelySDK");
// optimizelyManagerBuilder.withLogger(customLogger);
// }

OptimizelyManager optimizelyManager = optimizelyManagerBuilder.build(context);

optimizelyManager.initialize(context, null, (OptimizelyClient client) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import androidx.annotation.NonNull;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
Expand All @@ -26,16 +27,22 @@

import com.optimizely.optimizely_flutter_sdk.helper_classes.ArgumentsParser;

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;

import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.*;

import java.util.Map;

import io.flutter.embedding.engine.plugins.activity.ActivityAware;

/** OptimizelyFlutterSdkPlugin */
public class OptimizelyFlutterSdkPlugin extends OptimizelyFlutterClient implements FlutterPlugin, ActivityAware, MethodCallHandler {

public static MethodChannel channel;
private Appender<ILoggingEvent> flutterLogbackAppender;

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
Expand Down Expand Up @@ -156,12 +163,35 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
channel = new MethodChannel(binding.getBinaryMessenger(), "optimizely_flutter_sdk");
channel.setMethodCallHandler(this);

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.addAppender(flutterLogbackAppender);

context = binding.getApplicationContext();
}

@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public String getDefaultLogLevel() {
return (String) arguments.get(Constants.RequestParameterKey.DEFAULT_LOG_LEVEL);
}

public Boolean getCustomLogger() {
return (Boolean) arguments.get(Constants.RequestParameterKey.CUSTOM_LOGGER);
}

public String getFlagKey() {
return (String) arguments.get(Constants.RequestParameterKey.FLAG_KEY);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static class RequestParameterKey {
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 CUSTOM_LOGGER = "customLogger";
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";
Expand Down
11 changes: 11 additions & 0 deletions example/lib/custom_logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart';
import 'package:flutter/foundation.dart';

class CustomLogger implements OptimizelyLogger {
@override
void log(OptimizelyLogLevel level, String message) {
if (kDebugMode) {
print('[OPTIMIZELY] ${level.name.toUpperCase()}: $message');
}
}
}
9 changes: 7 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math';
import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart';
import 'package:optimizely_flutter_sdk_example/custom_logger.dart';

void main() {
runApp(const MyApp());
Expand All @@ -28,16 +29,20 @@ class _MyAppState extends State<MyApp> {
OptimizelyDecideOption.includeReasons,
OptimizelyDecideOption.excludeVariables
};
final customLogger = CustomLogger();

var flutterSDK = OptimizelyFlutterSdk("X9mZd2WDywaUL9hZXyh9A",
datafilePeriodicDownloadInterval: 10 * 60,
eventOptions: const EventOptions(
batchSize: 1, timeInterval: 60, maxQueueSize: 10000),
defaultLogLevel: OptimizelyLogLevel.debug,
defaultDecideOptions: defaultOptions);
defaultDecideOptions: defaultOptions,
logger: customLogger,
);
var response = await flutterSDK.initializeClient();

setState(() {
uiResponse = "Optimizely Client initialized: ${response.success} ";
uiResponse = "[Optimizely] Client initialized: ${response.success} ";
});

var rng = Random();
Expand Down
1 change: 1 addition & 0 deletions ios/Classes/HelperClasses/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ struct RequestParameterKey {
static let reasons = "reasons"
static let decideOptions = "optimizelyDecideOption"
static let defaultLogLevel = "defaultLogLevel"
static let customLogger = "customLogger"
static let eventBatchSize = "eventBatchSize"
static let eventTimeInterval = "eventTimeInterval"
static let eventMaxQueueSize = "eventMaxQueueSize"
Expand Down
39 changes: 39 additions & 0 deletions ios/Classes/OptimizelyFlutterLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Flutter
import Optimizely

public class OptimizelyFlutterLogger: NSObject, OPTLogger {
static var LOGGER_CHANNEL: String = "optimizely_flutter_sdk_logger";

public static var logLevel: OptimizelyLogLevel = .info

private static var loggerChannel: FlutterMethodChannel?

public required override init() {
super.init()
}

public static func setChannel(_ channel: FlutterMethodChannel) {
loggerChannel = channel
}

public func log(level: OptimizelyLogLevel, message: String) {
// Early return if level check fails
guard level.rawValue <= OptimizelyFlutterLogger.logLevel.rawValue else {
return
}

// Ensure we have a valid channel
guard let channel = Self.loggerChannel else {
print("[OptimizelyFlutterLogger] ERROR: No logger channel available!")
return
}

// https://docs.flutter.dev/platform-integration/platform-channels#jumping-to-the-main-thread-in-ios
DispatchQueue.main.async {
channel.invokeMethod("log", arguments: [
"level": level.rawValue,
"message": message
])
}
}
}
18 changes: 18 additions & 0 deletions ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,21 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin {
return UUID().uuidString
}

static var registrar: FlutterPluginRegistrar?
/// Registers optimizely_flutter_sdk channel to communicate with the flutter sdk to receive requests and send responses
public static func register(with registrar: FlutterPluginRegistrar) {
self.registrar = registrar
channel = FlutterMethodChannel(name: "optimizely_flutter_sdk", binaryMessenger: registrar.messenger())
let instance = SwiftOptimizelyFlutterSdkPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)

// Separate logger channel for outgoing log calls
let taskQueue = registrar.messenger().makeBackgroundTaskQueue?()
let loggerChannel = FlutterMethodChannel(name: OptimizelyFlutterLogger.LOGGER_CHANNEL,
binaryMessenger: registrar.messenger(),
codec: FlutterStandardMethodCodec.sharedInstance(),
taskQueue: taskQueue)
OptimizelyFlutterLogger.setChannel(loggerChannel)
}

/// Part of FlutterPlugin protocol to handle communication with flutter sdk
Expand Down Expand Up @@ -110,6 +120,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin {
var defaultLogLevel = OptimizelyLogLevel.info
if let logLevel = parameters[RequestParameterKey.defaultLogLevel] as? String {
defaultLogLevel = Utils.getDefaultLogLevel(logLevel)
OptimizelyFlutterLogger.logLevel = defaultLogLevel
}

// SDK Settings Default Values
Expand Down Expand Up @@ -163,9 +174,16 @@ 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.customLogger] as? Bool, useCustomLogger {
logger = OptimizelyFlutterLogger()
}

// Creating new instance
let optimizelyInstance = OptimizelyClient(
sdkKey:sdkKey,
logger:logger,
eventDispatcher: eventDispatcher,
datafileHandler: datafileHandler,
periodicDownloadInterval: datafilePeriodicDownloadInterval,
Expand Down
51 changes: 37 additions & 14 deletions lib/optimizely_flutter_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_respon
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';
import 'package:optimizely_flutter_sdk/src/logger/flutter_logger.dart';
import 'package:optimizely_flutter_sdk/src/logger/logger_bridge.dart';

export 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'
show ClientPlatform, ListenerType;
Expand All @@ -53,6 +55,8 @@ 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;
export 'package:optimizely_flutter_sdk/src/logger/flutter_logger.dart'
show OptimizelyLogger;

/// The main client class for the Optimizely Flutter SDK.
///
Expand All @@ -68,20 +72,37 @@ class OptimizelyFlutterSdk {
final Set<OptimizelyDecideOption> _defaultDecideOptions;
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;
}
OptimizelyFlutterSdk(this._sdkKey,
{EventOptions eventOptions = const EventOptions(),
int datafilePeriodicDownloadInterval =
10 * 60, // Default time interval in seconds
Map<ClientPlatform, DatafileHostOptions> datafileHostOptions = const {},
Set<OptimizelyDecideOption> defaultDecideOptions = const {},
OptimizelyLogLevel defaultLogLevel = OptimizelyLogLevel.info,
SDKSettings sdkSettings = const SDKSettings()})
: _eventOptions = eventOptions,
_datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval,
_datafileHostOptions = datafileHostOptions,
_defaultDecideOptions = defaultDecideOptions,
_defaultLogLevel = defaultLogLevel,
_sdkSettings = sdkSettings;
{EventOptions eventOptions = const EventOptions(),
int datafilePeriodicDownloadInterval = 10 * 60,
Map<ClientPlatform, DatafileHostOptions> datafileHostOptions = const {},
Set<OptimizelyDecideOption> defaultDecideOptions = const {},
OptimizelyLogLevel defaultLogLevel = OptimizelyLogLevel.info,
SDKSettings sdkSettings = const SDKSettings(),
OptimizelyLogger? logger}) // Add logger parameter
: _eventOptions = eventOptions,
_datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval,
_datafileHostOptions = datafileHostOptions,
_defaultDecideOptions = defaultDecideOptions,
_defaultLogLevel = defaultLogLevel,
_sdkSettings = sdkSettings {
// Set the logger if provided
if (logger != null) {
setLogger(logger);
} else {
logWarning("Logger not provided.");
}
}

/// Starts Optimizely SDK (Synchronous) with provided sdkKey.
Future<BaseResponse> initializeClient() async {
Expand All @@ -92,7 +113,9 @@ class OptimizelyFlutterSdk {
_datafileHostOptions,
_defaultDecideOptions,
_defaultLogLevel,
_sdkSettings);
_sdkSettings,
_customLogger
);
}

/// Use the activate method to start an experiment.
Expand Down
Loading