View Javadoc

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