blob: dec18b26c0ba1d8627a0e779558059494592b698 [file] [log] [blame]
Matthias Andreas Benkard80909242022-02-03 20:47:47 +01001// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02005package eu.mulk.quarkus.googlecloud.jsonlogging;
6
7import io.smallrye.common.constraint.Nullable;
8import java.time.Instant;
9import java.util.List;
10import java.util.Map;
11import javax.json.Json;
12import javax.json.JsonObject;
13import javax.json.JsonObjectBuilder;
14
15/**
16 * A JSON log entry compatible with Google Cloud Logging.
17 *
18 * <p>Roughly (but not quite) corresponds to Google Cloud Logging's <a
19 * href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry">LogEntry</a>
20 * structure.
21 *
Matthias Andreas Benkard596486a2021-08-30 12:14:43 +020022 * <p>A few of the fields are <a href="https://cloud.google.com/logging/docs/structured-logging">
23 * treated specially</a> by the fluentd instance running in Google Kubernetes Engine. All other
24 * fields end up in the jsonPayload field on the Google Cloud Logging side.
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020025 */
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020026final class LogEntry {
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020027
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020028 private final String message;
29 private final String severity;
30 private final Timestamp timestamp;
31 @Nullable private final String trace;
32 @Nullable private final String spanId;
33 private final SourceLocation sourceLocation;
34 private final Map<String, String> labels;
35 private final List<StructuredParameter> parameters;
36 private final Map<String, String> mappedDiagnosticContext;
37 @Nullable private final String nestedDiagnosticContext;
38 @Nullable private final String type;
39
40 LogEntry(
41 String message,
42 String severity,
43 Timestamp timestamp,
44 @Nullable String trace,
45 @Nullable String spanId,
46 SourceLocation sourceLocation,
47 Map<String, String> labels,
48 List<StructuredParameter> parameters,
49 Map<String, String> mappedDiagnosticContext,
50 @Nullable String nestedDiagnosticContext,
51 @Nullable String type) {
52 this.message = message;
53 this.severity = severity;
54 this.timestamp = timestamp;
55 this.trace = trace;
56 this.spanId = spanId;
57 this.sourceLocation = sourceLocation;
58 this.labels = labels;
59 this.parameters = parameters;
60 this.mappedDiagnosticContext = mappedDiagnosticContext;
61 this.nestedDiagnosticContext = nestedDiagnosticContext;
62 this.type = type;
63 }
64
65 static final class SourceLocation {
66
67 @Nullable private final String file;
68 @Nullable private final String line;
69 @Nullable private final String function;
70
71 SourceLocation(@Nullable String file, @Nullable String line, @Nullable String function) {
72 this.file = file;
73 this.line = line;
74 this.function = function;
75 }
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020076
77 JsonObject json() {
Matthias Andreas Benkarda78c9572021-05-18 21:43:36 +020078 var b = Json.createObjectBuilder();
79
80 if (file != null) {
81 b.add("file", file);
82 }
83
84 if (line != null) {
85 b.add("line", line);
86 }
87
88 if (function != null) {
89 b.add("function", function);
90 }
91
92 return b.build();
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020093 }
94 }
95
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020096 static final class Timestamp {
97
98 private final long seconds;
99 private final int nanos;
100
101 Timestamp(long seconds, int nanos) {
102 this.seconds = seconds;
103 this.nanos = nanos;
104 }
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200105
106 Timestamp(Instant t) {
107 this(t.getEpochSecond(), t.getNano());
108 }
109
110 JsonObject json() {
111 return Json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
112 }
113 }
114
115 JsonObjectBuilder json() {
116 var b = Json.createObjectBuilder();
117
118 if (trace != null) {
Matthias Andreas Benkard596486a2021-08-30 12:14:43 +0200119 b.add("logging.googleapis.com/trace", trace);
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200120 }
121
122 if (spanId != null) {
Matthias Andreas Benkard596486a2021-08-30 12:14:43 +0200123 b.add("logging.googleapis.com/spanId", spanId);
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200124 }
125
126 if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) {
127 b.add("nestedDiagnosticContext", nestedDiagnosticContext);
128 }
129
130 if (!labels.isEmpty()) {
Matthias Andreas Benkard596486a2021-08-30 12:14:43 +0200131 b.add("logging.googleapis.com/labels", jsonOfStringMap(labels));
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200132 }
133
134 if (type != null) {
135 b.add("@type", type);
136 }
137
138 return b.add("message", message)
139 .add("severity", severity)
140 .add("timestamp", timestamp.json())
Matthias Andreas Benkard596486a2021-08-30 12:14:43 +0200141 .add("logging.googleapis.com/sourceLocation", sourceLocation.json())
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200142 .addAll(jsonOfStringMap(mappedDiagnosticContext))
143 .addAll(jsonOfParameterMap(parameters));
144 }
145
146 private static JsonObjectBuilder jsonOfStringMap(Map<String, String> stringMap) {
147 return stringMap.entrySet().stream()
148 .reduce(
149 Json.createObjectBuilder(),
150 (acc, x) -> acc.add(x.getKey(), x.getValue()),
151 JsonObjectBuilder::addAll);
152 }
153
154 private static JsonObjectBuilder jsonOfParameterMap(List<StructuredParameter> parameters) {
155 return parameters.stream()
156 .reduce(
157 Json.createObjectBuilder(),
158 (acc, p) -> acc.addAll(p.json()),
159 JsonObjectBuilder::addAll);
160 }
161}