blob: 066f7092f07ade55db69ee39c47477dd394d97f4 [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 Benkard692f48d2021-08-31 21:06:50 +020032 private final List<StructuredParameterProvider> parameterProviders;
33 private final List<LabelProvider> labelProviders;
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020034
Matthias Andreas Benkard42da9f12021-09-02 18:47:28 +020035 /**
36 * Constructs a {@link Formatter}.
37 *
38 * @param parameterProviders the {@link StructuredParameterProvider}s to apply to each log entry.
39 * @param labelProviders the {@link LabelProvider}s to apply to each log entry.
40 */
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020041 public Formatter(
42 Collection<StructuredParameterProvider> parameterProviders,
43 Collection<LabelProvider> labelProviders) {
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020044 this.parameterProviders = List.copyOf(parameterProviders);
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020045 this.labelProviders = List.copyOf(labelProviders);
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020046 }
47
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020048 @Override
49 public String format(ExtLogRecord logRecord) {
50 var message = formatMessageWithStackTrace(logRecord);
51
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020052 List<StructuredParameter> parameters = new ArrayList<>();
53 Map<String, String> labels = new HashMap<>();
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020054
55 for (var parameterProvider : parameterProviders) {
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020056 var parameter = parameterProvider.getParameter();
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020057 if (parameter != null) {
58 parameters.add(parameter);
59 }
60 }
61
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020062 for (var labelProvider : labelProviders) {
63 var providedLabels = labelProvider.getLabels();
64 if (providedLabels != null) {
65 for (var label : providedLabels) {
66 labels.put(label.key(), label.value());
67 }
68 }
69 }
70
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020071 if (logRecord.getParameters() != null) {
72 for (var parameter : logRecord.getParameters()) {
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020073 if (parameter instanceof StructuredParameter) {
74 parameters.add((StructuredParameter) parameter);
75 } else if (parameter instanceof Label) {
76 var label = (Label) parameter;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020077 labels.put(label.key(), label.value());
78 }
79 }
80 }
81
82 var mdc = logRecord.getMdcCopy();
83 var ndc = logRecord.getNdc();
84
85 var sourceLocation =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020086 new LogEntry.SourceLocation(
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020087 logRecord.getSourceFileName(),
88 String.valueOf(logRecord.getSourceLineNumber()),
89 String.format(
90 "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020091
92 var entry =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020093 new LogEntry(
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020094 message,
95 severityOf(logRecord.getLevel()),
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020096 new LogEntry.Timestamp(logRecord.getInstant()),
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020097 null,
98 null,
99 sourceLocation,
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200100 labels,
101 parameters,
102 mdc,
103 ndc,
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200104 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
105
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200106 return entry.json().build().toString() + "\n";
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200107 }
108
109 /**
110 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
111 * {@link ExtLogRecord#getThrown()} exception if any.
112 */
113 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
114 var messageStringWriter = new StringWriter();
115 var messagePrintWriter = new PrintWriter(messageStringWriter);
116 messagePrintWriter.append(this.formatMessage(logRecord));
117
118 if (logRecord.getThrown() != null) {
119 messagePrintWriter.println();
120 logRecord.getThrown().printStackTrace(messagePrintWriter);
121 }
122
123 messagePrintWriter.close();
124 return messageStringWriter.toString();
125 }
126
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +0200127 /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200128 private static String severityOf(Level level) {
129 if (level.intValue() < 500) {
130 return TRACE_LEVEL;
131 } else if (level.intValue() < 700) {
132 return DEBUG_LEVEL;
133 } else if (level.intValue() < 900) {
134 return INFO_LEVEL;
135 } else if (level.intValue() < 1000) {
136 return WARNING_LEVEL;
137 } else {
138 return ERROR_LEVEL;
139 }
140 }
141}