You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LogbackJsonLayout.java 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.process.logging;
  21. import ch.qos.logback.classic.spi.ILoggingEvent;
  22. import ch.qos.logback.classic.spi.IThrowableProxy;
  23. import ch.qos.logback.classic.spi.StackTraceElementProxy;
  24. import ch.qos.logback.core.CoreConstants;
  25. import ch.qos.logback.core.LayoutBase;
  26. import com.google.gson.stream.JsonWriter;
  27. import java.io.IOException;
  28. import java.io.StringWriter;
  29. import java.time.Instant;
  30. import java.time.ZoneId;
  31. import java.time.format.DateTimeFormatter;
  32. import java.util.List;
  33. import java.util.Locale;
  34. import java.util.Map;
  35. import java.util.regex.Pattern;
  36. import org.apache.commons.lang3.StringUtils;
  37. import static java.lang.String.format;
  38. import static java.util.Objects.requireNonNull;
  39. /**
  40. * Formats logs in JSON.
  41. * <p>
  42. * Strongly inspired by https://github.com/qos-ch/logback/blob/master/logback-classic/src/main/java/ch/qos/logback/classic/html/DefaultThrowableRenderer.java
  43. */
  44. public class LogbackJsonLayout extends LayoutBase<ILoggingEvent> {
  45. static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
  46. .withLocale(Locale.US)
  47. .withZone(ZoneId.systemDefault());
  48. private static final Pattern NEWLINE_REGEXP = Pattern.compile("\n");
  49. private final String processKey;
  50. private final String nodeName;
  51. private final List<String> exclusions;
  52. public LogbackJsonLayout(String processKey, String nodeName) {
  53. this(processKey, nodeName, List.of());
  54. }
  55. public LogbackJsonLayout(String processKey, String nodeName, List<String> exclusions) {
  56. this.processKey = requireNonNull(processKey);
  57. this.nodeName = nodeName;
  58. this.exclusions = exclusions;
  59. }
  60. String getProcessKey() {
  61. return processKey;
  62. }
  63. @Override
  64. public String doLayout(ILoggingEvent event) {
  65. StringWriter output = new StringWriter();
  66. try (JsonWriter json = new JsonWriter(output)) {
  67. json.beginObject();
  68. if (!"".equals(nodeName)) {
  69. json.name("nodename").value(nodeName);
  70. }
  71. json.name("process").value(processKey);
  72. for (Map.Entry<String, String> entry : event.getMDCPropertyMap().entrySet()) {
  73. if (entry.getValue() != null && !exclusions.contains(entry.getKey())) {
  74. json.name(entry.getKey()).value(entry.getValue());
  75. }
  76. }
  77. json
  78. .name("timestamp").value(DATE_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp())))
  79. .name("severity").value(event.getLevel().toString())
  80. .name("logger").value(event.getLoggerName())
  81. .name("message").value(NEWLINE_REGEXP.matcher(event.getFormattedMessage()).replaceAll("\r"));
  82. IThrowableProxy tp = event.getThrowableProxy();
  83. if (tp != null) {
  84. json.name("stacktrace").beginArray();
  85. int nbOfTabs = 0;
  86. while (tp != null) {
  87. printFirstLine(json, tp, nbOfTabs);
  88. render(json, tp, nbOfTabs);
  89. tp = tp.getCause();
  90. nbOfTabs++;
  91. }
  92. json.endArray();
  93. }
  94. json.endObject();
  95. } catch (Exception e) {
  96. e.printStackTrace();
  97. throw new IllegalStateException("BUG - fail to create JSON", e);
  98. }
  99. output.write(System.lineSeparator());
  100. return output.toString();
  101. }
  102. private static void render(JsonWriter output, IThrowableProxy tp, int nbOfTabs) throws IOException {
  103. String tabs = StringUtils.repeat("\t", nbOfTabs);
  104. int commonFrames = tp.getCommonFrames();
  105. StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
  106. for (int i = 0; i < stepArray.length - commonFrames; i++) {
  107. StackTraceElementProxy step = stepArray[i];
  108. output.value(tabs + step.toString());
  109. }
  110. if (commonFrames > 0) {
  111. output.value(tabs + "... " + commonFrames + " common frames omitted");
  112. }
  113. for (IThrowableProxy suppressed : tp.getSuppressed()) {
  114. output.value(format("%sSuppressed: %s: %s", tabs, suppressed.getClassName(), suppressed.getMessage()));
  115. render(output, suppressed, nbOfTabs + 1);
  116. }
  117. }
  118. private static void printFirstLine(JsonWriter output, IThrowableProxy tp, int nbOfTabs) throws IOException {
  119. String tabs = StringUtils.repeat("\t", nbOfTabs);
  120. int commonFrames = tp.getCommonFrames();
  121. if (commonFrames > 0) {
  122. output.value(tabs + CoreConstants.CAUSED_BY);
  123. }
  124. output.value(tabs + tp.getClassName() + ": " + tp.getMessage());
  125. }
  126. }