// --- BEGIN COPYRIGHT BLOCK --- // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package com.netscape.cmscore.util; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Hashtable; import java.util.StringTokenizer; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.cmsutil.util.Utils; public class Debug implements ISubsystem { private static Debug mInstance = new Debug(); private static boolean mShowCaller = false; /* This dateformatter is used to put the date on each debug line. But the DateFormatter is not thread safe, so I create a thread-local DateFormatter for each thread */ private static String DATE_PATTERN = "dd/MMM/yyyy:HH:mm:ss"; private static ThreadLocal mFormatObject = new ThreadLocal() { protected synchronized SimpleDateFormat initialValue() { return new SimpleDateFormat(DATE_PATTERN); } }; /* the dateformatter should be accessed with this function */ private static SimpleDateFormat getDateFormatter() { return mFormatObject.get(); } public static final boolean ON = false; public static final int OBNOXIOUS = 10; public static final int VERBOSE = 5; public static final int INFORM = 1; // the difference between this and 'ON' is that this is always // guaranteed to log to 'mOut', whereas other parts of the server // may do: // if (Debug.ON) { // System.out.println(".."); // } // I want to make sure that any Debug.trace() is not logged to // System.out if the server is running under watchdog private static boolean TRACE_ON = false; private static int mDebugLevel = VERBOSE; private static PrintStream mOut = null; private static Hashtable mHK = null; static { if (TRACE_ON == true) { mOut = System.out; } } public static void trace(int level, String t) { trace(level, t, null, true); } /** * Output a debug message at the output stream sepcified in the init() * method. This method is very lightweight if debugging is turned off, since * it will return immediately. However, the caller should be aware that * if the argument to Debug.trace() is an object whose toString() is * expensive, that this toString() will still be called in any case. * In such a case, it is wise to wrap the Debug.trace like this: * *
     * if (Debug.on()) {
     *     Debug.trace("obj is: " + obj);
     * }
     * 
