blob: 0c691b756d40b41def7cc83533689ab14f97b602 [file] [log] [blame]
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02001package eu.mulk.quarkus.googlecloud.jsonlogging;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +02002
3import java.io.PrintWriter;
4import java.io.StringWriter;
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02005import java.util.ArrayList;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +02006import java.util.HashMap;
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02007import java.util.List;
8import java.util.Map;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +02009import java.util.logging.Level;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020010import org.jboss.logmanager.ExtFormatter;
11import org.jboss.logmanager.ExtLogRecord;
12
13/**
14 * Formats log records as JSON for consumption by Google Cloud Logging.
15 *
16 * <p>Meant to be used in containers running on Google Kubernetes Engine (GKE).
17 *
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020018 * @see LogEntry
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020019 */
Matthias Andreas Benkardc066d892021-05-11 21:27:23 +020020public class Formatter extends ExtFormatter {
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020021
22 private static final String TRACE_LEVEL = "TRACE";
23 private static final String DEBUG_LEVEL = "DEBUG";
24 private static final String INFO_LEVEL = "INFO";
25 private static final String WARNING_LEVEL = "WARNING";
26 private static final String ERROR_LEVEL = "ERROR";
27
28 private static final String ERROR_EVENT_TYPE =
29 "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
30
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020031 @Override
32 public String format(ExtLogRecord logRecord) {
33 var message = formatMessageWithStackTrace(logRecord);
34
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020035 List<StructuredParameter> parameters = new ArrayList<>();
36 Map<String, String> labels = new HashMap<>();
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020037 if (logRecord.getParameters() != null) {
38 for (var parameter : logRecord.getParameters()) {
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020039 if (parameter instanceof StructuredParameter sparam) {
40 parameters.add(sparam);
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020041 } else if (parameter instanceof Label label) {
42 labels.put(label.key(), label.value());
43 }
44 }
45 }
46
47 var mdc = logRecord.getMdcCopy();
48 var ndc = logRecord.getNdc();
49
50 var sourceLocation =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020051 new LogEntry.SourceLocation(
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020052 logRecord.getSourceFileName(),
53 String.valueOf(logRecord.getSourceLineNumber()),
54 String.format(
55 "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020056
57 var entry =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020058 new LogEntry(
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020059 message,
60 severityOf(logRecord.getLevel()),
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020061 new LogEntry.Timestamp(logRecord.getInstant()),
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020062 null,
63 null,
64 sourceLocation,
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020065 labels,
66 parameters,
67 mdc,
68 ndc,
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020069 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
70
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020071 return entry.json().build().toString() + "\n";
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020072 }
73
74 /**
75 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
76 * {@link ExtLogRecord#getThrown()} exception if any.
77 */
78 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
79 var messageStringWriter = new StringWriter();
80 var messagePrintWriter = new PrintWriter(messageStringWriter);
81 messagePrintWriter.append(this.formatMessage(logRecord));
82
83 if (logRecord.getThrown() != null) {
84 messagePrintWriter.println();
85 logRecord.getThrown().printStackTrace(messagePrintWriter);
86 }
87
88 messagePrintWriter.close();
89 return messageStringWriter.toString();
90 }
91
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020092 /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020093 private static String severityOf(Level level) {
94 if (level.intValue() < 500) {
95 return TRACE_LEVEL;
96 } else if (level.intValue() < 700) {
97 return DEBUG_LEVEL;
98 } else if (level.intValue() < 900) {
99 return INFO_LEVEL;
100 } else if (level.intValue() < 1000) {
101 return WARNING_LEVEL;
102 } else {
103 return ERROR_LEVEL;
104 }
105 }
106}