• Stars
    star
    1,788
  • Rank 26,002 (Top 0.6 %)
  • Language
    Java
  • License
    MIT License
  • Created about 9 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An extensible Java library for HTTP request and response logging

Logbook: HTTP request and response logging

Logbook

Stability: Active Build Status Coverage Status Javadoc Release Maven Central License Project Map

Logbook noun, /lษ‘ษก bสŠk/: A book in which measurements from the ship's log are recorded, along with other salient details of the voyage.

Logbook is an extensible Java library to enable complete request and response logging for different client- and server-side technologies. It satisfies a special need by a) allowing web application developers to log any HTTP traffic that an application receives or sends b) in a way that makes it easy to persist and analyze it later. This can be useful for traditional log analysis, meeting audit requirements or investigating individual historic traffic issues.

Logbook is ready to use out of the box for most common setups. Even for uncommon applications and technologies, it should be simple to implement the necessary interfaces to connect a library/framework/etc. to it.

Features

  • Logging: of HTTP requests and responses, including the body; partial logging (no body) for unauthorized requests
  • Customization: of logging format, logging destination, and conditions that request to log
  • Support: for Servlet containers, Apacheโ€™s HTTP client, Square's OkHttp, and (via its elegant API) other frameworks
  • Optional obfuscation of sensitive data
  • Spring Boot Auto Configuration
  • Scalyr compatible
  • Sensible defaults

Dependencies

  • Java 8 (for Spring 6 / Spring Boot 3 and JAX-RS 3.x, Java 17 is required)
  • Any build tool using Maven Central, or direct download
  • Servlet Container (optional)
  • Apache HTTP Client 4.x or 5.x (optional)
  • JAX-RS 3.x (aka Jakarta RESTful Web Services) Client and Server (optional)
  • JAX-RS 2.x Client and Server (optional)
  • Netty 4.x (optional)
  • OkHttp 2.x or 3.x (optional)
  • Spring 6.x or Spring 5.x (optional, see instructions below)
  • Spring Boot 3.x or 2.x (optional)
  • Ktor (optional)
  • logstash-logback-encoder 5.x (optional)

Installation

Add the following dependency to your project:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-core</artifactId>
    <version>${logbook.version}</version>
</dependency>

Spring 5 / Spring Boot 2 Support

For Spring 5 / Spring Boot 2 backwards compatibility please add the following import:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-servlet</artifactId>
    <version>${logbook.version}</version>
    <classifier>javax</classifier>
</dependency>

Additional modules/artifacts of Logbook always share the same version number.

