com.sta.mlogger.MLogger Maven / Gradle / Ivy
package com.sta.mlogger;
import java.util.Properties;
import java.util.Stack;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.function.BiConsumer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import com.sta.mutils.ThrowingRunnable;
/**
* Name: MLogger
* Description: Realisierung einer Logger-Schnittstelle f?r diverse Projekte.
* Gedacht ist diese Klasse als eine zentrale Stelle f?r alle Logging-Aktionen.
* Durch die Registration von MLogEntries kann je nach den Anforderungen im
* konkreten Projekt ein anderer (realer) Logger verwendet werden. Der
* einfachste Fall ist in MLogEntry4Text gezeigt. Hier wird einfach ein
* PrintWriter zur Ausgabe verwendet. Soll z. B. ein Standard-Logger benutzt
* werden (k?nnte die Variante von Apache sein), mu? lediglich ein
* MLogEntry4xxx (abgeleitet von MLogEntry) erzeugt werden, der im einfachsten
* Fall nur die Methode MLogEntry.println(String) ?berschreibt. Eine Instanz
* dieses MLogEntry4xxx ist dann beim Programmstart (vor der ersten
* Logging-Aktion) beim Logger zu registrieren (addMLogEntry(MLogEntry)). Falls
* bei der ersten Logging-Aktion (noch) kein MLogEntry registriert wurde,
* wird ein Standard-MLogEntry (MLogEntry4Text) erzeugt und registriert.
* Beim Logger k?nnen (wie bereits beschrieben) auch mehrere MLogEntries
* registriert werden. Alle Logging-Aktionen werden dann an alle registrierten
* MLogEntries weitergeleitet. Somit sind diese daf?r verantwortlich,
* entsprechend dem LogLevel eine Selektion vorzunehmen. Die Standard-Methoden
* in der Basisklasse MLogEntry bieten daf?r bereits die notwendige
* Unterst?tzung.
* Ab Version 1.01 wird die Ausgabe ohne Zeilenumbruch nicht mehr unterst?tzt.
* Mit Version 1.21 erfolgte die Umstellung auf Java 1.8 (ff.) und erstmalige
* Verwendung einer Lambda-Konstruktion, um den Text f?r auszugebende
* Nachrichten erst dann zu erstellen, wenn dieser tats?chlich ben?tigt wird.
* Mit Version 1.26 wird die optionale Verwendung eines Thread-Contexts als
* "MDC" (Mapped Diagnostic Context) umgesetzt.
* Mit Version 1.27 erfolgt die Umstellung auf Maven.
* Mit Version 1.32 erfolgt die Verteilung ?ber Maven-Central.
*
* Copyright: Copyright (c) 2002-2014, 2017-2021
* Company: >StA-Soft<
* @author StA
* @version 1.38
*/
public class MLogger
{
/**
* MLogger-Version.
*/
public static final String VERSION = MLoggerVersionHelper.VERSION;
/**
* Copyright.
*/
public static final String COPYRIGHT = MLoggerVersionHelper.COPYRIGHT;
/**
* Firma.
*/
public static final String COMPANY = MLoggerVersionHelper.COMPANY;
/**
* Name des MLogger-Property-Files.
*/
private static final String PROP_FILENAME = "mlogger.properties";
/**
* Name des optionalen System-Propertys f?r das Verzeichnis des MLogger-Property-Files.
*/
public static final String SYSTEM_PROPERTY_NAME = "mlogger.config.dir";
/**
* Menge der registrierten MLogEntries, die f?r alle Threads gelten, f?r die
* keine speziellen MLogEntries definiert wurden.
*/
private static Vector statMLogEntries = new Vector();
/**
* Hash-Tabelle mit Vektoren mit MLogEntries, die f?r einen konkreten Thread
* definiert sind. Schl?ssel ist der Thread-Name oder die Thread-Id.
*/
private static Hashtable statThreadEntries = new Hashtable();
/**
* Maximale Anzahl an Eintr?gen in einem Stack.
*/
private static final int MAX_THREAD_STACK_SIZE = 100;
/**
* Zeitpunkt der letzten F?llstands?berpr?fung ?ber alle Stacks.
*/
private static long statLastFullCheck = 0;
/**
* Hash-Tabelle mit Stacks f?r Log-Schl?ssel zur Zeitmessung von der
* Start-Log-Meldung bis zum "Ok" bzw. "Error".
*/
private static Hashtable statThreadStacks = new Hashtable();
//===========================================================================
/**
* Eintrag in einem Stack.
*/
private static class ThreadStackEntry
{
/**
* Text.
*/
private String myText;
/**
* Zeit.
*/
private long myTime;
/**
* Constructor mit Text.
* @param pText Text
*/
protected ThreadStackEntry(String pText)
{
myText = pText;
myTime = System.currentTimeMillis();
}
/**
* Text ermitteln.
* @return Text
*/
public String getText()
{
return myText;
}
/**
* Zeit ermitteln.
* @return Zeit
*/
public long getTime()
{
return myTime;
}
}
//===========================================================================
/**
* Version ermitteln.
* @return Version
*/
public static String getVersion()
{
return VERSION;
}
/**
* Copyright ermitteln.
* @return Copyright
*/
public static String getCopyright()
{
return COPYRIGHT;
}
/**
* Company ermitteln.
* @return Company
*/
public static String getCompany()
{
return COMPANY;
}
//===========================================================================
/**
* String f?r Einr?ckung liefern. Die Methode wurde u. a. deshalb hier
* eingef?hrt und als "static" deklariert, damit sie auch von anderen Klassen
* m?glichst einfach verwendet werden kann.
* @param indent Anzahl der Leerzeichen
* @return String aus vorgegebener Anzahl von Leerzeichen
* @see #indentStrg(String, int)
*/
public static String indent(int indent)
{
StringBuilder sb = new StringBuilder(indent);
for (int i = 0; i < indent; i++)
{
sb.append(' ');
}
return sb.toString();
}
/**
* String pStrg pro Zeile (!) um indent Leerzeichen einr?cken. Zeilenenden
* werden also beachtet. Auch diese Methode wurde u. a. deshalb hier
* eingef?hrt und als "static" deklariert, damit sie auch von anderen Klassen
* m?glichst einfach verwendet werden kann.
* @param pStrg der String, der zeilenweise "einger?ckt" werden soll
* @param indent Anzahl der Leerzeichen
* @return Ergebnis-String
* @see #indent(int)
*/
public static String indentStrg(String pStrg, int indent)
{
StringBuilder sb = new StringBuilder();
String si = indent(indent);
StringTokenizer st = new StringTokenizer(pStrg, "\n\r");
while (st.hasMoreTokens())
{
sb.append(si).append(st.nextToken()).append('\n');
}
return sb.toString();
}
//---------------------------------------------------------------------------
/**
* MLogEntry registrieren bzw. hinzuf?gen.
* @param pMLogEntry ein initialisierter MLogEntry
*/
public static synchronized void addMLogEntry(MLogEntry pMLogEntry)
{
statMLogEntries.add(pMLogEntry);
}
/**
* Anzahl der schon vorhandenen MLogEntries ermitteln.
* @return die Anzahl
*/
public static synchronized int getMLogEntryCount()
{
return statMLogEntries.size();
}
/**
* MLogEntry an Index i ermitteln.
* @param i der Index
* @return der MLogEntry
*/
public static synchronized MLogEntry getMLogEntry(int i)
{
return (MLogEntry) statMLogEntries.get(i);
}
/**
* MLogEntries als Enumeration ermitteln.
* @return Enumeration der MLogEntries
*/
public static synchronized Enumeration getMLogEntries()
{
return statMLogEntries.elements();
}
/**
* MLogEntry an Index i entfernen.
* @param i der Index
*/
public static synchronized void removeMLogEntry(int i)
{
statMLogEntries.remove(i);
}
/**
* MLogEntry entfernen.
* @param pMLogEntry ein MLogEntry, der bereits enthalten ist
*/
public static synchronized void removeMLogEntry(MLogEntry pMLogEntry)
{
statMLogEntries.remove(pMLogEntry);
}
//---------------------------------------------------------------------------
/**
* Thread-Eintrag ermitteln.
* @return Thread-Eintrag
*/
private static Vector getThreadEntry()
{
String key = Thread.currentThread().getName();
return (Vector) statThreadEntries.get(key);
}
/**
* Pr?fen ob Thread-Eintrag vorhanden, falls nicht: anlegen.
* @return Thread-Eintrag
*/
private static Vector checkThreadEntry()
{
String key = Thread.currentThread().getName();
Vector v = (Vector) statThreadEntries.get(key);
if (v == null)
{
v = new Vector();
statThreadEntries.put(key, v);
}
return v;
}
/**
* MLogEntry registrieren bzw. hinzuf?gen (thread-spezifisch).
* @param pMLogEntry ein initialisierter MLogEntry
*/
public static synchronized void addMLogEntryTS(MLogEntry pMLogEntry)
{
Vector v = checkThreadEntry();
v.add(pMLogEntry);
}
/*
public static synchronized void addMLogEntryTSx(MLogEntry pMLogEntry)
{
// Pr?fe, ob ein solche MLogEntry bereits (Thread-spezifisch) hinzugef?gt
// wurde. Falls ja: f?ge eben diesen statt dem ?bergebenen erneut hinzu.
Enumeration e = statThreadEntries.elements();
while (e.hasMoreElements())
{
Vector vv = (Vector) e.nextElement();
int i = vv.indexOf(pMLogEntry);
if (i >= 0)
{
pMLogEntry = (MLogEntry) vv.get(i);
break;
}
}
Vector v = checkThreadEntry();
v.add(pMLogEntry);
}
*/
/**
* Anzahl der schon vorhandenen MLogEntries ermitteln (thread-spezifisch).
* @return die Anzahl
*/
public static synchronized int getMLogEntryCountTS()
{
Vector v = getThreadEntry();
return v != null ? v.size() : 0;
}
/**
* MLogEntry an Index i ermitteln (thread-spezifisch).
* @param i der Index
* @return der MLogEntry
*/
public static synchronized MLogEntry getMLogEntryTS(int i)
{
Vector v = getThreadEntry();
return v != null ? (MLogEntry) v.get(i) : null;
}
/**
* MLogEntries als Enumeration ermitteln (thread-spezifisch).
* @return Enumeration der MLogEntries
*/
public static synchronized Enumeration getMLogEntriesTS()
{
Vector v = getThreadEntry();
return v != null ? v.elements() : null;
}
/**
* MLogEntry an Index i entfernen (thread-spezifisch).
* @param i der Index
*/
public static synchronized void removeMLogEntryTS(int i)
{
Vector v = getThreadEntry();
if (v != null)
{
v.remove(i);
}
}
/**
* MLogEntry entfernen (thread-spezifisch).
* @param pMLogEntry ein MLogEntry, der bereits enthalten ist
*/
public static synchronized void removeMLogEntryTS(MLogEntry pMLogEntry)
{
Vector v = getThreadEntry();
if (v != null)
{
v.remove(pMLogEntry);
}
}
//---------------------------------------------------------------------------
/**
* Fall (2), siehe checkMagic(String).
* @param s auszugebender Text
* @param i Index des ":" im Text
* @return ggf. manipulierter auszugebender Text
*/
private static String checkMagic(String s, int i)
{
String text = s.substring(0, i); // ohne ":" und nachfolgenden Text
String add = "";
String key = Thread.currentThread().getName();
Stack stack = (Stack) statThreadStacks.get(key);
if (stack != null)
{
if (stack.size() != 0)
{
//
// Variante 1:
//
// Nur das oberste Stack-Element pr?fen, falls das passt, Zeit eintragen
// und Element entfernen.
//
/*
ThreadStackEntry entry = (ThreadStackEntry) stack.peek();
if (text.equals(entry.getText()))
{
stack.pop();
if (stack.size() == 0)
{
statThreadStacks.remove(key);
}
long time = System.currentTimeMillis() - entry.getTime();
add = time < 1000 ? "" + time + " ms" : time < 1000000 ? "" + ((double) time / 1000) + " s" : "" + (time / 1000) + " s";
}
else
{
add = "";
}
*/
//
// Variante 2:
//
// Den Stack von oben beginnend nach einem passenden Element
// durchsuchen, falls gefunden die Zeit eintragen und dieses Element
// und alle dar?ber liegenden entfernen.
//
int size = stack.size();
int j = size;
boolean found = false;
while ((j > 0) && !found)
{
j--;
ThreadStackEntry entry = (ThreadStackEntry) stack.get(j);
if (text.equals(entry.getText()))
{
while (size != j)
{
size--;
stack.remove(size);
}
if (size == 0)
{
statThreadStacks.remove(key);
}
long time = System.currentTimeMillis() - entry.getTime();
add = time < 1000 ? "" + time + " ms" : time < 1000000 ? "" + ((double) time / 1000) + " s" : "" + (time / 1000) + " s";
found = true;
}
}
if (!found)
{
add = "";
}
}
else
{
add = "";
}
}
else
{
add = "";
}
s = s.substring(0, i + 2) + add + s.substring(i + 1); // Space 2x (!)
return s;
}
/**
* Pr?fen, ob der auszugebende Text (1) mit 3 Punkten ohne vorangestelltes
* Leerzeichen oder (2) auf ": Ok." bzw. ": Error." endet.
* Im Fall (1) wird der Text vor den 3 Punkten samt Zeitstempel registriert.
* Im Fall (2) wird nach dem Textteil vor dem ":" in den registrierten Texten
* gesucht und falls gefunden, die zeitliche Differenz zu dem Eintrag in den
* auszugebenden Text eingef?gt, au?erdem wird der Registrierungseintrag (und
* ggf. sp?ter hinzugef?gte) wieder entfernt.
* @param s auszugebender Text
* @return ggf. manipulierter auszugebender Text
*/
private static String checkMagic(String s)
{
String res = s;
if (s != null)
{
int len = s.length();
if (s.endsWith("...") && (len > 3) && (s.charAt(len - 4) != ' '))
{
// Anfang
String text = s.substring(0, len - 3); // ohne die 3 Punkte
String key = Thread.currentThread().getName();
Stack stack = (Stack) statThreadStacks.get(key);
if (stack == null)
{
stack = new Stack();
statThreadStacks.put(key, stack);
}
stack.push(new ThreadStackEntry(text));
// Sicherheitspr?fung, damit der Speicher nicht voll l?uft
// (auf Maximalzahl von Eintr?gen pr?fen)
if (stack.size() > MAX_THREAD_STACK_SIZE)
{
stack.remove(0);
}
// Sicherheitspr?fung ?ber alle Thread-Stack-Eint?ge
synchronized (statThreadStacks)
{
long curtime = System.currentTimeMillis();
if (statLastFullCheck < curtime - 1 * 60 * 1000)
{
statLastFullCheck = curtime;
long time = curtime - 60 * 60 * 1000;
Enumeration e = statThreadStacks.keys();
while (e.hasMoreElements())
{
String k = (String) e.nextElement();
// if (!key.equals(k))
// {
Stack v = (Stack) statThreadStacks.get(key);
while ((v.size() != 0) && (((ThreadStackEntry) v.get(0)).getTime() < time))
{
ThreadStackEntry tse = (ThreadStackEntry) v.get(0);
System.out.println("MLogger: remove entry [ " + tse.getText() + " ]."); // hier explizit n?tig
v.remove(0);
}
if (v.size() == 0)
{
System.out.println("MLogger: remove stack."); // hier explizit n?tig
statThreadStacks.remove(k);
}
// }
}
}
}
}
else if (s.endsWith(": Ok.") && (len > 5) && (s.charAt(len - 6) != ' '))
{
res = checkMagic(s, len - 5);
}
else if (s.endsWith(": Error.") && (len > 8) && (s.charAt(len - 9) != ' '))
{
res = checkMagic(s, len - 8);
}
}
return res;
}
/**
* Sicherstellen, da? wenigstens ein MLogEntry vorhanden ist. Falls keiner
* vorhanden ist, wird ein Standard-MLogEntry (MLogEntry4Text) hinzugef?gt.
*
* Neu (ab Version 1.24 vom 17.01.2018): Falls die Datei "mlogger.properties"
* im Klassenpfad und darin ein Eintrag namens "StdMLogEntry" samt Wert
* gefunden wird, dann wird versucht, eine Instanz der angegebenen Klasse
* zu erzeugen und auf MLogEntry zu casten. Falls dies gelingt, wird diese
* Instanz als Standard-MLogEntry verwendet, andernfalls wie bisher eine
* Instanz von MLogEntry4Text.
*
* @see #statMLogEntries
* @see #addMLogEntry(MLogEntry)
*/
public static synchronized void checkMLogEntries()
{
if (statMLogEntries.size() == 0)
{
MLogEntry mle = null;
InputStream is = null;
try
{
// zuerst die Property-Datei im aktuellen Verzeichnis suchen
try
{
is = new FileInputStream(PROP_FILENAME);
}
catch (IOException ex)
{
// "Can't find " + PROP_FILENAME + "."
}
// falls nicht gefunden: System-Property mlogger.config.dir pr?fen und falls vorhanden in diesem Verzeichnis suchen
if (is == null)
{
String dir = System.getProperty(SYSTEM_PROPERTY_NAME);
if (dir != null)
{
try
{
is = new FileInputStream(new File(dir, PROP_FILENAME));
}
catch (IOException ex)
{
// "Can't find " + PROP_FILENAME + "."
}
}
}
// falls auch nicht gefunden, dann im Klassenpfad suchen
if (is == null)
{
is = MLogger.class.getResourceAsStream("/" + PROP_FILENAME);
}
// falls gefunden: verarbeiten, sonst ohne Property-Datei weiter
if (is != null)
{
System.out.println("Found " + PROP_FILENAME + "."); // hier explizit n?tig, da vor MLogger-Initialisierung
Properties p = new Properties();
try
{
p.load(is);
}
catch (Exception ex)
{
System.out.println("Error loading properties: " + ex.getMessage()); // hier explizit n?tig, da vor MLogger-Initialisierung
ex.printStackTrace(); // hier explizit n?tig, da vor MLogger-Initialisierung
}
try
{
String cn = p.getProperty("StdMLogEntry");
if (cn != null)
{
cn = cn.trim();
if (cn.length() > 0)
{
Class c = Class.forName(cn);
mle = (MLogEntry) c.newInstance();
}
}
}
catch (Exception ex)
{
System.out.println("Can't create new instance of mlogentry: " + ex.getMessage()); // hier explizit n?tig, da vor MLogger-Initialisierung
ex.printStackTrace(); // hier explizit n?tig, da vor MLogger-Initialisierung
}
if (mle != null)
{
mle.init(p);
}
}
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException ioex)
{
}
}
}
addMLogEntry(mle != null ? mle : new MLogEntry4Text());
}
}
/**
* Name der Methode ermitteln, die den Logger aufgerufen hat. Dieser
* Methodenname wird dann als "Ort" verwendet.
* @param idx Index im StackTrace f?r Ermittlung des Methodennamen,
* alt: 3, neu: 4
* @return Methodenname incl. Packages (Fehlerfall: null)
*/
private static String getCallerMethodName(int idx)
{
Throwable t = new Throwable("X");
StackTraceElement[] stes = t.getStackTrace();
/*
for (int i = 0; i < stes.length; i++)
{
"[" + i + "] = " + stes[i].getClassName() + "." + stes[i].getMethodName()
}
*/
//
// Aufbau:
//
// [0] = "getCallerMethodName(int)" "getCallerMethodName(int)"
// [1] = "dispatch(int, BiConsumer)" "checkLevel(int)"
// [2] = "print*(int, *)" "checkxxx()"
// [3] = eine der MLogger-Methoden
//
// => Also ist idx mal = 4 (bei normalen Ausgaben) und mal = 3 (bei checkLevel)
//
// Deutlich besser w?re es aber wohl, das Feld von vorne beginnen zu durchlaufen,
// bis keine MLogger-Methode mehr gefunden wurde (au?er vielleicht main(...), damit die Tests dort noch funktionieren).
// Au?erdem m?ssten Methoden aus dem SLF4J-SimpleLogger "?berlesen" werden, damit man dessen Aufrufstellen findet.
//
int i = 0;
while (i < stes.length)
{
StackTraceElement ste = stes[i];
String cni = ste.getClassName();
if (!"com.sta.mlogger.MLogger".equals(cni))
{
if (!"org.slf4j.impl.SimpleLogger".equals(cni))
{
String mni = ste.getMethodName();
return cni + "." + mni;
// break;
}
}
else
{
String mni = ste.getMethodName();
if ("main".equals(mni))
{
return cni + "." + mni;
// break;
}
}
i++;
}
// R?ckfall auf die urspr?ngliche Realisierung
String cn = stes[idx].getClassName();
String mn = stes[idx].getMethodName();
/*
int i = cn.lastIndexOf('.');
if (i >= 0)
{
cn = cn.substring(i + 1);
}
*/
return cn + "." + mn;
}
/**
* Verteilung der auszugebenden Nachricht bzw. des Message-Builders an die zust?ndigen MLogEntries.
* @param pLevel vorgegebener LogLevel
* @param c Consumer mit MLogEntry und Location
*/
private static void dispatch(int pLevel, BiConsumer c)
{
String location = null;
//
// Zuerst Thread-MLogEntries...
//
Vector v = getThreadEntry();
if (v != null)
{
int size = v.size();
for (int i = size - 1; i >= 0; i--)
{
MLogEntry le = (MLogEntry) v.get(i);
if (location == null)
{
location = getCallerMethodName(4);
}
if (le.checkLevel(location, pLevel))
{
// le.println(location, pLevel, pStrg);
c.accept(le, location);
}
if (le.getBreakAfterOutput())
{
return;
}
}
}
//
// ... dann die "normalen" MLogEntries
//
int size = statMLogEntries.size();
for (int i = size - 1; i >= 0; i--)
{
MLogEntry le = (MLogEntry) statMLogEntries.get(i);
if (location == null)
{
location = getCallerMethodName(4);
}
if (le.checkLevel(location, pLevel))
{
// le.println(location, pLevel, pStrg);
c.accept(le, location);
}
// "BreakAfterOutput" pr?fen (siehe obere Schleife)?
}
}
/**
* Text ausgeben.
* Diese Nachricht wird an alle registrierten MLogEntries weitergereicht.
* Wichtig ist, dass diese Methode immer von Methoden aus MLogger selbst
* aufgerufen wird. Diese (aufrufenden) Methon m?ssen direkt von au?erhalb
* aufgrufen worden sein. Auf dieser Basis wird derzeit der Ort (Methodenname)
* ermittelt.
* @param pLevel vorgegebener LogLevel
* @param pStrg der auszugebende Text
* @see #checkMLogEntries()
*/
private static void println(int pLevel, String pStrg)
{
checkMLogEntries();
String msg = checkMagic(pStrg);
dispatch(pLevel, (le, location) -> le.println(location, pLevel, msg));
}
/**
* Text ausgeben (ohne Zeilenumbruch).
* Diese Nachricht wird an alle registrierten MLogEntries weitergereicht.
* Wichtig ist, dass diese Methode immer von Methoden aus MLogger selbst
* aufgerufen wird. Diese (aufrufenden) Methon m?ssen direkt von au?erhalb
* aufgrufen worden sein. Auf dieser Basis wird derzeit der Ort (Methodenname)
* ermittelt.
* @param pLevel vorgegebener LogLevel
* @param pStrg der auszugebende Text
* @see #checkMLogEntries()
*/
private static void print(int pLevel, String pStrg)
{
checkMLogEntries();
String msg = checkMagic(pStrg);
dispatch(pLevel, (le, location) -> le.print(location, pLevel, msg));
}
/**
* Text bei Bedarf erstellen und unter bestimmtem Level ausgeben.
* @param pLevel Level
* @param mb Message-Builder
*/
private static void println(int pLevel, IMessageBuilder mb)
{
checkMLogEntries();
IMessageBuilder mb2 = mb instanceof MMessageBuilder ? mb : new MMessageBuilder(mb);
dispatch(pLevel, (le, location) -> le.println(location, pLevel, mb2));
}
/**
* Text bei Bedarf erstellen und unter bestimmtem Level ausgeben (ohne Zeilenumbruch).
* @param pLevel Level
* @param mb Message-Builder
*/
private static void print(int pLevel, IMessageBuilder mb)
{
checkMLogEntries();
// doprintln(pLevel, mb instanceof MMessageBuilder ? mb : new MMessageBuilder(mb));
// === Versuch ... ===
IMessageBuilder mb2 = mb instanceof MMessageBuilder ? mb : new MMessageBuilder(mb);
dispatch(pLevel, (le, location) -> le.print(location, pLevel, mb2));
// === ... Versuch ===
}
/**
* Pr?fen, ob eine Nachricht mit einem bestimmten Level ausgegeben w?rde.
* @param pLevel Level
* @return true: Nachricht w?rde ausgegeben
*/
private static synchronized boolean checkLevel(int pLevel)
{
String location = null;
Enumeration e1 = statMLogEntries.elements();
while (e1.hasMoreElements())
{
MLogEntry le = (MLogEntry) e1.nextElement();
if (location == null)
{
location = getCallerMethodName(3);
}
if (le.checkLevel(location, pLevel))
{
return true;
}
}
Vector v = getThreadEntry();
if (v != null)
{
Enumeration e2 = v.elements();
while (e2.hasMoreElements())
{
MLogEntry le = (MLogEntry) e2.nextElement();
if (location == null)
{
location = getCallerMethodName(3);
}
if (le.checkLevel(location, pLevel))
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------
/**
* StackTrace der Exception (des "Throwable") holen und an pStrg anf?gen,
* dabei wird jeweils ein "\n" zwischen den Zeilen eingef?gt (auch nach pStrg).
* @param pStrg zuvor stehender Text (kann auch null sein)
* @param t die Exception (das "Throwable")
* @return pStrg + StackTrace
*/
public static String convThrowable2String(String pStrg, Throwable t)
{
if (t != null)
{
/*
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(baos));
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
BufferedReader br = new BufferedReader(new InputStreamReader(bais));
try
{
String s;
while ((s = br.readLine()) != null)
{
if (pStrg == null)
{
pStrg = s;
}
else
{
pStrg += "\n" + s;
}
}
}
catch (IOException e)
{
}
*/
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
String s = sw.toString();
return pStrg + "\n" + s;
}
return pStrg;
}
//---------------------------------------------------------------------------
/**
* Text als "Error" (Fehler) ausgeben.
* Bsp.:
*
* Error (CallerMethodName): This is the given text.
*
* @param pStrg der auszugebende Text
*/
public static void err(String pStrg)
{
println(MLogEntry.ERROR, pStrg);
}
/**
* Text bei Bedarf erstellen und als "Error" (Fehler) ausgeben.
* @param mb Message-Builder
*/
public static void err(IMessageBuilder mb)
{
println(MLogEntry.ERROR, mb);
}
/**
* Text und StackTrace der Exception als "Error" (Fehler) ausgeben.
* Bsp.:
*
* Error (CallerMethodName): This is the given text.
* java.lang.NullPointerException
* at MyClass.mash(MyClass.java:9)
* at MyClass.crunch(MyClass.java:6)
* at MyClass.main(MyClass.java:3)
*
* @param pStrg der auszugebende Text
* @param t a throwable (exception or error), which should be shown
*/
public static void err(String pStrg, Throwable t)
{
// StackTrace der Exception holen und an pStrg anf?gen
// alt:
// pStrg = convThrowable2String(pStrg, t);
// println(MLogEntry.ERROR, pStrg);
// neu:
println(MLogEntry.ERROR, () -> convThrowable2String(pStrg, t));
}
/**
* Text bei Bedarf erstellen und als "Error" (Fehler) ausgeben.
* @param mb Message-Builder
* @param t a throwable (exception or error), which should be shown
*/
public static void err(IMessageBuilder mb, Throwable t)
{
println(MLogEntry.ERROR, () -> convThrowable2String(mb.getMessage(), t));
}
/**
* Pr?fen ob Fehler ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkerr()
{
return checkLevel(MLogEntry.ERROR);
}
//---------------------------------------------------------------------------
/**
* Text als "Warning" (Warnung) ausgeben.
* Bsp.:
* Warning (CallerMethodName): This is the given text.
* @param pStrg der auszugebende Text
*/
public static void wrn(String pStrg)
{
println(MLogEntry.WARNING, pStrg);
}
/**
* Text bei Bedarf erstellen und als "Warning" (Warnung) ausgeben.
* @param mb Message-Builder
*/
public static void wrn(IMessageBuilder mb)
{
println(MLogEntry.WARNING, mb);
}
/**
* Text und StackTrace der Exception als "Warning" (Warnung) ausgeben.
* Bsp.:
*
* Error (CallerMethodName): This is the given text.
* java.lang.NullPointerException
* at MyClass.mash(MyClass.java:9)
* at MyClass.crunch(MyClass.java:6)
* at MyClass.main(MyClass.java:3)
*
* @param pStrg der auszugebende Text
* @param t a throwable (exception or error), which should be shown
*/
public static void wrn(String pStrg, Throwable t)
{
// StackTrace der Exception holen und an pStrg anf?gen
// alt:
// pStrg = convThrowable2String(pStrg, t);
// println(MLogEntry.WARNING, pStrg);
// neu:
println(MLogEntry.WARNING, () -> convThrowable2String(pStrg, t));
}
/**
* Text bei Bedarf erstellen und als "Debug" (Debug-Nachricht) ausgeben.
* @param mb Message-Builder
* @param t a throwable (exception or error), which should be shown
*/
public static void wrn(IMessageBuilder mb, Throwable t)
{
println(MLogEntry.WARNING, () -> convThrowable2String(mb.getMessage(), t));
}
/**
* Pr?fen ob Warnungen ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkwrn()
{
return checkLevel(MLogEntry.WARNING);
}
//---------------------------------------------------------------------------
/**
* Text als "Info" (wichtige Information) ausgeben.
* Bsp.:
* Info (CallerMethodName): This is the given text.
* @param pStrg der auszugebende Text
*/
public static void inf(String pStrg)
{
println(MLogEntry.INFO, pStrg);
}
/**
* Text bei Bedarf erstellen und als "Info" (wichtige Information) ausgeben.
* @param mb Message-Builder
*/
public static void inf(IMessageBuilder mb)
{
println(MLogEntry.INFO, mb);
}
/**
* Text bei Bedarf erstellen und als "Info" (wichtige Information) ausgeben.
* @param pFormat Text bzw. Format-Angaben
* @param pObjects Parameter-Objekte (kann auch leer/null sein)
*/
public static void inf(String pFormat, Object... pObjects)
{
println(MLogEntry.INFO, new MMessageFormater(pFormat, pObjects));
}
/**
* Leerzeile als "Info" (wichtige Information) ausgeben.
*/
public static void inf()
{
println(MLogEntry.INFO, "");
}
/**
* Pr?fen ob Infos ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkinf()
{
return checkLevel(MLogEntry.INFO);
}
//---------------------------------------------------------------------------
/**
* Text als "Message" (allgemeine Nachricht) ausgeben.
* Bsp.:
* Message (CallerMethodName): This is the given text.
* @param pStrg der auszugebende Text
*/
public static void msg(String pStrg)
{
println(MLogEntry.MESSAGE, pStrg);
}
/**
* Text bei Bedarf erstellen und als "Message" (allgemeine Nachricht)
* ausgeben.
* @param mb Message-Builder
*/
public static void msg(IMessageBuilder mb)
{
println(MLogEntry.MESSAGE, mb);
}
/**
* Text bei Bedarf erstellen und als "Message" (allgemeine Nachricht) ausgeben.
* @param pFormat Text bzw. Format-Angaben
* @param pObjects Parameter-Objekte (kann auch leer/null sein)
*/
public static void msg(String pFormat, Object... pObjects)
{
println(MLogEntry.MESSAGE, new MMessageFormater(pFormat, pObjects));
}
/**
* Leerzeile als "Message" (allgemeine Nachricht) ausgeben.
*/
public static void msg()
{
println(MLogEntry.MESSAGE, "");
}
/**
* Pr?fen ob Nachrichten ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkmsg()
{
return checkLevel(MLogEntry.MESSAGE);
}
//---------------------------------------------------------------------------
/**
* Text als "Debug" (Debug-Nachricht) ausgeben.
* Bsp.:
* Debug (CallerMethodName): This is the given text.
* @param pStrg der auszugebende Text
*/
public static void deb(String pStrg)
{
println(MLogEntry.DEBUG, pStrg);
}
/**
* Text bei Bedarf erstellen und als "Debug" (Debug-Nachricht) ausgeben.
* @param mb Message-Builder
*/
public static void deb(IMessageBuilder mb)
{
println(MLogEntry.DEBUG, mb);
}
/**
* Text bei Bedarf erstellen und als "Debug" (Debug-Nachricht) ausgeben.
* @param pFormat Text bzw. Format-Angaben
* @param pObjects Parameter-Objekte (kann auch leer/null sein)
*/
public static void deb(String pFormat, Object... pObjects)
{
println(MLogEntry.DEBUG, new MMessageFormater(pFormat, pObjects));
}
/**
* Leerzeile als "Debug" (Debug-Nachricht) ausgeben.
*/
public static void deb()
{
println(MLogEntry.DEBUG, "");
}
/**
* Pr?fen ob Debug-Meldungen ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkdeb()
{
return checkLevel(MLogEntry.DEBUG);
}
//---------------------------------------------------------------------------
/**
* Text ausgeben.
* @param pStrg der auszugebende Text
* @see #println()
*/
public static void println(String pStrg)
{
println(MLogEntry.ALL, pStrg);
}
/**
* Text bei Bedarf erstellen und ausgeben.
* @param mb Message-Builder
*/
public static void println(IMessageBuilder mb)
{
println(MLogEntry.ALL, mb);
}
/**
* Text bei Bedarf erstellen und ausgeben.
* @param pFormat Text bzw. Format-Angaben
* @param pObjects Parameter-Objekte (kann auch leer/null sein)
*/
public static void println(String pFormat, Object... pObjects)
{
println(MLogEntry.ALL, new MMessageFormater(pFormat, pObjects));
}
/**
* Leerzeile ausgeben.
* @see #println(String)
*/
public static void println()
{
println(MLogEntry.ALL, "");
}
/**
* Text ausgeben (ohne Zeilenumbruch).
* @param pStrg der auszugebende Text
* @see #println()
*/
public static void print(String pStrg)
{
print(MLogEntry.ALL, pStrg);
}
/**
* Text bei Bedarf erstellen und ausgeben (ohne Zeilenumbruch).
* @param mb Message-Builder
*/
public static void print(IMessageBuilder mb)
{
print(MLogEntry.ALL, mb);
}
/**
* Text bei Bedarf erstellen und ausgeben (ohne Zeilenumbruch).
* @param pFormat Text bzw. Format-Angaben
* @param pObjects Parameter-Objekte (kann auch leer/null sein)
*/
public static void print(String pFormat, Object... pObjects)
{
print(MLogEntry.ALL, new MMessageFormater(pFormat, pObjects));
}
/**
* Pr?fen ob Text-Meldungen ausgegeben w?rden.
* @return true: ja
*/
public static boolean checkall()
{
return checkLevel(MLogEntry.ALL);
}
//===========================================================================
/**
* Ausf?hrung mit einfachem Logging.
* @param pFormat Format, z. B. "%msg"
* @param pMaxLevel max. Log-Level, z. B. MLogEntry.ALL
* @param r Runnable
* @param Exception-Typ
* @throws E Exception, die ggf. geworfen wird
*/
public static void runWithSimpleLogging(String pFormat, int pMaxLevel, ThrowingRunnable r) throws E
{
MLogEntry mle = new MLogEntry4Text();
mle.setMLogFormat(pFormat);
mle.setMaxLevel(pMaxLevel);
mle.setBreakAfterOutput(true);
MLogger.addMLogEntryTS(mle);
try
{
r.run();
}
finally
{
MLogger.removeMLogEntryTS(mle);
}
}
/**
* Ausf?hrung mit einfachem Logging (ALL).
* @param pFormat Format, z. B. "%msg"
* @param r Runnable
* @param Exception-Typ
* @throws E Exception, die ggf. geworfen wird
*/
public static void runWithSimpleLogging(String pFormat, ThrowingRunnable r) throws E
{
runWithSimpleLogging(pFormat, MLogEntry.ALL, r);
}
/**
* Ausf?hrung mit einfachem Logging ("%msg").
* @param r Runnable
* @param pMaxLevel max. Log-Level, z. B. MLogEntry.ALL
* @param Exception-Typ
* @throws E Exception, die ggf. geworfen wird
*/
public static void runWithSimpleLogging(int pMaxLevel, ThrowingRunnable r) throws E
{
runWithSimpleLogging("%msg", pMaxLevel, r);
}
/**
* Ausf?hrung mit einfachem Logging ("%msg", ALL).
* @param r Runnable
* @param Exception-Typ
* @throws E Exception, die ggf. geworfen wird
*/
public static void runWithSimpleLogging(ThrowingRunnable r) throws E
{
runWithSimpleLogging("%msg", MLogEntry.ALL, r);
}
//===========================================================================
/**
* Testweise warten.
* @param ms Dauer in ms
*/
private static void sleep(long ms)
{
try
{
Thread.sleep(ms);
}
catch (Exception e)
{
}
}
/**
* Main-Methode zum Test der Logger-Funktionen.
* @param args Dummy
*/
public static void main(String[] args)
{
// Falls ein Argument angegeben ist, handelt es sich um einen Dateinamen,
// f?r den dann ein MLogEntry4File erzeugt wird.
if ((args.length == 7) || (args.length == 9))
{
MLogEntry4File le = new MLogEntry4File();
le.setBaseDirectory(args[0].equals(".") ? null : args[0]);
le.setBaseFileName(args[1].equals(".") ? null : args[1]);
le.setDateTimeSeparator(args[2].equals(".") ? null : args[2]);
le.setDateTimeFormat(args[3].equals(".") ? null : args[3]);
le.setIndexSeparator(args[4].equals(".") ? null : args[4]);
le.setIndexFormat(args[5].equals(".") ? null : args[5]);
le.setExtension(args[6].equals(".") ? null : args[6]);
if (args.length == 9)
{
le.setMaxFileSize(args[7] == null ? -1 : new Long(args[7]).longValue());
le.setMaxFileCount(args[8] == null ? -1 : new Integer(args[8]).intValue());
}
MLogger.addMLogEntryTS(le);
}
else if ((args.length == 1) && (args[0].equals("-test")))
{
MLogger.deb("Start test...");
MLogger.getMLogEntry(0).setMaxLevel(MLogEntry.MESSAGE);
Thread t1 = new Thread()
{
@Override
public void run()
{
MLogEntry4File le = new MLogEntry4File();
MLogger.addMLogEntryTS(le);
int i = 0;
while (true)
{
MLogger.deb("This is Thread 1 message " + i);
// sleep(1000);
i++;
}
}
};
Thread t2 = new Thread()
{
@Override
public void run()
{
MLogEntry4File le = new MLogEntry4File();
MLogger.addMLogEntryTS(le);
int i = 0;
while (true)
{
MLogger.deb("This is Thread 2 message " + i);
// sleep(1000);
i++;
}
}
};
t1.start();
sleep(500);
t2.start();
try
{
System.in.read();
}
catch (Exception e)
{
}
t1.interrupt();
t2.interrupt();
}
else if (args.length != 0)
{
MLogger.err("Invalid syntax.");
MLogger.inf("Syntax: mlogger [ -test ] | [ basedir basefn datetimesep datetimefrm indexsep indexfrm ext [ maxfs maxfc ] ]");
MLogger.inf(" basedir = base directory for log files, without ending file separator ('.' = null)");
MLogger.inf(" basefn = base file name for log files, without path and without extension ('.' = null)");
MLogger.inf(" datetimesep = separator for log files between basefn and date/time ('.' = null)");
MLogger.inf(" datetimefrm = format of date and time, if used in log file name ('.' = null)");
MLogger.inf(" indexsep = separator prior index, if index is not 0 ('.' = null)");
MLogger.inf(" indexfrm = number format for index, if index is not 0 ('.' = null)");
MLogger.inf(" (\"0000\" meens 4 digits, \"####\" meens maximum 4 digits)");
MLogger.inf(" ext = extension for log files incl. '.' (only '.' = null)");
MLogger.inf(" maxfs = maximum file size (in bytes)");
MLogger.inf(" maxfc = maximum file count");
MLogger.inf("Examples:");
MLogger.inf(" mlogger d:\\temp\\sta\\sta2sta log _ yyyy-MM-dd-HH-mm _ 0000 .txt");
MLogger.inf(" mlogger d:\\temp\\sta\\sta2sta log _ yyyy-MM-dd-HH-mm _ 0000 .txt 256 3");
return;
}
MLogger.msg("MLogger Version " + VERSION + " Copyright (c) " + COPYRIGHT + " " + COMPANY);
MLogger.msg("ThreadName = " + Thread.currentThread().getName());
MLogger.err("This is an error message (with throwable) from MLogger.", new Exception("Hallo World!"));
MLogger.err("This is an error message (without throwable) from MLogger.");
MLogger.wrn("This is a warning message from MLogger.");
MLogger.inf("This is an information message from MLogger.");
MLogger.msg("This is a 'normal' message from MLogger.");
MLogger.deb("This is a debug message from MLogger.");
MLogger.println("This is a message from MLogger.");
MLogger.println();
/*
MLogger.println();
MLogger.println("LineTest...");
MLogger.print("Here comes a line input.\nInput>");
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
MLogger.println("Result: " + line);
}
catch (Exception ex)
{
MLogger.err("", ex);
}
MLogger.print("Here comes a character input.\nInput>");
try
{
int i = System.in.read();
char ch = (char) i;
MLogger.println("Result: " + ch);
}
catch (Exception ex)
{
MLogger.err("", ex);
}
MLogger.println("LineTest: Ok.");
MLogger.println();
*/
}
//===========================================================================
/**
* Dummy-Constructor.
*/
protected MLogger()
{
}
}