blob: 4c70e6feb3860bf390c942a5cb215313461f5b7a [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 */
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020021final class LogEntry {
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020022
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020023 private final String message;
24 private final String severity;
25 private final Timestamp timestamp;
26 @Nullable private final String trace;
27 @Nullable private final String spanId;
28 private final SourceLocation sourceLocation;
29 private final Map<String, String> labels;
30 private final List<StructuredParameter> parameters;
31 private final Map<String, String> mappedDiagnosticContext;
32 @Nullable private final String nestedDiagnosticContext;
33 @Nullable private final String type;
34
35 LogEntry(
36 String message,
37 String severity,
38 Timestamp timestamp,
39 @Nullable String trace,
40 @Nullable String spanId,
41 SourceLocation sourceLocation,
42 Map<String, String> labels,
43 List<StructuredParameter> parameters,
44 Map<String, String> mappedDiagnosticContext,
45 @Nullable String nestedDiagnosticContext,
46 @Nullable String type) {
47 this.message = message;
48 this.severity = severity;
49 this.timestamp = timestamp;
50 this.trace = trace;
51 this.spanId = spanId;
52 this.sourceLocation = sourceLocation;
53 this.labels = labels;
54 this.parameters = parameters;
55 this.mappedDiagnosticContext = mappedDiagnosticContext;
56 this.nestedDiagnosticContext = nestedDiagnosticContext;
57 this.type = type;
58 }
59
60 static final class SourceLocation {
61
62 @Nullable private final String file;
63 @Nullable private final String line;
64 @Nullable private final String function;
65
66 SourceLocation(@Nullable String file, @Nullable String line, @Nullable String function) {
67 this.file = file;
68 this.line = line;
69 this.function = function;
70 }
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020071
72 JsonObject json() {
73 return Json.createObjectBuilder()
74 .add("file", file)
75 .add("line", line)
76 .add("function", function)
77 .build();
78 }
79 }
80
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020081 static final class Timestamp {
82
83 private final long seconds;
84 private final int nanos;
85
86 Timestamp(long seconds, int nanos) {
87 this.seconds = seconds;
88 this.nanos = nanos;
89 }
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020090
91 Timestamp(Instant t) {
92 this(t.getEpochSecond(), t.getNano());
93 }
94
95 JsonObject json() {
96 return Json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
97 }
98 }
99
100 JsonObjectBuilder json() {
101 var b = Json.createObjectBuilder();
102
103 if (trace != null) {
104 b.add("trace", trace);
105 }
106
107 if (spanId != null) {
108 b.add("spanId", spanId);
109 }
110
111 if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) {
112 b.add("nestedDiagnosticContext", nestedDiagnosticContext);
113 }
114
115 if (!labels.isEmpty()) {
116 b.add("labels", jsonOfStringMap(labels));
117 }
118
119 if (type != null) {
120 b.add("@type", type);
121 }
122
123 return b.add("message", message)
124 .add("severity", severity)
125 .add("timestamp", timestamp.json())
126 .add("sourceLocation", sourceLocation.json())
127 .addAll(jsonOfStringMap(mappedDiagnosticContext))
128 .addAll(jsonOfParameterMap(parameters));
129 }
130
131 private static JsonObjectBuilder jsonOfStringMap(Map<String, String> stringMap) {
132 return stringMap.entrySet().stream()
133 .reduce(
134 Json.createObjectBuilder(),
135 (acc, x) -> acc.add(x.getKey(), x.getValue()),
136 JsonObjectBuilder::addAll);
137 }
138
139 private static JsonObjectBuilder jsonOfParameterMap(List<StructuredParameter> parameters) {
140 return parameters.stream()
141 .reduce(
142 Json.createObjectBuilder(),
143 (acc, p) -> acc.addAll(p.json()),
144 JsonObjectBuilder::addAll);
145 }
146}