Configure Logging and Error Handling
Why configure logging
Section titled “Why configure logging”The Optimizely SDK operates silently by default. When an experiment does not behave as expected — a flag returns the wrong variation, events vanish, or the datafile fails to load — logging is your primary diagnostic tool. Routing SDK logs into your existing observability stack (Datadog, Splunk, CloudWatch) makes debugging fast and avoids the guesswork of reproducing issues in production.
What you will do
Section titled “What you will do”- Set the SDK log level
- Implement a custom logger for production
- Handle initialization failures gracefully
- Debug experiment behavior with decision reasons
Step 1: Set the log level
Section titled “Step 1: Set the log level”Configure log levels
import { createInstance, enums } from '@optimizely/optimizely-sdk';
const optimizely = createInstance({
sdkKey: 'YOUR_SDK_KEY',
logLevel: enums.LOG_LEVEL.INFO, // ERROR | WARNING | INFO | DEBUG
}); import logging
from optimizely import optimizely, logger as opti_logger
# Option 1: Use the built-in logger with a level
client = optimizely.Optimizely(
sdk_key='YOUR_SDK_KEY',
logger=opti_logger.SimpleLogger(min_level=logging.INFO),
)
# Option 2: Configure Python's logging module
logging.getLogger('optimizely').setLevel(logging.WARNING) using OptimizelySDK;
using Microsoft.Extensions.Logging;
// The .NET SDK integrates with ILoggerFactory
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole().SetMinimumLevel(LogLevel.Information);
});
var optimizely = OptimizelyFactory.NewDefaultInstance(
"YOUR_SDK_KEY",
loggerFactory
); Step 2: Implement a custom logger
Section titled “Step 2: Implement a custom logger”Route SDK logs to your production logging infrastructure instead of the console.
Custom logger for production
import { createInstance, enums } from '@optimizely/optimizely-sdk';
import pino from 'pino';
const appLogger = pino({ level: 'info' });
const customLogger = {
log(level, message) {
switch (level) {
case enums.LOG_LEVEL.ERROR:
appLogger.error({ sdk: 'optimizely' }, message);
break;
case enums.LOG_LEVEL.WARNING:
appLogger.warn({ sdk: 'optimizely' }, message);
break;
case enums.LOG_LEVEL.INFO:
appLogger.info({ sdk: 'optimizely' }, message);
break;
case enums.LOG_LEVEL.DEBUG:
appLogger.debug({ sdk: 'optimizely' }, message);
break;
}
},
};
const optimizely = createInstance({
sdkKey: 'YOUR_SDK_KEY',
logger: customLogger,
}); import logging
from optimizely import optimizely
# Configure a handler that sends to your logging service
logger = logging.getLogger('optimizely')
logger.setLevel(logging.INFO)
# Example: JSON handler for log aggregation
handler = logging.StreamHandler()
formatter = logging.Formatter(
'{"time": "%(asctime)s", "level": "%(levelname)s", '
'"sdk": "optimizely", "message": "%(message)s"}'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
client = optimizely.Optimizely(sdk_key='YOUR_SDK_KEY') // Use Serilog, NLog, or any ILoggerFactory-compatible framework
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341") // Or your log aggregator
.CreateLogger();
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog();
});
var optimizely = OptimizelyFactory.NewDefaultInstance(
"YOUR_SDK_KEY", loggerFactory
); Step 3: Handle initialization failures
Section titled “Step 3: Handle initialization failures”The SDK should never crash your application. Wrap initialization and flag evaluation in error boundaries.
Defensive initialization
let optimizely;
try {
optimizely = createInstance({ sdkKey: 'YOUR_SDK_KEY' });
const { success } = await optimizely.onReady({ timeout: 5000 });
if (!success) {
console.warn('Optimizely timed out -- using defaults');
}
} catch (error) {
console.error('Optimizely init failed:', error);
// Application continues without experimentation
}
// Safe wrapper for all flag evaluations
function safeDecide(flagKey, user) {
if (!optimizely || !user) return { enabled: false, variables: {} };
try {
return user.decide(flagKey);
} catch (error) {
console.error(`Flag evaluation failed for ${flagKey}:`, error);
return { enabled: false, variables: {} };
}
} try:
client = optimizely.Optimizely(sdk_key='YOUR_SDK_KEY')
except Exception as e:
logging.error(f'Optimizely init failed: {e}')
client = None
def safe_decide(flag_key, user):
if not client or not user:
return type('Decision', (), {'enabled': False, 'variables': {}})()
try:
return user.decide(flag_key)
except Exception as e:
logging.error(f'Flag evaluation failed for {flag_key}: {e}')
return type('Decision', (), {'enabled': False, 'variables': {}})() Optimizely? optimizely = null;
try
{
optimizely = OptimizelyFactory.NewDefaultInstance("YOUR_SDK_KEY");
}
catch (Exception ex)
{
logger.LogError(ex, "Optimizely init failed");
}
public OptimizelyDecision SafeDecide(string flagKey, OptimizelyUserContext? user)
{
if (optimizely == null || user == null)
return OptimizelyDecision.NewErrorDecision(flagKey);
try { return user.Decide(flagKey); }
catch (Exception ex)
{
_logger.LogError(ex, "Flag eval failed: {Flag}", flagKey);
return OptimizelyDecision.NewErrorDecision(flagKey);
}
} Step 4: Debug with decision reasons
Section titled “Step 4: Debug with decision reasons”Enable decision reasons to understand why a user received a particular variation.
Get decision reasons
const decision = user.decide('checkout_flow', [
'INCLUDE_REASONS',
]);
console.log('Variation:', decision.variationKey);
console.log('Reasons:', decision.reasons);
// Example output:
// ['User meets audience conditions.', 'User bucketed into variation_b.'] decision = user.decide('checkout_flow', ['INCLUDE_REASONS'])
print('Variation:', decision.variation_key)
print('Reasons:', decision.reasons) var options = new[] { OptimizelyDecideOption.INCLUDE_REASONS };
var decision = user.Decide("checkout_flow", options);
Console.WriteLine($"Variation: {decision.VariationKey}");
foreach (var reason in decision.Reasons)
Console.WriteLine($"Reason: {reason}"); Troubleshooting
Section titled “Troubleshooting”| Issue | Cause | Fix |
|---|---|---|
| No log output | Logger not configured or level too high | Set level to DEBUG temporarily |
| SDK errors crash the app | Missing try/catch around SDK calls | Wrap all SDK calls in error boundaries |
| Decision reasons empty | INCLUDE_REASONS option not passed | Add the option to the decide call |
| Debug mode floods logs | DEBUG level in production | Use INFO or WARNING in production environments |