View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.util;
17  
18  import java.io.PrintWriter;
19  import java.io.Serializable;
20  import java.io.StringWriter;
21  import java.util.Set;
22  
23  import org.apache.logging.log4j.Level;
24  import org.apache.logging.log4j.core.Appender;
25  import org.apache.logging.log4j.core.Core;
26  import org.apache.logging.log4j.core.Filter;
27  import org.apache.logging.log4j.core.Layout;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.appender.AbstractAppender;
30  import org.apache.logging.log4j.core.config.Property;
31  import org.apache.logging.log4j.core.config.plugins.Plugin;
32  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
33  import org.apache.logging.log4j.core.config.plugins.PluginElement;
34  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
35  import org.codelibs.fess.helper.LogNotificationHelper;
36  import org.codelibs.fess.helper.LogNotificationHelper.LogNotificationEvent;
37  
38  /**
39   * Custom Log4j2 Appender that captures log events into a buffer for notification purposes.
40   */
41  @Plugin(name = "LogNotificationAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
42  public class LogNotificationAppender extends AbstractAppender {
43  
44      private static final int MAX_THROWABLE_LENGTH = 500;
45  
46      private static final Set<String> EXCLUDED_LOGGERS = Set.of( //
47              "org.codelibs.fess.helper.NotificationHelper", //
48              "org.codelibs.fess.util.LogNotificationAppender", //
49              "org.codelibs.fess.job.LogNotificationJob", //
50              "org.codelibs.fess.timer.LogNotificationTarget", //
51              "org.codelibs.fess.helper.LogNotificationHelper", //
52              "org.codelibs.curl" //
53      );
54  
55      private final Level minLevel;
56  
57      /**
58       * Constructs a new LogNotificationAppender.
59       *
60       * @param name the appender name
61       * @param filter the filter to apply
62       * @param layout the layout to use
63       * @param ignoreExceptions whether to ignore exceptions
64       * @param properties the appender properties
65       * @param minLevel the minimum log level to capture
66       */
67      protected LogNotificationAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
68              final boolean ignoreExceptions, final Property[] properties, final Level minLevel) {
69          super(name, filter, layout, ignoreExceptions, properties);
70          this.minLevel = minLevel;
71      }
72  
73      @Override
74      public void append(final LogEvent event) {
75          final String loggerName = event.getLoggerName();
76          if (loggerName != null) {
77              for (final String excluded : EXCLUDED_LOGGERS) {
78                  if (loggerName.startsWith(excluded)) {
79                      return;
80                  }
81              }
82          }
83  
84          if (!event.getLevel().isMoreSpecificThan(getEffectiveMinLevel())) {
85              return;
86          }
87  
88          try {
89              if (!ComponentUtil.getFessConfig().isLogNotificationEnabled()) {
90                  return;
91              }
92          } catch (final Exception e) {
93              return;
94          }
95  
96          final LogNotificationHelper helper;
97          try {
98              helper = ComponentUtil.getLogNotificationHelper();
99          } catch (final Exception e) {
100             return;
101         }
102 
103         String throwableStr = null;
104         final Throwable thrown = event.getThrown();
105         if (thrown != null) {
106             final StringWriter sw = new StringWriter();
107             thrown.printStackTrace(new PrintWriter(sw));
108             throwableStr = sw.toString();
109             if (throwableStr.length() > MAX_THROWABLE_LENGTH) {
110                 throwableStr = throwableStr.substring(0, MAX_THROWABLE_LENGTH);
111             }
112         }
113 
114         final LogNotificationEvent notificationEvent = new LogNotificationEvent( //
115                 event.getTimeMillis(), //
116                 event.getLevel().name(), //
117                 loggerName, //
118                 event.getMessage().getFormattedMessage(), //
119                 throwableStr //
120         );
121         helper.offer(notificationEvent);
122     }
123 
124     private Level getEffectiveMinLevel() {
125         try {
126             final String levelStr = ComponentUtil.getSystemProperties().getProperty("fess.log.notification.level");
127             if (levelStr != null) {
128                 return Level.toLevel(levelStr, minLevel);
129             }
130         } catch (final Exception e) {
131             // ComponentUtil not initialized yet
132         }
133         return minLevel;
134     }
135 
136     /**
137      * Factory method to create a LogNotificationAppender instance.
138      *
139      * @param name the appender name
140      * @param minLevel the minimum log level string (defaults to ERROR)
141      * @param filter the filter to apply
142      * @param layout the layout to use
143      * @return a new LogNotificationAppender instance, or null if name is null
144      */
145     @PluginFactory
146     public static LogNotificationAppender createAppender( //
147             @PluginAttribute("name") final String name, //
148             @PluginAttribute(value = "minLevel", defaultString = "ERROR") final String minLevel, //
149             @PluginElement("Filter") final Filter filter, //
150             @PluginElement("Layout") final Layout<? extends Serializable> layout) {
151         if (name == null) {
152             LOGGER.error("No name provided for LogNotificationAppender");
153             return null;
154         }
155         final Level level = Level.toLevel(minLevel, Level.ERROR);
156         return new LogNotificationAppender(name, filter, layout, true, Property.EMPTY_ARRAY, level);
157     }
158 }