blob: c8bb3109b033f945ef15c32b129acf2401ecf2eb [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 Benkard692f48d2021-08-31 21:06:50 +020035 public Formatter(
36 Collection<StructuredParameterProvider> parameterProviders,
37 Collection<LabelProvider> labelProviders) {
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020038 this.parameterProviders = List.copyOf(parameterProviders);
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020039 this.labelProviders = List.copyOf(labelProviders);
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020040 }
41
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020042 @Override
43 public String format(ExtLogRecord logRecord) {
44 var message = formatMessageWithStackTrace(logRecord);
45
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020046 List<StructuredParameter> parameters = new ArrayList<>();
47 Map<String, String> labels = new HashMap<>();
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020048
49 for (var parameterProvider : parameterProviders) {
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020050 var parameter = parameterProvider.getParameter();
Matthias Andreas Benkard82d7e442021-08-29 08:34:11 +020051 if (parameter != null) {
52 parameters.add(parameter);
53 }
54 }
55
Matthias Andreas Benkard692f48d2021-08-31 21:06:50 +020056 for (var labelProvider : labelProviders) {
57 var providedLabels = labelProvider.getLabels();
58 if (providedLabels != null) {
59 for (var label : providedLabels) {
60 labels.put(label.key(), label.value());
61 }
62 }
63 }
64
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020065 if (logRecord.getParameters() != null) {
66 for (var parameter : logRecord.getParameters()) {
Matthias Andreas Benkard121a6312021-05-12 05:41:25 +020067 if (parameter instanceof StructuredParameter) {
68 parameters.add((StructuredParameter) parameter);
69 } else if (parameter instanceof Label) {
70 var label = (Label) parameter;
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020071 labels.put(label.key(), label.value());
72 }
73 }
74 }
75
76 var mdc = logRecord.getMdcCopy();
77 var ndc = logRecord.getNdc();
78
79 var sourceLocation =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020080 new LogEntry.SourceLocation(
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020081 logRecord.getSourceFileName(),
82 String.valueOf(logRecord.getSourceLineNumber()),
83 String.format(
84 "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020085
86 var entry =
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020087 new LogEntry(
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020088 message,
89 severityOf(logRecord.getLevel()),
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020090 new LogEntry.Timestamp(logRecord.getInstant()),
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020091 null,
92 null,
93 sourceLocation,
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +020094 labels,
95 parameters,
96 mdc,
97 ndc,
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020098 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
99
Matthias Andreas Benkardb8fbc372021-05-11 06:50:45 +0200100 return entry.json().build().toString() + "\n";
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200101 }
102
103 /**
104 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
105 * {@link ExtLogRecord#getThrown()} exception if any.
106 */
107 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
108 var messageStringWriter = new StringWriter();
109 var messagePrintWriter = new PrintWriter(messageStringWriter);
110 messagePrintWriter.append(this.formatMessage(logRecord));
111
112 if (logRecord.getThrown() != null) {
113 messagePrintWriter.println();
114 logRecord.getThrown().printStackTrace(messagePrintWriter);
115 }
116
117 messagePrintWriter.close();
118 return messageStringWriter.toString();
119 }
120
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +0200121 /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200122 private static String severityOf(Level level) {
123 if (level.intValue() < 500) {
124 return TRACE_LEVEL;
125 } else if (level.intValue() < 700) {
126 return DEBUG_LEVEL;
127 } else if (level.intValue() < 900) {
128 return INFO_LEVEL;
129 } else if (level.intValue() < 1000) {
130 return WARNING_LEVEL;
131 } else {
132 return ERROR_LEVEL;
133 }
134 }
135}