Skip to content

Configure Logging and Error Handling

⏱ 15 minutes intermediate

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.

  1. Set the SDK log level
  2. Implement a custom logger for production
  3. Handle initialization failures gracefully
  4. Debug experiment behavior with decision reasons
Configure log levels
javascript
import { createInstance, enums } from '@optimizely/optimizely-sdk';

const optimizely = createInstance({
  sdkKey: 'YOUR_SDK_KEY',
  logLevel: enums.LOG_LEVEL.INFO, // ERROR | WARNING | INFO | DEBUG
});
python
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)
csharp
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
);

Route SDK logs to your production logging infrastructure instead of the console.

Custom logger for production
javascript
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,
});
python
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')
csharp
// 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
);

The SDK should never crash your application. Wrap initialization and flag evaluation in error boundaries.

Defensive initialization
javascript
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: {} };
  }
}
python
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': {}})()
csharp
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);
    }
}

Enable decision reasons to understand why a user received a particular variation.

Get decision reasons
javascript
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.']
python
decision = user.decide('checkout_flow', ['INCLUDE_REASONS'])

print('Variation:', decision.variation_key)
print('Reasons:', decision.reasons)
csharp
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}");
IssueCauseFix
No log outputLogger not configured or level too highSet level to DEBUG temporarily
SDK errors crash the appMissing try/catch around SDK callsWrap all SDK calls in error boundaries
Decision reasons emptyINCLUDE_REASONS option not passedAdd the option to the decide call
Debug mode floods logsDEBUG level in productionUse INFO or WARNING in production environments