blob: c6e177cbd0d8472ba4228f975ff34946f1dad9bd [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 Benkard82d7e442021-08-29 08:34:11 +02006import java.util.Collection;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +02007import java.util.HashMap;
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +02008import java.util.List;
9import java.util.Map;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020010import java.util.logging.Level;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020011import org.jboss.logmanager.ExtFormatter;
12import org.jboss.logmanager.ExtLogRecord;
13
14/**
15 * Formats log records as JSON for consumption by Google Cloud Logging.
16 *
17 * <p>Meant to be used in containers running on Google Kubernetes Engine (GKE).
18 *
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020019 * @see LogEntry
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020020 */
Matthias Andreas Benkardc066d892021-05-11 21:27:23 +020021public class Formatter extends ExtFormatter {
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020022
23 private static final String TRACE_LEVEL = "TRACE";
24 private static final String DEBUG_LEVEL = "DEBUG";
25 private static final String INFO_LEVEL = "INFO";
26 private static final String WARNING_LEVEL = "WARNING";
27 private static final String ERROR_LEVEL = "ERROR";
28
29 private static final String ERROR_EVENT_TYPE =
30 "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
31
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020032 private final List<ParameterProvider> parameterProviders;
33
34 public Formatter(Collection<ParameterProvider> parameterProviders) {
35 this.parameterProviders = List.copyOf(parameterProviders);
36 }
37
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020038 @Override
39 public String format(ExtLogRecord logRecord) {
40 var message = formatMessageWithStackTrace(logRecord);
41
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020042 List<StructuredParameter> parameters = new ArrayList<>();
43 Map<String, String> labels = new HashMap<>();
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020044
45 for (var parameterProvider : parameterProviders) {
46 var parameter = parameterProvider.get();
47 if (parameter != null) {
48 parameters.add(parameter);
49 }
50 }
51
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020052 if (logRecord.getParameters() != null) {
53 for (var parameter : logRecord.getParameters()) {
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020054 if (parameter instanceof StructuredParameter) {
55 parameters.add((StructuredParameter) parameter);
56 } else if (parameter instanceof Label) {
57 var label = (Label) parameter;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020058 labels.put(label.key(), label.value());
59 }
60 }
61 }
62
63 var mdc = logRecord.getMdcCopy();
64 var ndc = logRecord.getNdc();
65
66 var sourceLocation =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020067 new LogEntry.SourceLocation(
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020068 logRecord.getSourceFileName(),
69 String.valueOf(logRecord.getSourceLineNumber()),
70 String.format(
71 "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020072
73 var entry =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020074 new LogEntry(
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020075 message,
76 severityOf(logRecord.getLevel()),
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020077 new LogEntry.Timestamp(logRecord.getInstant()),
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020078 null,
79 null,
80 sourceLocation,
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020081 labels,
82 parameters,
83 mdc,
84 ndc,
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020085 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
86
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020087 return entry.json().build().toString() + "\n";
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020088 }
89
90 /**
91 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
92 * {@link ExtLogRecord#getThrown()} exception if any.
93 */
94 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
95 var messageStringWriter = new StringWriter();
96 var messagePrintWriter = new PrintWriter(messageStringWriter);
97 messagePrintWriter.append(this.formatMessage(logRecord));
98
99 if (logRecord.getThrown() != null) {
100 messagePrintWriter.println();
101 logRecord.getThrown().printStackTrace(messagePrintWriter);
102 }
103
104 messagePrintWriter.close();
105 return messageStringWriter.toString();
106 }
107
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +0200108 /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200109 private static String severityOf(Level level) {
110 if (level.intValue() < 500) {
111 return TRACE_LEVEL;
112 } else if (level.intValue() < 700) {
113 return DEBUG_LEVEL;
114 } else if (level.intValue() < 900) {
115 return INFO_LEVEL;
116 } else if (level.intValue() < 1000) {
117 return WARNING_LEVEL;
118 } else {
119 return ERROR_LEVEL;
120 }
121 }
122}