CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-http-client

HTTP client abstraction for LangChain4j with synchronous/asynchronous execution and Server-Sent Events (SSE) streaming support

Overview
Eval results
Files

logging-configuration.mddocs/guides/

Logging Configuration Guide

This guide covers HTTP request/response logging using the LoggingHttpClient decorator.

Basic Logging Setup

Log Requests and Responses

import dev.langchain4j.http.client.*;
import dev.langchain4j.http.client.log.LoggingHttpClient;
import java.time.Duration;

// Create base HTTP client
HttpClient baseClient = HttpClientBuilderLoader.loadHttpClientBuilder()
    .connectTimeout(Duration.ofSeconds(10))
    .readTimeout(Duration.ofSeconds(30))
    .build();

// Wrap with logging
HttpClient loggingClient = new LoggingHttpClient(
    baseClient,
    true,   // log requests
    true    // log responses
);

// Use the logging client
HttpRequest request = HttpRequest.builder()
    .method(HttpMethod.POST)
    .url("https://api.example.com/data")
    .addHeader("Authorization", "Bearer secret-token-12345")
    .body("{\"key\":\"value\"}")
    .build();

SuccessfulHttpResponse response = loggingClient.execute(request);

Output:

INFO  HTTP request:
- method: POST
- url: https://api.example.com/data
- headers: [Authorization: Beare...45]
- body: {"key":"value"}

INFO  HTTP response:
- status code: 200
- headers: [Content-Type: application/json]
- body: {"result":"success"}

Selective Logging

Log Only Requests

HttpClient loggingClient = new LoggingHttpClient(
    baseClient,
    true,    // log requests
    false    // do NOT log responses
);

Log Only Responses

HttpClient loggingClient = new LoggingHttpClient(
    baseClient,
    false,   // do NOT log requests
    true     // log responses
);

Custom Logger

Using Custom SLF4J Logger

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger customLogger = LoggerFactory.getLogger("com.myapp.HttpClient");

HttpClient loggingClient = new LoggingHttpClient(
    baseClient,
    true,
    true,
    customLogger
);

Sensitive Header Masking

The LoggingHttpClient automatically masks sensitive headers.

Automatically Masked Headers

  • Authorization
  • X-API-Key
  • X-Auth-Token
  • Any header containing api-key (case-insensitive)

Masking Examples

Original: Bearer sk-1234567890abcdef
Masked:   Beare...ef

Original: 12345
Masked:   ...

Original: api_key_abc123xyz789
Masked:   api_k...89

Logging Levels

Request and Response Logging

  • Level: INFO
  • Content: Method, URL, headers (masked), body

SSE Event Logging

  • Level: DEBUG
  • Content: Event type and data for each server-sent event
import dev.langchain4j.http.client.sse.*;

HttpClient loggingClient = new LoggingHttpClient(baseClient, true, true);

HttpRequest request = HttpRequest.builder()
    .method(HttpMethod.POST)
    .url("https://api.example.com/stream")
    .addHeader("Accept", "text/event-stream")
    .body("{\"prompt\":\"Hello\"}")
    .build();

loggingClient.execute(request, new ServerSentEventListener() {
    @Override
    public void onOpen(SuccessfulHttpResponse response) {
        // Initial response is logged automatically
        System.out.println("Stream opened");
    }

    @Override
    public void onEvent(ServerSentEvent event) {
        // Each event is logged automatically at DEBUG level
        System.out.println("Received: " + event.data());
    }

    @Override
    public void onError(Throwable throwable) {
        System.err.println("Error: " + throwable.getMessage());
    }
});

SSE Output:

INFO  HTTP request:
- method: POST
- url: https://api.example.com/stream
- headers: [Accept: text/event-stream]
- body: {"prompt":"Hello"}

INFO  HTTP response:
- status code: 200
- headers: [Content-Type: text/event-stream]
- body: null

DEBUG ServerSentEvent { event = null, data = "Hello" }
DEBUG ServerSentEvent { event = null, data = "world" }
DEBUG ServerSentEvent { event = "done", data = "" }

SLF4J Configuration

Logback Configuration

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

    <!-- HTTP client logging -->
    <logger name="dev.langchain4j.http.client.log.LoggingHttpClient" level="INFO" />

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Log4j2 Configuration

<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="dev.langchain4j.http.client.log.LoggingHttpClient" level="info" />
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

Disable SSE Event Logging

To disable verbose SSE event logging while keeping request/response logging:

<!-- Logback -->
<logger name="dev.langchain4j.http.client.log.LoggingHttpClient" level="INFO" />

This sets the level to INFO, which excludes DEBUG-level SSE event logs.

Environment-Based Configuration

