Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.intellisrc.core.Log.groovy Maven / Gradle / Ivy
package com.intellisrc.core
/**
* @since 2/11/17.
*/
import groovy.transform.CompileStatic
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import static com.intellisrc.core.AnsiColor.*
@CompileStatic
/**
* @author Alberto Lepe
*
*/
class Log {
static public final int MAX_LOG_LINE_LENGTH = Config.get("log.wrap.length", 500)
static public List ignoreFromStack = Config.get("log.stack.ignore", [
"slf4j",
"java.lang",
"java.util",
"jdk.internal",
"com.sun.proxy",
"groovy.lang",
"org.codehaus.groovy",
"org.spockframework",
"org.junit",
"org.gradle"
])
/**
* No instance
*/
protected Log() {}
/**
* Main interface to use to send log messages to
*/
static interface Printer {
void print(Level level, Info stack, String msg, List args, Throwable throwable)
}
/**
* Default printer: SLF4J
*/
static protected Logger logger
/**
* Allow to attach other printers on runtime:
*/
protected static synchronized final List printers = []
static void addPrinter(Printer toSet) {
printers << toSet
}
/**
* Initialize printers. It will look for SLF4J compatible logger. If it is not found,
* will use a simple one.
*/
static {
boolean enabled = Config.get("log.enable", ! Config.get("log.disable", false))
if(enabled) {
logger = LoggerFactory.getLogger(Config.get("log.name", "app"))
if (logger?.name && logger.name != "NOP") {
printers << (Printer) {
Level level, Info stack, String msg, List args, Throwable throwable ->
switch (level) {
case Level.TRACE:
if(args.size()) {
logger.trace(msg, args.size() == 1 ? args.first() : args.toArray())
} else {
logger.trace(msg)
}
break
case Level.DEBUG:
if(args.size()) {
logger.debug(msg, args.size() == 1 ? args.first() : args.toArray())
} else {
logger.debug(msg)
}
break
case Level.INFO:
if(args.size()) {
logger.info(msg, args.size() == 1 ? args.first() : args.toArray(Object.class))
} else {
logger.info(msg)
}
break
case Level.WARN:
if(throwable) {
if(args.size()) {
logger.warn(msg, args.size() == 1 ? args.first() : args.toArray(), throwable)
} else {
logger.warn(msg, throwable)
}
} else {
if(args.size()) {
logger.warn(msg, args.size() == 1 ? args.first() : args.toArray())
} else {
logger.warn(msg)
}
}
break
case Level.ERROR:
if(throwable) {
if(args.size()) {
logger.error(msg, args.size() == 1 ? args.first() : args.toArray(), throwable)
} else {
logger.error(msg, throwable)
}
} else {
if (args.size()) {
logger.error(msg, args.size() == 1 ? args.first() : args.toArray())
} else {
logger.error(msg)
}
}
break
}
}
} else {
println "---------------------------------------------------------------------------------------------"
println YELLOW + "You can use any 'slf4j' compatible logger like: " + RESET
println YELLOW + " * Log Module (Recommended) : " + GREEN + Log.packageName.replace(".core", "") + ":log" + RESET
println YELLOW + " * SLF4J Simple : " + CYAN + "org.slf4j:slf4j-simple " + RESET
println YELLOW + " * Log4J : " + CYAN + "org.slf4j:slf4j-log4j12" + RESET
println YELLOW + " * JDK Logging : " + CYAN + "org.slf4j:slf4j-jdk14" + RESET
println YELLOW + " * Jakarta Commons : " + CYAN + "org.slf4j:slf4j-jcl" + RESET
println YELLOW + " * LogBack : " + CYAN + "ch.qos.logback:logback-classic" + RESET
println "---------------------------------------------------------------------------------------------"
printers << (Printer) {
Level level, Info stack, String msg, List args, Throwable throwable ->
println getLogLine(level, stack, formatString(msg, args))
if (level == Level.ERROR) {
if (throwable) {
if (throwable.message) {
println getLogLine(Level.ERROR, stack, "\t" + throwable.message)
}
StringWriter sw = new StringWriter()
PrintWriter pw = new PrintWriter(sw)
throwable.printStackTrace(pw)
sw.toString().eachLine {
String line ->
if (!line.startsWith("\t")) {
println getLogLine(Level.INFO, stack, line)
} else {
println getLogLine(Level.TRACE, stack, line)
}
}
}
}
}
}
}
}
/**
* Return a line of the Log (automatically adding color or not)
* @return
*/
protected static String getLogLine(Level level, Info stack, String msg) {
String time = SysClock.dateTime.YMDHmsS
String levelStr = level.name().charAt(0)
if(levelStr == "T") {
levelStr = "V" //Change it to verbose
}
return (time + "\t" + "[" + levelStr + "]\t" + stack.className + "\t" + stack.methodName + ":" + stack.lineNumber + "\t" + msg)
}
/**
* Information about log location:
*/
static class Info {
String packageName
String className
String methodName
String fileName
int lineNumber
}
/**
* t is alias of v
*/
static synchronized void t(String msg, Object... args) {
v(msg, args)
}
static synchronized void v(String msg, Object... args) {
log(Level.TRACE, msg, args)
}
static synchronized void d(String msg, Object... args) {
log(Level.DEBUG, msg, args)
}
static synchronized void i(String msg, Object... args) {
log(Level.INFO, msg, args)
}
static synchronized void w(String msg, Object... args) {
log(Level.WARN, msg, args)
}
static synchronized void e(String msg, Object... args) {
log(Level.ERROR, msg, args)
}
/**
* Extracts Throwable from argument list, calls printWrap()
* and any other listener
* @param lvl
* @param msg
* @param args
*/
protected static void log(Level lvl, String msg, Object... args) {
Info stack = stack()
LinkedList listArgs = List.of(args) as LinkedList
Throwable throwable = null
if(!listArgs.isEmpty() && listArgs.last() instanceof Throwable) {
throwable = (Throwable) listArgs.pollLast()
} else if(lvl == Level.ERROR) {
throwable = new Exception("Generic Exception generated in Log")
}
printers.each {
Printer printer ->
printWrap(printer, lvl, stack, msg, throwable, listArgs)
}
}
/**
* This method will try to wrap messages in multiple lines when they are too long
*/
protected static void printWrap(Printer printer, Level level, Info stack, String msg, Throwable throwable, List args) {
msg.eachLine {
String line ->
while( line.length() > 0) {
int splitPos = Math.min(MAX_LOG_LINE_LENGTH, line.length())
for (int i = splitPos-1; line.length() > MAX_LOG_LINE_LENGTH && i >= 0; i--) {
if (" \t,.;:?!{}()[]/\\".indexOf(line.charAt(i) as String) != -1) {
splitPos = i
break
}
}
splitPos = Math.min(splitPos + 1, line.length())
msg = line.substring(0, splitPos)
line = line.substring(splitPos)
printer.print(level, stack, msg, args, throwable)
}
}
}
/**
* Generate information about the log (class, line number, method, etc)
* @param index : how many steps back in the stack trace we will skip
* t to identify the class
*/
static Info stack(List ignoreList = []) {
ignoreList << Log.class.packageName
ignoreList.addAll(ignoreFromStack)
int STACK_DEPTH = 5 // How many to skip to reach this method
StackTraceElement[] stackTrace = new Throwable().getStackTrace()
if (stackTrace.length < STACK_DEPTH) {
throw new IllegalStateException("Synthetic stacktrace didn't have enough elements") // are you using proguard?
}
StackTraceElement caller = stackTrace.find {
StackTraceElement item ->
boolean containsKeyword = ignoreList.any { item.className.toLowerCase().contains(it.toLowerCase()) }
return ! containsKeyword
}
if(! caller) {
caller = stackTrace[STACK_DEPTH - 1]
}
String className = caller.className
//Remove closure information:
if(className.contains('$')) {
className -= ~/\$.*/
}
Info stack
try {
int dotIndex = className.lastIndexOf('.')
stack = new Info(
packageName : className.substring(0, dotIndex),
className : className.substring( dotIndex + 1),
fileName : caller.fileName,
methodName : caller.methodName,
lineNumber : caller.lineNumber > 0 ? caller.lineNumber : 0
)
} catch (ClassNotFoundException ignored) {
stack = new Info(
packageName : "not.found",
className : "Unknown",
fileName : "?",
methodName : "?",
lineNumber : 0
)
}
return stack
}
/**
* Format with %
* @param msg
* @param args
* @return
*/
static String formatString(String msg, List args) {
if(!args.empty) {
LinkedList params = args as LinkedList
List convParams = []
String newMsg = msg
msg.findAll(/(\{}|%[$0-9.(a-zA-Z+-])/).each {
String found ->
if (found == "{}" || found == "%s") {
newMsg = newMsg.replaceFirst(/\{}/, "%s")
Object val = params.empty ? "" : params.pollFirst()
String converted
switch (val) {
case float: case Float: case double: case Double: case BigDecimal:
converted = String.format("%.4f", val)
break
case LocalTime:
converted = (val as LocalTime).HHmmss
break
case LocalDate:
converted = (val as LocalDate).YMD
break
case LocalDateTime:
converted = (val as LocalDateTime).YMDHms
break
case InetAddress:
converted = (val as InetAddress).hostAddress
break
case File:
converted = (val as File).absolutePath
break
case byte[]:
converted = (val as byte[]).encodeHex()
break
default:
converted = val.toString()
}
convParams << converted
} else {
convParams << params.pollFirst()
}
}
try {
[ // Support for single '%' in messages, like: ("This is %d% supported", 100)
/%%/ : '%',
/%$/ : '%%',
/%([^0-9.a-zA-Z(%+-])/ : '%%$1'
].each {
newMsg = newMsg.replaceAll(it.key, it.value)
}
newMsg = String.format(newMsg, convParams.toArray())
msg = newMsg
} catch (Exception ignore) { // If something fails, try to execute it just like that:
try {
newMsg = String.format(msg, args)
msg = newMsg
} catch(Exception e) {
Log.e("Invalid format in message: [$msg]", e) //Set inline to prevent stack overflow
}
}
}
return msg
}
}