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      protected void write(StringBuilder buf, Throwable t) {
73          Consumer<String> sink = logSink;
74          if (sink != null) {
75              sink.accept(buf.toString());
76              if (t != null) {
77                  writeThrowable(t, sink);
78              }
79          } else {
80              super.write(buf, t);
81          }
82      }
83  
84      @Override
85      protected void writeThrowable(Throwable t, PrintStream stream) {
86          writeThrowable(t, stream::println);
87      }
88  
89      protected void writeThrowable(Throwable t, Consumer<String> stream) {
90          if (t == null) {
91              return;
92          }
93          MessageBuilder builder = builder().failure(t.getClass().getName());
94          if (t.getMessage() != null) {
95              builder.a(": ").failure(t.getMessage());
96          }
97          stream.accept(builder.toString());
98  
99          printStackTrace(t, stream, "");
100     }
101 
102     protected void printStackTrace(Throwable t, Consumer<String> stream, String prefix) {
103         MessageBuilder builder = builder();
104         for (StackTraceElement e : t.getStackTrace()) {
105             builder.a(prefix);
106             builder.a("    ");
107             builder.strong("at");
108             builder.a(" ");
109             builder.a(e.getClassName());
110             builder.a(".");
111             builder.a(e.getMethodName());
112             builder.a("(");
113             builder.strong(getLocation(e));
114             builder.a(")");
115             stream.accept(builder.toString());
116             builder.setLength(0);
117         }
118         for (Throwable se : t.getSuppressed()) {
119             writeThrowable(se, stream, "Suppressed", prefix + "    ");
120         }
121         Throwable cause = t.getCause();
122         if (cause != null && t != cause) {
123             writeThrowable(cause, stream, "Caused by", prefix);
124         }
125     }
126 
127     protected void writeThrowable(Throwable t, Consumer<String> stream, String caption, String prefix) {
128         MessageBuilder builder =
129                 builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName());
130         if (t.getMessage() != null) {
131             builder.a(": ").failure(t.getMessage());
132         }
133         stream.accept(builder.toString());
134 
135         printStackTrace(t, stream, prefix);
136     }
137 
138     protected String getLocation(final StackTraceElement e) {
139         assert e != null;
140 
141         if (e.isNativeMethod()) {
142             return "Native Method";
143         } else if (e.getFileName() == null) {
144             return "Unknown Source";
145         } else if (e.getLineNumber() >= 0) {
146             return e.getFileName() + ":" + e.getLineNumber();
147         } else {
148             return e.getFileName();
149         }
150     }
151 
152     public void configure(int defaultLogLevel) {
153         String levelString = recursivelyComputeLevelString();
154         if (levelString != null) {
155             this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString);
156         } else {
157             this.currentLogLevel = defaultLogLevel;
158         }
159         traceRenderedLevel = builder().trace("TRACE").build();
160         debugRenderedLevel = builder().debug("DEBUG").build();
161         infoRenderedLevel = builder().info("INFO").build();
162         warnRenderedLevel = builder().warning("WARNING").build();
163         errorRenderedLevel = builder().error("ERROR").build();
164     }
165 
166     public void setLogLevel(int logLevel) {
167         this.currentLogLevel = logLevel;
168     }
169 }