Documentation

Logs with Java

Use Logs for Lumigo with our Java Distribution.

Overview

How does the logging instrumentation work? (essentially we hook into the logging library code seamlessly, worth detailing)

Scope

Lumigo supports Logback and Log4j libraries when working through Java. Refer to this table to see which versions are supported.

Basics

Instrumentation setup

For instrumentation setup, follow the instructions in the linked article. It provides instructions on both automatic, no-code instrumentation and manual instrumentation.

How to work with logging on the platform

Ensure that Lumigo's OpenTelemetry distribution for Java is properly instrumented in your application through a JVM. Follow the instructions in Instrumentation Setup to properly activate the distribution.

To view, search, and filter logs based on these attributes, navigate to Lumigo's page.

Logging within an active span context

Ensure your application is instrumented using the Lumigo OpenTelemetry distribution. When a log is emitted within an active span, Lumigo automatically correlates the log with the corresponding trace. Load the Lumigo OpenTelemetry Distribution for Java through a JVM (Java Virtual Machine) before your application is loaded. The two supported ways to achieve this are:

export JAVA_TOOL_OPTIONS="-javaagent:<path-to-lumigo-otel-javaagent>"
java -javaagent:<path-to-lumigo-otel-javaagent> -jar app.jar

To test logging through Java with an HTTP server, try the following:

package org.example;

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Main {

    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws IOException {
        // Create a new HttpServer instance listening on port 8000
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);

        // Create a new context for handling HTTP requests at the root ("/")
        server.createContext("/", new RootHandler());

        // Start the server
        server.setExecutor(null); // Creates a default executor
        server.start();

        System.out.println("Server is running on http://localhost:8000/");

    }

    // Custom HttpHandler to process requests to the root "/"
    static class RootHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            // Prepare the response
            String response = "Welcome to my simple Java web server!";

            for (int i = 0; i < 1; i++) {
                try (CloseableHttpClient client = HttpClients.createDefault()) {
                    final ClassicHttpRequest httpGet = ClassicRequestBuilder.get("https://api.chucknorris.io/jokes/random").build();
                    client.execute(httpGet, httpResponse -> {
                        final HttpEntity entity = httpResponse.getEntity();
                        if (entity != null) {
                            // Convert the response entity to a String and print it
                            String result = EntityUtils.toString(entity);
                            logger.info("Response from the API: {}", result);
                        }
                        return null;
                    });
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }

            // Set the response header (200 OK) and the length of the response body
            exchange.sendResponseHeaders(200, response.getBytes().length);

            // Get the response body output stream
            OutputStream os = exchange.getResponseBody();

            // Write the response and close the output stream
            os.write(response.getBytes());
            os.close();
        }
    }
}

Advanced

Troubleshooting cases where logs are not being sent

If logs are not appearing in the Lumigo platform, enable the LUMIGO_DEBUG_LOGDUMP environment variable.

LUMIGO_DEBUG_LOGDUMP: This functions similarly to LUMIGO_DEBUG_SPANDUMP, but for logs instead of spans. This option is only effective when LUMIGO_ENABLE_LOGS is set to true.

This outputs additional debug information to help diagnose why logs are not being sent. Check the application logs for messages prefixed with [Lumigo Log Dump] to identify potential issues. You should also ensure the Lumigo instrumentation is correctly initialized and verify network connectivity to Lumigo's backend.

Troubleshooting cases where logs are not correlated to traces

Verify that logs are emitted within an active span, and use the Lumigo debug logs (LUMIGO_DEBUG_LOGDUMP) to inspect log traces and diagnose correlation issues. You should also ensure the Lumigo distribution is up-to-date and correctly configured for your environment.

Cases not covered by the distribution

We do not cover every scenario with our distribution. Because Lumigo only supports log4j and logback logging libraries, you will need to provide the explicit setup and libraries they use, unlike something like java.util.logging (JUL) which does not require as much setup.

To define inheritance through log4j or logback, you will need to input it manually, as opposed to the automatic inheritance of log levels and handlers based on logger naming hierarchy achievable in JUL.

You cannot use automatic inheritance like in JUL, as it is not supported:

Logger parent = Logger.getLogger("com.example");
Logger child = Logger.getLogger("com.example.module");

parent.setLevel(Level.WARNING);
System.out.println(child.getLevel()); // null → inherits from parent

Instead, you need to first modify the config file, such as logback.xml in Logback's case:

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>[%logger] %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Parent logger -->
  <logger name="com.example" level="INFO" additivity="true">
    <appender-ref ref="STDOUT"/>
  </logger>

  <!-- Child logger inherits from com.example -->
  <logger name="com.example.module" additivity="true" />
  
  <!-- Root logger -->
  <root level="ERROR">
    <appender-ref ref="STDOUT"/>
  </root>
</configuration>

For scenarios not covered by Lumigo's distribution, we have several alternate setups you can use:

  • Vanilla OpenTelemetry setup: Implement standard OpenTelemetry instrumentation for advanced use cases.
  • Kubernetes log collection: Use a centralized log collector (e.g., Fluent Bit, Fluentd) to aggregate and forward logs.

Refer to Lumigo documentation for detailed guidance on integrating with these alternative setups.