blob: 8bcea2bad713e47a28b020502cc01121822897e1 [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 jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.spi.JsonProvider;
import java.time.Instant;
import java.util.List;
import java.util.Map;
/**
* 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;
@Nullable 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,
@Nullable 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(JsonProvider 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(JsonProvider json) {
return json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build();
}
}
JsonObjectBuilder json(JsonProvider 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(json, labels));
}
if (type != null) {
b.add("@type", type);
}
b.add("message", message).add("severity", severity).add("timestamp", timestamp.json(json));
if (sourceLocation != null) {
b.add("logging.googleapis.com/sourceLocation", sourceLocation.json(json));
}
return b.addAll(jsonOfStringMap(json, mappedDiagnosticContext))
.addAll(jsonOfParameterMap(json, parameters));
}
private JsonObjectBuilder jsonOfStringMap(JsonProvider json, Map<String, String> stringMap) {
return stringMap.entrySet().stream()
.reduce(
json.createObjectBuilder(),
(acc, x) -> acc.add(x.getKey(), x.getValue()),
JsonObjectBuilder::addAll);
}
private JsonObjectBuilder jsonOfParameterMap(
JsonProvider json, List<StructuredParameter> parameters) {
return parameters.stream()
.reduce(
json.createObjectBuilder(),
(acc, p) -> acc.addAll(p.json()),
JsonObjectBuilder::addAll);
}
}