001: /*
002: This software is OSI Certified Open Source Software.
003: OSI Certified is a certification mark of the Open Source Initiative.
004:
005: The license (Mozilla version 1.0) can be read at the MMBase site.
006: See http://www.MMBase.org/license
007:
008: */
009:
010: package org.mmbase.util.logging;
011:
012: import java.lang.reflect.Method;
013:
014: import java.util.*;
015:
016: import org.mmbase.util.ApplicationContextReader;
017: import org.mmbase.util.ResourceWatcher;
018: import org.mmbase.util.ResourceLoader;
019: import org.mmbase.util.xml.DocumentReader;
020:
021: /**
022: * With this class the logging is configured and it supplies the `Logger' objects.
023: * <p>
024: * For example:
025: * <code>
026: * <pre>
027: * <tt>
028: * <b><font color=#0000FF>import</font></b> org.mmbase.util.logging.Logging;
029: * <b><font color=#0000FF>import</font></b> org.mmbase.util.logging.Logger;
030: * <b><font color=#0000FF>import</font></b> org.mmbase.util.logging.Level;
031: *
032: * <b><font color=#0000FF>public</font></b> <b><font color=#0000FF>class</font></b> test {
033: *
034: * <b><font color=#0000FF>static</font></b> {
035: * Logging.configure(<font color=#FF0000>"log.xml"</font>);
036: * }
037: *
038: * <b><font color=#0000FF>static</font></b> Logger log = Logging.getLoggerInstance(test.<b><font color=#0000FF>class</font></b>.getName());
039: *
040: * <b><font color=#0000FF>public</font></b> <b><font color=#0000FF>static</font></b> <font color=#009900>void</font> main(String[] args) {
041: * log.debug(<font color=#FF0000>"start"</font>);
042: * log.info(<font color=#FF0000>"Entering application."</font>);
043: *
044: * log.setPriority(Level.TRACE);
045: * <b><font color=#0000FF>if</font></b> (log.isDebugEnabled()) {
046: * log.debug(<font color=#FF0000>"debug een"</font>);
047: * log.trace(<font color=#FF0000>"trace twee"</font>);
048: * }
049: * log.info(<font color=#FF0000>"info"</font>);
050: * log.service(<font color=#FF0000>"service"</font>);
051: *
052: *
053: * Logging.shutdown();
054: * }
055: * }
056: * </tt>
057: * </pre>
058: * </code>
059: * </p>
060: *
061: * @author Michiel Meeuwissen
062: * @version $Id: Logging.java,v 1.44 2007/06/21 16:17:21 michiel Exp $
063: */
064:
065: public class Logging {
066:
067: private static Class<?> logClass = SimpleTimeStampImpl.class; // default Logger Implementation
068: private static boolean configured = false;
069: private static final Logger log = getLoggerInstance(Logging.class); // logger for this class itself
070:
071: /**
072: * The category for logging info about pages (like stop / start). Also if pages take the
073: * initiative for logging themselves they should log below this category.
074: * @since MMBase-1.7
075: */
076: public final static String PAGE_CATEGORY = "org.mmbase.PAGE";
077:
078: private static ResourceLoader resourceLoader;
079:
080: /**
081: * @since MMBase-1.8
082: */
083: private static ResourceWatcher configWatcher;
084:
085: private static String machineName = "localhost";
086:
087: private Logging() {
088: // this class has no instances.
089: }
090:
091: /**
092: * @since MMBase-1.8
093: */
094: public static String getMachineName() {
095: return machineName;
096: }
097:
098: /**
099: * @since MMBase-1.8
100: */
101: public static void setMachineName(String mn) {
102: machineName = mn;
103: }
104:
105: /**
106: * @since MMBase-1.8.5
107: */
108: public static Map<String, String> getInitParameters() {
109: try {
110: Map<String, String> contextMap = ApplicationContextReader
111: .getProperties("mmbase-logging");
112: return contextMap;
113: } catch (javax.naming.NamingException ne) {
114: log
115: .debug("Can't obtain properties from application context: "
116: + ne.getMessage());
117: return new HashMap<String, String>();
118: }
119: }
120:
121: /**
122: * Configure the logging system.
123: *
124: * @param configFile Path to an xml-file in which is described
125: * which class must be used for logging, and how this will be
126: * configured (typically the name of another configuration file).
127: *
128: */
129:
130: public static void configure(ResourceLoader rl, String configFile) {
131: resourceLoader = rl;
132: configWatcher = new ResourceWatcher(rl) {
133: public void onChange(String s) {
134: configure(resourceLoader, s);
135: }
136: };
137:
138: if (configFile == null) {
139: log
140: .info("No configfile given, default configuration will be used.");
141: return;
142: }
143:
144: // There is a problem when dtd's for the various modules are on a remote
145: // machine and this machine is down. Log4j will hang without an error and if
146: // SimpleImpl is used in log.xml it too will constantly try to connect to the
147: // machine for the dtd's without giving an error! This line might give a hint
148: // where to search for these kinds of problems..
149:
150: log.info("Configuring logging with " + configFile);
151: ///System.out.println("(If logging does not start then dtd validation might be a problem on your server)");
152:
153: configWatcher.add(configFile);
154: configWatcher.setDelay(10 * 1000); // check every 10 secs if config changed
155: configWatcher.start();
156:
157: DocumentReader reader;
158: try {
159: reader = new DocumentReader(resourceLoader
160: .getInputSource(configFile), Logging.class);
161: } catch (Exception e) {
162: log.error("Could not open " + configFile + " " + e, e);
163: return;
164: }
165: if (reader == null) {
166: log.error("No " + configFile);
167: return;
168: }
169:
170: String classToUse = SimpleImpl.class.getName(); // default
171: String configuration = "stderr,info"; // default
172:
173: Map<String, String> overrides = getInitParameters();
174: try { // to read the XML configuration file
175: String claz = overrides.containsKey("class") ? overrides
176: .get("class") : reader
177: .getElementValue("logging.class");
178: if (claz != null) {
179: classToUse = claz;
180: }
181: String config = overrides.containsKey("configuration") ? overrides
182: .get("configuration")
183: : reader.getElementValue("logging.configuration");
184: if (config != null)
185: configuration = config;
186: } catch (Exception e) {
187: log.error("Exception during parsing: " + e.getMessage(), e);
188: }
189:
190: log.info("Class to use for logging " + classToUse);
191: // System.out.println("(Depending on your selected logging system no more logging");
192: // System.out.println("might be written to this file. See the configuration of the");
193: // System.out.println("selected logging system for more hints where logging will appear)");
194: Class<?> logClassCopy = logClass; // if something's wrong, we can restore the current value.
195: try { // to find the configured class
196: logClass = Class.forName(classToUse);
197: if (configured) {
198: if (!logClassCopy.equals(logClass)) {
199: log
200: .warn("Tried to change logging implementation from "
201: + logClassCopy
202: + " to "
203: + logClass
204: + ". This is not really possible (most static instances are unreachable). Trying anyway as requested, if this gives strange results, you might need to restart.");
205: }
206: }
207:
208: } catch (ClassNotFoundException e) {
209: log.error("Could not find class " + classToUse);
210: log.error(e.toString());
211: logClass = logClassCopy;
212: } catch (Throwable e) {
213: log.error("Exception to find class " + classToUse + ": "
214: + e);
215: log.info("Falling back to " + logClassCopy.getName());
216: logClass = logClassCopy;
217: }
218: // System.out.println("logging to " + getLocations());
219: configureClass(configuration);
220: configured = true;
221: log.service("Logging configured");
222: log.debug("Now watching " + configWatcher.getResources());
223: log.debug("Replacing wrappers " + LoggerWrapper.getWrappers());
224: for (LoggerWrapper wrapper : LoggerWrapper.getWrappers()) {
225: wrapper.setLogger(getLoggerInstance(wrapper.getName()));
226: log.debug("Replaced logger " + wrapper.getName());
227: }
228:
229: ResourceLoader.initLogging();
230: }
231:
232: /**
233: * Calls the 'configure' static method of the used logging class,
234: * or does nothing if it doesn't exist. You could call this method
235: * if you want to avoid using 'configure', which parses an XML file.
236: **/
237:
238: public static void configureClass(String configuration) {
239: try { // to configure
240: // System.out.println("Found class " + logClass.getName());
241: Method conf = logClass.getMethod("configure", String.class);
242: conf.invoke(null, configuration);
243: } catch (NoSuchMethodException e) {
244: log.debug("Could not find configure method in "
245: + logClass.getName());
246: // okay, simply don't configure
247: } catch (java.lang.reflect.InvocationTargetException e) {
248: log
249: .error(
250: "Invocation Exception while configuration class. "
251: + logClass
252: + " with configuration String '"
253: + configuration + "' :"
254: + e.getMessage(), e);
255: } catch (Exception e) {
256: log.error("", e);
257: }
258: }
259:
260: /**
261: * Logging is configured with a log file. This method returns the File which was used.
262: */
263: public static ResourceLoader getResourceLoader() {
264: return resourceLoader;
265: }
266:
267: /**
268: * After configuring the logging system, you can get Logger instances to log with.
269: *
270: * @param s A string describing the `category' of the Logger. This is a log4j concept.
271: */
272:
273: public static Logger getLoggerInstance(String s) {
274: // call the getLoggerInstance static method of the logclass:
275: try {
276: Method getIns = logClass.getMethod("getLoggerInstance",
277: String.class);
278: Logger logger = (Logger) getIns.invoke(null, s);
279: if (configured) {
280: return logger;
281: } else {
282: return new LoggerWrapper(logger, s);
283: }
284: } catch (Exception e) {
285: log.warn(e);
286: return SimpleImpl.getLoggerInstance(s);
287: }
288: }
289:
290: /**
291: * Most Logger categories in MMBase are based on class name.
292: * @since MMBase-1.6.4
293: */
294: public static Logger getLoggerInstance(Class<?> cl) {
295: return getLoggerInstance(cl.getName());
296: }
297:
298: /**
299: * Returns a Set of String which indicates where your logging can
300: * be (If this is implemented in the class).
301: */
302: /*
303: public static Set getLocations() {
304: // call the getLoggerInstance static method of the logclass:
305: try {
306: Method getIns = logClass.getMethod("getLocations", new Class[] {} );
307: return (Set) getIns.invoke(null, new Object[] {});
308: } catch (Exception e) {
309: HashSet result = new HashSet();
310: result.add("<could not be determined>");
311: return result;
312: }
313: }
314: */
315:
316: /**
317: * If the configured Logger implements a shutdown static method,
318: * it will be called. (the log4j Category does).
319: *
320: */
321: public static void shutdown() {
322: try {
323: if (configured) {
324: for (LoggerWrapper wrapper : LoggerWrapper
325: .getWrappers()) {
326: wrapper.setLogger(SimpleImpl
327: .getLoggerInstance("org.mmbase.SHUTDOWN"));
328: }
329: if (logClass != null) {
330: Method shutdown = logClass.getMethod("shutdown");
331: shutdown.invoke(null);
332: }
333: configured = false;
334: }
335: } catch (NoSuchMethodException e) {
336: // System.err.println("No such method"); // okay, nothing to shutdown.
337: } catch (Throwable e) {
338: System.err.println(e + stackTrace(e));
339: }
340:
341: }
342:
343: /**
344: * Returns the stacktrace of the current call. This can be used to get a stacktrace
345: * when no exception was thrown and my help determine the root cause of an error message
346: * (what class called the method that gave the error message.
347: * @since MMBase-1.7
348: *
349: **/
350: public static String stackTrace() {
351: return stackTrace(-1);
352: }
353:
354: /**
355: * @since MMBase-1.7
356: */
357: public static String stackTrace(int max) {
358: Exception e = new Exception("logging.stacktrace");
359: /*
360: StackTraceElement[] stack = e.getStackTrace();
361: java.util.List stackList = new java.util.ArrayList(java.util.Arrays.asList(stack));
362: stackList.remove(0); // is Logging.stackTrace, which is hardly interesting
363: e.setStackTrace((StackTraceElement[])stackList.toArray());
364: */
365: return stackTrace(e, max);
366: }
367:
368: /**
369: * Returns the stacktrace of an exception as a string, which can
370: * be logged handy. Doing simply e.printStackTrace() would dump
371: * the stack trace to standard error, which with the log4j
372: * implementation will appear in the log file too, but this is a
373: * little nicer.
374: *
375: * It is also possible to call 'error' or 'fatal' with an extra argument.
376: *
377: * @param e the Throwable from which the stack trace must be stringified.
378: *
379: **/
380: public static String stackTrace(Throwable e) {
381: return stackTrace(e, -1);
382: }
383:
384: /**
385: * Also returns a stringified stack trace to log, but no deeper than given max.
386: * @since MMBase-1.7
387: */
388: public static String stackTrace(Throwable e, int max) {
389: StackTraceElement[] stackTrace = e.getStackTrace();
390: String message = e.getMessage();
391: StringBuffer buf = new StringBuffer(e.getClass().getName()
392: + ": ");
393: if (message == null) {
394:
395: } else {
396: buf.append(message);
397: }
398: for (int i = 0; i < stackTrace.length; i++) {
399: if (i == max)
400: break;
401: buf.append("\n at ").append(stackTrace[i]);
402: }
403: Throwable t = e.getCause();
404: if (t != null) {
405: buf.append(stackTrace(t, max));
406: }
407: return buf.toString();
408: }
409:
410: /**
411: * @since MMBase-1.8
412: */
413: public static String applicationStacktrace() {
414: Exception e = new Exception("logging.showApplicationStacktrace");
415: return applicationStacktrace(e);
416: }
417:
418: /**
419: * @since MMBase-1.8
420: */
421: public static String applicationStacktrace(Throwable e) {
422: StringBuffer buf = new StringBuffer("Application stacktrace");
423:
424: // Get the stack trace
425: StackTraceElement stackTrace[] = e.getStackTrace();
426: // stackTrace[0] contains the method that created the exception.
427: // stackTrace[stackTrace.length-1] contains the oldest method call.
428: // Enumerate each stack element.
429:
430: boolean mmbaseClassesFound = false;
431: int appended = 0;
432: for (StackTraceElement element : stackTrace) {
433: String className = element.getClassName();
434:
435: if (className.indexOf("org.mmbase") > -1) {
436: mmbaseClassesFound = true;
437: // show mmbase taglib
438: if (className.indexOf("bridge.jsp.taglib") > -1) {
439: buf.append("\n at ").append(element);
440: appended++;
441: }
442: } else {
443: if (mmbaseClassesFound) {
444: // show none mmbase method which invoked an mmbase method.
445: buf.append("\n at ").append(element);
446: appended++;
447: break;
448: }
449: // show compiled jsp lines
450: if (className.indexOf("_jsp") > -1) {
451: buf.append("\n at ").append(element);
452: appended++;
453: }
454: }
455: }
456: if (appended == 0) {
457: for (int i = 2; i < stackTrace.length; i++) {
458: buf.append("\n at ").append(stackTrace[i]);
459: }
460: }
461: return buf.toString();
462: }
463:
464: }
|