Conditional Logging

public class HttpClientFactory {
    public static HttpClient create() {
        HttpClient baseClient = HttpClientBuilderLoader.loadHttpClientBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .readTimeout(Duration.ofSeconds(30))
            .build();

        // Enable logging only in development
        String env = System.getenv("ENVIRONMENT");
        if ("development".equals(env) || "dev".equals(env)) {
            return new LoggingHttpClient(baseClient, true, true);
        }

        return baseClient;
    }
}

Configuration from Properties

public class ConfigurableLoggingClient {
    public static HttpClient create(HttpClient baseClient) {
        boolean logRequests = Boolean.parseBoolean(
            System.getProperty("http.log.requests", "false")
        );
        boolean logResponses = Boolean.parseBoolean(
            System.getProperty("http.log.responses", "false")
        );

        if (logRequests || logResponses) {
            return new LoggingHttpClient(baseClient, logRequests, logResponses);
        }

        return baseClient;
    }
}

// Usage with system properties:
// java -Dhttp.log.requests=true -Dhttp.log.responses=true ...

Spring Boot Integration

Spring Configuration

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpClientConfiguration {

    @Value("${http.logging.enabled:false}")
    private boolean loggingEnabled;

    @Value("${http.logging.requests:true}")
    private boolean logRequests;

    @Value("${http.logging.responses:true}")
    private boolean logResponses;

    @Bean
    public HttpClient httpClient() {
        HttpClient baseClient = HttpClientBuilderLoader.loadHttpClientBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .readTimeout(Duration.ofSeconds(30))
            .build();

        if (loggingEnabled) {
            return new LoggingHttpClient(baseClient, logRequests, logResponses);
        }

        return baseClient;
    }
}

application.properties

http.logging.enabled=true
http.logging.requests=true
http.logging.responses=false

Separate Loggers for Different Clients

Logger apiLogger = LoggerFactory.getLogger("com.myapp.api.HttpClient");
Logger authLogger = LoggerFactory.getLogger("com.myapp.auth.HttpClient");

HttpClient apiClient = new LoggingHttpClient(baseClient, true, true, apiLogger);
HttpClient authClient = new LoggingHttpClient(baseClient, true, false, authLogger);

Logback Configuration for Separate Loggers

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

    <!-- API client - log everything -->
    <logger name="com.myapp.api.HttpClient" level="DEBUG" />

    <!-- Auth client - log only errors -->
    <logger name="com.myapp.auth.HttpClient" level="WARN" />

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Performance Considerations

Impact of Logging

  1. Logging overhead: Logging adds overhead to each request. Disable in production if performance is critical.

  2. Large response bodies: Logging large response bodies can consume significant memory and I/O. Consider disabling response logging for endpoints returning large payloads.

  3. SSE event logging: DEBUG-level logging of each SSE event can be very verbose. Set log level to INFO to suppress.

  4. String concatenation: Logging performs string operations which can impact performance under high load.

Conditional Response Body Logging

public class SmartLoggingHttpClient extends LoggingHttpClient {
    private final long maxBodyLength;

    public SmartLoggingHttpClient(HttpClient delegate, long maxBodyLength) {
        super(delegate, true, shouldLogResponses());
        this.maxBodyLength = maxBodyLength;
    }

    private static boolean shouldLogResponses() {
        // Only log response bodies if explicitly enabled
        return "true".equals(System.getenv("LOG_HTTP_RESPONSES"));
    }

    // Custom implementation could check response size before logging
}

Security Considerations

  1. Automatic masking: While common sensitive headers are automatically masked, review your specific headers to ensure no secrets are logged.

  2. Custom headers: Headers with custom names containing secrets (e.g., X-Custom-Secret) should be manually reviewed.

  3. Request/response bodies: Bodies are logged in full. Ensure request/response bodies don't contain sensitive data, or disable body logging.

  4. Log file access: Ensure log files are properly secured with appropriate file system permissions.

  5. Log aggregation: When using centralized logging systems, ensure logs are transmitted and stored securely.

Best Practices

  1. Development vs Production: Enable verbose logging in development, minimal or no logging in production.

  2. Selective logging: Log requests for debugging, but consider disabling response body logging in production.

  3. Use custom loggers: Use different loggers for different API clients to control logging granularity.

  4. Monitor log volume: SSE streaming can generate large volumes of logs. Set appropriate log levels.

  5. Secure log storage: Ensure logs are stored securely and access is controlled.

  6. Rotate logs: Implement log rotation to prevent disk space issues.

Related Documentation

  • API Reference: Logging API
  • Examples: Logging Examples

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-http-client@1.11.0

docs

index.md

installation.md

quick-start.md

tile.json