Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 1 | package eu.mulk.quarkus.googlecloud.jsonlogging; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 2 | |
| 3 | import java.io.PrintWriter; |
| 4 | import java.io.StringWriter; |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 5 | import java.util.ArrayList; |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 6 | import java.util.Collection; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 7 | import java.util.HashMap; |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 8 | import java.util.List; |
| 9 | import java.util.Map; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 10 | import java.util.logging.Level; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 11 | import org.jboss.logmanager.ExtFormatter; |
| 12 | import 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 Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 19 | * @see LogEntry |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 20 | */ |
Matthias Andreas Benkard | c066d89 | 2021-05-11 21:27:23 +0200 | [diff] [blame] | 21 | public class Formatter extends ExtFormatter { |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 22 | |
| 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 Benkard | 692f48d | 2021-08-31 21:06:50 +0200 | [diff] [blame] | 32 | private final List<StructuredParameterProvider> parameterProviders; |
| 33 | private final List<LabelProvider> labelProviders; |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 34 | |
Matthias Andreas Benkard | 42da9f1 | 2021-09-02 18:47:28 +0200 | [diff] [blame] | 35 | /** |
| 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 Benkard | 692f48d | 2021-08-31 21:06:50 +0200 | [diff] [blame] | 41 | public Formatter( |
| 42 | Collection<StructuredParameterProvider> parameterProviders, |
| 43 | Collection<LabelProvider> labelProviders) { |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 44 | this.parameterProviders = List.copyOf(parameterProviders); |
Matthias Andreas Benkard | 692f48d | 2021-08-31 21:06:50 +0200 | [diff] [blame] | 45 | this.labelProviders = List.copyOf(labelProviders); |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 46 | } |
| 47 | |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 48 | @Override |
| 49 | public String format(ExtLogRecord logRecord) { |
| 50 | var message = formatMessageWithStackTrace(logRecord); |
| 51 | |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 52 | List<StructuredParameter> parameters = new ArrayList<>(); |
| 53 | Map<String, String> labels = new HashMap<>(); |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 54 | |
| 55 | for (var parameterProvider : parameterProviders) { |
Matthias Andreas Benkard | 692f48d | 2021-08-31 21:06:50 +0200 | [diff] [blame] | 56 | var parameter = parameterProvider.getParameter(); |
Matthias Andreas Benkard | 82d7e44 | 2021-08-29 08:34:11 +0200 | [diff] [blame] | 57 | if (parameter != null) { |
| 58 | parameters.add(parameter); |
| 59 | } |
| 60 | } |
| 61 | |
Matthias Andreas Benkard | 692f48d | 2021-08-31 21:06:50 +0200 | [diff] [blame] | 62 | 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 Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 71 | if (logRecord.getParameters() != null) { |
| 72 | for (var parameter : logRecord.getParameters()) { |
Matthias Andreas Benkard | 121a631 | 2021-05-12 05:41:25 +0200 | [diff] [blame] | 73 | if (parameter instanceof StructuredParameter) { |
| 74 | parameters.add((StructuredParameter) parameter); |
| 75 | } else if (parameter instanceof Label) { |
| 76 | var label = (Label) parameter; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 77 | 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 Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 86 | new LogEntry.SourceLocation( |
Matthias Andreas Benkard | 4bae5f1 | 2021-05-03 19:16:48 +0200 | [diff] [blame] | 87 | logRecord.getSourceFileName(), |
| 88 | String.valueOf(logRecord.getSourceLineNumber()), |
| 89 | String.format( |
| 90 | "%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName())); |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 91 | |
| 92 | var entry = |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 93 | new LogEntry( |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 94 | message, |
| 95 | severityOf(logRecord.getLevel()), |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 96 | new LogEntry.Timestamp(logRecord.getInstant()), |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 97 | null, |
| 98 | null, |
| 99 | sourceLocation, |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 100 | labels, |
| 101 | parameters, |
| 102 | mdc, |
| 103 | ndc, |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 104 | logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null); |
| 105 | |
Matthias Andreas Benkard | b8fbc37 | 2021-05-11 06:50:45 +0200 | [diff] [blame] | 106 | return entry.json().build().toString() + "\n"; |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 107 | } |
| 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 Benkard | 4bae5f1 | 2021-05-03 19:16:48 +0200 | [diff] [blame] | 127 | /** Computes the Google Cloud Logging severity corresponding to a given {@link Level}. */ |
Matthias Andreas Benkard | c8144a9 | 2021-05-03 08:04:53 +0200 | [diff] [blame] | 128 | 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 | } |