| 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; |
| } |
| } |
| } |