Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

Optimizely will be sunsetting Full Stack Experimentation on July 29, 2024. See the recommended Feature Experimentation migration timeline and documentation.

Dev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Customize logger

This topic describes how to customize the log information about experiments to help with debugging.

The logger logs information about your experiments to help you with debugging. You can customize where log information is sent and what kind of information is tracked.

In the Java SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you include a SLF4J implementation in your dependencies. For the Java SDK, we require the use of an SLF4J implementation.

To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client. See the code example below.

❗️

log4j Vulnerability Advisory

Due to a recently announced security exploit in the Log4J library, we recommend upgrading to version 2.16.0 or higher as soon as possible if you are using it alongside the SDK. View Apache's documentation on the Log4j vulnerability for more information.

Add Log4j dependency to Gradle file

implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.20.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.20.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.20.0'

Add log4j2.properties to your resources directory

// Set the root logger level to INFO and its appender to the console
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n

// Specify the loggers
rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = STDOUT

For finer control over logging, for example to control the logging level per package or to control the logging destination, we recommend you use logback.

implementation 'ch.qos.logback:logback-classic:1.1.7'

Save the logback.xml file in your resources directory. Below is a simple example of capturing Optimizely log levels and piping them somewhere else.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <appender name="MEMORY" class="com.optimizely.intellij.plugin.utils.LogAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="com.optimizely" level="debug" additivity="false">
        <appender-ref ref="MEMORY"/>
    </logger>

    <root level="error">
        <appender-ref ref="CONSOLE"/>
    </root>

The preceding example pipes all debug logs to error messages. These are then sent to a different log appender.

You can also set logging levels for any Optimizely package, giving you control over logs granularity. In the following example, we simply set the log level for the highest-level package, com.optimizely:

<logger name="com.optimizely" level="debug" />

Finally, the following general example show you how to write a custom appender rather than using one of the many default appenders:

public class LogAppender extends AppenderBase<ILoggingEvent> {
    int counter = 0;
    public static AtomicBoolean captureLogging = new AtomicBoolean(false);
    public static List<String> logs = Collections.synchronizedList(new ArrayList<String>());

    PatternLayoutEncoder encoder;

    @Override
    public void start() {
        if (this.encoder == null) {
            addError("No encoder set for the appender named ["+ name +"].");
            return;
        }

        try {
            encoder.init(System.out);
        } catch (IOException e) {
        }
        super.start();
    }

    public static void clearLogs() {
        synchronized (logs) {
            logs.clear();
        }
    }

    public void append(ILoggingEvent event) {
        // output the events as formatted by our layout
        try {
            this.encoder.doEncode(event);
            if (captureLogging.get()) {
                synchronized (logs) {
                    logs.add(event.getFormattedMessage());
                }
            }
        } catch (IOException e) {
        }

        // prepare for next event
        counter++;
    }

    public PatternLayoutEncoder getEncoder() {
        return encoder;
    }

    public void setEncoder(PatternLayoutEncoder encoder) {
        this.encoder = encoder;
    }
}

Log levels

The log levels for the Java SDK:

  • ERROR – Events that prevent feature flags from functioning correctly (for example, invalid datafile in initialization and invalid feature keys) are logged. The user can take action to correct.
  • WARNING – Events that do not prevent feature flags from functioning correctly, but can have unexpected outcomes (for example, future API deprecation, logger or error handler are not set properly, and nil values from getters) are logged.
  • INFO – Events of significance (for example, activate started, activate succeeded, tracking started, and tracking succeeded) are logged. This is helpful in showing the lifecycle of an API call.
  • DEBUG – Any information related to errors that can help us debug the issue (for example, the feature flag is not running, user is not included in the rollout) are logged.