blob: a44e8b57e573521c23396f228cd25f35bfd5d834 [file] [log] [blame]
package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.logging.Level;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbException;
import org.jboss.logmanager.ExtFormatter;
import org.jboss.logmanager.ExtLogRecord;
/**
* Formats log records as JSON for consumption by Google Cloud Logging.
*
* <p>Meant to be used in containers running on Google Kubernetes Engine (GKE).
*
* @see GoogleCloudLogEntry
*/
class GoogleCloudLoggingFormatter extends ExtFormatter {
private static final String TRACE_LEVEL = "TRACE";
private static final String DEBUG_LEVEL = "DEBUG";
private static final String INFO_LEVEL = "INFO";
private static final String WARNING_LEVEL = "WARNING";
private static final String ERROR_LEVEL = "ERROR";
private static final String ERROR_EVENT_TYPE =
"type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
private final Jsonb jsonb;
GoogleCloudLoggingFormatter(Jsonb jsonb) {
this.jsonb = jsonb;
}
@Override
public String format(ExtLogRecord logRecord) {
var message = formatMessageWithStackTrace(logRecord);
var parameters = new HashMap<String, Object>();
var labels = new HashMap<String, String>();
if (logRecord.getParameters() != null) {
for (var parameter : logRecord.getParameters()) {
if (parameter instanceof KeyValueParameter kvparam) {
parameters.put(kvparam.key(), kvparam.value());
} else if (parameter instanceof Label label) {
labels.put(label.key(), label.value());
}
}
}
var mdc = logRecord.getMdcCopy();
var ndc = logRecord.getNdc();
var sourceLocation =
new GoogleCloudLogEntry.SourceLocation(
logRecord.getSourceFileName(), String.valueOf(logRecord.getSourceLineNumber()), String.format("%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
var entry =
new GoogleCloudLogEntry(
message,
severityOf(logRecord.getLevel()),
new GoogleCloudLogEntry.Timestamp(logRecord.getInstant()),
null,
null,
sourceLocation,
labels.isEmpty() ? null : labels,
parameters.isEmpty() ? null : parameters,
mdc.isEmpty() ? null : mdc,
ndc.isEmpty() ? null : ndc,
logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
try {
return jsonb.toJson(entry) + "\n";
} catch (JsonbException e) {
e.printStackTrace();
return message + "\n";
}
}
/**
* Formats the log message corresponding to {@code logRecord} including a stack trace of the
* {@link ExtLogRecord#getThrown()} exception if any.
*/
private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
var messageStringWriter = new StringWriter();
var messagePrintWriter = new PrintWriter(messageStringWriter);
messagePrintWriter.append(this.formatMessage(logRecord));
if (logRecord.getThrown() != null) {
messagePrintWriter.println();
logRecord.getThrown().printStackTrace(messagePrintWriter);
}
messagePrintWriter.close();
return messageStringWriter.toString();
}
/**
* Computes the Google Cloud Logging severity corresponding to a given {@link Level}.
*/
private static String severityOf(Level level) {
if (level.intValue() < 500) {
return TRACE_LEVEL;
} else if (level.intValue() < 700) {
return DEBUG_LEVEL;
} else if (level.intValue() < 900) {
return INFO_LEVEL;
} else if (level.intValue() < 1000) {
return WARNING_LEVEL;
} else {
return ERROR_LEVEL;
}
}
}