View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.pattern;
18  
19  import java.util.Locale;
20  
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.config.Configuration;
23  import org.apache.logging.log4j.core.config.plugins.Plugin;
24  import org.apache.logging.log4j.core.util.ArrayUtils;
25  import org.apache.logging.log4j.core.util.Loader;
26  import org.apache.logging.log4j.message.Message;
27  import org.apache.logging.log4j.message.MultiformatMessage;
28  import org.apache.logging.log4j.status.StatusLogger;
29  import org.apache.logging.log4j.util.StringBuilderFormattable;
30  
31  /**
32   * Returns the event's rendered message in a StringBuilder.
33   */
34  @Plugin(name = "MessagePatternConverter", category = PatternConverter.CATEGORY)
35  @ConverterKeys({ "m", "msg", "message" })
36  public final class MessagePatternConverter extends LogEventPatternConverter {
37  
38      private static final String NOLOOKUPS = "nolookups";
39  
40      private final String[] formats;
41      private final Configuration config;
42      private final TextRenderer textRenderer;
43      private final boolean noLookups;
44  
45      /**
46       * Private constructor.
47       * 
48       * @param options
49       *            options, may be null.
50       */
51      private MessagePatternConverter(final Configuration config, final String[] options) {
52          super("Message", "message");
53          this.formats = options;
54          this.config = config;
55          final int noLookupsIdx = loadNoLookups(options);
56          this.noLookups = noLookupsIdx >= 0;
57          this.textRenderer = loadMessageRenderer(noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options);
58      }
59  
60      private int loadNoLookups(final String[] options) {
61          if (options != null) {
62              for (int i = 0; i < options.length; i++) {
63                  final String option = options[i];
64                  if (NOLOOKUPS.equalsIgnoreCase(option)) {
65                      return i;
66                  }
67              }
68          }
69          return -1;
70      }
71  
72      private TextRenderer loadMessageRenderer(final String[] options) {
73          if (options != null) {
74              for (final String option : options) {
75                  switch (option.toUpperCase(Locale.ROOT)) {
76                  case "ANSI":
77                      if (Loader.isJansiAvailable()) {
78                          return new JAnsiTextRenderer(options, JAnsiTextRenderer.DefaultMessageStyleMap);
79                      }
80                      StatusLogger.getLogger()
81                              .warn("You requested ANSI message rendering but JANSI is not on the classpath.");
82                      return null;
83                  case "HTML":
84                      return new HtmlTextRenderer(options);
85                  }
86              }
87          }
88          return null;
89      }
90  
91      /**
92       * Obtains an instance of pattern converter.
93       *
94       * @param config
95       *            The Configuration.
96       * @param options
97       *            options, may be null.
98       * @return instance of pattern converter.
99       */
100     public static MessagePatternConverter newInstance(final Configuration config, final String[] options) {
101         return new MessagePatternConverter(config, options);
102     }
103 
104     /**
105      * {@inheritDoc}
106      */
107     @Override
108     public void format(final LogEvent event, final StringBuilder toAppendTo) {
109         final Message msg = event.getMessage();
110         if (msg instanceof StringBuilderFormattable) {
111 
112             final boolean doRender = textRenderer != null;
113             final StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
114 
115             final StringBuilderFormattable stringBuilderFormattable = (StringBuilderFormattable) msg;
116             final int offset = workingBuilder.length();
117             stringBuilderFormattable.formatTo(workingBuilder);
118 
119             // TODO can we optimize this?
120             if (config != null && !noLookups) {
121                 for (int i = offset; i < workingBuilder.length() - 1; i++) {
122                     if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
123                         final String value = workingBuilder.substring(offset, workingBuilder.length());
124                         workingBuilder.setLength(offset);
125                         workingBuilder.append(config.getStrSubstitutor().replace(event, value));
126                     }
127                 }
128             }
129             if (doRender) {
130                 textRenderer.render(workingBuilder, toAppendTo);
131             }
132             return;
133         }
134         if (msg != null) {
135             String result;
136             if (msg instanceof MultiformatMessage) {
137                 result = ((MultiformatMessage) msg).getFormattedMessage(formats);
138             } else {
139                 result = msg.getFormattedMessage();
140             }
141             if (result != null) {
142                 toAppendTo.append(config != null && result.contains("${")
143                         ? config.getStrSubstitutor().replace(event, result) : result);
144             } else {
145                 toAppendTo.append("null");
146             }
147         }
148     }
149 }