KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tc > logging > TCLogging


1 /*
2  * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
3  * notice. All rights reserved.
4  */

5 package com.tc.logging;
6
7 import org.apache.commons.io.FileUtils;
8 import org.apache.log4j.Appender;
9 import org.apache.log4j.ConsoleAppender;
10 import org.apache.log4j.Level;
11 import org.apache.log4j.Logger;
12 import org.apache.log4j.PatternLayout;
13 import org.apache.log4j.PropertyConfigurator;
14 import org.apache.log4j.RollingFileAppender;
15 import org.apache.log4j.varia.NullAppender;
16
17 import com.tc.util.Assert;
18 import com.tc.util.ProductInfo;
19
20 import java.io.File JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.PrintWriter JavaDoc;
23 import java.io.RandomAccessFile JavaDoc;
24 import java.io.StringWriter JavaDoc;
25 import java.nio.channels.FileChannel JavaDoc;
26 import java.nio.channels.FileLock JavaDoc;
27 import java.nio.channels.OverlappingFileLockException JavaDoc;
28 import java.util.Arrays JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.Properties JavaDoc;
31
32 /**
33  * Factory class for obtaining TCLogger instances.
34  *
35  * @author teck
36  */

37 public class TCLogging {
38
39   private static final int MAX_BUFFERED_LOG_MESSAGES = 10 * 1000;
40
41   private static final String JavaDoc TERRACOTTA_L1_LOG_FILE_NAME = "terracotta-client.log";
42   private static final String JavaDoc TERRACOTTA_L2_LOG_FILE_NAME = "terracotta-server.log";
43   private static final String JavaDoc TERRACOTTA_GENERIC_LOG_FILE_NAME = "terracotta-generic.log";
44
45   private static final String JavaDoc LOCK_FILE_NAME = ".terracotta-logging.lock";
46
47   private static final String JavaDoc INTERNAL_LOGGER_NAMESPACE = "com.tc";
48   private static final String JavaDoc INTERNAL_LOGGER_NAMESPACE_WITH_DOT = INTERNAL_LOGGER_NAMESPACE + ".";
49
50   private static final String JavaDoc CUSTOMER_LOGGER_NAMESPACE = "com.terracottatech";
51   private static final String JavaDoc CUSTOMER_LOGGER_NAMESPACE_WITH_DOT = CUSTOMER_LOGGER_NAMESPACE + ".";
52
53   private static final String JavaDoc CONSOLE_LOGGER_NAME = CUSTOMER_LOGGER_NAMESPACE + ".console";
54
55   private static final String JavaDoc BENCH_LOGGER_NAME = "terracotta.bench";
56   private static final String JavaDoc MAX_LOG_FILE_SIZE = "512MB";
57   private static final int MAX_BACKUPS = 20;
58   private static final String JavaDoc LOG4J_PROPERTIES_FILENAME = ".tc.dev.log4j.properties";
59
60   private static final String JavaDoc CONSOLE_PATTERN = "%d %p - %m%n";
61   private static final String JavaDoc CONSOLE_PATTERN_DEVELOPMENT = "%d [%t] %p %c - %m%n";
62   // This next pattern is used when we're *only* logging to the console.
63
private static final String JavaDoc CONSOLE_LOGGING_ONLY_PATTERN = "[TC] %d %p - %m%n";
64   private static final String JavaDoc FILE_AND_JMX_PATTERN = "%d [%t] %p %c - %m%n";
65
66   private static TCLogger console;
67   private static JMXAppender jmxAppender;
68   private static Appender consoleAppender;
69   private static DelegatingAppender delegateFileAppender;
70   private static DelegatingAppender delegateBufferingAppender;
71   private static boolean buffering;
72
73   private static Logger[] allLoggers;
74
75   private static File JavaDoc currentLoggingDirectory;
76   private static FileLock JavaDoc currentLoggingDirectoryFileLock = null;
77   private static boolean lockingDisabled = false;
78
79   public static JMXAppender getJMXAppender() {
80     return jmxAppender;
81   }
82
83   public static TCLogger getLogger(Class JavaDoc clazz) {
84     if (clazz == null) { throw new IllegalArgumentException JavaDoc("Class cannot be null"); }
85     return getLogger(clazz.getName());
86   }
87
88   public static TCLogger getLogger(String JavaDoc name) {
89     if ((name == null) || !name.startsWith(INTERNAL_LOGGER_NAMESPACE_WITH_DOT)) {
90       // this comment here to make formatter sane
91
throw new IllegalArgumentException JavaDoc("Logger not in valid namepsace (ie. '" + INTERNAL_LOGGER_NAMESPACE_WITH_DOT
92           + "' ): " + name);
93     }
94
95     return new TCLoggerImpl(name);
96   }
97
98   /**
99    * This method lets you get a logger w/o any name restrictoins. FOR TESTS ONLY (ie. not for shipping code)
100    */

101   public static TCLogger getTestingLogger(String JavaDoc name) {
102     if (name == null) { throw new IllegalArgumentException JavaDoc("Name cannot be null"); }
103     return new TCLoggerImpl(name);
104   }
105
106   /**
107    * This method lets you get a logger w/o any name restrictoins. FOR TESTS ONLY (ie. not for shipping code)
108    */

109   public static TCLogger getTestingLogger(Class JavaDoc clazz) {
110     if (clazz == null) { throw new IllegalArgumentException JavaDoc("Class cannot be null"); }
111     return getTestingLogger(clazz.getName());
112   }
113
114   public static TCLogger getBenchLogger() {
115     return new TCLoggerImpl(BENCH_LOGGER_NAME);
116   }
117
118   // You want to look at CustomerLogging to get customer facing logger instances
119
static TCLogger getCustomerLogger(String JavaDoc name) {
120     if (name == null) { throw new IllegalArgumentException JavaDoc("name cannot be null"); }
121
122     name = CUSTOMER_LOGGER_NAMESPACE_WITH_DOT + name;
123
124     if (CONSOLE_LOGGER_NAME.equals(name)) { throw new IllegalArgumentException JavaDoc("Illegal name: " + name); }
125
126     return new TCLoggerImpl(name);
127   }
128
129   // this method not public on purpose, use CustomerLogging.getConsoleLogger() instead
130
static TCLogger getConsoleLogger() {
131     return console;
132   }
133
134   private static void reportLoggingError(Exception JavaDoc e) {
135     reportLoggingError(null, e);
136   }
137
138   private static void reportLoggingError(String JavaDoc message, Exception JavaDoc e) {
139     StringBuffer JavaDoc errorMsg = new StringBuffer JavaDoc("\n");
140
141     if (message != null) {
142       errorMsg.append("WARN: ").append(message).append("\n");
143     }
144
145     if (e != null) {
146       StringWriter JavaDoc sw = new StringWriter JavaDoc();
147       e.printStackTrace(new PrintWriter JavaDoc(sw));
148       errorMsg.append(sw.toString());
149     }
150
151     System.err.println(errorMsg.toString());
152   }
153
154   private static boolean developmentConfiguration() {
155     try {
156       File JavaDoc props = new File JavaDoc(System.getProperty("user.dir"), LOG4J_PROPERTIES_FILENAME);
157
158       if (props.canRead() && props.isFile()) {
159         Logger.getRootLogger().setLevel(Level.INFO);
160         PropertyConfigurator.configure(props.getAbsolutePath());
161         return true;
162       }
163     } catch (Exception JavaDoc e) {
164       reportLoggingError(e);
165     }
166
167     return false;
168   }
169
170   /**
171    * <strong>FOR TESTS ONLY</strong>. This allows tests to successfully blow away directories containing log files on
172    * Windows. This is a bit of a hack, but working around it is otherwise an enormous pain &mdash; tests only fail on
173    * Windows, and you must then very carefully go around, figure out exactly why, and then work around it. Use of this
174    * method makes everything a great deal simpler.
175    */

176   public static synchronized void disableLocking() {
177     lockingDisabled = true;
178
179     if (currentLoggingDirectoryFileLock != null) {
180       try {
181         currentLoggingDirectoryFileLock.release();
182         currentLoggingDirectoryFileLock = null;
183       } catch (IOException JavaDoc ioe) {
184         throw Assert.failure("Unable to release file lock?", ioe);
185       }
186     }
187   }
188
189   public static final int PROCESS_TYPE_GENERIC = 0;
190   public static final int PROCESS_TYPE_L1 = 1;
191   public static final int PROCESS_TYPE_L2 = 2;
192
193   public static void setLogDirectory(File JavaDoc theDirectory, int processType) {
194     Assert.assertNotNull(theDirectory);
195
196     if (theDirectory.getName().trim().equalsIgnoreCase("stdout:")
197         || theDirectory.getName().trim().equalsIgnoreCase("stderr:")) {
198       if (currentLoggingDirectory != null
199           && currentLoggingDirectory.getName().trim().equalsIgnoreCase(theDirectory.getName())) {
200         // Nothing to do; great!
201
return;
202       }
203
204       delegateFileAppender.setDelegate(new NullAppender());
205       consoleAppender.setLayout(new PatternLayout(CONSOLE_LOGGING_ONLY_PATTERN));
206
207       // Logger.addAppender() doesn't double-add, so this is safe
208
Logger.getRootLogger().addAppender(consoleAppender);
209
210       if (buffering) {
211         BufferingAppender realBufferingAppender = (BufferingAppender) delegateBufferingAppender
212             .setDelegate(new NullAppender());
213         realBufferingAppender.stopAndSendContentsTo(consoleAppender);
214         realBufferingAppender.close();
215         buffering = false;
216       }
217
218       boolean stdout = theDirectory.getName().trim().equalsIgnoreCase("stdout:");
219       getConsoleLogger().info("All logging information now output to standard " + (stdout ? "output" : "error") + ".");
220
221       return;
222     }
223
224     synchronized (TCLogging.class) {
225       if (currentLoggingDirectory != null) {
226         try {
227           if (theDirectory.getCanonicalPath().equals(currentLoggingDirectory.getCanonicalPath())) { return; }
228         } catch (IOException JavaDoc ioe) {
229           // oh, well -- what can we do? we'll continue on.
230
}
231       }
232     }
233
234     try {
235       FileUtils.forceMkdir(theDirectory);
236     } catch (IOException JavaDoc ioe) {
237       reportLoggingError("We can't create the directory '" + theDirectory.getAbsolutePath()
238           + "' that you specified for your logs.", ioe);
239       return;
240     }
241
242     if (!theDirectory.canWrite()) {
243       // formatting
244
reportLoggingError("The log directory, '" + theDirectory.getAbsolutePath() + "', can't be written to.", null);
245       return;
246     }
247
248     FileLock JavaDoc thisDirectoryLock = null;
249
250     if (!lockingDisabled) {
251       File JavaDoc lockFile = new File JavaDoc(theDirectory, LOCK_FILE_NAME);
252
253       try {
254         if (!lockFile.exists()) lockFile.createNewFile();
255         Assert.eval(lockFile.exists());
256         FileChannel JavaDoc channel = new RandomAccessFile JavaDoc(lockFile, "rw").getChannel();
257         thisDirectoryLock = channel.tryLock();
258
259         if (thisDirectoryLock == null) {
260           reportLoggingError("The log directory, '" + theDirectory.getAbsolutePath()
261               + "', is already in use by another " + "Terracotta process. Logging will proceed to the console only.",
262               null);
263           return;
264         }
265
266       } catch (OverlappingFileLockException JavaDoc ofle) {
267         // This VM already holds the lock; no problem
268
} catch (IOException JavaDoc ioe) {
269         reportLoggingError("We can't lock the file '" + lockFile.getAbsolutePath() + "', to make sure that only one "
270             + "Terracotta process is using this directory for logging. This may be a permission "
271             + "issue, or some unexpected error. Logging will proceed to the console only.", ioe);
272         return;
273       }
274     }
275
276     RollingFileAppender newFileAppender;
277
278     String JavaDoc logFileName;
279
280     switch (processType) {
281       case PROCESS_TYPE_L1:
282         logFileName = TERRACOTTA_L1_LOG_FILE_NAME;
283         break;
284
285       case PROCESS_TYPE_L2:
286         logFileName = TERRACOTTA_L2_LOG_FILE_NAME;
287         break;
288
289       case PROCESS_TYPE_GENERIC:
290         logFileName = TERRACOTTA_GENERIC_LOG_FILE_NAME;
291         break;
292
293       default:
294         throw Assert.failure("Unknown process type: " + processType);
295     }
296
297     String JavaDoc logFilePath = new File JavaDoc(theDirectory, logFileName).getAbsolutePath();
298
299     synchronized (TCLogging.class) {
300       try {
301         newFileAppender = new RollingFileAppender(new PatternLayout(FILE_AND_JMX_PATTERN), logFilePath, true);
302         newFileAppender.setName("file appender");
303         newFileAppender.setMaxFileSize(MAX_LOG_FILE_SIZE);
304         newFileAppender.setMaxBackupIndex(MAX_BACKUPS);
305
306         // This makes us start with a new file each time.
307
newFileAppender.rollOver();
308
309         // Note: order of operations is very important here. We start the new appender before we close and remove the
310
// old one so that you don't drop any log records.
311
Appender oldFileAppender = delegateFileAppender.setDelegate(newFileAppender);
312
313         if (oldFileAppender != null) {
314           oldFileAppender.close();
315         }
316
317         if (buffering) {
318           BufferingAppender realBufferingAppender = (BufferingAppender) delegateBufferingAppender
319               .setDelegate(new NullAppender());
320           realBufferingAppender.stopAndSendContentsTo(delegateFileAppender);
321           realBufferingAppender.close();
322           buffering = false;
323         }
324
325         currentLoggingDirectory = theDirectory;
326
327         if (currentLoggingDirectoryFileLock != null) currentLoggingDirectoryFileLock.release();
328         currentLoggingDirectoryFileLock = thisDirectoryLock;
329       } catch (IOException JavaDoc ioe) {
330         reportLoggingError("We were unable to switch the logging system to log to '" + logFilePath + "'.", ioe);
331       }
332     }
333
334     getConsoleLogger().info("Log file: '" + logFilePath + "'.");
335     writeSystemProperties();
336   }
337
338   static {
339     ClassLoader JavaDoc prevLoader = Thread.currentThread().getContextClassLoader();
340     Thread.currentThread().setContextClassLoader(TCLogging.class.getClassLoader());
341
342     try {
343       currentLoggingDirectory = null;
344
345       Logger jettyLogger = Logger.getLogger("org.mortbay");
346       jettyLogger.setLevel(Level.OFF);
347
348       Logger internalLogger = Logger.getLogger(INTERNAL_LOGGER_NAMESPACE);
349       Logger customerLogger = Logger.getLogger(CUSTOMER_LOGGER_NAMESPACE);
350       Logger consoleLogger = Logger.getLogger(CONSOLE_LOGGER_NAME);
351       Logger benchLogger = Logger.getLogger(BENCH_LOGGER_NAME);
352
353       allLoggers = new Logger[] { internalLogger, customerLogger, consoleLogger, benchLogger };
354
355       console = new TCLoggerImpl(CONSOLE_LOGGER_NAME);
356
357       internalLogger.setLevel(Level.INFO);
358       customerLogger.setLevel(Level.INFO);
359       consoleLogger.setLevel(Level.INFO);
360       benchLogger.setLevel(Level.INFO);
361
362       boolean isDev = developmentConfiguration();
363
364       consoleAppender = new ConsoleAppender(new PatternLayout(CONSOLE_PATTERN), ConsoleAppender.SYSTEM_ERR);
365
366       if (!isDev) {
367         // Only the console logger goes to the console (by default)
368
consoleLogger.addAppender(consoleAppender);
369       } else {
370         consoleAppender.setLayout(new PatternLayout(CONSOLE_PATTERN_DEVELOPMENT));
371         // For non-customer environments, send all logging to the console...
372
Logger.getRootLogger().addAppender(consoleAppender);
373       }
374
375       delegateFileAppender = new DelegatingAppender(new NullAppender());
376       addToAllLoggers(delegateFileAppender);
377
378       BufferingAppender realBufferingAppender;
379       realBufferingAppender = new BufferingAppender(MAX_BUFFERED_LOG_MESSAGES);
380       realBufferingAppender.setName("buffering appender");
381       delegateBufferingAppender = new DelegatingAppender(realBufferingAppender);
382       addToAllLoggers(delegateBufferingAppender);
383       buffering = true;
384
385       // all logging goes to JMX based appender
386
jmxAppender = new JMXAppender();
387       jmxAppender.setLayout(new PatternLayout(FILE_AND_JMX_PATTERN));
388       jmxAppender.setName("JMX appender");
389       addToAllLoggers(jmxAppender);
390
391       if (!isDev) {
392         CustomerLogging.getGenericCustomerLogger().info("New logging session started.");
393       }
394
395       writeVersion();
396     } catch (Exception JavaDoc e) {
397       reportLoggingError(e);
398     } finally {
399       Thread.currentThread().setContextClassLoader(prevLoader);
400     }
401   }
402
403   // for test use only!
404
public static void addAppender(String JavaDoc loggerName, TCAppender appender) {
405     new TCLoggerImpl(loggerName).getLogger().addAppender(new Log4JAappenderToTCAppender(appender));
406   }
407
408   private static void addToAllLoggers(Appender appender) {
409     for (int i = 0; i < allLoggers.length; ++i)
410       allLoggers[i].addAppender(appender);
411   }
412
413   private static void writeVersion() {
414     ProductInfo info = ProductInfo.getThisProductInfo();
415     CustomerLogging.getConsoleLogger().info(info.toLongString());
416     getLogger(TCLogging.class).info(info.toLongString());
417   }
418
419   private static void writeSystemProperties() {
420     Properties JavaDoc properties = System.getProperties();
421     int maxKeyLength = 1;
422
423     Iterator JavaDoc iter = properties.keySet().iterator();
424     while (iter.hasNext()) {
425       String JavaDoc key = (String JavaDoc) iter.next();
426       maxKeyLength = Math.max(maxKeyLength, key == null ? 0 : key.length());
427     }
428
429     StringBuffer JavaDoc data = new StringBuffer JavaDoc();
430     data.append("========================================================================\n");
431     data.append("All Java System Properties for this Terracotta instance:\n");
432
433     String JavaDoc[] keys = (String JavaDoc[]) properties.keySet().toArray(new String JavaDoc[properties.size()]);
434     Arrays.sort(keys);
435     for (int i = 0; i < keys.length; ++i) {
436       String JavaDoc key = keys[i];
437       String JavaDoc value = (String JavaDoc) properties.get(key);
438
439       while (key.length() < maxKeyLength)
440         key += " ";
441
442       data.append(key + ": " + value + "\n");
443     }
444
445     data.append("========================================================================\n");
446
447     getLogger(TCLogging.class).info(data.toString());
448   }
449
450   // This method for use in tests only
451
public static void closeFileAppender() {
452     if (delegateFileAppender != null) delegateFileAppender.close();
453   }
454
455 }
456
Popular Tags