Alternatively, you can import our bill of materials...

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.zalando</groupId>
      <artifactId>logbook-bom</artifactId>
      <version>${logbook.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
... which allows you to omit versions:
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-core</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-httpclient</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-jaxrs</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-json</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-netty</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-okhttp</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-okhttp2</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-servlet</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-ktor-common</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-ktor-client</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-ktor-server</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-ktor</artifactId>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-logstash</artifactId>
</dependency>

The logbook logger must be configured to trace level in order to log the requests and responses. With Spring Boot 2 (using Logback) this can be accomplished by adding the following line to your application.properties

logging.level.org.zalando.logbook: TRACE

Usage

All integrations require an instance of Logbook which holds all configuration and wires all necessary parts together. You can either create one using all the defaults:

Logbook logbook = Logbook.create();

or create a customized version using the LogbookBuilder:

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();

Strategy

Logbook used to have a very rigid strategy how to do request/response logging:

  • Requests/responses are logged separately
  • Requests/responses are logged soon as possible
  • Requests/responses are logged as a pair or not logged at all
    (i.e. no partial logging of traffic)

Some of those restrictions could be mitigated with custom HttpLogWriter implementations, but they were never ideal.

Starting with version 2.0 Logbook now comes with a Strategy pattern at its core. Make sure you read the documentation of the Strategy interface to understand the implications.

Logbook comes with some built-in strategies:

Attribute Extractor

Starting with version 3.4.0, Logbook is equipped with a feature called Attribute Extractor. Attributes are basically a list of key/value pairs that can be extracted from request and/or response, and logged with them. The idea was sprouted from issue 381, where a feature was requested to extract the subject claim from JWT tokens in the authorization header.

The AttributeExtractor interface has two extract methods: One that can extract attributes from the request only, and one that has both request and response at its avail. The both return an instance of the HttpAttributes class, which is basically a fancy Map<String, Object>. Notice that since the map values are of type Object, they should have a proper toString() method in order for them to appear in the logs in a meaningful way. Alternatively, log formatters can work around this by implementing their own serialization logic. For instance, the built-in log formatter JsonHttpLogFormatter uses ObjectMapper to serialize the values.

Here is an example:

final class OriginExtractor implements AttributeExtractor {

  @Override
  public HttpAttributes extract(final HttpRequest request) {
    return HttpAttributes.of("origin", request.getOrigin());
  }
    
}

Logbook must then be created by registering this attribute extractor:

final Logbook logbook = Logbook.builder()
        .attributeExtractor(new OriginExtractor())
        .build();

This will result in request logs to include something like:

"attributes":{"origin":"LOCAL"}

For more advanced examples, look at the JwtFirstMatchingClaimExtractor and JwtAllMatchingClaimsExtractor classes. The former extracts the first claim matching a list of claim names from the request JWT token. The latter extracts all claims matching a list of claim names from the request JWT token.

If you require to incorporate multiple AttributeExtractors, you can use the class CompositeAttributeExtractor:

final List<AttributeExtractor> extractors = List.of(
    extractor1,
    extractor2,
    extractor3
);

final Logbook logbook = Logbook.builder()
        .attributeExtractor(new CompositeAttributeExtractor(extractors))
        .build();

Phases

Logbook works in several different phases:

  1. Conditional,
  2. Filtering,
  3. Formatting and
  4. Writing

Each phase is represented by one or more interfaces that can be used for customization. Every phase has a sensible default.

Conditional

Logging HTTP messages and including their bodies is a rather expensive task, so it makes a lot of sense to disable logging for certain requests. A common use case would be to ignore health check requests from a load balancer, or any request to management endpoints typically issued by developers.

Defining a condition is as easy as writing a special Predicate that decides whether a request (and its corresponding response) should be logged or not. Alternatively you can use and combine predefined predicates:

Logbook logbook = Logbook.builder()
    .condition(exclude(
        requestTo("/health"),
        requestTo("/admin/**"),
        contentType("application/octet-stream"),
        header("X-Secret", newHashSet("1", "true")::contains)))
    .build();

Exclusion patterns, e.g. /admin/**, are loosely following Ant's style of path patterns without taking the the query string of the URL into consideration.

Filtering

The goal of Filtering is to prevent the logging of certain sensitive parts of HTTP requests and responses. This usually includes the Authorization header, but could also apply to certain plaintext query or form parameters โ€” e.g. password.

Logbook supports different types of filters:

Type Operates on Applies to Default
QueryFilter Query string request access_token
PathFilter Path request n/a
HeaderFilter Header (single key-value pair) both Authorization
BodyFilter Content-Type and body both json: access_token and refresh_token
form: client_secret and password
RequestFilter HttpRequest request Replace binary, multipart and stream bodies.
ResponseFilter HttpResponse response Replace binary, multipart and stream bodies.

QueryFilter, PathFilter, HeaderFilter and BodyFilter are relatively high-level and should cover all needs in ~90% of all cases. For more complicated setups one should fallback to the low-level variants, i.e. RequestFilter and ResponseFilter respectively (in conjunction with ForwardingHttpRequest/ForwardingHttpResponse).

You can configure filters like this:

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();

You can configure as many filters as you want - they will run consecutively.

JsonPath body filtering (experimental)

You can apply JSON Path filtering to JSON bodies. Here are some examples:

import static org.zalando.logbook.json.JsonPathBodyFilters.jsonPath;
import static java.util.regex.Pattern.compile;

Logbook logbook = Logbook.builder()
        .bodyFilter(jsonPath("$.password").delete())
        .bodyFilter(jsonPath("$.active").replace("unknown"))
        .bodyFilter(jsonPath("$.address").replace("X"))
        .bodyFilter(jsonPath("$.name").replace(compile("^(\\w).+"), "$1."))
        .bodyFilter(jsonPath("$.friends.*.name").replace(compile("^(\\w).+"), "$1."))
        .bodyFilter(jsonPath("$.grades.*").replace(1.0))
        .build();

Take a look at the following example, before and after filtering was applied:

Before
{
  "id": 1,
  "name": "Alice",
  "password": "s3cr3t",
  "active": true,
  "address": "Anhalter StraรŸe 17 13, 67278 Bockenheim an der WeinstraรŸe",
  "friends": [
    {
      "id": 2,
      "name": "Bob"
    },
    {
      "id": 3,
      "name": "Charlie"
    }
  ],
  "grades": {
    "Math": 1.0,
    "English": 2.2,
    "Science": 1.9,
    "PE": 4.0
  }
}
After
{
  "id": 1,
  "name": "Alice",
  "active": "unknown",
  "address": "XXX",
  "friends": [
    {
      "id": 2,
      "name": "B."
    },
    {
      "id": 3,
      "name": "C."
    }
  ],
  "grades": {
    "Math": 1.0,
    "English": 1.0,
    "Science": 1.0,
    "PE": 1.0
  }
}

Correlation

Logbook uses a correlation id to correlate requests and responses. This allows match-related requests and responses that would usually be located in different places in the log file.

If the default implementation of the correlation id is insufficient for your use case, you may provide a custom implementation:

Logbook logbook = Logbook.builder()
    .correlationId(new CustomCorrelationId())
    .build();

Formatting

Formatting defines how requests and responses will be transformed to strings basically. Formatters do not specify where requests and responses are logged to โ€” writers do that work.

Logbook comes with two different default formatters: HTTP and JSON.

HTTP

HTTP is the default formatting style, provided by the DefaultHttpLogFormatter. It is primarily designed to be used for local development and debugging, not for production use. This is because itโ€™s not as readily machine-readable as JSON.

Request
Incoming Request: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b
GET http://example.org/test HTTP/1.1
Accept: application/json
Host: localhost
Content-Type: text/plain

Hello world!
Response
Outgoing Response: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b
Duration: 25 ms
HTTP/1.1 200
Content-Type: application/json

{"value":"Hello world!"}
JSON

JSON is an alternative formatting style, provided by the JsonHttpLogFormatter. Unlike HTTP, it is primarily designed for production use โ€” parsers and log consumers can easily consume it.

Requires the following dependency:

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>logbook-json</artifactId>
</dependency>
Request
{
  "origin": "remote",
  "type": "request",
  "correlation": "2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b",
  "protocol": "HTTP/1.1",
  "sender": "127.0.0.1",
  "method": "GET",
  "uri": "http://example.org/test",
  "host": "example.org",
  "path": "/test",
  "scheme": "http",
  "port": null,
  "headers": {
    "Accept": ["application/json"],
    "Content-Type": ["text/plain"]
  },
  "body": "Hello world!"
}
Response
{
  "origin": "local",
  "type": "response",
  "correlation": "2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b",
  "duration": 25,
  "protocol": "HTTP/1.1",
  "status": 200,
  "headers": {
    "Content-Type": ["text/plain"]
  },
  "body": "Hello world!"
}

Note: Bodies of type application/json (and application/*+json) will be inlined into the resulting JSON tree. I.e., a JSON response body will not be escaped and represented as a string:

{
  "origin": "local",
  "type": "response",
  "correlation": "2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b",
  "duration": 25,
  "protocol": "HTTP/1.1",
  "status": 200,
  "headers": {
    "Content-Type": ["application/json"]
  },
  "body": {
    "greeting": "Hello, world!"
  }
}
Common Log Format

The Common Log Format (CLF) is a standardized text file format used by web servers when generating server log files. The format is supported via the CommonsLogFormatSink:

185.85.220.253 - - [02/Aug/2019:08:16:41 0000] "GET /search?q=zalando HTTP/1.1" 200 -
Extended Log Format

The Extended Log Format (ELF) is a standardised text file format, like Common Log Format (CLF), that is used by web servers when generating log files, but ELF files provide more information and flexibility. The format is supported via the ExtendedLogFormatSink. Also see W3C document.

Default fields:

date time c-ip s-dns cs-method cs-uri-stem cs-uri-query sc-status sc-bytes cs-bytes time-taken cs-protocol cs(User-Agent) cs(Cookie) cs(Referrer)

Default log output example:

2019-08-02 08:16:41 185.85.220.253 localhost POST /search ?q=zalando 200 21 20 0.125 HTTP/1.1 "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0" "name=value" "https://example.com/page?q=123"

Users may override default fields with their custom fields through the constructor of ExtendedLogFormatSink:

new ExtendedLogFormatSink(new DefaultHttpLogWriter(),"date time cs(Custom-Request-Header) sc(Custom-Response-Header)")

For Http header fields: cs(Any-Header) and sc(Any-Header), users could specify any headers they want to extract from the request.

Other supported fields are listed in the value of ExtendedLogFormatSink.Field, which can be put in the custom field expression.

cURL

cURL is an alternative formatting style, provided by the CurlHttpLogFormatter which will render requests as executable cURL commands. Unlike JSON, it is primarily designed for humans.

Request
curl -v -X GET 'http://localhost/test' -H 'Accept: application/json'
Response

See HTTP or provide own fallback for responses:

new CurlHttpLogFormatter(new JsonHttpLogFormatter());
Splunk

Splunk is an alternative formatting style, provided by the SplunkHttpLogFormatter which will render requests and response as key-value pairs.

Request
origin=remote type=request correlation=2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b protocol=HTTP/1.1 sender=127.0.0.1 method=POST uri=http://example.org/test host=example.org scheme=http port=null path=/test headers={Accept=[application/json], Content-Type=[text/plain]} body=Hello world!

Response
origin=local type=response correlation=2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b duration=25 protocol=HTTP/1.1 status=200 headers={Content-Type=[text/plain]} body=Hello world!

Writing

Writing defines where formatted requests and responses are written to. Logbook comes with three implementations: Logger, Stream and Chunking.

Logger

By default, requests and responses are logged with an slf4j logger that uses the org.zalando.logbook.Logbook category and the log level trace. This can be customized:

Logbook logbook = Logbook.builder()
    .sink(new DefaultSink(
            new DefaultHttpLogFormatter(),
            new DefaultHttpLogWriter()
    ))
    .build();
Stream

An alternative implementation is to log requests and responses to a PrintStream, e.g. System.out or System.err. This is usually a bad choice for running in production, but can sometimes be useful for short-term local development and/or investigation.

Logbook logbook = Logbook.builder()
    .sink(new DefaultSink(
            new DefaultHttpLogFormatter(),
            new StreamHttpLogWriter(System.err)
    ))
    .build();
Chunking

The ChunkingSink will split long messages into smaller chunks and will write them individually while delegating to another sink:

Logbook logbook = Logbook.builder()
    .sink(new ChunkingSink(sink, 1000))
    .build();

Sink

The combination of HttpLogFormatter and HttpLogWriter suits most use cases well, but it has limitations. Implementing the Sink interface directly allows for more sophisticated use cases, e.g. writing requests/responses to a structured persistent storage like a database.

Multiple sinks can be combined into one using the CompositeSink.

Servlet

Youโ€™ll have to register the LogbookFilter as a Filter in your filter chain โ€” either in your web.xml file (please note that the xml approach will use all the defaults and is not configurable):

<filter>
    <filter-name>LogbookFilter</filter-name>
    <filter-class>org.zalando.logbook.servlet.LogbookFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>LogbookFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

or programmatically, via the ServletContext:

context.addFilter("LogbookFilter", new LogbookFilter(logbook))
    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*"); 

Beware: The ERROR dispatch is not supported. You're strongly advised to produce error responses within the REQUEST or ASNYC dispatch.

The LogbookFilter will, by default, treat requests with a application/x-www-form-urlencoded body not different from any other request, i.e you will see the request body in the logs. The downside of this approach is that you won't be able to use any of the HttpServletRequest.getParameter*(..) methods. See issue #94 for some more details.

Form Requests

As of Logbook 1.5.0, you can now specify one of three strategies that define how Logbook deals with this situation by using the logbook.servlet.form-request system property:

Value Pros Cons
body (default) Body is logged Downstream code can not use getParameter*()
parameter Body is logged (but it's reconstructed from parameters) Downstream code can not use getInputStream()
off Downstream code can decide whether to use getInputStream() or getParameter*() Body is not logged

Security

Secure applications usually need a slightly different setup. You should generally avoid logging unauthorized requests, especially the body, because it quickly allows attackers to flood your logfile โ€” and, consequently, your precious disk space. Assuming that your application handles authorization inside another filter, you have two choices:

  • Don't log unauthorized requests
  • Log unauthorized requests without the request body

You can easily achieve the former setup by placing the LogbookFilter after your security filter. The latter is a little bit more sophisticated. Youโ€™ll need two LogbookFilter instances โ€” one before your security filter, and one after it:

context.addFilter("SecureLogbookFilter", new SecureLogbookFilter(logbook))
    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*");
context.addFilter("securityFilter", new SecurityFilter())
    .addMappingForUrlPatterns(EnumSet.of(REQUEST), true, "/*");
context.addFilter("LogbookFilter", new LogbookFilter(logbook))
    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*");

The first logbook filter will log unauthorized requests only. The second filter will log authorized requests, as always.

HTTP Client

The logbook-httpclient module contains both an HttpRequestInterceptor and an HttpResponseInterceptor to use with the HttpClient:

CloseableHttpClient client = HttpClientBuilder.create()
        .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))
        .addInterceptorFirst(new LogbookHttpResponseInterceptor())
        .build();

Since the LogbookHttpResponseInterceptor is incompatible with the HttpAsyncClient there is another way to log responses:

CloseableHttpAsyncClient client = HttpAsyncClientBuilder.create()
        .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))
        .build();
        
// and then wrap your response consumer
client.execute(producer, new LogbookHttpAsyncResponseConsumer<>(consumer), callback)

HTTP Client 5

The logbook-httpclient5 module contains an ExecHandler to use with the HttpClient:

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

The Handler should be added first, such that a compression is performed after logging and decompression is performed before logging.

To avoid a breaking change, there is also an HttpRequestInterceptor and an HttpResponseInterceptor to use with the HttpClient, which works fine as long as compression (or other ExecHandlers) is not used:

CloseableHttpClient client = HttpClientBuilder.create()
        .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))
        .addResponseInterceptorFirst(new LogbookHttpResponseInterceptor())
        .build();

Since the LogbookHttpResponseInterceptor is incompatible with the HttpAsyncClient there is another way to log responses:

CloseableHttpAsyncClient client = HttpAsyncClientBuilder.create()
        .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))
        .build();
        
// and then wrap your response consumer
client.execute(producer, new LogbookHttpAsyncResponseConsumer<>(consumer), callback)

JAX-RS 2.x and 3.x (aka Jakarta RESTful Web Services)

Note

Support for JAX-RS 2.x

JAX-RS 2.x (legacy) support was dropped in Logbook 3.0 to 3.6.

As of Logbook 3.7, JAX-RS 2.x support is back.

However, you need to add the javax classifier to use the proper Logbook module:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-jaxrs</artifactId>
    <version>${logbook.version}</version>
    <classifier>javax</classifier>
</dependency>

You should also make sure that the following dependencies are on your classpath. By default, logbook-jaxrs imports jersey-client 3.x, which is not compatible with JAX-RS 2.x:

The logbook-jaxrs module contains:

A LogbookClientFilter to be used for applications making HTTP requests

client.register(new LogbookClientFilter(logbook));

A LogbookServerFilter for be used with HTTP servers

resourceConfig.register(new LogbookServerFilter(logbook));

JDK HTTP Server

The logbook-jdkserver module provides support for JDK HTTP server and contains:

A LogbookFilter to be used with the builtin server

httpServer.createContext(path,handler).getFilters().add(new LogbookFilter(logbook))

Netty

The logbook-netty module contains:

A LogbookClientHandler to be used with an HttpClient:

HttpClient httpClient =
        HttpClient.create()
                .doOnConnected(
                        (connection -> connection.addHandlerLast(new LogbookClientHandler(logbook)))
                );

A LogbookServerHandler for use used with an HttpServer:

HttpServer httpServer =
        HttpServer.create()
                .doOnConnection(
                        connection -> connection.addHandlerLast(new LogbookServerHandler(logbook))
                );

Spring WebFlux

Users of Spring WebFlux can pick any of the following options:

  • Programmatically create a NettyWebServer (passing an HttpServer)
  • Register a custom NettyServerCustomizer
  • Programmatically create a ReactorClientHttpConnector (passing an HttpClient)
  • Register a custom WebClientCustomizer
  • Use separate connector-independent module logbook-spring-webflux

Micronaut

Users of Micronaut can follow the official docs on how to integrate Logbook with Micronaut.

โš ๏ธ Even though Quarkus and Vert.x use Netty under the hood, unfortunately neither of them allows accessing or customizing it (yet).

OkHttp v2.x

The logbook-okhttp2 module contains an Interceptor to use with version 2.x of the OkHttpClient:

OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new LogbookInterceptor(logbook));

If you're expecting gzip-compressed responses you need to register our GzipInterceptor in addition. The transparent gzip support built into OkHttp will run after any network interceptor which forces logbook to log compressed binary responses.

OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new LogbookInterceptor(logbook));
client.networkInterceptors().add(new GzipInterceptor());

OkHttp v3.x

The logbook-okhttp module contains an Interceptor to use with version 3.x of the OkHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(new LogbookInterceptor(logbook))
        .build();

If you're expecting gzip-compressed responses you need to register our GzipInterceptor in addition. The transparent gzip support built into OkHttp will run after any network interceptor which forces logbook to log compressed binary responses.

OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(new LogbookInterceptor(logbook))
        .addNetworkInterceptor(new GzipInterceptor())
        .build();

Ktor

The logbook-ktor-client module contains:

A LogbookClient to be used with an HttpClient:

private val client = HttpClient(CIO) {
    install(LogbookClient) {
        logbook = logbook
    }
}

The logbook-ktor-server module contains:

A LogbookServer to be used with an Application:

private val server = embeddedServer(CIO) {
    install(LogbookServer) {
        logbook = logbook
    }
}

Alternatively, you can use logbook-ktor, which ships both logbook-ktor-client and logbook-ktor-server modules.

Spring

The logbook-spring module contains a ClientHttpRequestInterceptor to use with RestTemplate:

    LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor(logbook);
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getInterceptors().add(interceptor);

Spring Boot Starter

Logbook comes with a convenient auto configuration for Spring Boot users. It sets up all of the following parts automatically with sensible defaults:

  • Servlet filter
  • Second Servlet filter for unauthorized requests (if Spring Security is detected)
  • Header-/Parameter-/Body-Filters
  • HTTP-/JSON-style formatter
  • Logging writer

Instead of declaring a dependency to logbook-core declare one to the Spring Boot Starter:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>${logbook.version}</version>
</dependency>

Every bean can be overridden and customized if needed, e.g. like this:

@Bean
public BodyFilter bodyFilter() {
    return merge(
            defaultValue(), 
            replaceJsonStringProperty(singleton("secret"), "XXX"));
}

Please refer to LogbookAutoConfiguration or the following table to see a list of possible integration points:

Type Name Default
FilterRegistrationBean secureLogbookFilter Based on LogbookFilter
FilterRegistrationBean logbookFilter Based on LogbookFilter
Logbook Based on condition, filters, formatter and writer
Predicate<HttpRequest> requestCondition No filter; is later combined with logbook.exclude and logbook.exclude
HeaderFilter Based on logbook.obfuscate.headers
PathFilter Based on logbook.obfuscate.paths
QueryFilter Based on logbook.obfuscate.parameters
BodyFilter BodyFilters.defaultValue(), see filtering
RequestFilter RequestFilters.defaultValue(), see filtering
ResponseFilter ResponseFilters.defaultValue(), see filtering
Strategy DefaultStrategy
AttributeExtractor NoOpAttributeExtractor
Sink DefaultSink
HttpLogFormatter JsonHttpLogFormatter
HttpLogWriter DefaultHttpLogWriter

Multiple filters are merged into one.

Autoconfigured beans from logbook-spring

Some classes from logbook-spring are included in the auto configuration.

You can autowire LogbookClientHttpRequestInterceptor with code like:

private final RestTemplate restTemplate;
MyClient(RestTemplateBuilder builder, LogbookClientHttpRequestInterceptor interceptor){
  this.restTemplate = builder
    .additionalInterceptors(interceptor)
    .build();
}

Configuration

The following tables show the available configuration (sorted alphabetically):

Configuration Description Default
logbook.attribute-extractors List of AttributeExtractors, including configurations such as type (currently JwtFirstMatchingClaimExtractor or JwtAllMatchingClaimsExtractor), claim-names and claim-key. []
logbook.filter.enabled Enable the LogbookFilter true
logbook.filter.form-request-mode Determines how form requests are handled body
logbook.filters.body.default-enabled Enables/disables default body filters that are collected by java.util.ServiceLoader true
logbook.format.style Formatting style (http, json, curl or splunk) json
logbook.httpclient.decompress-response Enables/disables additional decompression process for HttpClient with gzip encoded body (to logging purposes only). This means extra decompression and possible performance impact. false (disabled)
logbook.minimum-status Minimum status to enable logging (status-at-least and body-only-if-status-at-least) 400
logbook.obfuscate.headers List of header names that need obfuscation [Authorization]
logbook.obfuscate.json-body-fields List of JSON body fields to be obfuscated []
logbook.obfuscate.parameters List of parameter names that need obfuscation [access_token]
logbook.obfuscate.paths List of paths that need obfuscation. Check Filtering for syntax. []
logbook.obfuscate.replacement A value to be used instead of an obfuscated one XXX
logbook.predicate.include Include only certain paths and methods (if defined) []
logbook.predicate.exclude Exclude certain paths and methods (overrides logbook.preidcates.include) []
logbook.secure-filter.enabled Enable the SecureLogbookFilter true
logbook.strategy Strategy (default, status-at-least, body-only-if-status-at-least, without-body) default
logbook.write.chunk-size Splits log lines into smaller chunks of size up-to chunk-size. 0 (disabled)
logbook.write.max-body-size Truncates the body up to max-body-size and appends ....
โš ๏ธ Logbook will still buffer the full body, if the request is eligible for logging, regardless of the logbook.write.max-body-size value
-1 (disabled)
Example configuration
logbook:
  predicate:
    include:
      - path: /api/**
        methods: 
         - GET
         - POST
      - path: /actuator/**
    exclude:
      - path: /actuator/health
      - path: /api/admin/**
        methods: 
         - POST
  filter.enabled: true
  secure-filter.enabled: true
  format.style: http
  strategy: body-only-if-status-at-least
  minimum-status: 400
  obfuscate:
    headers:
      - Authorization
      - X-Secret
    parameters:
      - access_token
      - password
  write:
    chunk-size: 1000
  attribute-extractors:
    - type: JwtFirstMatchingClaimExtractor
      claim-names: [ "sub", "subject" ]
      claim-key: Principal
    - type: JwtAllMatchingClaimsExtractor
      claim-names: [ "sub", "iat" ]

logstash-logback-encoder

For basic Logback configuraton

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

configure Logbook with a LogstashLogbackSink

HttpLogFormatter formatter = new JsonHttpLogFormatter();
LogstashLogbackSink sink = new LogstashLogbackSink(formatter);

for outputs like

{
  "@timestamp" : "2019-03-08T09:37:46.239+01:00",
  "@version" : "1",
  "message" : "GET http://localhost/test?limit=1",
  "logger_name" : "org.zalando.logbook.Logbook",
  "thread_name" : "main",
  "level" : "TRACE",
  "level_value" : 5000,
  "http" : {
     // logbook request/response contents
  }
}

Customizing default Logging Level

You have the flexibility to customize the default logging level by initializing LogstashLogbackSink with a specific level. For instance:

LogstashLogbackSink sink = new LogstashLogbackSink(formatter, Level.INFO); 

Known Issues

  1. The Logbook Servlet Filter interferes with downstream code using getWriter and/or getParameter*(). See Servlet for more details.
  2. The Logbook Servlet Filter does NOT support ERROR dispatch. You're strongly encouraged to not use it to produce error responses.

Getting Help with Logbook

If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.

Getting Involved/Contributing

To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For more details, check the contribution guidelines.

Alternatives

Credits and References

Creative Commons (Attribution-Share Alike 3.0 Unported Grand Turk, a replica of a three-masted 6th rate frigate from Nelson's days - logbook and charts by JoJan is licensed under a Creative Commons (Attribution-Share Alike 3.0 Unported).

More Repositories

1

patroni

A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes
Python
6,267
star
2

postgres-operator

Postgres operator creates and manages PostgreSQL clusters running in Kubernetes
Go
3,686
star
3

skipper

An HTTP router and reverse proxy for service composition, including use cases like Kubernetes Ingress
Go
3,088
star
4

restful-api-guidelines

A model set of guidelines for RESTful APIs and Events, created by Zalando
CSS
2,605
star
5

zalenium

A flexible and scalable container based Selenium Grid with video recording, live preview, basic auth & dashboard.
Java
2,385
star
6

SwiftMonkey

A framework for doing randomised UI testing of iOS apps
Swift
1,947
star
7

tailor

A streaming layout service for front-end microservices
JavaScript
1,728
star
8

tech-radar

Visualizing our technology choices
1,581
star
9

spilo

Highly available elephant herd: HA PostgreSQL cluster using Docker
Python
1,225
star
10

intellij-swagger

A plugin to help you easily edit Swagger and OpenAPI specification files inside IntelliJ IDEA
Java
1,172
star
11

problem-spring-web

A library for handling Problems in Spring Web MVC
Java
1,031
star
12

nakadi

A distributed event bus that implements a RESTful API abstraction on top of Kafka-like queues
Java
928
star
13

zally

A minimalistic, simple-to-use API linter
Kotlin
903
star
14

problem

A Java library that implements application/problem+json
Java
869
star
15

zalando-howto-open-source

Open Source guidance from Zalando, Europe's largest online fashion platform
799
star
16

go-keyring

Cross-platform keyring interface for Go
Go
689
star
17

gin-oauth2

Middleware for Gin Framework users who also want to use OAuth2
Go
579
star
18

zappr

An agent that enforces guidelines for your GitHub repositories
JavaScript
542
star
19

pg_view

Get a detailed, real-time view of your PostgreSQL database and system metrics
Python
494
star
20

engineering-principles

Our guidelines for building new applications and managing legacy systems
376
star
21

gulp-check-unused-css

A build tool for checking your HTML templates for unused CSS classes
CSS
359
star
22

zmon

Real-time monitoring of critical metrics & KPIs via elegant dashboards, Grafana3 visualizations & more
Shell
355
star
23

expan

Open-source Python library for statistical analysis of randomised control trials (A/B tests)
Python
325
star
24

PGObserver

A battle-tested, flexible & comprehensive monitoring solution for your PostgreSQL databases
Python
316
star
25

riptide

Client-side response routing for Spring
Java
292
star
26

jackson-datatype-money

Extension module to properly support datatypes of javax.money
Java
240
star
27

grafter

Grafter is a library to configure and wire Scala applications
Scala
240
star
28

opentracing-toolbox

Best-of-breed OpenTracing utilities, instrumentations and extensions
Java
180
star
29

elm-street-404

A fun WebGL game built with Elm
Elm
176
star
30

tokens

Java library for conveniently verifying and storing OAuth 2.0 service access tokens
Java
169
star
31

innkeeper

Simple route management API for Skipper
Scala
166
star
32

public-presentations

List of public talks by Zalando Tech: meetup presentations, recorded conference talks, slides
165
star
33

python-nsenter

Enter kernel namespaces from Python
Python
139
star
34

faux-pas

A library that simplifies error handling for Functional Programming in Java
Java
132
star
35

dress-code

The official style guide and framework for all Zalando Brand Solutions products
CSS
129
star
36

beard

A lightweight, logicless templating engine, written in Scala and inspired by Mustache
Scala
121
star
37

friboo

Utility library for writing microservices in Clojure, with support for Swagger and OAuth
Clojure
117
star
38

spring-cloud-config-aws-kms

Spring Cloud Config add-on that provides encryption via AWS KMS
Java
99
star
39

zalando.github.io

Open Source Documentation and guidelines for Zalando developers
HTML
86
star
40

failsafe-actuator

Endpoint library for the failsafe framework
Java
52
star
41

package-build

A toolset for building system packages using Docker and fpm-cookery
Ruby
35
star
42

ghe-backup

Github Enterprise backup at ZalandoTech (Kubernetes, AWS, Docker)
Shell
30
star
43

rds-health

discover anomalies, performance issues and optimization within AWS RDS
Go
26
star
44

backstage-plugin-api-linter

API Linter is a quality assurance tool that checks the compliance of API's specifications to Zalando's API rules.
TypeScript
12
star
45

.github

Standard github health files
1
star