├── 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 |
--------------------------------------------------------------------------------