{"id":13485905,"url":"https://github.com/zalando/logbook","last_synced_at":"2026-01-11T17:42:09.087Z","repository":{"id":2270400,"uuid":"42457394","full_name":"zalando/logbook","owner":"zalando","description":"An extensible Java library for HTTP request and response logging","archived":false,"fork":false,"pushed_at":"2025-04-18T04:36:31.000Z","size":6764,"stargazers_count":1914,"open_issues_count":31,"forks_count":265,"subscribers_count":67,"default_branch":"main","last_synced_at":"2025-04-24T01:56:04.361Z","etag":null,"topics":["client-side","http-logs","java","logbook","logger","logging","logs","monitoring","observability","plugin-extension","request-response","server-side","spring-boot","spring-boot-starter"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zalando.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-09-14T15:29:12.000Z","updated_at":"2025-04-23T14:55:38.000Z","dependencies_parsed_at":"2024-01-03T01:21:44.942Z","dependency_job_id":"81315c16-503f-4f24-be68-f2aab63a084b","html_url":"https://github.com/zalando/logbook","commit_stats":{"total_commits":1721,"total_committers":93,"mean_commits":18.50537634408602,"dds":0.6618245206275422,"last_synced_commit":"0f9abc04824049156a763aa7dcbba2278a81dd92"},"previous_names":["zalando/spring-web-logging"],"tags_count":105,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalando%2Flogbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalando%2Flogbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalando%2Flogbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalando%2Flogbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zalando","download_url":"https://codeload.github.com/zalando/logbook/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990460,"owners_count":21995774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client-side","http-logs","java","logbook","logger","logging","logs","monitoring","observability","plugin-extension","request-response","server-side","spring-boot","spring-boot-starter"],"created_at":"2024-07-31T18:00:33.854Z","updated_at":"2026-01-11T17:42:09.079Z","avatar_url":"https://github.com/zalando.png","language":"Java","readme":"# Logbook: HTTP request and response logging\r\n\r\n[![Logbook](docs/logbook.jpg)](#attributions)\r\n\r\n[![Stability: Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)\r\n![Build Status](https://github.com/zalando/logbook/workflows/build/badge.svg)\r\n[![Coverage Status](https://img.shields.io/coveralls/zalando/logbook/main.svg)](https://coveralls.io/r/zalando/logbook)\r\n[![Javadoc](http://javadoc.io/badge/org.zalando/logbook-core.svg)](http://www.javadoc.io/doc/org.zalando/logbook-core)\r\n[![Release](https://img.shields.io/github/release/zalando/logbook.svg)](https://github.com/zalando/logbook/releases)\r\n[![Maven Central](https://img.shields.io/maven-central/v/org.zalando/logbook-parent.svg)](https://maven-badges.herokuapp.com/maven-central/org.zalando/logbook-parent)\r\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/zalando/logbook/main/LICENSE)\r\n\r\n\u003e **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.\r\n\r\n**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\r\ndevelopers 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\r\nrequirements or investigating individual historic traffic issues.\r\n \r\nLogbook 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\r\nlibrary/framework/etc. to it.\r\n\r\n**Contents:**\n\n\u003c!-- toc --\u003e\n\n- [Features](#features)\n- [Dependencies](#dependencies)\n  * [Jackson Version Support](#jackson-version-support)\n- [Installation](#installation)\n- [Usage](#usage)\n  * [Strategy](#strategy)\n  * [Attribute Extractor](#attribute-extractor)\n  * [Phases](#phases)\n  * [Servlet](#servlet)\n  * [HTTP Client](#http-client)\n  * [HTTP Client 5](#http-client-5)\n  * [JAX-RS 3.x (aka Jakarta RESTful Web Services)](#jax-rs-3x-aka-jakarta-restful-web-services)\n  * [JDK HTTP Server](#jdk-http-server)\n  * [Netty](#netty)\n  * [OkHttp v2.x](#okhttp-v2x)\n  * [OkHttp v3.x](#okhttp-v3x)\n  * [Ktor](#ktor)\n  * [Spring](#spring)\n  * [Spring Boot Starter](#spring-boot-starter)\n  * [logstash-logback-encoder](#logstash-logback-encoder)\n- [Known Issues](#known-issues)\n- [Getting Help with Logbook](#getting-help-with-logbook)\n- [Getting Involved/Contributing](#getting-involvedcontributing)\n- [Alternatives](#alternatives)\n- [Credits and References](#credits-and-references)\n\n\u003c!-- tocstop --\u003e\n\n## Features\r\n\r\n- **Logging**: of HTTP requests and responses, including the body; partial logging (no body) for unauthorized requests\r\n- **Customization**: of logging format, logging destination, and conditions that request to log\r\n- **Support**: for Servlet containers, Apache's HTTP client, Square's OkHttp, and (via its elegant API) other frameworks\r\n- Optional obfuscation of sensitive data\r\n- [Spring Boot](http://projects.spring.io/spring-boot/) Auto Configuration\r\n- [Scalyr](docs/scalyr.md) compatible\r\n- Sensible defaults\r\n\r\n## Dependencies\r\n\r\n- **Java 17 or higher** (required - Spring 7 / Spring Boot 4 and JAX-RS 3.x)\r\n- Any build tool using Maven Central, or direct download\r\n- Servlet Container (optional)\r\n- Apache HTTP Client 4.x **or 5.x** (optional)\r\n- JAX-RS 3.x (aka Jakarta RESTful Web Services) Client and Server (optional)\r\n- Netty 4.x (optional)\r\n- OkHttp 2.x **or 3.x** (optional)\r\n- Spring **7.x** (optional)\r\n- Spring Boot **4.x** (optional)\r\n- Ktor (optional)\r\n- logstash-logback-encoder 5.x (optional)\r\n- **Jackson 2.x or 3.x** (optional, required for JSON formatting)\r\n\r\n### Jackson Version Support\r\n\r\nLogbook's core functionality works without Jackson. JSON formatting (logbook-json) and JWT attribute extraction (JwtClaimsExtractor, etc.) are **optional and support both Jackson 2 and Jackson 3 automatically**.\r\n\r\n**How it works:**\r\n\r\nLogbook detects which Jackson version is available on the classpath and uses the appropriate implementation:\r\n- If **Jackson 2** is available, Logbook uses Jackson 2 implementations (formatters, JWT extractors, and JSON compacting)\r\n- If **Jackson 3** is available, Logbook uses Jackson 3 implementations (formatters, JWT extractors, and JSON compacting)\r\n- If **both** are available, Jackson 3 is preferred\r\n- If **neither** is available, JSON formatting is disabled but core logging still works\r\n\r\n**For Spring Boot 3.x:**\r\n- Jackson 2 is provided by default via `spring-boot-starter-web` or `spring-boot-starter-jackson`\r\n- JSON formatting works out of the box with no additional configuration\r\n\r\n**For Spring Boot 4.x:**\r\n- Jackson 3 is provided by default via `spring-boot-starter-jackson`\r\n- JSON formatting works out of the box with Jackson 3\r\n- If you want to use Jackson 2 instead, add it explicitly:\r\n  ```xml\r\n  \u003cdependency\u003e\r\n      \u003cgroupId\u003ecom.fasterxml.jackson.core\u003c/groupId\u003e\r\n      \u003cartifactId\u003ejackson-databind\u003c/artifactId\u003e\r\n      \u003cversion\u003e2.X.X\u003c/version\u003e\r\n  \u003c/dependency\u003e\r\n  ```\r\n\r\n**Manual configuration (if not using Spring Boot):**\r\n\r\nAdd either Jackson 2 or Jackson 3 (or both):\r\n\r\nJackson 2:\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003ecom.fasterxml.jackson.core\u003c/groupId\u003e\r\n    \u003cartifactId\u003ejackson-databind\u003c/artifactId\u003e\r\n    \u003cversion\u003e2.X.X\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\nJackson 3:\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003etools.jackson.core\u003c/groupId\u003e\r\n    \u003cartifactId\u003ejackson-databind\u003c/artifactId\u003e\r\n    \u003cversion\u003e3.X.X\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\n## Installation\r\n\r\nAdd the following dependency to your project:\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-core\u003c/artifactId\u003e\r\n    \u003cversion\u003e${logbook.version}\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\nAdditional modules/artifacts of Logbook always share the same version number.\r\n\r\nAlternatively, you can import our *bill of materials*...\r\n\r\n```xml\r\n\u003cdependencyManagement\u003e\r\n  \u003cdependencies\u003e\r\n    \u003cdependency\u003e\r\n      \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n      \u003cartifactId\u003elogbook-bom\u003c/artifactId\u003e\r\n      \u003cversion\u003e${logbook.version}\u003c/version\u003e\r\n      \u003ctype\u003epom\u003c/type\u003e\r\n      \u003cscope\u003eimport\u003c/scope\u003e\r\n    \u003c/dependency\u003e\r\n  \u003c/dependencies\u003e\r\n\u003c/dependencyManagement\u003e\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003e... which allows you to omit versions:\u003c/summary\u003e\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-core\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-httpclient\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-jaxrs\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-json\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-netty\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-okhttp\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-okhttp2\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-servlet\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-spring-boot-starter\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-ktor-common\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-ktor-client\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-ktor-server\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-ktor\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-logstash\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\u003c/details\u003e\r\n\r\nThe logbook logger must be configured to trace level in order to log the requests and responses. With Spring Boot (using Logback) this can be accomplished by adding the following line to your `application.properties`:\r\n\r\n```\r\nlogging.level.org.zalando.logbook: TRACE\r\n```\r\n\r\n## Usage\r\n\r\nAll integrations require an instance of `Logbook` which holds all configuration and wires all necessary parts together.\r\nYou can either create one using all the defaults:\r\n\r\n```java\r\nLogbook logbook = Logbook.create();\r\n```\r\nor create a customized version using the `LogbookBuilder`:\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .condition(new CustomCondition())\r\n    .queryFilter(new CustomQueryFilter())\r\n    .pathFilter(new CustomPathFilter())\r\n    .headerFilter(new CustomHeaderFilter())\r\n    .bodyFilter(new CustomBodyFilter())\r\n    .requestFilter(new CustomRequestFilter())\r\n    .responseFilter(new CustomResponseFilter())\r\n    .sink(new DefaultSink(\r\n            new CustomHttpLogFormatter(),\r\n            new CustomHttpLogWriter()\r\n    ))\r\n    .build();\r\n```\r\n\r\n### Strategy\r\n\r\nLogbook used to have a very rigid strategy how to do request/response logging:\r\n\r\n- Requests/responses are logged separately\r\n- Requests/responses are logged soon as possible\r\n- Requests/responses are logged as a pair or not logged at all  \r\n  (i.e. no partial logging of traffic)\r\n\r\nSome of those restrictions could be mitigated with custom [`HttpLogWriter`](#writing)\r\nimplementations, but they were never ideal.\r\n\r\nStarting with version 2.0 Logbook now comes with a [Strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern)\r\nat its core. Make sure you read the documentation of the [`Strategy`](logbook-api/src/main/java/org/zalando/logbook/Strategy.java)\r\ninterface to understand the implications.\r\n\r\nLogbook comes with some built-in strategies:\r\n\r\n- [`BodyOnlyIfStatusAtLeastStrategy`](logbook-core/src/main/java/org/zalando/logbook/core/BodyOnlyIfStatusAtLeastStrategy.java)\r\n- [`StatusAtLeastStrategy`](logbook-core/src/main/java/org/zalando/logbook/core/StatusAtLeastStrategy.java)\r\n- [`WithoutBodyStrategy`](logbook-core/src/main/java/org/zalando/logbook/core/WithoutBodyStrategy.java)\r\n\r\n### Attribute Extractor\r\nStarting with version 3.4.0, Logbook is equipped with a feature called *Attribute Extractor*. Attributes are basically a\r\nlist of key/value pairs that can be extracted from request and/or response, and logged with them. The idea was sprouted\r\nfrom [issue 381](https://github.com/zalando/logbook/issues/381), where a feature was requested to extract the subject\r\nclaim from JWT tokens in the authorization header.\r\n\r\nThe `AttributeExtractor` interface has two `extract` methods: One that can extract attributes from the request only, and\r\none that has both request and response at its avail. The both return an instance of the `HttpAttributes` class, which is\r\nbasically a fancy `Map\u003cString, Object\u003e`. Notice that since the map values are of type `Object`, they should have a \r\nproper `toString()` method in order for them to appear in the logs in a meaningful way. Alternatively, log formatters \r\ncan work around this by implementing their own serialization logic. For instance, the built-in log formatter \r\n`JsonHttpLogFormatter` uses `ObjectMapper` to serialize the values.\r\n\r\nHere is an example:\r\n\r\n```java\r\nfinal class OriginExtractor implements AttributeExtractor {\r\n\r\n  @Override\r\n  public HttpAttributes extract(final HttpRequest request) {\r\n    return HttpAttributes.of(\"origin\", request.getOrigin());\r\n  }\r\n    \r\n}\r\n```\r\n\r\nLogbook must then be created by registering this attribute extractor:\r\n\r\n```java\r\nfinal Logbook logbook = Logbook.builder()\r\n        .attributeExtractor(new OriginExtractor())\r\n        .build();\r\n```\r\n\r\nThis will result in request logs to include something like:\r\n```text\r\n\"attributes\":{\"origin\":\"LOCAL\"}\r\n```\r\n\r\nFor more advanced examples, look at the `JwtFirstMatchingClaimExtractor` and `JwtAllMatchingClaimsExtractor` classes.\r\nThe former extracts the first claim matching a list of claim names from the request JWT token.\r\nThe latter extracts all claims matching a list of claim names from the request JWT token.\r\n\r\nIf you require to incorporate multiple `AttributeExtractor`s, you can use the class `CompositeAttributeExtractor`:\r\n\r\n```java\r\nfinal List\u003cAttributeExtractor\u003e extractors = List.of(\r\n    extractor1,\r\n    extractor2,\r\n    extractor3\r\n);\r\n\r\nfinal Logbook logbook = Logbook.builder()\r\n        .attributeExtractor(new CompositeAttributeExtractor(extractors))\r\n        .build();\r\n```\r\n\r\n### Phases\r\n\r\nLogbook works in several different phases:\r\n\r\n1. [Conditional](#conditional),\r\n2. [Filtering](#filtering),\r\n3. [Formatting](#formatting) and\r\n4. [Writing](#writing)\r\n\r\nEach phase is represented by one or more interfaces that can be used for customization. Every phase has a sensible default.\r\n\r\n#### Conditional\r\n\r\nLogging 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*\r\nrequests from a load balancer, or any request to management endpoints typically issued by developers.\r\n\r\nDefining 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\r\npredefined predicates:\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .condition(exclude(\r\n        requestTo(\"/health\"),\r\n        requestTo(\"/admin/**\"),\r\n        contentType(\"application/octet-stream\"),\r\n        header(\"X-Secret\", newHashSet(\"1\", \"true\")::contains)))\r\n    .build();\r\n```\r\n\r\nExclusion patterns, e.g. `/admin/**`, are loosely following [Ant's style of path patterns](https://ant.apache.org/manual/dirtasks.html#patterns)\r\nwithout taking the the query string of the URL into consideration.\r\n\r\n#### Filtering\r\n\r\nThe goal of *Filtering* is to prevent the logging of certain sensitive parts of HTTP requests and responses. This\r\nusually includes the *Authorization* header, but could also apply to certain plaintext query or form parameters —\r\ne.g. *password*.\r\n\r\nLogbook supports different types of filters:\r\n\r\n| Type             | Operates on                    | Applies to | Default                                                                                            |\r\n|------------------|--------------------------------|------------|----------------------------------------------------------------------------------------------------|\r\n| `QueryFilter`    | Query string                   | request    | `access_token`                                                                                     |\r\n| `PathFilter`     | Path                           | request    | n/a                                                                                                |\r\n| `HeaderFilter`   | Header (single key-value pair) | both       | `Authorization`                                                                                    |\r\n| `BodyFilter`     | Content-Type and body          | both       | json: `access_token` and `refresh_token`\u003cbr\u003e form: `client_secret`, `password` and `refresh_token` |\r\n| `RequestFilter`  | `HttpRequest`                  | request    | Replace binary, multipart and stream bodies.                                                       |\r\n| `ResponseFilter` | `HttpResponse`                 | response   | Replace binary, multipart and stream bodies.                                                       |\r\n\r\n`QueryFilter`, `PathFilter`, `HeaderFilter` and `BodyFilter` are relatively high-level and should cover all needs in ~90% of all\r\ncases. For more complicated setups one should fallback to the low-level variants, i.e. `RequestFilter` and `ResponseFilter`\r\nrespectively (in conjunction with `ForwardingHttpRequest`/`ForwardingHttpResponse`).\r\n\r\nYou can configure filters like this:\r\n\r\n```java\r\nimport static org.zalando.logbook.core.HeaderFilters.authorization;\r\nimport static org.zalando.logbook.core.HeaderFilters.eachHeader;\r\nimport static org.zalando.logbook.core.QueryFilters.accessToken;\r\nimport static org.zalando.logbook.core.QueryFilters.replaceQuery;\r\n\r\nLogbook logbook = Logbook.builder()\r\n        .requestFilter(RequestFilters.replaceBody(message -\u003e contentType(\"audio/*\").test(message) ? \"mmh mmh mmh mmh\" : null))\r\n        .responseFilter(ResponseFilters.replaceBody(message -\u003e contentType(\"*/*-stream\").test(message) ? \"It just keeps going and going...\" : null))\r\n        .queryFilter(accessToken())\r\n        .queryFilter(replaceQuery(\"password\", \"\u003csecret\u003e\"))\r\n        .headerFilter(authorization())\r\n        .headerFilter(eachHeader(\"X-Secret\"::equalsIgnoreCase, \"\u003csecret\u003e\"))\r\n        .build();\r\n```\r\n\r\nYou can configure as many filters as you want - they will run consecutively.\r\n\r\n##### JsonPath body filtering (experimental)\r\n\r\nYou can apply [JSON Path](https://github.com/json-path/JsonPath) filtering to JSON bodies.\r\nHere are some examples:\r\n\r\n```java\r\nimport static org.zalando.logbook.json.JsonPathBodyFilters.jsonPath;\r\nimport static java.util.regex.Pattern.compile;\r\n\r\nLogbook logbook = Logbook.builder()\r\n        .bodyFilter(jsonPath(\"$.password\").delete())\r\n        .bodyFilter(jsonPath(\"$.active\").replace(\"unknown\"))\r\n        .bodyFilter(jsonPath(\"$.address\").replace(\"X\"))\r\n        .bodyFilter(jsonPath(\"$.name\").replace(compile(\"^(\\\\w).+\"), \"$1.\"))\r\n        .bodyFilter(jsonPath(\"$.friends.*.name\").replace(compile(\"^(\\\\w).+\"), \"$1.\"))\r\n        .bodyFilter(jsonPath(\"$.grades.*\").replace(1.0))\r\n        .build();\r\n```\r\n\r\nTake a look at the following example, before and after filtering was applied:\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eBefore\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"id\": 1,\r\n  \"name\": \"Alice\",\r\n  \"password\": \"s3cr3t\",\r\n  \"active\": true,\r\n  \"address\": \"Anhalter Straße 17 13, 67278 Bockenheim an der Weinstraße\",\r\n  \"friends\": [\r\n    {\r\n      \"id\": 2,\r\n      \"name\": \"Bob\"\r\n    },\r\n    {\r\n      \"id\": 3,\r\n      \"name\": \"Charlie\"\r\n    }\r\n  ],\r\n  \"grades\": {\r\n    \"Math\": 1.0,\r\n    \"English\": 2.2,\r\n    \"Science\": 1.9,\r\n    \"PE\": 4.0\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eAfter\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"id\": 1,\r\n  \"name\": \"Alice\",\r\n  \"active\": \"unknown\",\r\n  \"address\": \"XXX\",\r\n  \"friends\": [\r\n    {\r\n      \"id\": 2,\r\n      \"name\": \"B.\"\r\n    },\r\n    {\r\n      \"id\": 3,\r\n      \"name\": \"C.\"\r\n    }\r\n  ],\r\n  \"grades\": {\r\n    \"Math\": 1.0,\r\n    \"English\": 1.0,\r\n    \"Science\": 1.0,\r\n    \"PE\": 1.0\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n#### Correlation\r\n\r\nLogbook 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.\r\n\r\nIf the default implementation of the correlation id is insufficient for your use case, you may provide a custom implementation:\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .correlationId(new CustomCorrelationId())\r\n    .build();\r\n```\r\n\r\n#### Formatting\r\n\r\n*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.\r\n\r\nLogbook comes with two different default formatters: *HTTP* and *JSON*.\r\n\r\n##### HTTP\r\n\r\n*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\r\nnot as readily machine-readable as JSON.\r\n\r\n###### Request\r\n\r\n```http\r\nIncoming Request: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b\r\nGET http://example.org/test HTTP/1.1\r\nAccept: application/json\r\nHost: localhost\r\nContent-Type: text/plain\r\n\r\nHello world!\r\n```\r\n\r\n###### Response\r\n\r\n```http\r\nOutgoing Response: 2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b\r\nDuration: 25 ms\r\nHTTP/1.1 200\r\nContent-Type: application/json\r\n\r\n{\"value\":\"Hello world!\"}\r\n```\r\n\r\n##### JSON\r\n\r\n*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.\r\n\r\nRequires the following dependency:\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n  \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n  \u003cartifactId\u003elogbook-json\u003c/artifactId\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\n###### Request\r\n\r\n```json\r\n{\r\n  \"origin\": \"remote\",\r\n  \"type\": \"request\",\r\n  \"correlation\": \"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b\",\r\n  \"protocol\": \"HTTP/1.1\",\r\n  \"sender\": \"127.0.0.1\",\r\n  \"method\": \"GET\",\r\n  \"uri\": \"http://example.org/test\",\r\n  \"host\": \"example.org\",\r\n  \"path\": \"/test\",\r\n  \"scheme\": \"http\",\r\n  \"port\": null,\r\n  \"headers\": {\r\n    \"Accept\": [\"application/json\"],\r\n    \"Content-Type\": [\"text/plain\"]\r\n  },\r\n  \"body\": \"Hello world!\"\r\n}\r\n```\r\n\r\n###### Response\r\n\r\n```json\r\n{\r\n  \"origin\": \"local\",\r\n  \"type\": \"response\",\r\n  \"correlation\": \"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b\",\r\n  \"duration\": 25,\r\n  \"protocol\": \"HTTP/1.1\",\r\n  \"status\": 200,\r\n  \"headers\": {\r\n    \"Content-Type\": [\"text/plain\"]\r\n  },\r\n  \"body\": \"Hello world!\"\r\n}\r\n```\r\n\r\nNote: Bodies of type `application/json` (and `application/*+json`) will be *inlined* into the resulting JSON tree. I.e.,\r\na JSON response body will **not** be escaped and represented as a string:\r\n\r\n```json\r\n{\r\n  \"origin\": \"local\",\r\n  \"type\": \"response\",\r\n  \"correlation\": \"2d66e4bc-9a0d-11e5-a84c-1f39510f0d6b\",\r\n  \"duration\": 25,\r\n  \"protocol\": \"HTTP/1.1\",\r\n  \"status\": 200,\r\n  \"headers\": {\r\n    \"Content-Type\": [\"application/json\"]\r\n  },\r\n  \"body\": {\r\n    \"greeting\": \"Hello, world!\"\r\n  }\r\n}\r\n```\r\n\r\n\r\n\u003e [!NOTE]  \r\n\u003e Logbook is using [BodyFilters](#Filtering) to inline json payload or to find fields for obfuscation. \r\n\u003e Filters for JSON bodies are using Jackson, which comes with a defect of dropping off precision from floating point \r\n\u003e numbers (see [FasterXML/jackson-core/issues/984](https://github.com/FasterXML/jackson-core/issues/984)).\r\n\u003e \r\n\u003e This can be changed by passing different `JsonGeneratorWrapper` implementations  to the filter respective filters. \r\n\u003e Available wrappers:\r\n\u003e * `DefaultJsonGeneratorWrapper` - default implementation, which doesn't alter Jackson's `JsonGenerator` behavior\r\n\u003e * `NumberAsStringJsonGeneratorWrapper` - writes floating point numbers as strings, and preserves their precision.\r\n\u003e * `PreciseFloatJsonGeneratorWrapper` - writes floating point with precision, may lead to a performance penalty as \r\n\u003e BigDecimal is usually used as the representation accessed from JsonParser.\r\n\r\n\r\n##### Common Log Format\r\n\r\nThe Common Log Format ([CLF](https://httpd.apache.org/docs/trunk/logs.html#common)) is a standardized text file format used by web servers when generating server log files. The format is supported via\r\nthe `CommonsLogFormatSink`:\r\n\r\n```text\r\n185.85.220.253 - - [02/Aug/2019:08:16:41 0000] \"GET /search?q=zalando HTTP/1.1\" 200 -\r\n```\r\n\r\n##### Extended Log Format\r\n\r\nThe Extended Log Format ([ELF](https://en.wikipedia.org/wiki/Extended_Log_Format)) is a standardised text file format, like Common Log Format (CLF), that is used by web servers when generating log\r\nfiles, but ELF files provide more information and flexibility. The format is supported via the `ExtendedLogFormatSink`.\r\nAlso see [W3C](https://www.w3.org/TR/WD-logfile.html) document.\r\n\r\nDefault fields:\r\n\r\n```text\r\ndate 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)\r\n```\r\n\r\nDefault log output example:\r\n\r\n```text\r\n2019-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\"\r\n```\r\n\r\nUsers may override default fields with their custom fields through the constructor of `ExtendedLogFormatSink`:\r\n\r\n```java\r\nnew ExtendedLogFormatSink(new DefaultHttpLogWriter(),\"date time cs(Custom-Request-Header) sc(Custom-Response-Header)\")\r\n```\r\n\r\nFor Http header fields: `cs(Any-Header)` and `sc(Any-Header)`, users could specify any headers they want to extract from the request.\r\n\r\nOther supported fields are listed in the value of `ExtendedLogFormatSink.Field`, which can be put in the custom field expression.\r\n\r\n##### cURL\r\n\r\n*cURL* is an alternative formatting style, provided by the `CurlHttpLogFormatter` which will render requests as\r\nexecutable [`cURL`](https://curl.haxx.se/) commands. Unlike JSON, it is primarily designed for humans.\r\n\r\n###### Request\r\n\r\n```bash\r\ncurl -v -X GET 'http://localhost/test' -H 'Accept: application/json'\r\n```\r\n\r\n###### Response\r\n\r\nSee [HTTP](#http) or provide own fallback for responses:\r\n\r\n```java\r\nnew CurlHttpLogFormatter(new JsonHttpLogFormatter());\r\n```\r\n\r\n##### Splunk\r\n\r\n*Splunk* is an alternative formatting style, provided by the `SplunkHttpLogFormatter` which will render\r\nrequests and response as key-value pairs.\r\n\r\n###### Request\r\n\r\n```text\r\norigin=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!\r\n\r\n```\r\n\r\n###### Response\r\n\r\n```text\r\norigin=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!\r\n```\r\n\r\n#### Writing\r\n\r\nWriting defines where formatted requests and responses are written to. Logbook comes with three implementations:\r\nLogger, Stream and Chunking.\r\n\r\n##### Logger\r\n\r\nBy default, requests and responses are logged with an *slf4j* logger that uses the `org.zalando.logbook.Logbook`\r\ncategory and the log level `trace`. This can be customized:\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .sink(new DefaultSink(\r\n            new DefaultHttpLogFormatter(),\r\n            new DefaultHttpLogWriter()\r\n    ))\r\n    .build();\r\n```\r\n\r\n##### Stream\r\n\r\nAn 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\r\nuseful for short-term local development and/or investigation.\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .sink(new DefaultSink(\r\n            new DefaultHttpLogFormatter(),\r\n            new StreamHttpLogWriter(System.err)\r\n    ))\r\n    .build();\r\n```\r\n\r\n##### Chunking\r\n\r\nThe `ChunkingSink` will split long messages into smaller chunks and will write them individually while delegating to another sink:\r\n\r\n```java\r\nLogbook logbook = Logbook.builder()\r\n    .sink(new ChunkingSink(sink, 1000))\r\n    .build();\r\n\r\n```\r\n\r\n#### Sink\r\n\r\nThe combination of `HttpLogFormatter` and `HttpLogWriter` suits most use cases well, but it has limitations.\r\nImplementing the `Sink` interface directly allows for more sophisticated use cases, e.g. writing requests/responses\r\nto a structured persistent storage like a database.\r\n\r\nMultiple sinks can be combined into one using the `CompositeSink`.\r\n\r\n### Servlet\r\n\r\nYou’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):\r\n\r\n```xml\r\n\u003cfilter\u003e\r\n    \u003cfilter-name\u003eLogbookFilter\u003c/filter-name\u003e\r\n    \u003cfilter-class\u003eorg.zalando.logbook.servlet.LogbookFilter\u003c/filter-class\u003e\r\n\u003c/filter\u003e\r\n\u003cfilter-mapping\u003e\r\n    \u003cfilter-name\u003eLogbookFilter\u003c/filter-name\u003e\r\n    \u003curl-pattern\u003e/*\u003c/url-pattern\u003e\r\n    \u003cdispatcher\u003eREQUEST\u003c/dispatcher\u003e\r\n    \u003cdispatcher\u003eASYNC\u003c/dispatcher\u003e\r\n\u003c/filter-mapping\u003e\r\n```\r\n\r\nor programmatically, via the `ServletContext`:\r\n\r\n```java\r\ncontext.addFilter(\"LogbookFilter\", new LogbookFilter(logbook))\r\n    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, \"/*\"); \r\n```\r\n\r\n**Beware**: The `ERROR` dispatch is not supported. You're strongly advised to produce error responses within the\r\n`REQUEST` or `ASNYC` dispatch.\r\n\r\nThe `LogbookFilter` will, by default, treat requests with a `application/x-www-form-urlencoded` body not different from\r\nany other request, i.e you will see the request body in the logs. The downside of this approach is that you won't be\r\nable to use any of the `HttpServletRequest.getParameter*(..)` methods. See issue [#94](../../issues/94) for some more\r\ndetails.\r\n\r\n#### Form Requests\r\n\r\nAs of Logbook 1.5.0, you can now specify one of three strategies that define how Logbook deals with this situation by\r\nusing the `logbook.servlet.form-request` system property:\r\n\r\n| Value            | Pros                                                                              | Cons                                               |\r\n|------------------|-----------------------------------------------------------------------------------|----------------------------------------------------|\r\n| `body` (default) | Body is logged                                                                    | Downstream code can **not use `getParameter*()`**  |\r\n| `parameter`      | Body is logged (but it's reconstructed from parameters)                           | Downstream code can **not use `getInputStream()`** |\r\n| `off`            | Downstream code can decide whether to use `getInputStream()` or `getParameter*()` | Body is **not logged**                             |\r\n\r\n#### Security\r\n\r\nSecure 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 —\r\nand, consequently, your precious disk space. Assuming that your application handles authorization inside another filter, you have two choices:\r\n\r\n- Don't log unauthorized requests\r\n- Log unauthorized requests without the request body\r\n\r\nYou 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\r\nyour security filter, and one after it:\r\n\r\n```java\r\ncontext.addFilter(\"SecureLogbookFilter\", new SecureLogbookFilter(logbook))\r\n    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, \"/*\");\r\ncontext.addFilter(\"securityFilter\", new SecurityFilter())\r\n    .addMappingForUrlPatterns(EnumSet.of(REQUEST), true, \"/*\");\r\ncontext.addFilter(\"LogbookFilter\", new LogbookFilter(logbook))\r\n    .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, \"/*\");\r\n```\r\n\r\nThe first logbook filter will log unauthorized requests **only**. The second filter will log authorized requests, as always.\r\n\r\n### HTTP Client\r\n\r\nThe `logbook-httpclient` module contains both an `HttpRequestInterceptor` and an `HttpResponseInterceptor` to use with the `HttpClient`:\r\n\r\n```java\r\nCloseableHttpClient client = HttpClientBuilder.create()\r\n        .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))\r\n        .addInterceptorFirst(new LogbookHttpResponseInterceptor())\r\n        .build();\r\n```\r\n\r\nSince the `LogbookHttpResponseInterceptor` is incompatible with the `HttpAsyncClient` there is another way to log responses:\r\n\r\n```java\r\nCloseableHttpAsyncClient client = HttpAsyncClientBuilder.create()\r\n        .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))\r\n        .build();\r\n        \r\n// and then wrap your response consumer\r\nclient.execute(producer, new LogbookHttpAsyncResponseConsumer\u003c\u003e(consumer), callback)\r\n```\r\n\r\n### HTTP Client 5\r\n\r\nThe `logbook-httpclient5` module contains an `ExecHandler` to use with the `HttpClient`:\r\n```java\r\nCloseableHttpClient client = HttpClientBuilder.create()\r\n        .addExecInterceptorFirst(\"Logbook\", new LogbookHttpExecHandler(logbook))\r\n        .build();\r\n```\r\nThe Handler should be added first, such that a compression is performed after logging and decompression is performed before logging.\r\n\r\nTo 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\r\nnot used:\r\n\r\n```java\r\nCloseableHttpClient client = HttpClientBuilder.create()\r\n        .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))\r\n        .addResponseInterceptorFirst(new LogbookHttpResponseInterceptor())\r\n        .build();\r\n```\r\n\r\nSince the `LogbookHttpResponseInterceptor` is incompatible with the `HttpAsyncClient` there is another way to log responses:\r\n\r\n```java\r\nCloseableHttpAsyncClient client = HttpAsyncClientBuilder.create()\r\n        .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook))\r\n        .build();\r\n        \r\n// and then wrap your response consumer\r\nclient.execute(producer, new LogbookHttpAsyncResponseConsumer\u003c\u003e(consumer), callback)\r\n```\r\n\r\n### JAX-RS 3.x (aka Jakarta RESTful Web Services)\r\n\r\nThe `logbook-jaxrs` module contains:\r\n\r\nA `LogbookClientFilter` to be used for applications making HTTP requests\r\n\r\n```java\r\nclient.register(new LogbookClientFilter(logbook));\r\n```\r\n\r\nA `LogbookServerFilter` for be used with HTTP servers\r\n\r\n```java\r\nresourceConfig.register(new LogbookServerFilter(logbook));\r\n```\r\n\r\n### JDK HTTP Server\r\n\r\nThe `logbook-jdkserver` module provides support for\r\n[JDK HTTP server](https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpServer.html)\r\nand contains:\r\n\r\nA `LogbookFilter` to be used with the builtin server\r\n\r\n```java\r\nhttpServer.createContext(path,handler).getFilters().add(new LogbookFilter(logbook))\r\n```\r\n\r\n### Netty\r\n\r\nThe `logbook-netty` module contains:\r\n\r\nA `LogbookClientHandler` to be used with an `HttpClient`:\r\n\r\n```java\r\nHttpClient httpClient =\r\n        HttpClient.create()\r\n                .doOnConnected(\r\n                        (connection -\u003e connection.addHandlerLast(new LogbookClientHandler(logbook)))\r\n                );\r\n```\r\n\r\nA `LogbookServerHandler` for use used with an `HttpServer`:\r\n\r\n```java\r\nHttpServer httpServer =\r\n        HttpServer.create()\r\n                .doOnConnection(\r\n                        connection -\u003e connection.addHandlerLast(new LogbookServerHandler(logbook))\r\n                );\r\n```\r\n\r\n#### Spring WebFlux\r\n\r\nUsers of Spring WebFlux can pick any of the following options:\r\n\r\n- Programmatically create a `NettyWebServer` (passing an `HttpServer`)\r\n- Register a custom `NettyServerCustomizer`\r\n- Programmatically create a `ReactorClientHttpConnector` (passing an `HttpClient`)\r\n- Register a custom `WebClientCustomizer`\r\n- Use separate connector-independent module `logbook-spring-webflux`\r\n\r\n#### Micronaut\r\n\r\nUsers of Micronaut can follow the [official docs](https://docs.micronaut.io/snapshot/guide/index.html#nettyClientPipeline) on how to integrate Logbook with Micronaut.\r\n\r\n:warning: Even though Quarkus and Vert.x use Netty under the hood, unfortunately neither of them allows accessing or customizing it (yet).\r\n\r\n### OkHttp v2.x\r\n\r\nThe `logbook-okhttp2` module contains an `Interceptor` to use with version 2.x of the `OkHttpClient`:\r\n\r\n```java\r\nOkHttpClient client = new OkHttpClient();\r\nclient.networkInterceptors().add(new LogbookInterceptor(logbook));\r\n```\r\n\r\nIf you're expecting gzip-compressed responses you need to register our `GzipInterceptor` in addition.\r\nThe transparent gzip support built into OkHttp will run after any network interceptor which forces\r\nlogbook to log compressed binary responses.\r\n\r\n```java\r\nOkHttpClient client = new OkHttpClient();\r\nclient.networkInterceptors().add(new LogbookInterceptor(logbook));\r\nclient.networkInterceptors().add(new GzipInterceptor());\r\n```\r\n\r\n### OkHttp v3.x\r\n\r\nThe `logbook-okhttp` module contains an `Interceptor` to use with version 3.x of the `OkHttpClient`:\r\n\r\n```java\r\nOkHttpClient client = new OkHttpClient.Builder()\r\n        .addNetworkInterceptor(new LogbookInterceptor(logbook))\r\n        .build();\r\n```\r\n\r\nIf you're expecting gzip-compressed responses you need to register our `GzipInterceptor` in addition.\r\nThe transparent gzip support built into OkHttp will run after any network interceptor which forces\r\nlogbook to log compressed binary responses.\r\n\r\n```java\r\nOkHttpClient client = new OkHttpClient.Builder()\r\n        .addNetworkInterceptor(new LogbookInterceptor(logbook))\r\n        .addNetworkInterceptor(new GzipInterceptor())\r\n        .build();\r\n```\r\n\r\n### Ktor\r\n\r\nThe `logbook-ktor-client` module contains:\r\n\r\nA `LogbookClient` to be used with an `HttpClient`:\r\n\r\n```kotlin\r\nprivate val client = HttpClient(CIO) {\r\n    install(LogbookClient) {\r\n        logbook = logbook\r\n    }\r\n}\r\n```\r\n\r\nThe `logbook-ktor-server` module contains:\r\n\r\nA `LogbookServer` to be used with an `Application`:\r\n\r\n```kotlin\r\nprivate val server = embeddedServer(CIO) {\r\n    install(LogbookServer) {\r\n        logbook = logbook\r\n    }\r\n}\r\n```\r\n\r\nAlternatively, you can use `logbook-ktor`, which ships both `logbook-ktor-client` and `logbook-ktor-server` modules.\r\n\r\n### Spring\r\nThe `logbook-spring` module contains a `ClientHttpRequestInterceptor` to use with `RestTemplate`:\r\n\r\n```java\r\n    LogbookClientHttpRequestInterceptor interceptor = new LogbookClientHttpRequestInterceptor(logbook);\r\n    RestTemplate restTemplate = new RestTemplate();\r\n    restTemplate.getInterceptors().add(interceptor);\r\n```\r\n\r\n### Spring Boot Starter\r\n\r\nLogbook comes with a convenient auto configuration for Spring Boot users. It sets up all of the following parts automatically with sensible defaults:\r\n\r\n- Servlet filter\r\n- Second Servlet filter for unauthorized requests (if Spring Security is detected)\r\n- Header-/Parameter-/Body-Filters\r\n- HTTP-/JSON-style formatter\r\n- Logging writer\r\n\r\nInstead of declaring a dependency to `logbook-core` declare one to the Spring Boot Starter:\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eorg.zalando\u003c/groupId\u003e\r\n    \u003cartifactId\u003elogbook-spring-boot-starter\u003c/artifactId\u003e\r\n    \u003cversion\u003e${logbook.version}\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\nEvery bean can be overridden and customized if needed, e.g. like this:\r\n\r\n```java\r\n@Bean\r\npublic BodyFilter bodyFilter() {\r\n    return merge(\r\n            defaultValue(), \r\n            replaceJsonStringProperty(singleton(\"secret\"), \"XXX\"));\r\n}\r\n```\r\n\r\nPlease refer to [`LogbookAutoConfiguration`](logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java)\r\nor the following table to see a list of possible integration points:\r\n\r\n| Type                     | Name                  | Default                                                                   |\r\n|--------------------------|-----------------------|---------------------------------------------------------------------------|\r\n| `FilterRegistrationBean` | `secureLogbookFilter` | Based on `LogbookFilter`                                                  |\r\n| `FilterRegistrationBean` | `logbookFilter`       | Based on `LogbookFilter`                                                  |\r\n| `Logbook`                |                       | Based on condition, filters, formatter and writer                         |\r\n| `Predicate\u003cHttpRequest\u003e` | `requestCondition`    | No filter; is later combined with `logbook.exclude` and `logbook.exclude` |\r\n| `HeaderFilter`           |                       | Based on `logbook.obfuscate.headers`                                      |\r\n| `PathFilter`             |                       | Based on `logbook.obfuscate.paths`                                        |\r\n| `QueryFilter`            |                       | Based on `logbook.obfuscate.parameters`                                   |\r\n| `BodyFilter`             |                       | `BodyFilters.defaultValue()`, see [filtering](#filtering)                 |\r\n| `RequestFilter`          |                       | `RequestFilters.defaultValue()`, see [filtering](#filtering)              |\r\n| `ResponseFilter`         |                       | `ResponseFilters.defaultValue()`, see [filtering](#filtering)             |\r\n| `Strategy`               |                       | `DefaultStrategy`                                                         |\r\n| `AttributeExtractor`     |                       | `NoOpAttributeExtractor`                                                  |\r\n| `Sink`                   |                       | `DefaultSink`                                                             |\r\n| `HttpLogFormatter`       |                       | `JsonHttpLogFormatter`                                                    |\r\n| `HttpLogWriter`          |                       | `DefaultHttpLogWriter`                                                    |\r\n\r\nMultiple filters are merged into one.\r\n\r\n#### Autoconfigured beans from `logbook-spring`\r\nSome classes from `logbook-spring` are included in the auto configuration.\r\n\r\nYou can autowire `LogbookClientHttpRequestInterceptor` with code like:\r\n```java\r\nprivate final RestTemplate restTemplate;\r\nMyClient(RestTemplateBuilder builder, LogbookClientHttpRequestInterceptor interceptor){\r\n  this.restTemplate = builder\r\n    .additionalInterceptors(interceptor)\r\n    .build();\r\n}\r\n```\r\n\r\n#### Configuration\r\n\r\nThe following tables show the available configuration (sorted alphabetically):\r\n\r\n| Configuration                            | Description                                                                                                                                                                                                         | Default            |\r\n|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|\r\n| `logbook.attribute-extractors`           | List of [AttributeExtractor](#attribute-extractor)s, including configurations such as `type` (currently `JwtFirstMatchingClaimExtractor` or `JwtAllMatchingClaimsExtractor`), `claim-names` and `claim-key`.        | `[]`               |\r\n| `logbook.filter.enabled`                 | Enable the [`LogbookFilter`](#servlet)                                                                                                                                                                              | `true`             |\r\n| `logbook.filter.form-request-mode`       | Determines how [form requests](#form-requests) are handled                                                                                                                                                          | `body`             |\r\n| `logbook.filters.body.default-enabled`   | Enables/disables default body filters that are collected by java.util.ServiceLoader                                                                                                                                 | `true`             |\r\n| `logbook.format.style`                   | [Formatting style](#formatting) (`http`, `json`, `curl` or `splunk`)                                                                                                                                                | `json`             |\r\n| `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) |\r\n| `logbook.minimum-status`                 | Minimum status to enable logging (`status-at-least` and `body-only-if-status-at-least`)                                                                                                                             | `400`              |\r\n| `logbook.obfuscate.headers`              | List of header names that need obfuscation                                                                                                                                                                          | `[Authorization]`  |\r\n| `logbook.obfuscate.json-body-fields`     | List of JSON body fields to be obfuscated                                                                                                                                                                           | `[]`               |\r\n| `logbook.obfuscate.parameters`           | List of parameter names that need obfuscation                                                                                                                                                                       | `[access_token]`   |\r\n| `logbook.obfuscate.paths`                | List of paths that need obfuscation. Check [Filtering](#filtering) for syntax.                                                                                                                                      | `[]`               |\r\n| `logbook.obfuscate.replacement`          | A value to be used instead of an obfuscated one                                                                                                                                                                     | `XXX`              |\r\n| `logbook.predicate.include`              | Include only certain paths and methods (if defined)                                                                                                                                                                 | `[]`               |\r\n| `logbook.predicate.exclude`              | Exclude certain  paths and methods  (overrides `logbook.predicate.include`)                                                                                                                                         | `[]`               |\r\n| `logbook.secure-filter.enabled`          | Enable the [`SecureLogbookFilter`](#servlet)                                                                                                                                                                        | `true`             |\r\n| `logbook.strategy`                       | [Strategy](#strategy) (`default`, `status-at-least`, `body-only-if-status-at-least`, `without-body`)                                                                                                                | `default`          |\r\n| `logbook.write.chunk-size`               | Splits log lines into smaller chunks of size up-to `chunk-size`.                                                                                                                                                    | `0` (disabled)     |\r\n| `logbook.write.max-body-size`            | Truncates the body up to `max-body-size` characters and appends `...`.  \u003cbr/\u003e :warning: 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)    |\r\n\r\n##### Example configuration\r\n\r\n```yaml\r\nlogbook:\r\n  predicate:\r\n    include:\r\n      - path: /api/**\r\n        methods: \r\n         - GET\r\n         - POST\r\n      - path: /actuator/**\r\n    exclude:\r\n      - path: /actuator/health\r\n      - path: /api/admin/**\r\n        methods: \r\n         - POST\r\n  filter.enabled: true\r\n  secure-filter.enabled: true\r\n  format.style: http\r\n  strategy: body-only-if-status-at-least\r\n  minimum-status: 400\r\n  obfuscate:\r\n    headers:\r\n      - Authorization\r\n      - X-Secret\r\n    parameters:\r\n      - access_token\r\n      - password\r\n  write:\r\n    chunk-size: 1000\r\n  attribute-extractors:\r\n    - type: JwtFirstMatchingClaimExtractor\r\n      claim-names: [ \"sub\", \"subject\" ]\r\n      claim-key: Principal\r\n    - type: JwtAllMatchingClaimsExtractor\r\n      claim-names: [ \"sub\", \"iat\" ]\r\n```\r\n\r\n### logstash-logback-encoder\r\n\r\nFor basic Logback configuraton\r\n\r\n```\r\n\u003cappender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\"\u003e\r\n    \u003cencoder class=\"net.logstash.logback.encoder.LogstashEncoder\"/\u003e\r\n\u003c/appender\u003e\r\n```\r\n\r\nconfigure Logbook with a `LogstashLogbackSink`\r\n\r\n```\r\nHttpLogFormatter formatter = new JsonHttpLogFormatter();\r\nLogstashLogbackSink sink = new LogstashLogbackSink(formatter);\r\n```\r\n\r\nfor outputs like\r\n\r\n```\r\n{\r\n  \"@timestamp\" : \"2019-03-08T09:37:46.239+01:00\",\r\n  \"@version\" : \"1\",\r\n  \"message\" : \"GET http://localhost/test?limit=1\",\r\n  \"logger_name\" : \"org.zalando.logbook.Logbook\",\r\n  \"thread_name\" : \"main\",\r\n  \"level\" : \"TRACE\",\r\n  \"level_value\" : 5000,\r\n  \"http\" : {\r\n     // logbook request/response contents\r\n  }\r\n}\r\n```\r\n\r\n#### Customizing default Logging Level\r\n\r\nYou have the flexibility to customize the default logging level by initializing `LogstashLogbackSink` with a specific level. For instance:\r\n\r\n```\r\nLogstashLogbackSink sink = new LogstashLogbackSink(formatter, Level.INFO); \r\n```\r\n\r\n## Known Issues\r\n\r\n1. The Logbook Servlet Filter interferes with downstream code using `getWriter` and/or `getParameter*()`. See [Servlet](#servlet) for more details.\r\n2. The Logbook Servlet Filter does **NOT** support `ERROR` dispatch. You're strongly encouraged to not use it to produce error responses.\r\n\r\n## Getting Help with Logbook\r\n\r\nIf you have questions, concerns, bug reports, etc., please file an issue in this repository's [Issue Tracker](https://github.com/zalando/logbook/issues).\r\n\r\n## Getting Involved/Contributing\r\n\r\nTo contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For\r\nmore details, check the [contribution guidelines](.github/CONTRIBUTING.md).\r\n\r\n## Alternatives\r\n\r\n- [Apache HttpClient Wire Logging](http://hc.apache.org/httpcomponents-client-4.5.x/logging.html)\r\n    - Client-side only\r\n    - Apache HttpClient exclusive\r\n    - Support for HTTP bodies\r\n- [Spring Boot Access Logging](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-configure-accesslogs)\r\n    - Spring application only\r\n    - Server-side only\r\n    - Tomcat/Undertow/Jetty exclusive\r\n    - **No** support for HTTP bodies\r\n- [Tomcat Request Dumper Filter](https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#Request_Dumper_Filter)\r\n    - Server-side only\r\n    - Tomcat exclusive\r\n    - **No** support for HTTP bodies\r\n- [logback-access](http://logback.qos.ch/access.html)\r\n    - Server-side only\r\n    - Any servlet container\r\n    - Support for HTTP bodies\r\n\r\n## Credits and References\r\n\r\n![Creative Commons (Attribution-Share Alike 3.0 Unported](https://licensebuttons.net/l/by-sa/3.0/80x15.png)\r\n[*Grand Turk, a replica of a three-masted 6th rate frigate from Nelson's days - logbook and charts*](https://commons.wikimedia.org/wiki/File:Grand_Turk(34).jpg)\r\nby [JoJan](https://commons.wikimedia.org/wiki/User:JoJan) is licensed under a\r\n[Creative Commons (Attribution-Share Alike 3.0 Unported)](http://creativecommons.org/licenses/by-sa/3.0/).\n","funding_links":[],"categories":["6. Collector","Projects","Java","Logging","日志库","项目"],"sub_categories":["Logging","日志记录"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalando%2Flogbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzalando%2Flogbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalando%2Flogbook/lists"}