blob: 69ebc9300ab5140e70ca3446a3ab9c7bec91910e [file] [log] [blame]
// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
package eu.mulk.quarkus.googlecloud.jsonlogging;
import io.smallrye.common.constraint.Nullable;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
/**
* A JSON log entry compatible with Google Cloud Logging.
*
* <p>Roughly (but not quite) corresponds to Google Cloud Logging's <a
* href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry">LogEntry</a>
* structure.
*
* <p>A few of the fields are <a href="https://cloud.google.com/logging/docs/structured-logging">
* treated specially</a> by the fluentd instance running in Google Kubernetes Engine. All other
* fields end up in the jsonPayload field on the Google Cloud Logging side.
*/
final class LogEntry {
private final String message;
private final String severity;
private final Timestamp timestamp;
@Nullable private final String trace;
@Nullable private final String spanId;
private final SourceLocation sourceLocation;
private final Map<String, String> labels;
private final List<StructuredParameter> parameters;
private final Map<String, String> mappedDiagnosticContext;
@Nullable private final String nestedDiagnosticContext;
@Nullable private final String type;
LogEntry(
String message,
String severity,
Timestamp timestamp,
@Nullable String trace,
@Nullable String spanId,
SourceLocation sourceLocation,
Map<String, String> labels,
List<StructuredParameter> parameters,
Map<String, String> mappedDiagnosticContext,
@Nullable String nestedDiagnosticContext,
@Nullable String type) {
this.message = message;
this.severity = severity;
this.timestamp = timestamp;
this.trace = trace;
this.spanId = spanId;
this.sourceLocation = sourceLocation;
this.labels = labels;
this.parameters = parameters;
this.mappedDiagnosticContext = mappedDiagnosticContext;
this.nestedDiagnosticContext = nestedDiagnosticContext;
this.type = type;
}
static final class SourceLocation {
@Nullable private final String file;
@Nullable private final String line;
@Nullable private final String function;
SourceLocation(@Nullable String file, @Nullable String line, @Nullable String function) {
this.file = file;
this.line = line;
this.function = function;
}
JsonObject json() {
var b = Json.createObjectBuilder();
if (file != null) {
b.add("file", file);
}
if (line != null) {
b.add("line", line);
}
if (function != null) {
b.add("function", function);
}
return b.build();
}
}
static final class Timestamp {
private final long seconds;
private final int nanos;
Timestamp(long seconds, int nanos) {
this.seconds = seconds;
this.nanos = nanos;
}
Timestamp(Instant t) {
this(t.getEpochSecond(), t.getNano());
}
JsonObject json() {
return Json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
}
}
JsonObjectBuilder json() {
var b = Json.createObjectBuilder();
if (trace != null) {
b.add("logging.googleapis.com/trace", trace);
}
if (spanId != null) {
b.add("logging.googleapis.com/spanId", spanId);
}
if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) {
b.add("nestedDiagnosticContext", nestedDiagnosticContext);
}
if (!labels.isEmpty()) {
b.add("logging.googleapis.com/labels", jsonOfStringMap(labels));
}
if (type != null) {
b.add("@type", type);
}
return b.add("message", message)
.add("severity", severity)
.add("timestamp", timestamp.json())
.add("logging.googleapis.com/sourceLocation", sourceLocation.json())
.addAll(jsonOfStringMap(mappedDiagnosticContext))
.addAll(jsonOfParameterMap(parameters));
}
private static JsonObjectBuilder jsonOfStringMap(Map<String, String> stringMap) {
return stringMap.entrySet().stream()
.reduce(
Json.createObjectBuilder(),
(acc, x) -> acc.add(x.getKey(), x.getValue()),
JsonObjectBuilder::addAll);
}
private static JsonObjectBuilder jsonOfParameterMap(List<StructuredParameter> parameters) {
return parameters.stream()
.reduce(
Json.createObjectBuilder(),
(acc, p) -> acc.addAll(p.json()),
JsonObjectBuilder::addAll);
}
}