* * @param level the message level. If this is >= than the currently set * level (set with setLevel() ), the message is printed * @param t the message to print * @param ignoreStack when walking the stack to determine the * location of the method that called the trace() method, * ignore any classes with this string in. Can be null * @param printCaller if true, (and if static mShowCaller is true) * dump caller information in this format: * (source-file:line) methodname(): */ public static void trace(int level, String t, String ignoreStack, boolean printCaller) { String callerinfo = ""; if (!TRACE_ON) return; if (level >= mDebugLevel) { if (mShowCaller && printCaller) { String method = ""; String fileAndLine = ""; try { Throwable tr = new Throwable(); StackTraceElement ste[] = tr.getStackTrace(); int i = 0; while ((i < ste.length) && (ste[i].getMethodName().toLowerCase().indexOf("debug") > -1) || (ste[i].getMethodName().toLowerCase().indexOf("hashkey") > -1) || (ste[i].getClassName().toLowerCase().indexOf("propconfigstore") > -1) || (ste[i].getClassName().toLowerCase().indexOf("argblock") > -1) || (ste[i].getClassName().toLowerCase().indexOf("debug") > -1) || (ste[i].getMethodName().toLowerCase().indexOf("trace") > -1)) i++; if (i < ste.length) { fileAndLine = ste[i].getFileName() + ":" + ste[i].getLineNumber(); method = ste[i].getMethodName() + "()"; } callerinfo = fileAndLine + ":" + method + " "; } catch (Exception f) { } } outputTraceMessage(callerinfo + t); } } private static void outputTraceMessage(String t) { if (!TRACE_ON) return; SimpleDateFormat d = getDateFormatter(); if (mOut != null && d != null) { mOut.println("[" + d.format(new Date()) + "][" + Thread.currentThread().getName() + "]: " + t); mOut.flush(); } } private static boolean hkdotype(String type) { if (mHK != null && mHK.get(type) != null) { return true; } else { return false; } } public static void traceHashKey(String type, String key) { if (hkdotype(type)) { trace("GET r=" + type + ",k=" + key); } } public static void traceHashKey(String type, String key, String val) { if (hkdotype(type)) { trace("GET r=" + type + ",k=" + key + ",v=" + val); } } public static void traceHashKey(String type, String key, String val, String def) { if (hkdotype(type)) { trace("GET r=" + type + ",k=" + key + ",v=" + val + ",d=" + def); } } public static void putHashKey(String type, String key, String value) { if (hkdotype(type)) { outputTraceMessage("PUT r=" + type + ",k=" + key + ",v=" + value); } } public static void trace(String t) { trace(VERBOSE, t); } public static void print(int level, String t) { if (!TRACE_ON) return; if (mOut != null) { if (level >= mDebugLevel) mOut.print(t); } } public static void print(String t) { print(VERBOSE, t); } private static void printNybble(byte b) { if (mOut == null) return; if (b < 10) mOut.write('0' + b); else mOut.write('a' + b - 10); } /** * If tracing enabled, dump a byte array to debugging printstream * as hex, colon-seperated bytes, 16 bytes to a line */ public static void print(byte[] b) { if (!TRACE_ON) return; if (mOut == null) return; for (int i = 0; i < b.length; i++) { printNybble((byte) ((b[i] & 0xf0) >> 4)); printNybble((byte) (b[i] & 0x0f)); mOut.print(" "); if (((i % 16) == 15) && i != b.length) mOut.println(""); } mOut.println(""); mOut.flush(); } /** * Print the current stack trace to the debug printstream */ public static void printStackTrace() { if (!TRACE_ON) return; Exception e = new Exception("Debug"); printStackTrace(e); } /** * Print the stack trace of the named exception * to the debug printstream */ public static void printStackTrace(Throwable e) { if (!TRACE_ON) return; if (mOut == null) return; e.printStackTrace(mOut); } /** * Set the current debugging level. You can use: * *
     * OBNOXIOUS = 10
     * VERBOSE   = 5
     * INFORM    = 1
     * 
* * Or another value */ public static void setLevel(int level) { mDebugLevel = level; } public static int getLevel(int level) { return mDebugLevel; } /** * Test if debugging is on. Do NOT write to System.out in your debug code */ public static boolean on() { return TRACE_ON; } /* ISubsystem methods: */ public static String ID = "debug"; private static IConfigStore mConfig = null; public String getId() { return ID; } public void setId(String id) { ID = id; } private static final String PROP_ENABLED = "enabled"; private static final String PROP_FILENAME = "filename"; private static final String PROP_HASHKEYS = "hashkeytypes"; private static final String PROP_SHOWCALLER = "showcaller"; private static final String PROP_LEVEL = "level"; private static final String PROP_APPEND = "append"; /** * Debug subsystem initialization. This subsystem is usually * given the following parameters: * *
     * debug.enabled   : (true|false) default false
     * debug.filename  : can be a pathname, or STDOUT
     * debug.hashkeytypes: comma-separated list of hashkey types
     *    possible values:  "CS.cfg"
     * debug.showcaller: (true|false) default false  [show caller method name for Debug.trace()]
     * 
*/ public void init(ISubsystem owner, IConfigStore config) { mConfig = config; String filename = null; String hashkeytypes = null; boolean append = true; try { TRACE_ON = mConfig.getBoolean(PROP_ENABLED, false); if (TRACE_ON) { filename = mConfig.getString(PROP_FILENAME, null); if (filename == null) { TRACE_ON = false; } hashkeytypes = mConfig.getString(PROP_HASHKEYS, null); mShowCaller = mConfig.getBoolean(PROP_SHOWCALLER, false); append = mConfig.getBoolean(PROP_APPEND, true); } if (TRACE_ON) { if (filename.equals("STDOUT")) { mOut = System.out; } else { if (!Utils.isNT()) { // Always insure that a physical file exists! Utils.exec("touch " + filename); Utils.exec("chmod 00640 " + filename); } OutputStream os = new FileOutputStream(filename, append); mOut = new PrintStream(os, true); /* true == autoflush */ } if (hashkeytypes != null) { StringTokenizer st = new StringTokenizer(hashkeytypes, ",", false); mHK = new Hashtable(); while (st.hasMoreElements()) { String hkr = st.nextToken(); mHK.put(hkr, "true"); } } } outputTraceMessage("============================================"); outputTraceMessage("===== DEBUG SUBSYSTEM INITIALIZED ======="); outputTraceMessage("============================================"); int level = mConfig.getInteger(PROP_LEVEL, VERBOSE); setLevel(level); } catch (Exception e) { // Don't do anything. Logging is not set up yet, and // we can't write to STDOUT. } } public void startup() { } public void shutdown() { } public IConfigStore getConfigStore() { return mConfig; } // for singleton public static Debug getInstance() { return mInstance; } }