blob: 43940335b7f7ec45dfcf1ff141bd37a8c4bee434 [file] [log] [blame]
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02001package eu.mulk.quarkus.googlecloud.jsonlogging;
2
3import io.smallrye.common.constraint.Nullable;
4import java.time.Instant;
5import java.util.List;
6import java.util.Map;
7import javax.json.Json;
8import javax.json.JsonObject;
9import javax.json.JsonObjectBuilder;
10
11/**
12 * A JSON log entry compatible with Google Cloud Logging.
13 *
14 * <p>Roughly (but not quite) corresponds to Google Cloud Logging's <a
15 * href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry">LogEntry</a>
16 * structure.
17 *
18 * <p>A few of the fields are treated specially by the fluentd instance running in Google Kubernetes
19 * Engine. All other fields end up in the jsonPayload field on the Google Cloud Logging side.
20 */
21record LogEntry(
22 String message,
23 String severity,
24 Timestamp timestamp,
25 @Nullable String trace,
26 @Nullable String spanId,
27 SourceLocation sourceLocation,
28 Map<String, String> labels,
29 List<StructuredParameter> parameters,
30 Map<String, String> mappedDiagnosticContext,
31 @Nullable String nestedDiagnosticContext,
32 @Nullable String type) {
33
34 static record SourceLocation(
35 @Nullable String file, @Nullable String line, @Nullable String function) {
36
37 JsonObject json() {
38 return Json.createObjectBuilder()
39 .add("file", file)
40 .add("line", line)
41 .add("function", function)
42 .build();
43 }
44 }
45
46 static record Timestamp(long seconds, int nanos) {
47
48 Timestamp(Instant t) {
49 this(t.getEpochSecond(), t.getNano());
50 }
51
52 JsonObject json() {
53 return Json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
54 }
55 }
56
57 JsonObjectBuilder json() {
58 var b = Json.createObjectBuilder();
59
60 if (trace != null) {
61 b.add("trace", trace);
62 }
63
64 if (spanId != null) {
65 b.add("spanId", spanId);
66 }
67
68 if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) {
69 b.add("nestedDiagnosticContext", nestedDiagnosticContext);
70 }
71
72 if (!labels.isEmpty()) {
73 b.add("labels", jsonOfStringMap(labels));
74 }
75
76 if (type != null) {
77 b.add("@type", type);
78 }
79
80 return b.add("message", message)
81 .add("severity", severity)
82 .add("timestamp", timestamp.json())
83 .add("sourceLocation", sourceLocation.json())
84 .addAll(jsonOfStringMap(mappedDiagnosticContext))
85 .addAll(jsonOfParameterMap(parameters));
86 }
87
88 private static JsonObjectBuilder jsonOfStringMap(Map<String, String> stringMap) {
89 return stringMap.entrySet().stream()
90 .reduce(
91 Json.createObjectBuilder(),
92 (acc, x) -> acc.add(x.getKey(), x.getValue()),
93 JsonObjectBuilder::addAll);
94 }
95
96 private static JsonObjectBuilder jsonOfParameterMap(List<StructuredParameter> parameters) {
97 return parameters.stream()
98 .reduce(
99 Json.createObjectBuilder(),
100 (acc, p) -> acc.addAll(p.json()),
101 JsonObjectBuilder::addAll);
102 }
103}