View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.slf4j;
20  
21  import java.io.PrintStream;
22  import java.util.function.Consumer;
23  
24  import org.apache.maven.api.services.MessageBuilder;
25  
26  import static org.apache.maven.jline.MessageUtils.builder;
27  
28  /**
29   * Logger for Maven, that support colorization of levels and stacktraces. This class implements 2 methods introduced in
30   * slf4j-simple provider local copy.
31   *
32   * @since 3.5.0
33   */
34  public class MavenSimpleLogger extends MavenBaseLogger {
35  
36      private String traceRenderedLevel;
37      private String debugRenderedLevel;
38      private String infoRenderedLevel;
39      private String warnRenderedLevel;
40      private String errorRenderedLevel;
41  
42      static Consumer<String> logSink;
43  
44      public static final String DEFAULT_LOG_LEVEL_KEY = "org.slf4j.simpleLogger.defaultLogLevel";
45  
46      public static void setLogSink(Consumer<String> logSink) {
47          MavenSimpleLogger.logSink = logSink;
48      }
49  
50      MavenSimpleLogger(String name) {
51          super(name);
52      }
53  
54      @Override
55      protected String renderLevel(int level) {
56          if (traceRenderedLevel == null) {
57              traceRenderedLevel = builder().trace("TRACE").build();
58              debugRenderedLevel = builder().debug("DEBUG").build();
59              infoRenderedLevel = builder().info("INFO").build();
60              warnRenderedLevel = builder().warning("WARNING").build();
61              errorRenderedLevel = builder().error("ERROR").build();
62          }
63          return switch (level) {
64              case LOG_LEVEL_TRACE -> traceRenderedLevel;
65              case LOG_LEVEL_DEBUG -> debugRenderedLevel;
66              case LOG_LEVEL_INFO -> infoRenderedLevel;
67              case LOG_LEVEL_WARN -> warnRenderedLevel;
68              default -> errorRenderedLevel;
69          };
70      }
71  
72      @Override
73      protected void write(StringBuilder buf, Throwable t) {
74          Consumer<String> sink = logSink;
75          if (sink != null) {
76              sink.accept(buf.toString());
77              if (t != null) {
78                  writeThrowable(t, sink);
79              }
80          } else {
81              super.write(buf, t);
82          }
83      }
84  
85      @Override
86      protected void writeThrowable(Throwable t, PrintStream stream) {
87          writeThrowable(t, stream::println);
88      }
89  
90      protected void writeThrowable(Throwable t, Consumer<String> stream) {
91          if (t == null) {
92              return;
93          }
94          MessageBuilder builder = builder().failure(t.getClass().getName());
95          if (t.getMessage() != null) {
96              builder.a(": ").failure(t.getMessage());
97          }
98          stream.accept(builder.toString());
99  
100         printStackTrace(t, stream, "");
101     }
102 
103     protected void printStackTrace(Throwable t, Consumer<String> stream, String prefix) {
104         MessageBuilder builder = builder();
105         for (StackTraceElement e : t.getStackTrace()) {
106             builder.a(prefix);
107             builder.a("    ");
108             builder.strong("at");
109             builder.a(" ");
110             builder.a(e.getClassName());
111             builder.a(".");
112             builder.a(e.getMethodName());
113             builder.a("(");
114             builder.strong(getLocation(e));
115             builder.a(")");
116             stream.accept(builder.toString());
117             builder.setLength(0);
118         }
119         for (Throwable se : t.getSuppressed()) {
120             writeThrowable(se, stream, "Suppressed", prefix + "    ");
121         }
122         Throwable cause = t.getCause();
123         if (cause != null && t != cause) {
124             writeThrowable(cause, stream, "Caused by", prefix);
125         }
126     }
127 
128     protected void writeThrowable(Throwable t, Consumer<String> stream, String caption, String prefix) {
129         MessageBuilder builder =
130                 builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName());
131         if (t.getMessage() != null) {
132             builder.a(": ").failure(t.getMessage());
133         }
134         stream.accept(builder.toString());
135 
136         printStackTrace(t, stream, prefix);
137     }
138 
139     protected String getLocation(final StackTraceElement e) {
140         assert e != null;
141 
142         if (e.isNativeMethod()) {
143             return "Native Method";
144         } else if (e.getFileName() == null) {
145             return "Unknown Source";
146         } else if (e.getLineNumber() >= 0) {
147             return e.getFileName() + ":" + e.getLineNumber();
148         } else {
149             return e.getFileName();
150         }
151     }
152 
153     public void configure(int defaultLogLevel) {
154         String levelString = recursivelyComputeLevelString();
155         if (levelString != null) {
156             this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
157         } else {
158             this.currentLogLevel = defaultLogLevel;
159         }
160         traceRenderedLevel = builder().trace("TRACE").build();
161         debugRenderedLevel = builder().debug("DEBUG").build();
162         infoRenderedLevel = builder().info("INFO").build();
163         warnRenderedLevel = builder().warning("WARNING").build();
164         errorRenderedLevel = builder().error("ERROR").build();
165     }
166 
167     public void setLogLevel(int logLevel) {
168         this.currentLogLevel = logLevel;
169     }
170 }