blob: 1437b17f2f1ee6d6ad19bf37ebf27e8d595f0497 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.core.startup;
import java.io.IOException;
import java.util.Stack;
import java.util.Arrays;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.XMLFormatter;
/** Logger that will enable the logging of important events during the startup
* annotated with real time and possibly time differences.
*
* @author Petr Nejedly, Jesse Glick
*/
public class StartLog {
private static final Logger LOG = Logger.getLogger("org.netbeans.log.startup"); // NOI18N
private static final Stack<String> actions = new Stack<String>();
private static final Stack<Throwable> places = new Stack<Throwable>();
private static final boolean DEBUG_NESTING = Boolean.getBoolean("org.netbeans.log.startup.debug"); // NOI18N
private static final String logProp;
private static final String logFileProp;
static final Handler impl;
static {
try {
logProp = System.getProperty( "org.netbeans.log.startup" ); // NOI18N
// NOI18N
logFileProp = System.getProperty( "org.netbeans.log.startup.logfile" ); // NOI18N
if(logProp == null)
impl = new StartImpl();
else if("print".equals(logProp))
impl = new PrintImpl();
else if("tests".equals(logProp))
impl = new PerformanceTestsImpl();
else if("xml".equals(logProp)) {
if (logFileProp == null) {
throw new NullPointerException("Specify also 'org.netbeans.log.startup.logfile' property!"); // NOI18N
}
FileHandler h = new FileHandler(logFileProp);
h.setFormatter(new SimplerFormatter());
impl = h;
} else
throw new Error("Unknown org.netbeans.log.startup value [" + logProp + "], it should be (print or tests or xml) !"); // NOI18N
register();
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
static void register() {
LOG.setUseParentHandlers(false);
LOG.addHandler(impl);
LOG.setLevel(impl.getLevel());
}
static void unregister() {
LOG.removeHandler(impl);
}
/** Start running some interval action.
* @param action some identifying string
*/
public static void logStart( String action ) {
if (willLog()) {
LOG.log(Level.FINE, "start", action); // NOI18N
actions.push(action);
if (DEBUG_NESTING) {
places.push(new Throwable("logStart called here:")); // NOI18N
}
}
}
/** Note that something happened, but not an interval.
* The log will note only the time elapsed since the last interesting event.
* @param note some identifying string
*/
public static void logProgress( String note ) {
if( willLog() ) {
LOG.log(Level.FINE, "progress", note); // NOI18N
}
}
/** Stop running some interval action.
* The log will note the time elapsed since the start of the action.
* Actions <em>must</em> be properly nested.
* @param action some identifying string
*/
public static void logEnd( String action ) {
if (willLog()) {
String old = actions.empty() ? null : actions.pop();
Throwable oldplace = DEBUG_NESTING && !places.empty() ? places.pop() : null;
if (!action.equals(old)) {
// Error, not ISE; don't want this caught and reported
// with ErrorManager, for then you get a wierd cycle!
if (oldplace != null) {
oldplace.printStackTrace();
} else {
System.err.println("Either ending too soon, or no info about caller of unmatched start log."); // NOI18N
System.err.println("Try running with -J-Dorg.netbeans.log.startup.debug=true"); // NOI18N
}
Error e = new Error("StartLog mismatch: ending '" + action + "' but expecting '" + old + "'; rest of stack: " + actions); // NOI18N
// Print stack trace since you can get strange situations
// when ErrorManager tries to report it - may need to initialize
// ErrorManager, etc.
e.printStackTrace();
// Presumably you did want to keep on going at this point.
System.err.flush();
LOG.setLevel(Level.OFF);
}
LOG.log(Level.FINE, "end", action); // NOI18N
}
}
public static boolean willLog() {
return LOG.isLoggable(Level.FINE);
}
/** Logs the startup time. The begining is tracked by this class.
* The end is passed as argument.
*/
public static void logMeasuredStartupTime(long end){
LOG.log(Level.FINE, "finish", end);
if("tests".equals(logProp)) {
impl.flush();
}
}
/** The dummy, no-op implementation */
private static class StartImpl extends Handler {
final long zero = System.nanoTime()/1000000;
StartImpl() {}
void start( String action, long time ) {}
void progress( String note, long time ) {}
void end( String action, long time ) {}
boolean willLog() {
return false;
}
@Override
public Level getLevel() {
return willLog() ? Level.FINEST : Level.OFF;
}
@Override
public void publish(LogRecord rec) {
Object[] args = rec.getParameters();
String msg = (args.length >= 1 && args[0] instanceof String) ? (String)args[0] : ""; // NOI18N
long time = System.nanoTime()/1000000;
if ("start".equals(rec.getMessage())) { // NOI18N
start(msg, time);
return;
}
if ("end".equals(rec.getMessage())) { // NOI18N
end(msg, time);
return;
}
if ("progress".equals(rec.getMessage())) { // NOI18N
progress(msg, time);
return;
}
}
@Override
public void flush() {
}
@Override
public final void close() throws SecurityException {
}
}
private static class PrintImpl extends StartImpl {
PrintImpl() {}
private Stack<Long> starts = new Stack<Long>();
long prog;
private int indent = 0;
@Override
synchronized void start( String action, long time ) {
starts.push(time);
prog=time;
System.err.println( getIndentString(indent) + "@" +
(time - zero) + " - " + action + " started" // NOI18N
);
indent += 2;
}
@Override
synchronized void progress( String note, long time ) {
System.err.println( getIndentString(indent) + "@" +
(time - zero) + " - " + note + " dT=" + (time - prog) // NOI18N
);
prog = time;
}
@Override
synchronized void end( String action, long time ) {
indent -= 2;
long start = starts.pop();
prog = time;
System.err.println( getIndentString(indent) + "@" +
(time - zero) + " - " + action + " finished, took " + // NOI18N
(time - start) + "ms" // NOI18N
);
}
@Override
boolean willLog() {
return true;
}
private char[] spaces = new char[0];
private String getIndentString( int indent ) {
if( spaces.length < indent ) {
spaces = new char[Math.max( spaces.length*2, indent+10 )];
Arrays.fill( spaces, ' ');
}
return new String(spaces,0, indent);
}
}
private static class PerformanceTestsImpl extends StartImpl {
private StringBuffer logs = new StringBuffer();
private Stack<Long> starts = new Stack<Long>();
long prog;
private int indent = 0;
PerformanceTestsImpl() {}
@Override
synchronized void start( String action, long time ) {
starts.push(time);
prog=time;
log(getIndentString(indent) + "@" +
(time - zero) + " - " + action + " started" // NOI18N
);
indent += 2;
}
@Override
synchronized void progress( String note, long time ) {
log(getIndentString(indent) + "@" +
(time - zero) + " - " + note + " dT=" + (time - prog) // NOI18N
);
prog = time;
}
@Override
synchronized void end( String action, long time ) {
indent -= 2;
long start = starts.pop();
prog = time;
log(getIndentString(indent) + "@" +
(time - zero) + " - " + action + " finished, took " + // NOI18N
(time - start) + "ms" // NOI18N
);
}
@Override
boolean willLog() {
return true;
}
private char[] spaces = new char[0];
private String getIndentString( int indent ) {
if( spaces.length < indent ) {
spaces = new char[Math.max( spaces.length*2, indent+10 )];
Arrays.fill( spaces, ' ');
}
return new String(spaces,0, indent);
}
synchronized void log(String log){
logs.append("\n" + log);
}
@Override
public void publish(LogRecord rec) {
super.publish(rec);
if ("finish".equals(rec.getMessage())) { // NOI18N
long end = (Long)rec.getParameters()[0];
log("IDE starts t = " + Long.toString(zero) + "\nIDE is running t=" + Long.toString(end) + "\n");
}
}
@Override
public synchronized void flush(){
if(logFileProp!=null){
try{
java.io.File logFile = new java.io.File(logFileProp);
java.io.FileWriter writer = new java.io.FileWriter(logFile);
writer.write(logs.toString());
writer.close();
}catch (Exception exc){
System.err.println("EXCEPTION rises during startup logging:");
exc.printStackTrace(System.err);
}
} else
throw new IllegalStateException("You are trying to log startup logs to unexisting file. You have to set property org.netbeans.log.startup.logfile.");
}
}
private static class SimplerFormatter extends XMLFormatter {
@Override
public String getHead(Handler h) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"");
sb.append(" encoding='UTF-8'?>\n");
sb.append("<log>\n");
return sb.toString();
}
}
}