Logbook

repository·main·Indexed 24 days ago

https://github.com/zalando/logbook

An extensible Java library for complete HTTP request and response logging, supporting both client-side and server-side technologies. It provides features for capturing, persisting, and analyzing HTTP traffic, including sensitive data filtering via Query, Path, Header, and Body filters, JSON Path filtering, and custom attribute extraction. Logbook supports multiple formatting options including HTTP, JSON, Common Log Format (CLF), Extended Log Format (ELF), and cURL commands.

Tokens
10.5K
Snippets
28
Records
48
Agent score
85%

What's inside zalando-logbook

  1. How Logbook strategies work

    main

    A Strategy defines how requests and responses are logged. Logbook uses the Strategy pattern to allow different behaviors regarding timing and pairing of logs.

    Built-in strategies include:

    • BodyOnlyIfStatusAtLeastStrategy: Controls body logging based on HTTP status.
    • StatusAtLeastStrategy: Controls logging based on HTTP status.
    • WithoutBodyStrategy: Logs requests/responses without their bodies.

    Refer to the Strategy interface documentation for detailed implications of each implementation.

  2. Jackson version support for JSON formatting

    main

    Logbook's core functionality does not require Jackson, but JSON formatting (logbook-json) and JWT attribute extraction require it. Logbook automatically detects the available Jackson version on the classpath:

    • If Jackson 2 is available, Logbook uses Jackson 2 implementations.
    • If Jackson 3 is available, Logbook uses Jackson 3 implementations.
    • If both are available, Jackson 3 is preferred.
    • If neither is available, JSON formatting is disabled but core logging continues to work.

    Spring Boot 4.x Note

    Spring Boot 4.x provides Jackson 3 by default. If you need to force Jackson 2, add it explicitly to your dependencies.

  3. Choose between HTTP and JSON formatting

    main

    Formatters define how requests and responses are transformed into strings. They do not handle the actual writing to a destination (that is the role of Writers).

    HTTP Formatter

    Provided by DefaultHttpLogFormatter. It is designed for local development and debugging. It is not machine-readable and is not recommended for production.

    JSON Formatter

    Provided by JsonHttpLogFormatter. It is designed for production use as it is easily parsed by log consumers.

    Note: Using the JSON formatter requires the logbook-json dependency:

    <dependency>
      <groupId>org.zalando</groupId>
      <artifactId>logbook-json</artifactId>
    </dependency>
  4. Handle JSON precision in Logbook

    main

    Logbook uses Jackson to inline JSON payloads. Because Jackson can drop precision from floating-point numbers, you can provide a custom JsonGeneratorWrapper to your filters to mitigate this.

    Available wrappers:

    • DefaultJsonGeneratorWrapper: Default behavior.
    • NumberAsStringJsonGeneratorWrapper: Writes floating-point numbers as strings to preserve precision.
    • PreciseFloatJsonGeneratorWrapper: Writes floating-point with precision (uses BigDecimal), which may incur a performance penalty.
  5. Initialize Logbook with defaults or custom configuration

    main

    All Logbook integrations require a Logbook instance. You can create a basic instance using default settings or use the LogbookBuilder to customize filters, conditions, and sinks.

    To use defaults:

    Logbook logbook = Logbook.create();

    To customize the instance, use the builder pattern to register various filters and sinks:

    Logbook logbook = Logbook.builder()
        .condition(new CustomCondition())
        .queryFilter(new CustomQueryFilter())
        .pathFilter(new CustomPathFilter())
        .headerFilter(new CustomHeaderFilter())
        .bodyFilter(new CustomBodyFilter())
        .requestFilter(new CustomRequestFilter())
        .responseFilter(new CustomResponseFilter())
        .sink(new DefaultSink(
                new CustomHttpLogFormatter(),
                new CustomHttpLogWriter()
        ))
        .build();
  6. Install Logbook core

    main

    To use Logbook, add the logbook-core dependency to your project. All additional Logbook modules (like logbook-servlet, logbook-netty, etc.) share the same version number as the core library.

    <dependency>
        <groupId>org.zalando</groupId>
        <artifactId>logbook-core</artifactId>
        <version>${logbook.version}</version>
    </dependency>
  7. Configure Logbook logging level

    main

    Logbook requires the logger to be set to TRACE level to capture requests and responses. If you are using Spring Boot with Logback, add the following to your application.properties:

    logging.level.org.zalando.logbook: TRACE

    logging.level.org.zalando.logbook: TRACE
  8. Configure logging conditions to exclude requests

    main

    Use condition to decide whether a request (and its response) should be logged. This is useful for ignoring health checks or management endpoints to save resources.

    Conditions are defined using Predicate objects. You can combine predefined predicates using exclude().

    Logbook logbook = Logbook.builder()
        .condition(exclude(
            requestTo("/health"),
            requestTo("/admin/**"),
            contentType("application/octet-stream"),
            header("X-Secret", newHashSet("1", "true")::contains)))
        .build();
  9. Install Logbook using the Bill of Materials (BOM)

    main

    To manage versions easily across multiple Logbook modules, import the logbook-bom in your <dependencyManagement> section. This allows you to omit version tags when declaring specific Logbook dependencies.

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.zalando</groupId>
          <artifactId>logbook-bom</artifactId>
          <version>${logbook.version}</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
  10. Running benchmarks during development

    main
    While command-line execution is the most accurate method, you can run benchmarks as standalone programs using the main(..) method directly from your IDE. This is useful for rapid development and when using profilers like VisualVM to drill down into specific method-level performance.
  11. Integrate Logbook with Apache HttpClient 5

    main

    Use the logbook-httpclient5 module.

    Recommended approach (using ExecHandler): Add the LogbookHttpExecHandler first to ensure logging occurs before compression and after decompression.

    CloseableHttpClient client = HttpClientBuilder.create()
            .addExecInterceptorFirst("Logbook", new LogbookHttpExecHandler(logbook))
            .build();

    Alternative approach (using Interceptors): Use this if you are not using compression or other ExecHandlers:

    CloseableHttpClient client = HttpClientBuilder.create()
            .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))
            .addResponseInterceptorFirst(new LogbookHttpResponseInterceptor())
            .build();
  12. Filter sensitive data in requests and responses

    main

    Filtering prevents sensitive information (like passwords or authorization headers) from being logged. Logbook provides high-level filters for common tasks and low-level filters for complex logic.

    High-level Filters

    TypeOperates onApplies toDefault
    QueryFilterQuery stringrequestaccess_token
    PathFilterPathrequestn/a
    HeaderFilterHeader (key-value)bothAuthorization
    BodyFilterContent-Type and bodybothjson: access_token, refresh_token; form: client_secret, password, refresh_token
    RequestFilterHttpRequestrequestReplaces binary/multipart/stream bodies
    ResponseFilterHttpResponseresponseReplaces binary/multipart/stream bodies

    Example Configuration

    import static org.zalando.logbook.core.HeaderFilters.authorization;
    import static org.zalando.logbook.core.HeaderFilters.eachHeader;
    import static org.zalando.logbook.core.QueryFilters.accessToken;
    import static org.zalando.logbook.core.QueryFilters.replaceQuery;
    
    Logbook logbook = Logbook.builder()
            .requestFilter(RequestFilters.replaceBody(message -> contentType("audio/*").test(message) ? "mmh mmh mmh mmh" : null))
            .responseFilter(ResponseFilters.replaceBody(message -> contentType("*/*-stream").test(message) ? "It just keeps going and going..." : null))
            .queryFilter(accessToken())
            .queryFilter(replaceQuery("password", "<secret>"))
            .headerFilter(authorization())
            .headerFilter(eachHeader("X-Secret"::equalsIgnoreCase, "<secret>"))
            .build();