|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| Log.java | 20.3% | 27.4% | 36.7% | 26.3% |
|
||||||||||||||
| 1 |
package abbot;
|
|
| 2 |
|
|
| 3 |
import java.io.*;
|
|
| 4 |
import java.lang.reflect.InvocationTargetException;
|
|
| 5 |
import java.util.*;
|
|
| 6 |
|
|
| 7 |
import javax.swing.SwingUtilities;
|
|
| 8 |
|
|
| 9 |
/**
|
|
| 10 |
Various logging, assertion, and debug routines. Typical usage is to
|
|
| 11 |
include the following code
|
|
| 12 |
<blockquote><code><pre>
|
|
| 13 |
public static void main(String[] args) {
|
|
| 14 |
args = Log.init(args)
|
|
| 15 |
...
|
|
| 16 |
}
|
|
| 17 |
</pre></code></blockquote>
|
|
| 18 |
at an application's main entry point. This way the Log class can remove its
|
|
| 19 |
options from the full set passed into the application. See the
|
|
| 20 |
{@link #init(String[]) Log.init} method for initialization
|
|
| 21 |
options. <p>
|
|
| 22 |
|
|
| 23 |
General usage notes on public functions:<p>
|
|
| 24 |
|
|
| 25 |
<ul>
|
|
| 26 |
<li><b>warn</b><br>
|
|
| 27 |
Programmer warnings; things that you think shouldn't be happening or
|
|
| 28 |
indicate something might be wrong. Warnings typically mean "Something
|
|
| 29 |
happened that I didn't expect would happen".<p>
|
|
| 30 |
<li><b>log</b><br>
|
|
| 31 |
Important information that might be needed for later reference; things the
|
|
| 32 |
user or debugger might be interested in. By default, all messages go
|
|
| 33 |
here. Logs are made available so that the customer may provide us with an
|
|
| 34 |
accurate record of software activity.<br>
|
|
| 35 |
All warnings and failed assertions are written to the log. Debug
|
|
| 36 |
statements are also written to log in non-release code.<p>
|
|
| 37 |
<li><b>debug</b><br>
|
|
| 38 |
Any messages which might be useful for debugging (non-release code
|
|
| 39 |
only).<p>
|
|
| 40 |
<li><b>assertTrue</b><br>
|
|
| 41 |
Assumed preconditions for proper execution, also referred to as
|
|
| 42 |
invariants.<p>
|
|
| 43 |
</ul>
|
|
| 44 |
<p>
|
|
| 45 |
|
|
| 46 |
Per-class stack trace depth can be specified when adding a class, e.g.
|
|
| 47 |
classname[:stack-depth].<p>
|
|
| 48 |
|
|
| 49 |
@author twall
|
|
| 50 |
@version $Revision: 1.26 $
|
|
| 51 |
*/
|
|
| 52 |
|
|
| 53 |
final public class Log { |
|
| 54 |
/** No instantiations. */
|
|
| 55 | 0 |
private Log() { }
|
| 56 |
/** Global final to determine whether debugging code is generated. This
|
|
| 57 |
should be changed to false to build production code. */
|
|
| 58 |
public static final boolean DEBUG_BUILD = true; |
|
| 59 |
/** Mnemonic to print all lines of a stack trace. */
|
|
| 60 |
public static final int FULL_STACK = 0; |
|
| 61 |
/** Mnemonic to print the default number of lines of stack trace. */
|
|
| 62 |
private static final int CLASS_STACK_DEPTH = -1; |
|
| 63 |
/** Basic warning categories. FIXME use these.
|
|
| 64 |
public static final int ERROR = 0x0001;
|
|
| 65 |
public static final int WARNING = 0x0002;
|
|
| 66 |
public static final int DEBUG = 0x0004;
|
|
| 67 |
public static final int INFO = 0x0008;*/
|
|
| 68 |
|
|
| 69 |
private static class LogSynchronizer extends Object { } |
|
| 70 |
/** Synchronize message output. */
|
|
| 71 |
private static LogSynchronizer synchronizer = new LogSynchronizer(); |
|
| 72 |
/** Whether any debugging output is enabled. */
|
|
| 73 |
public static boolean expectDebugOutput; |
|
| 74 |
/** Enable assert checks. */
|
|
| 75 |
private static boolean assertChecks = DEBUG_BUILD; |
|
| 76 |
/** Whether to terminate on assertion failures. */
|
|
| 77 |
private static boolean exitOnAssertionFailure = false; |
|
| 78 |
/** Whether to log messages. Default on so that we capture output until
|
|
| 79 |
* the log file has been set or not set in {@link #init(String[])}.
|
|
| 80 |
*/
|
|
| 81 |
private static boolean logMessages = true; |
|
| 82 |
/** Whether to print programmer warnings. */
|
|
| 83 |
private static boolean printConsoleWarnings = DEBUG_BUILD; |
|
| 84 |
/** Whether to show threads in debug output. */
|
|
| 85 |
private static boolean showThreads; |
|
| 86 |
/** Default number of lines of stack trace to print. */
|
|
| 87 |
private static int debugStackDepth = 1; |
|
| 88 |
/** Default number of lines of exception stack trace to print. */
|
|
| 89 |
private static int excStackDepth = FULL_STACK; |
|
| 90 |
/** Show timestamps in the log? */
|
|
| 91 |
private static boolean showTimestamp = true; |
|
| 92 |
private static java.text.DateFormat timestampFormat = |
|
| 93 |
new java.text.SimpleDateFormat("yyMMdd HH:mm:ss:SSS "); |
|
| 94 |
|
|
| 95 |
/** Strip this out of output, since it doesn't add information to see it
|
|
| 96 |
repeatedly. Some projects have <i>really</i> long prefixes. */
|
|
| 97 |
private static final String COMMON_PREFIX = null; |
|
| 98 |
/** Store which classes we want to see debug info for. FIXME make it a
|
|
| 99 |
map and make the value the debug level. */
|
|
| 100 |
private static HashMap debugged = new HashMap(); |
|
| 101 |
/** Store which classes we don't want to see debug info for */
|
|
| 102 |
private static HashSet notdebugged = new HashSet(); |
|
| 103 |
/** Debug all classes? */
|
|
| 104 |
private static boolean debugAll; |
|
| 105 |
/** Treat inner/anonymous classes as outer class? */
|
|
| 106 |
private static boolean debugInner = true; |
|
| 107 |
|
|
| 108 |
private static final String DEFAULT_LOGFILE_NAME = "abbot.log"; |
|
| 109 |
private static ByteArrayOutputStream preInitLog = |
|
| 110 |
new ByteArrayOutputStream();
|
|
| 111 |
private static PrintStream log = new PrintStream(preInitLog); |
|
| 112 |
|
|
| 113 |
// Save these for future use
|
|
| 114 |
static PrintStream systemOut = System.out;
|
|
| 115 |
static PrintStream systemErr = System.err;
|
|
| 116 |
|
|
| 117 |
/** Debug/log initialization, presumably from the command line.
|
|
| 118 |
<br>Recognized options:
|
|
| 119 |
<pre>
|
|
| 120 |
--debug all | className[:depth] | *.partialClassName[:depth]
|
|
| 121 |
--no-debug className | *.partialClassName
|
|
| 122 |
--log <log file name>
|
|
| 123 |
--no-timestamp
|
|
| 124 |
--enable-warnings
|
|
| 125 |
--show-threads
|
|
| 126 |
--stack-depth <depth>
|
|
| 127 |
--exception-depth <depth>
|
|
| 128 |
</pre>
|
|
| 129 |
*/
|
|
| 130 | 3 |
public static String[] init(String[] args){ |
| 131 |
|
|
| 132 | 3 |
if (!DEBUG_BUILD) {
|
| 133 |
// Redirect System.err & System.out
|
|
| 134 | 0 |
PrintStream nullStream = new PrintStream(new OutputStream() { |
| 135 | 0 |
public void write(int b) { |
| 136 |
} |
|
| 137 |
}); |
|
| 138 | 0 |
System.setErr(nullStream); |
| 139 | 0 |
System.setOut(nullStream); |
| 140 |
} |
|
| 141 | 3 |
Vector newArgs = new Vector(args.length);
|
| 142 | 3 |
for (int i=0;i < args.length;i++){ |
| 143 | 9 |
if (args[i].equals("--enable-warnings")){ |
| 144 | 0 |
printConsoleWarnings = true;
|
| 145 |
} |
|
| 146 | 9 |
else if (args[i].equals("--no-timestamp")) { |
| 147 | 0 |
showTimestamp = false;
|
| 148 |
} |
|
| 149 | 9 |
else if (args[i].equals("--show-threads")){ |
| 150 | 0 |
showThreads = true;
|
| 151 |
} |
|
| 152 | 9 |
else if (args[i].equals("--stack-depth")) { |
| 153 | 0 |
if (++i < args.length) {
|
| 154 | 0 |
try {
|
| 155 | 0 |
debugStackDepth = Integer.parseInt(args[i]); |
| 156 |
} |
|
| 157 |
catch(Exception exc) {
|
|
| 158 |
} |
|
| 159 |
} |
|
| 160 |
else {
|
|
| 161 | 0 |
internalWarn("Ignoring --stack-depth with no argument");
|
| 162 |
} |
|
| 163 |
} |
|
| 164 | 9 |
else if (args[i].equals("--exception-depth")) { |
| 165 | 0 |
if (++i < args.length) {
|
| 166 | 0 |
try {
|
| 167 | 0 |
excStackDepth = Integer.parseInt(args[i]); |
| 168 |
} |
|
| 169 |
catch(Exception exc) {
|
|
| 170 |
} |
|
| 171 |
} |
|
| 172 |
else {
|
|
| 173 | 0 |
internalWarn("Ignoring --exception-depth with no argument");
|
| 174 |
} |
|
| 175 |
} |
|
| 176 | 9 |
else if (args[i].equals("--debug") |
| 177 |
|| args[i].equals("--no-debug")) {
|
|
| 178 |
// since we're enabling some debugging, set the other settings
|
|
| 179 |
// to debug defaults...
|
|
| 180 | 0 |
boolean exclude = args[i].startsWith("--no"); |
| 181 |
|
|
| 182 |
// Re-enable stdout/stderr if they were removed
|
|
| 183 | 0 |
if (!DEBUG_BUILD) {
|
| 184 | 0 |
System.setOut(systemOut); |
| 185 | 0 |
System.setErr(systemErr); |
| 186 |
} |
|
| 187 | 0 |
if (++i < args.length) {
|
| 188 | 0 |
if (exclude)
|
| 189 | 0 |
removeDebugClass(args[i]); |
| 190 |
else
|
|
| 191 | 0 |
addDebugClass(args[i]); |
| 192 |
} |
|
| 193 |
else {
|
|
| 194 | 0 |
internalWarn("Ignoring " + args[i-1]
|
| 195 |
+ " with no argument");
|
|
| 196 |
} |
|
| 197 |
} |
|
| 198 | 9 |
else if (args[i].equals("--log")){ |
| 199 | 0 |
String filename = DEFAULT_LOGFILE_NAME; |
| 200 | 0 |
if (++i < args.length)
|
| 201 | 0 |
filename = args[i]; |
| 202 | 0 |
enableLogging(filename); |
| 203 |
} |
|
| 204 |
else {
|
|
| 205 | 9 |
newArgs.addElement(args[i]); |
| 206 |
} |
|
| 207 |
} |
|
| 208 | 3 |
String[] result = new String[newArgs.size()];
|
| 209 | 3 |
for (int i=0;i < result.length;i++){ |
| 210 | 9 |
result[i] = (String)newArgs.elementAt(i); |
| 211 |
} |
|
| 212 | 3 |
return result;
|
| 213 |
} |
|
| 214 |
|
|
| 215 |
/** Is log output enabled? */
|
|
| 216 | 5556 |
public static boolean loggingEnabled() { |
| 217 | 5556 |
return logMessages && log != null; |
| 218 |
} |
|
| 219 |
|
|
| 220 |
/** Enable log output to the given file. If the filename given is "-",
|
|
| 221 |
* stdout is used instead of a file.
|
|
| 222 |
*/
|
|
| 223 | 0 |
public static void enableLogging(String filename) { |
| 224 | 0 |
logMessages = true;
|
| 225 | 0 |
try {
|
| 226 | 0 |
if (filename.equals("-")) |
| 227 | 0 |
log = systemOut; |
| 228 |
else
|
|
| 229 | 0 |
log = new PrintStream(new FileOutputStream(filename), |
| 230 |
true);
|
|
| 231 | 0 |
String hostname = "127.0.0.1";
|
| 232 | 0 |
try {
|
| 233 | 0 |
hostname = java.net.InetAddress. |
| 234 |
getLocalHost().getHostName(); |
|
| 235 |
} |
|
| 236 |
catch(java.net.UnknownHostException uhe) {
|
|
| 237 | 0 |
internalWarn("Can't get hostname, using " + hostname);
|
| 238 |
} |
|
| 239 | 0 |
log("Log started on " + hostname);
|
| 240 | 0 |
if (!DEBUG_BUILD) {
|
| 241 |
// Prefer output to go to the log on a release build, since
|
|
| 242 |
// there may not be any console.
|
|
| 243 | 0 |
if (expectDebugOutput) {
|
| 244 | 0 |
systemOut.println("Output redirected. See the log "
|
| 245 |
+ "file for debug output.");
|
|
| 246 |
} |
|
| 247 | 0 |
System.setErr(log); |
| 248 | 0 |
System.setOut(log); |
| 249 |
} |
|
| 250 |
// Make sure the log gets closed on System.exit
|
|
| 251 | 0 |
Runtime.getRuntime().addShutdownHook(new Thread("Log shutdown hook") { |
| 252 | 0 |
public void run() { close(); } |
| 253 |
}); |
|
| 254 |
} |
|
| 255 |
catch (FileNotFoundException exc){
|
|
| 256 | 0 |
internalWarn("Can't open log file " + filename);
|
| 257 |
} |
|
| 258 |
} |
|
| 259 |
|
|
| 260 |
/** Sets the debug stack depth to the given amount */
|
|
| 261 | 0 |
public static void setDebugStackDepth(int depth) { |
| 262 | 0 |
debugStackDepth = depth; |
| 263 |
} |
|
| 264 |
|
|
| 265 |
/** Indicate the class name to exclude from debug output. */
|
|
| 266 | 0 |
public static void removeDebugClass(String id) { |
| 267 | 0 |
setDebugClass(id, false);
|
| 268 |
} |
|
| 269 |
|
|
| 270 |
/** Indicate that the given class should NOT be debugged
|
|
| 271 |
(assuming --debug all) */
|
|
| 272 | 0 |
public static void removeDebugClass(Class c) { |
| 273 | 0 |
notdebugged.add(c); |
| 274 | 0 |
debugged.remove(c); |
| 275 | 0 |
Log.debug("Debugging disabled for " + c);
|
| 276 | 0 |
expectDebugOutput = debugged.size() > 0 || debugAll; |
| 277 |
} |
|
| 278 |
|
|
| 279 |
/** Indicate the class name[:depth] to add to debug output. */
|
|
| 280 | 0 |
public static void addDebugClass(String id) { |
| 281 | 0 |
setDebugClass(id, true);
|
| 282 |
} |
|
| 283 |
|
|
| 284 |
/** Indicate the class to add to debug output. */
|
|
| 285 | 0 |
public static void addDebugClass(Class c) { |
| 286 | 0 |
addDebugClass(c, CLASS_STACK_DEPTH); |
| 287 |
} |
|
| 288 |
|
|
| 289 |
/** Indicate that debug messages should be output for the given class. */
|
|
| 290 | 0 |
public static void addDebugClass(Class c, int depth){ |
| 291 | 0 |
expectDebugOutput = true;
|
| 292 | 0 |
debugged.put(c, new Integer(depth));
|
| 293 | 0 |
notdebugged.remove(c); |
| 294 | 0 |
Log.debug("Debugging enabled for " + c);
|
| 295 |
} |
|
| 296 |
|
|
| 297 |
/** Parse the given string, which may should be of the format
|
|
| 298 |
"class[:depth]" */
|
|
| 299 | 0 |
private static void setDebugClass(String id, boolean enable) { |
| 300 | 0 |
if (id.equals("all")) { |
| 301 | 0 |
debugAll = enable; |
| 302 | 0 |
if (enable) {
|
| 303 | 0 |
notdebugged.clear(); |
| 304 | 0 |
expectDebugOutput = true;
|
| 305 |
} |
|
| 306 |
else {
|
|
| 307 | 0 |
debugged.clear(); |
| 308 | 0 |
expectDebugOutput = false;
|
| 309 |
} |
|
| 310 |
} |
|
| 311 |
else {
|
|
| 312 | 0 |
int colon = id.indexOf(":"); |
| 313 | 0 |
String className = colon == -1 ? id : id.substring(0, colon); |
| 314 | 0 |
try {
|
| 315 | 0 |
Class c = getClassFromDescriptor(className); |
| 316 | 0 |
try {
|
| 317 | 0 |
int depth = colon == -1
|
| 318 |
? debugStackDepth |
|
| 319 |
: Integer.parseInt(id.substring(colon+1)); |
|
| 320 | 0 |
if (enable)
|
| 321 | 0 |
addDebugClass(c, depth); |
| 322 |
else
|
|
| 323 | 0 |
removeDebugClass(c); |
| 324 |
} |
|
| 325 |
catch (NumberFormatException nfe) {
|
|
| 326 | 0 |
if (enable)
|
| 327 | 0 |
addDebugClass(c); |
| 328 |
else
|
|
| 329 | 0 |
removeDebugClass(c); |
| 330 |
} |
|
| 331 |
} |
|
| 332 |
catch (ClassNotFoundException exc){
|
|
| 333 | 0 |
internalWarn("Class '" + className + "' not found"); |
| 334 |
} |
|
| 335 |
} |
|
| 336 |
} |
|
| 337 |
|
|
| 338 |
/** Returns class from given name/descriptor. Descriptor can be either a
|
|
| 339 |
fully qualified classname, or a classname beginning with *. with
|
|
| 340 |
client-specific package and classname following.
|
|
| 341 |
*/
|
|
| 342 | 0 |
private static Class getClassFromDescriptor(String className) |
| 343 |
throws ClassNotFoundException {
|
|
| 344 | 0 |
if (COMMON_PREFIX != null && className.startsWith("*.")) { |
| 345 | 0 |
className = COMMON_PREFIX + className.substring(1); |
| 346 |
} |
|
| 347 | 0 |
return Class.forName(className);
|
| 348 |
} |
|
| 349 |
|
|
| 350 |
/** Return the requested number of levels of stack trace, not including
|
|
| 351 |
this call. Returns the full stack trace if LINES is FULL_STACK.
|
|
| 352 |
Skip the first POP frames of the trace, which is for excluding the
|
|
| 353 |
innermost stack frames when debug functions make nested calls.
|
|
| 354 |
The outermost call of getStackTrace itself is always removed from the
|
|
| 355 |
trace. */
|
|
| 356 | 185 |
private static String getStackTrace(int pop, int lines) { |
| 357 | 185 |
return getStackTrace(pop, lines, new Throwable("--debug--")); |
| 358 |
} |
|
| 359 |
|
|
| 360 |
/** Return the requested number of levels of stack trace, not including
|
|
| 361 |
this call. Returns the full stack trace if LINES is FULL_STACK.
|
|
| 362 |
Skip the first POP frames of the trace, which is for excluding the
|
|
| 363 |
innermost stack frames when debug functions make nested calls.
|
|
| 364 |
The outermost call of getStackTrace itself is always removed from the
|
|
| 365 |
trace. Provide an exception to use for the stack trace,
|
|
| 366 |
rather than using the current program location.
|
|
| 367 |
*/
|
|
| 368 | 202 |
private static String getStackTrace(int pop, int lines, Throwable thr) { |
| 369 | 202 |
String stack = getStackTrace(pop, thr); |
| 370 | 202 |
if (lines == FULL_STACK)
|
| 371 | 195 |
return stack;
|
| 372 | 7 |
return trimStackTrace(stack, lines);
|
| 373 |
} |
|
| 374 |
|
|
| 375 |
/** Return the stack trace contained in the given Throwable.
|
|
| 376 |
Skip the first POP frames of the trace, which is for excluding the
|
|
| 377 |
innermost stack frames when debug functions make nested calls.
|
|
| 378 |
The outermost call of getStackTrace itself is always removed from the
|
|
| 379 |
trace.
|
|
| 380 |
*/
|
|
| 381 | 202 |
private static String getStackTrace(int pop, Throwable thr){ |
| 382 |