blob: 3ec1fcc427f823ec4fde38569e80e68fa95e9d24 [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(
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +020057 logRecord.getSourceFileName(),
58 String.valueOf(logRecord.getSourceLineNumber()),
59 String.format(
60 "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +020061
62 var entry =
63 new GoogleCloudLogEntry(
64 message,
65 severityOf(logRecord.getLevel()),
66 new GoogleCloudLogEntry.Timestamp(logRecord.getInstant()),
67 null,
68 null,
69 sourceLocation,
70 labels.isEmpty() ? null : labels,
71 parameters.isEmpty() ? null : parameters,
72 mdc.isEmpty() ? null : mdc,
73 ndc.isEmpty() ? null : ndc,
74 logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
75
76 try {
77 return jsonb.toJson(entry) + "\n";
78 } catch (JsonbException e) {
79 e.printStackTrace();
80 return message + "\n";
81 }
82 }
83
84 /**
85 * Formats the log message corresponding to {@code logRecord} including a stack trace of the
86 * {@link ExtLogRecord#getThrown()} exception if any.
87 */
88 private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
89 var messageStringWriter = new StringWriter();
90 var messagePrintWriter = new PrintWriter(messageStringWriter);
91 messagePrintWriter.append(this.formatMessage(logRecord));
92
93 if (logRecord.getThrown() != null) {
94 messagePrintWriter.println();
95 logRecord.getThrown().printStackTrace(messagePrintWriter);
96 }
97
98 messagePrintWriter.close();
99 return messageStringWriter.toString();
100 }
101
Matthias Andreas Benkard4bae5f12021-05-03 19:16:48 +0200102 /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */
Matthias Andreas Benkardc8144a92021-05-03 08:04:53 +0200103 private static String severityOf(Level level) {
104 if (level.intValue() < 500) {
105 return TRACE_LEVEL;
106 } else if (level.intValue() < 700) {
107 return DEBUG_LEVEL;
108 } else if (level.intValue() < 900) {
109 return INFO_LEVEL;
110 } else if (level.intValue() < 1000) {
111 return WARNING_LEVEL;
112 } else {
113 return ERROR_LEVEL;
114 }
115 }
116}