Skip to content

Instantly share code, notes, and snippets.

@ravindranathakila
Created January 29, 2012 17:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ravindranathakila/1699656 to your computer and use it in GitHub Desktop.
Save ravindranathakila/1699656 to your computer and use it in GitHub Desktop.
Smart Logger (Accumulative Logger)
package ai.ilikeplaces.util;
import ai.ilikeplaces.doc.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import static ai.ilikeplaces.util.Loggers.LEVEL;
/**
* Lets try make a smart logger.
* <p/>
* Intention:
* Get a start message such as "Shutting down system"
* Wait for a confirmation such as "Done"
* If no confirmation within a specific time(sent with the message) print "Possible failure, See logs".
* <p/>
* Requirements:
* Accept a logger
* Accept a logger severity
* <p/>
* if you find any bugs, please notify somebody at ilikeplaces.com
* <p/>
* I did this for fun when noticing how Mandriva Linux(then again all linux) was shutting down.
* The status messages goes as "Stopping SASL.... [FAILED]"... "Stopping MySQL... [done]".
* This got me thinking, logging is not about just notifying. It is about reporting a process.
* Hence, this smart logger is to facilitate a start and time out, completion or delayed completion
* of a process *
* Created by IntelliJ IDEA.
* User: <a href="http://www.ilikeplaces.com"> http://www.ilikeplaces.com </a>
* Date: May 29, 2010
* Time: 8:28:17 PM
*/
@DOCUMENTATION(
LOGIC = @LOGIC(
@NOTE(
{
"scenario 1: no timeout. normal complete> sleep 0 , status false",
"scenario 2: no timeout, 2 normal completes> sleep 0, status true",
"scenario 3: with timeout, normal complete> sleep +ve, status false",
"scenario 4: with timeout, 2 normal completes> sleep +ve, status true",
"scenario 4: with timeout, recovered complete> sleep +ve, status true",
"scenario 4: with timeout, 2 recovered completes> sleep +ve, status true",
"summary 1: scenario 1 and 2 can be tracked by, sleep always 0, and if staus is true, throw an exception.",
"summary 2:"
}
)
),
TODO = @TODO(task = "Well there is something I noticed recently. Unless something goes wrong, logs are useful only for analytics. " +
"Hence, if we could implement a logger that logs an entire sequence of monitored events upon an exception, that'd be really cool! " +
"FOr example, a user logs in and does A, and B, and while doing C, he gets a server error/or not. " +
"If C threw an exception, both A and B get logged. If C didn't, nothing gets logged. " +
"This approach will dramatically reduce log entries. It has a memory penalty. " +
"But then again, this already can be done by a new complete method. Lets see. " +
"Hmmm.... Logging levels can be tuned to gain this feature so maybe this TODO is absurd! :D " +
"Yes, it is!"),
WARNING = @WARNING("Do not user any java.lang.Objects classes. The SmartLogger should be extremely memory efficient.")
)
@License(content = "This code is licensed under GNU AFFERO GENERAL PUBLIC LICENSE Version 3")
final public class SmartLogger extends Thread {
private static final String LOGGER_HAS_ALREADY_BEEN_COMPLETED = "Logger has already been completed!";
private static final String LOGGER_HAS_ALREADY_BEEN_COMPLETED_UNDER_TIMEOUT_LOGGING = "Logger has already been completed under timeout logging.";
private static final String SORRY_I_POSSIBLY_FAILED_TO_LOG = "SORRY! I POSSIBLY FAILED TO LOG:";
final Loggers.LEVEL level;
boolean recordedAtLeastOneError = false;
String logmsg;
long sleep;
long starTime = -1;//WARNING: DO NOT change this default value as it is used in IF/TERNARY conditions.
boolean logged = false;
/*The following string constants might look odd but we do not want any delays in a logger. Macro optimization!!*/
private static final String CAUSE_UNRESPONSIVE_IN_MILLIS = "{} <= cause UNRESPONSIVE in millis:";
private static final String SORRY_I_FAILED_TO_LOG_THIS_MESSAGE = "SORRY! I FAILED TO LOG THIS MESSAGE:";
private static final String CAUSE_RECOVERED_WITH_STATUS = " <= cause RECOVERED with status:";
private static final String TIME_TAKEN = "[Time Taken:";
private static final String CLOSE_SQUARE_BRACKET = "]";
private static final String COLON = ":";
private static final String EMPTY = "";
private static final String PIPE = "|";
/**
* Leaving optional parameters as null will simply ignore them.
* You can specify any number of optinal parameters as indicated by the underscore.
* At the time of commenting, the optional value startMsg_calcExecTime means,
* you can give a startMsg(you guessed right, a string) and a calcExecTime(boolean).
* Leaving any null will make the program simply ignore the value.
*
* @param logLevel
* @param logMessage
* @param timeout
* @param startMsg_calcExecTime
*/
public SmartLogger(final LEVEL logLevel, final String logMessage, final long timeout, final Object... startMsg_calcExecTime) {
if (startMsg_calcExecTime.length == 2) { //Placing this IF above as we need to log time asap.
if (startMsg_calcExecTime[1] != null) {
if ((Boolean) startMsg_calcExecTime[1]) {
starTime = System.currentTimeMillis();
}
}
if (startMsg_calcExecTime[0] != null) {
Loggers.log(logLevel, Loggers.EMBED, startMsg_calcExecTime[0]);
}
} else if (startMsg_calcExecTime.length == 1) {
if (startMsg_calcExecTime[0] != null) {
Loggers.log(logLevel, Loggers.EMBED, startMsg_calcExecTime[0]);
}
}
this.logmsg = logMessage != null ? logMessage : EMPTY;//I think this is cool, especially since null caused a bug which delayed a release by one day and...
this.sleep = timeout;
this.level = logLevel;
this.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
Loggers.log(LEVEL.ERROR, SORRY_I_POSSIBLY_FAILED_TO_LOG + logMessage, e);
}
});
start();
setPriority(Thread.NORM_PRIORITY);//Should we set this to Min? Hmmm.. then all method calls should be checked for asynchronosity
}
@Override
public void run() {
try {
if (sleep != 0) {//Sleep 0 avoids timeout. This is when this object will wait till task "complete"
Thread.sleep(sleep);
if (!status()) {
Loggers.log(level, CAUSE_UNRESPONSIVE_IN_MILLIS + sleep, logmsg);
sleep = -1;//To track that sleep value was consumed once
}
}
} catch (final InterruptedException e) {
Loggers.log(LEVEL.ERROR, SORRY_I_FAILED_TO_LOG_THIS_MESSAGE + logmsg, e);
}
}
private synchronized boolean status() {
return !(logged = !logged);//hehe, tricky. :D
}
/**
* Returns the time taken to execute task, or empty string, if calculations disabled
*
* @return
*/
private String timeTaken() {
return (starTime == -1) ? EMPTY : TIME_TAKEN + (System.currentTimeMillis() - starTime + CLOSE_SQUARE_BRACKET);
}
/**
* Leaving optional parameters as null will simply ignore them.
* You can specify any number of optinal parameters as indicated by the underscore.
* At the time of commenting, the optional value startMsg_calcExecTime means,
* you can give a startMsg(you guessed right, a string) and a calcExecTime(boolean).
* Leaving any null will make the program simply ignore the value.
*
* @param logLevel
* @param logMessage
* @param timeout
* @param startMsg_calcExecTime calcExecTime is of unit milliseconds
* @return
*/
static public SmartLogger start(final LEVEL logLevel, final String logMessage, final long timeout, final Object... startMsg_calcExecTime) {
return new SmartLogger(logLevel, logMessage, timeout, startMsg_calcExecTime);
}
public void appendToLogMSG(final String stringToBeAppended) {
logmsg += PIPE + stringToBeAppended;
}
private void appendToLogMSG(final String errorDescription, final Throwable t) {
logmsg += PIPE + errorDescription;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.flush();
sw.flush();
sw.toString();
logmsg += PIPE + sw;
recordedAtLeastOneError = true;
}
/**
* @param stringToBeAppended
*/
public void l(final String stringToBeAppended) {
appendToLogMSG(stringToBeAppended);
}
/**
* @param objectWithToStringOverridden
*/
public void l(final Object objectWithToStringOverridden) {
appendToLogMSG(objectWithToStringOverridden.toString());
}
/**
* @param errorDescription
* @param throwableErrorToBeLogged
*/
public void l(final String errorDescription, final Throwable throwableErrorToBeLogged) {
appendToLogMSG(errorDescription, throwableErrorToBeLogged);
}
static public void complete(final SmartLogger smartLogger, final LEVEL completeLevel, final String completeStatus) {
smartLogger.complete(completeLevel, completeStatus);
}
static public void complete(final SmartLogger smartLogger, final String completeStatus) {
smartLogger.complete(completeStatus);
}
/**
* @param completeLevel Log Level
* @param completeStatus Throwable type needed in case of ERROR
*/
public void complete(final LEVEL completeLevel, final Object completeStatus) {
if (sleep == 0) {
if (!status()) {
Loggers.log(completeLevel, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
if (recordedAtLeastOneError) {
Loggers.log(LEVEL.ERROR, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
}
} else { //this means with no timeout, the logger also was completed once. i.e. status=true. Hence error.
throw new RuntimeException(LOGGER_HAS_ALREADY_BEEN_COMPLETED);
}
} else if (sleep == -1) {//timeout happened //need to track if logging happens twice... or not... since this condition is triggered by us inside the new thread
Loggers.log(completeLevel, Loggers.EMBED + CAUSE_RECOVERED_WITH_STATUS + COLON + completeStatus + timeTaken(), logmsg);
} else {
if (!status()) {
Loggers.log(completeLevel, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
if (recordedAtLeastOneError) {
Loggers.log(LEVEL.ERROR, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
}
} else { //this means with no timeout, the logger also was completed once. i.e. status=true. Hence error.
throw new RuntimeException(LOGGER_HAS_ALREADY_BEEN_COMPLETED);
}
}
}
/**
* @param completeLevels
* @param completeStatus
*/
public void multiComplete(final LEVEL[] completeLevels, Object completeStatus) {
completeStatus = completeStatus != null ? completeStatus : "";
if (sleep == 0) {
if (!status()) {
for (final LEVEL completeLevel : completeLevels) {
Loggers.log(completeLevel, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
if (recordedAtLeastOneError) {
Loggers.log(LEVEL.ERROR, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
}
}
} else { //this means with no timeout, the logger also was completed once. i.e. status=true. Hence error.
throw new RuntimeException(LOGGER_HAS_ALREADY_BEEN_COMPLETED);
}
} else if (sleep == -1) {//timeout happened //need to track if logging happens twice... or not... since this condition is triggered by us inside the new thread
for (final LEVEL completeLevel : completeLevels) {
Loggers.log(completeLevel, Loggers.EMBED + CAUSE_RECOVERED_WITH_STATUS + COLON + completeStatus + timeTaken(), logmsg);
if (recordedAtLeastOneError) {
Loggers.log(LEVEL.ERROR, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
}
}
} else {
if (!status()) {
for (final LEVEL completeLevel : completeLevels) {
Loggers.log(completeLevel, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
if (recordedAtLeastOneError) {
Loggers.log(LEVEL.ERROR, Loggers.EMBED + COLON + completeStatus + timeTaken(), logmsg);
}
}
} else { //this means with no timeout, the logger also was completed once. i.e. status=true. Hence error.
throw new RuntimeException(LOGGER_HAS_ALREADY_BEEN_COMPLETED);
}
}
}
/**
* @param completeStatus Complete status or null if you want to drop all further logging
*/
public void complete(final String completeStatus) {
complete(level, completeStatus);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment