View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.module.launcher.log4j;
8   
9   import org.mule.module.launcher.MuleApplicationClassLoader;
10  import org.mule.module.reboot.MuleContainerBootstrapUtils;
11  import org.mule.module.reboot.MuleContainerSystemClassLoader;
12  
13  import java.io.File;
14  import java.io.IOException;
15  import java.net.URL;
16  import java.util.concurrent.ConcurrentHashMap;
17  import java.util.concurrent.ConcurrentMap;
18  
19  import org.apache.log4j.DailyRollingFileAppender;
20  import org.apache.log4j.Hierarchy;
21  import org.apache.log4j.Level;
22  import org.apache.log4j.Logger;
23  import org.apache.log4j.PatternLayout;
24  import org.apache.log4j.PropertyConfigurator;
25  import org.apache.log4j.helpers.LogLog;
26  import org.apache.log4j.spi.LoggerRepository;
27  import org.apache.log4j.spi.RepositorySelector;
28  import org.apache.log4j.spi.RootLogger;
29  import org.apache.log4j.xml.DOMConfigurator;
30  
31  public class ApplicationAwareRepositorySelector implements RepositorySelector
32  {
33      protected static final String PATTERN_LAYOUT = "%-5p %d [%t] %c: %m%n";
34  
35      protected static final Integer NO_CCL_CLASSLOADER = 0;
36      protected ConcurrentMap<Integer, LoggerRepository> repository = new ConcurrentHashMap<Integer, LoggerRepository>();
37  
38      // note that this is a direct log4j logger declaration, not a clogging one
39      protected Logger logger = Logger.getLogger(getClass());
40  
41      public LoggerRepository getLoggerRepository()
42      {
43          final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
44  
45          LoggerRepository repository = this.repository.get(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode());
46          if (repository == null)
47          {
48              final RootLogger root = new RootLogger(Level.INFO);
49              repository = new Hierarchy(root);
50  
51              try
52              {
53                  ConfigWatchDog configWatchDog = null;
54                  if (ccl instanceof MuleApplicationClassLoader)
55                  {
56                      MuleApplicationClassLoader muleCL = (MuleApplicationClassLoader) ccl;
57                      // check if there's an app-specific logging configuration available,
58                      // scope the lookup to this classloader only, as getResource() will delegate to parents
59                      // locate xml config first, fallback to properties format if not found
60                      URL appLogConfig = muleCL.findResource("log4j.xml");
61                      if (appLogConfig == null)
62                      {
63                          appLogConfig = muleCL.findResource("log4j.properties");
64                      }
65                      final String appName = muleCL.getAppName();
66                      if (appLogConfig == null)
67                      {
68                          // fallback to defaults
69                          String logName = String.format("mule-app-%s.log", appName);
70                          File logDir = new File(MuleContainerBootstrapUtils.getMuleHome(), "logs");
71                          File logFile = new File(logDir, logName);
72                          DailyRollingFileAppender fileAppender = new DailyRollingFileAppender(new PatternLayout(PATTERN_LAYOUT), logFile.getAbsolutePath(), "'.'yyyy-MM-dd");
73                          fileAppender.setAppend(true);
74                          fileAppender.activateOptions();
75                          root.addAppender(fileAppender);
76                      }
77                      else
78                      {
79                          configureFrom(appLogConfig, repository);
80                          if (appLogConfig.toExternalForm().startsWith("file:"))
81                          {
82                              // if it's not a file, no sense in monitoring it for changes
83                              configWatchDog = new ConfigWatchDog(muleCL, appLogConfig.getFile(), repository);
84                              configWatchDog.setName(String.format("[%s].log4j.config.monitor", appName));
85                          }
86                          else
87                          {
88                              if (logger.isInfoEnabled())
89                              {
90                                  logger.info(String.format("Logging config %s is not an external file, will not be monitored for changes", appLogConfig));
91                              }
92                          }
93                      }
94                  }
95                  else
96                  {
97                      // this is not an app init, use the top-level defaults
98                      File defaultSystemLog = new File(MuleContainerBootstrapUtils.getMuleHome(), "conf/log4j.xml");
99                      if (!defaultSystemLog.exists() && !defaultSystemLog.canRead())
100                     {
101                         defaultSystemLog = new File(MuleContainerBootstrapUtils.getMuleHome(), "conf/log4j.properties");
102                     }
103                     configureFrom(defaultSystemLog.toURL(), repository);
104 
105                     // only start a watchdog for the Mule container class loader. Other class loaders
106                     // (e.g. Jetty's WebAppClassLoader) should not start a watchdog
107                     if (ccl instanceof MuleContainerSystemClassLoader)
108                     {
109                         configWatchDog = new ConfigWatchDog(ccl, defaultSystemLog.getAbsolutePath(), repository);
110                         configWatchDog.setName("Mule.system.log4j.config.monitor");
111                     }
112                 }
113 
114                 final LoggerRepository previous = this.repository.putIfAbsent(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode(), repository);
115                 if (previous != null)
116                 {
117                     repository = previous;
118                 }
119 
120                 if (configWatchDog != null)
121                 {
122                     configWatchDog.start();
123                 }
124             }
125             catch (IOException e)
126             {
127                 throw new RuntimeException(e);
128             }
129         }
130 
131         return repository;
132     }
133 
134     protected void configureFrom(URL url, LoggerRepository repository)
135     {
136         if (url.toExternalForm().endsWith(".xml"))
137         {
138             new DOMConfigurator().doConfigure(url, repository);
139         }
140         else
141         {
142             new PropertyConfigurator().doConfigure(url, repository);
143         }
144     }
145 
146     // TODO rewrite using a single-threaded scheduled executor and terminate on undeploy/redeploy
147     // this is a modified and unified version from log4j to better fit Mule's app lifecycle
148     protected class ConfigWatchDog extends Thread
149     {
150 
151         protected LoggerRepository repository;
152         protected File file;
153         protected long lastModif = 0;
154         protected boolean warnedAlready = false;
155         protected volatile boolean interrupted = false;
156 
157         /**
158          * The default delay between every file modification check, set to 60
159          * seconds.
160          */
161         static final public long DEFAULT_DELAY = 60000;
162         /**
163          * The name of the file to observe  for changes.
164          */
165         protected String filename;
166 
167         /**
168          * The delay to observe between every check. By default set {@link
169          * #DEFAULT_DELAY}.
170          */
171         protected long delay = DEFAULT_DELAY;
172 
173         public ConfigWatchDog(final ClassLoader classLoader, String filename, LoggerRepository repository)
174         {
175             if (classLoader instanceof MuleApplicationClassLoader)
176             {
177                 ((MuleApplicationClassLoader) classLoader).addShutdownListener(new MuleApplicationClassLoader.ShutdownListener()
178                 {
179                     public void execute()
180                     {
181                         final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
182                         ApplicationAwareRepositorySelector.this.repository.remove(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode());
183                         interrupted = true;
184                     }
185                 });
186             }
187             this.filename = filename;
188             this.file = new File(filename);
189             this.lastModif = file.lastModified();
190             setDaemon(true);
191             this.repository = repository;
192             this.delay = 10000; // 10 secs
193         }
194 
195         public void doOnChange()
196         {
197             if (logger.isInfoEnabled())
198             {
199                 logger.info("Reconfiguring logging from: " + filename);
200             }
201             if (filename.endsWith(".xml"))
202             {
203                 new DOMConfigurator().doConfigure(filename, repository);
204             }
205             else
206             {
207                 new PropertyConfigurator().doConfigure(filename, repository);
208             }
209         }
210 
211         /**
212          * Set the delay to observe between each check of the file changes.
213          */
214         public void setDelay(long delay)
215         {
216             this.delay = delay;
217         }
218 
219         protected void checkAndConfigure()
220         {
221             boolean fileExists;
222             try
223             {
224                 fileExists = file.exists();
225             }
226             catch (SecurityException e)
227             {
228                 LogLog.warn("Was not allowed to read check file existence, file:[" + filename + "].");
229                 interrupted = true; // there is no point in continuing
230                 return;
231             }
232 
233             if (fileExists)
234             {
235                 long l = file.lastModified(); // this can also throw a SecurityException
236                 if (l > lastModif)
237                 {           // however, if we reached this point this
238                     lastModif = l;              // is very unlikely.
239                     doOnChange();
240                     warnedAlready = false;
241                 }
242             }
243             else
244             {
245                 if (!warnedAlready)
246                 {
247                     LogLog.debug("[" + filename + "] does not exist.");
248                     warnedAlready = true;
249                 }
250             }
251         }
252 
253         public void run()
254         {
255             while (!interrupted)
256             {
257                 try
258                 {
259                     Thread.sleep(delay);
260                 }
261                 catch (InterruptedException e)
262                 {
263                     interrupted = true;
264                     Thread.currentThread().interrupt();
265                     break;
266                 }
267                 checkAndConfigure();
268             }
269             if (logger.isDebugEnabled())
270             {
271                 logger.debug(getName() + " terminated successfully");
272             }
273         }
274 
275     }
276 }
277