├── README.md ├── filebeat ├── filebeat.yml └── service.template.json └── spring-boot ├── application.properties └── logback-spring.xml /README.md: -------------------------------------------------------------------------------- 1 | # Structured Logging 2 | 3 | This repository contains configuration for setting up Structured Logging 4 | in Spring Boot based Java projects. 5 | 6 | 7 | ## Spring Boot 8 | 9 | Add the dependency to 10 | [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) 11 | which gives us the mechanism to log data in JSON format: 12 | 13 | ```XML 14 | 15 | net.logstash.logback 16 | logstash-logback-encoder 17 | 4.9 18 | 19 | ``` 20 | 21 | If you have multiple Spring Boot services that cooperate to fulfill a request, 22 | you can optionally add 23 | [Spring Cloud Sleuth](http://cloud.spring.io/spring-cloud-sleuth/) to gain 24 | correlation IDs: 25 | 26 | ```XML 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-sleuth 30 | 31 | ``` 32 | 33 | No further setup is required for Sleuth unless you want to send data to 34 | [Zipkin](http://zipkin.io/) as well. 35 | 36 | Now set the `spring.application.name` property in your Spring Boot 37 | configuration. You can inject Maven's `project.name` into 38 | `application.properties` or `application.yml` at compile time: 39 | 40 | ``` 41 | spring.application.name = @project.name@ 42 | ``` 43 | 44 | The application name is added to the JSON log object. It is also used by 45 | Sleuth if set up to send data to Zipkin. 46 | 47 | 48 | ## Logback 49 | 50 | This repository contains a `logback-spring.xml` file that overrides the 51 | auto-configured logging configuration. It has appenders for the console, 52 | a traditional plain text format, and the JSON log file. Remove the ones 53 | that you don't need and adjust to include the data you need. 54 | 55 | While there are some aspects specific to Spring Boot, with minor editing 56 | the configuration file should be usable in other projects as well. 57 | 58 | 59 | ## Application Logging 60 | 61 | Use the static factories on [StructuredArguments](https://github.com/logstash/logstash-logback-encoder/blob/master/src/main/java/net/logstash/logback/argument/StructuredArguments.java) to create log statements. For example: 62 | 63 | ```Java 64 | LOG.info("{}; {} {}", value("action", "accepted order"), 65 | keyValue("customer", customerId), keyValue("order", orderId)); 66 | ``` 67 | 68 | In the JSON log file, this adds fields for "action", "customer", and "order" 69 | among several other fields provided by the logging system. In the plain text 70 | format this leads to the following output: 71 | 72 | ``` 73 | ... INFO ... : accepted order; customer=4711 order=123456 74 | ``` 75 | 76 | Note that the key of the `value()` factory is only present in the JSON output, 77 | not in the plain text format. Also, only arguments listed in the first 78 | parameter of the log statement are part of the plain text format (in the 79 | JSON file, all are present). 80 | 81 | You may want to define your own static factories to `StructuredArgument` to 82 | keep repetition minimal. 83 | 84 | 85 | ## Filebeat 86 | 87 | Once the application logs data in JSON format, you can use the 88 | [Filebeat](https://www.elastic.co/products/beats/filebeat) agent to 89 | tail the log files and hand them off to another system, storing them 90 | in ElasticSearch eventually. From there, you can search and visualize 91 | the logs in [Kibana](https://www.elastic.co/products/kibana). 92 | 93 | We need two different types of configuration for this: 94 | 95 | * Configuration tailing a log file and shipping it to ElasticSearch 96 | * ElasticSearch mappings matching the Logback logger configuration 97 | 98 | -------------------------------------------------------------------------------- /filebeat/filebeat.yml: -------------------------------------------------------------------------------- 1 | # Structured Logging Example 2 | 3 | filebeat.prospectors: 4 | - input_type: log 5 | paths: 6 | - /path/to/logs/*.json 7 | json: 8 | keys_under_root: true 9 | 10 | output.elasticsearch: 11 | hosts: 12 | - localhost:9200 13 | index: "filebeat-%{[service]:unknown-service}-%{+yyyy.MM.dd}" 14 | template: 15 | name: filebeat-service 16 | path: service.template.json 17 | 18 | -------------------------------------------------------------------------------- /filebeat/service.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "filebeat-*", 3 | "settings": { 4 | "index": { 5 | "number_of_shards": 3, 6 | "number_of_replicas": 2, 7 | "query": { 8 | "default_field": "message" 9 | } 10 | } 11 | }, 12 | "mappings": { 13 | "_default_": { 14 | "_all": { 15 | "enabled": false 16 | }, 17 | "date_detection": false, 18 | "dynamic_templates": [ 19 | { 20 | "all-keywords": { 21 | "match": "*", 22 | "mapping": { 23 | "type": "keyword" 24 | } 25 | } 26 | } 27 | ], 28 | "properties": { 29 | "@timestamp": { 30 | "type": "date" 31 | }, 32 | "offset": { 33 | "type": "long" 34 | }, 35 | "pid": { 36 | "type": "long" 37 | }, 38 | "beat": { 39 | "properties": { 40 | "hostname": { 41 | "type": "keyword" 42 | }, 43 | "name": { 44 | "type": "keyword" 45 | }, 46 | "version": { 47 | "type": "keyword" 48 | } 49 | } 50 | }, 51 | "exportable": { 52 | "type": "boolean" 53 | }, 54 | "thread": { 55 | "type": "text", 56 | "norms": false 57 | }, 58 | "logger": { 59 | "type": "text", 60 | "norms": false, 61 | "analyzer": "simple" 62 | }, 63 | "stack_trace": { 64 | "type": "text", 65 | "norms": false, 66 | "analyzer": "simple" 67 | }, 68 | "action": { 69 | "type": "text", 70 | "norms": false, 71 | "analyzer": "english" 72 | }, 73 | "message": { 74 | "type": "text", 75 | "norms": false, 76 | "analyzer": "english" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spring-boot/application.properties: -------------------------------------------------------------------------------- 1 | # Inject the contents of Maven's project.name tag. 2 | spring.application.name = @project.name@ 3 | -------------------------------------------------------------------------------- /spring-boot/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | DEBUG 21 | 22 | 23 | ${CONSOLE_LOG_PATTERN} 24 | utf8 25 | 26 | 27 | 28 | 29 | 30 | ${LOG_FILE} 31 | 32 | ${LOG_FILE}.%d{yyyy-MM-dd}.gz 33 | 7 34 | 35 | 36 | ${FILE_LOG_PATTERN} 37 | utf8 38 | 39 | 40 | 41 | 42 | 43 | ${LOG_FILE}.json 44 | 45 | ${LOG_FILE}.json.%d{yyyy-MM-dd}.gz 46 | 7 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | X-B3-TraceId 56 | X-B3-SpanId 57 | X-B3-ParentSpanId 58 | X-Span-Export 59 | 60 | 61 | 62 | 63 | UTC 64 | 65 | 66 | 67 | 68 | { 69 | "severity": "%level", 70 | "service": "${springAppName:-}", 71 | "trace": "%X{X-B3-TraceId:-}", 72 | "span": "%X{X-B3-SpanId:-}", 73 | "parent": "%X{X-B3-ParentSpanId:-}", 74 | "exportable": "%X{X-Span-Export:-}", 75 | "pid": ${PID:-}, 76 | "thread": "%thread", 77 | "logger": "%logger", 78 | "message": "%message" 79 | } 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | --------------------------------------------------------------------------------