blob: a44e8b57e573521c23396f228cd25f35bfd5d834 [file] [log] [blame]
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +02001package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
2
3import java.io.PrintWriter;
4import java.io.StringWriter;
5import java.util.HashMap;
6import java.util.logging.Level;
7import javax.json.bind.Jsonb;
8import javax.json.bind.JsonbException;
9import org.jboss.logmanager.ExtFormatter;
10import org.jboss.logmanager.ExtLogRecord;
11
12/**
13 * Formats log records as JSON for consumption by Google Cloud Logging.
14 *
15 * <p>Meant to be used in containers running on Google Kubernetes Engine (GKE).
16 *
17 * @see GoogleCloudLogEntry
18 */
19class GoogleCloudLoggingFormatter extends ExtFormatter {
20
21 private static final String TRACE_LEVEL = "TRACE";
22 private static final String DEBUG_LEVEL = "DEBUG";
23 private static final String INFO_LEVEL = "INFO";
24 private static final String WARNING_LEVEL = "WARNING";
25 private static final String ERROR_LEVEL = "ERROR";
26
27 private static final String ERROR_EVENT_TYPE =
28 "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
29
30 private final Jsonb jsonb;
31
32 GoogleCloudLoggingFormatter(Jsonb jsonb) {
33 this.jsonb = jsonb;
34 }
35
36 @Override
37 public String format(ExtLogRecord logRecord) {
38 var message = formatMessageWithStackTrace(logRecord);
39
40 var parameters = new HashMap<String, Object>();
41 var labels = new HashMap<String, String>();
42 if (logRecord.getParameters() != null) {
43 for (var parameter : logRecord.getParameters()) {
44 if (parameter instanceof KeyValueParameter kvparam) {
45 parameters.put(kvparam.key(), kvparam.value());
46 } else if (parameter instanceof Label label) {
47 labels.put(label.key(), label.value());
48 }
49 }
50 }
51
52 var mdc = logRecord.getMdcCopy();
53 var ndc = logRecord.getNdc();
54
55 var sourceLocation =
56 new GoogleCloudLogEntry.SourceLocation(
57 logRecord.getSourceFileName(), String.valueOf(logRecord.getSourceLineNumber()), String.format("%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
58
59 var entry =
60 new GoogleCloudLogEntry(
61 message,
62 severityOf(logRecord.getLevel()),
63 new GoogleCloudLogEntry.Timestamp(logRecord.getInstant()),
64 null,
65 null,
66 sourceLocation,
67 labels.isEmpty() ? null : labels,
68 parameters.isEmpty() ? null : parameters,
69 mdc.isEmpty() ? null : mdc,
70 ndc.isEmpty() ? null : ndc,
71 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
72
73 try {
74 return jsonb.toJson(entry) + "\n";
75 } catch (JsonbException e) {
76 e.printStackTrace();
77 return message + "\n";
78 }
79 }
80
81 /**
82 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
83 * {@link ExtLogRecord#getThrown()} exception if any.
84 */
85 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
86 var messageStringWriter = new StringWriter();
87 var messagePrintWriter = new PrintWriter(messageStringWriter);
88 messagePrintWriter.append(this.formatMessage(logRecord));
89
90 if (logRecord.getThrown() != null) {
91 messagePrintWriter.println();
92 logRecord.getThrown().printStackTrace(messagePrintWriter);
93 }
94
95 messagePrintWriter.close();
96 return messageStringWriter.toString();
97 }
98
99 /**
100 * Computes the Google Cloud Logging severity corresponding to a given {@link Level}.
101 */
102 private static String severityOf(Level level) {
103 if (level.intValue() < 500) {
104 return TRACE_LEVEL;
105 } else if (level.intValue() < 700) {
106 return DEBUG_LEVEL;
107 } else if (level.intValue() < 900) {
108 return INFO_LEVEL;
109 } else if (level.intValue() < 1000) {
110 return WARNING_LEVEL;
111 } else {
112 return ERROR_LEVEL;
113 }
114 }
115}