blob: 2be17a288f3be468f5c012fb939d18d79bfcf8f4 [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() {
Matthias Andreas Benkarda78c9572021-05-18 21:43:36 +020073 var b = Json.createObjectBuilder();
74
75 if (file != null) {
76 b.add("file", file);
77 }
78
79 if (line != null) {
80 b.add("line", line);
81 }
82
83 if (function != null) {
84 b.add("function", function);
85 }
86
87 return b.build();
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020088 }
89 }
90
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020091 static final class Timestamp {
92
93 private final long seconds;
94 private final int nanos;
95
96 Timestamp(long seconds, int nanos) {
97 this.seconds = seconds;
98 this.nanos = nanos;
99 }
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200100
101 Timestamp(Instant t) {
102 this(t.getEpochSecond(), t.getNano());
103 }
104
105 JsonObject json() {
106 return Json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
107 }
108 }
109
110 JsonObjectBuilder json() {
111 var b = Json.createObjectBuilder();
112
113 if (trace != null) {
114 b.add("trace", trace);
115 }
116
117 if (spanId != null) {
118 b.add("spanId", spanId);
119 }
120
121 if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) {
122 b.add("nestedDiagnosticContext", nestedDiagnosticContext);
123 }
124
125 if (!labels.isEmpty()) {
126 b.add("labels", jsonOfStringMap(labels));
127 }
128
129 if (type != null) {
130 b.add("@type", type);
131 }
132
133 return b.add("message", message)
134 .add("severity", severity)
135 .add("timestamp", timestamp.json())
136 .add("sourceLocation", sourceLocation.json())
137 .addAll(jsonOfStringMap(mappedDiagnosticContext))
138 .addAll(jsonOfParameterMap(parameters));
139 }
140
141 private static JsonObjectBuilder jsonOfStringMap(Map<String, String> stringMap) {
142 return stringMap.entrySet().stream()
143 .reduce(
144 Json.createObjectBuilder(),
145 (acc, x) -> acc.add(x.getKey(), x.getValue()),
146 JsonObjectBuilder::addAll);
147 }
148
149 private static JsonObjectBuilder jsonOfParameterMap(List<StructuredParameter> parameters) {
150 return parameters.stream()
151 .reduce(
152 Json.createObjectBuilder(),
153 (acc, p) -> acc.addAll(p.json()),
154 JsonObjectBuilder::addAll);
155 }
156}