// ############################################################################################# // Public interface // ############################################################################################# /** ******************************************************************************************** * Constructor for domain. * @param parent The parent domain. For root domains, this is null. * @param name The name of the domain. For root domains, this is null. **********************************************************************************************/ public Domain( Domain parent, AString name ) { // store parameters this.Name= name; this.Parent= parent; // create fields SubDomains= new List<Domain>(3); Data= new List<LoggerData>( parent == null ? 2 : parent.Data.Count ); // if we have a parent, we inherit all logger's verbosities if( parent != null ) foreach( LoggerData ldParent in parent.Data ) { LoggerData ld= new LoggerData( ldParent.Logger ); ld.LoggerVerbosity= ldParent.LoggerVerbosity; ld.Priority= ldParent.Priority; Data.Add( ld ); } FullPath= new AString(); Domain dom= this; do { if ( dom != this || dom.Parent == null ) FullPath.InsertAt( "/", 0 ); FullPath.InsertAt( dom.Name, 0 ); dom= dom.Parent; } while( dom != null ); }
// ############################################################################################# // Abstract methods (introduced) // ############################################################################################# /** **************************************************************************************** * This is the central method that derived logger classes have to implement to log a * message. When it is invoked the <em>Log Domains' Verbosity</em> was already checked against * parameter \p verbosity. The only action to take is to perform the log itself.<br> * Parameter \p logables contains at least one object, the one provided with the log * statement. Other objects that might be included in the list, are <em>Prefix Objects</em> * corresponding to the \p scope. * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. * @param logables The list of objects to log. * @param scope Information about the scope of the <em>Log Statement</em>. ******************************************************************************************/ abstract public void Log( Domain domain, Verbosity verbosity, List<Object> logables, ScopeInfo scope );
/** ******************************************************************************************** * * The implementation of the abstract method of parent class TextLogger. Logs messages to the * application console and/or the VStudio output window. * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param msg The log message. * @param scope Information about the scope of the <em>Log Statement</em>.. * @param lineNumber The line number of a multi-line message, starting with 0. For * single line messages this is -1. **********************************************************************************************/ override protected void logText( Domain domain, Verbosity verbosity, AString msg, ScopeInfo scope, int lineNumber) { // get actual console attributes ConsoleColor actualFGCol= Console.ForegroundColor; ConsoleColor actualBGCol= Console.BackgroundColor; // loop over message, print the parts between the escape sequences Tokenizer msgParts= new Tokenizer( msg, '\x1B' ); Substring actual= msgParts.Actual; Substring rest= msgParts.Rest; int column= 0; for(;;) { if ( msgParts.Next(Whitespaces.Keep).IsNotEmpty() ) { #if !(ALOX_WP71 || ALOX_WP8) Console.Write( msg.Buffer(), actual.Start, actual.Length() ); #else Console.Write( msg.ToString( 0, actual.Start, actual.Length() ); #endif column+= actual.Length(); } // end of loop? if ( !msgParts.HasNext() ) break; // found an ESC sequence char c= rest.Consume(); // Colors bool isForeGround= true; if( c == 'C' || c == 'c' ) { isForeGround= c== 'c'; c= rest.Consume(); int colNo= c - '0'; ALIB.ASSERT_WARNING( colNo >=0 && colNo <=9, "Unknown ESC-c code" ); // set color if( colNo >= 0 && colNo <= 8 || colNo == 8) { ConsoleColor[] cols= (isForeGround ? !IsBackgroundLight : IsBackgroundLight ) ? lightColors : darkColors; if ( isForeGround ) Console.ForegroundColor= cols[ colNo ]; else Console.BackgroundColor= cols[ colNo ]; } else if ( colNo == 9 ) { if ( isForeGround ) Console.ForegroundColor= actualFGCol; else Console.BackgroundColor= actualBGCol; } else { ALIB.WARNING( "Unknown ESC- code" ); } } // Styles else if ( c == 's' ) { // bold/italics style not supported in Windows console // reset all if ( rest.Consume() == 'a' ) { Console.ForegroundColor= actualFGCol; Console.BackgroundColor= actualBGCol; } } // auto tab / end of meta else if ( c == 't' || c == 'A') { bool endOfMeta= c == 'A'; c= rest.Consume(); int extraSpace= c >= '0' && c <= '9' ? (int) ( c - '0' ) : (int) ( c - 'A' ) + 10; int tabStop= AutoSizes.Next( column, extraSpace ); Util.WriteSpaces( Console.Out, tabStop - column ); column= tabStop; if ( endOfMeta ) { switch ( verbosity ) { case Verbosity.Verbose: Console.ForegroundColor= MsgColorVerbose; break; case Verbosity.Info: Console.ForegroundColor= MsgColorInfo; break; case Verbosity.Warning: Console.ForegroundColor= MsgColorWarning; break; case Verbosity.Error: Console.ForegroundColor= MsgColorError; break; default: break; } } } // Link (we just colorize links here) else if ( c == 'l' ) { if ( rest.Consume() == 'S' ) Console.ForegroundColor= IsBackgroundLight ? ConsoleColor.DarkBlue : ConsoleColor.Blue; else Console.ForegroundColor= actualFGCol; } else { ALIB.WARNING( "Unknown ESC code" ); } } // write loop // reset colors Console.ForegroundColor= actualFGCol; Console.BackgroundColor= actualBGCol; // write NL #if !(ALOX_WP71 || ALOX_WP8) Console.WriteLine(); #else Console.WriteLine(); #endif }
// ############################################################################################# // Abstract methods (introduced) // ############################################################################################# /** **************************************************************************************** * This abstract method introduced by this class "replaces" the the abstract method #Log * of parent class Logger which this class implements. In other words, descendants of this * class need to overwrite this method instead of \b %Do. This class %TextLogger is * responsible for generating meta information, doing text replacements, handle multi-line * messages, etc. and provides the textual representation of the whole log contents * to descendants using this method. * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param msg The log message. * @param scope Information about the scope of the <em>Log Statement</em>.. * @param lineNumber The line number of a multi-line message, starting with 0. * For single line messages this is -1. ******************************************************************************************/ abstract protected void logText( Domain domain, Verbosity verbosity, AString msg, ScopeInfo scope, int lineNumber);
// ############################################################################################# // Abstract method implementations // ############################################################################################# /** **************************************************************************************** * This is the implementation of the abstract method inherited from class Logger * that executes a log.<br> * This class implements this method and but exposes the new abstract method #logText. * This mechanism allows this class to do various things that are standard to Loggers * of type TextLogger. For example, meta information of the log invocation is formatted and * string replacements are performed. This way, descendants of this class will consume * a ready to use log buffer with all meta information and contents of all objects to be * included and their primary obligation is to copy the content into a corresponding * output stream. * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param logables The list of objects to log. * @param scope Information about the scope of the <em>Log Statement</em>.. ******************************************************************************************/ override public void Log( Domain domain, Verbosity verbosity, List<Object> logables, ScopeInfo scope ) { // clear Buffer and reset utility members logBuf.Clear(); AutoSizes.Start(); // << meta info << ESC::EOMETA int qtyESCTabsWritten= MetaInfo.Write( this, logBuf, domain, verbosity, scope ); logBuf._( ESC.EOMETA ); // convert msg object into an AString representation msgBuf._(); foreach( Object logable in logables ) { int i; for( i= ObjectConverters.Count - 1; i >= 0 ; i-- ) if ( ObjectConverters[i].ConvertObject( logable, msgBuf ) ) break; if ( i == -1 ) { AString msg= new AString( FmtUnknownObject ); msg.SearchAndReplace( "%", "" + logable.GetType().ToString() ); msgBuf._NC( msg ); } } // replace strings in message for ( int i= 0; i < replacements.Count ; i+=2 ) msgBuf.SearchAndReplace( replacements[i], replacements[i + 1] ); // check for empty messages if ( msgBuf.IsEmpty() ) { // log empty msg and quit if (usesStdStreams) ALIB.StdOutputStreamsLock.Acquire(); logText( domain, verbosity, logBuf, scope, -1 ); if (usesStdStreams) ALIB.StdOutputStreamsLock.Release(); return; } // single line output if ( MultiLineMsgMode == 0 ) { // replace line separators int cntReplacements=0; if ( MultiLineDelimiter != null ) cntReplacements+=msgBuf.SearchAndReplace( MultiLineDelimiter, MultiLineDelimiterRepl ); else { cntReplacements+=msgBuf.SearchAndReplace( "\r\n", MultiLineDelimiterRepl ); cntReplacements+=msgBuf.SearchAndReplace( "\r", MultiLineDelimiterRepl ); cntReplacements+=msgBuf.SearchAndReplace( "\n", MultiLineDelimiterRepl ); } // append msg to logBuf if ( cntReplacements == 0 ) { logBuf._( msgBuf ); } else { logBuf._( FmtMultiLinePrefix ); logBuf._( msgBuf ); logBuf._( FmtMultiLineSuffix ); } // now do the logging by calling our derived classes' logText logText( domain, verbosity, logBuf, scope, -1 ); // stop here return; } // multiple line output int qtyTabStops= AutoSizes.ActualIndex; int actStart=0; int lineNo=0; int lbLenBeforeMsgPart= logBuf.Length(); // loop over lines in msg while ( actStart < msgBuf.Length() ) { // find next end int delimLen; int actEnd; // no delimiter given: search "\r\n", then '\r', then '\n' if ( String.IsNullOrEmpty( MultiLineDelimiter ) ) { delimLen= 1; actEnd= msgBuf.IndexOf( '\n', actStart ); if( actEnd > actStart ) { if( msgBuf.CharAt(actEnd - 1) == '\r' ) { actEnd--; delimLen= 2; } } } else { delimLen=MultiLineDelimiter.Length; actEnd=msgBuf.IndexOf( MultiLineDelimiter, actStart ); } // not found a delimiter? - log the rest if ( actEnd < 0 ) { // single line? if ( lineNo == 0 ) { // append msg to logBuf logBuf._( msgBuf ); // now do the logging by calling our derived classes' logText if (usesStdStreams) ALIB.StdOutputStreamsLock.Acquire(); logText( domain, verbosity, logBuf, scope, -1 ); if (usesStdStreams) ALIB.StdOutputStreamsLock.Release(); // stop here return; } // store actual end actEnd= msgBuf.Length(); } // found a delimiter // signal start of multi line log if ( lineNo == 0 ) { if (usesStdStreams) ALIB.StdOutputStreamsLock.Acquire(); notifyMultiLineOp( Phase.Begin ); } // in mode 3, 4, meta info is deleted if ( lineNo == 0 && ( MultiLineMsgMode == 3 || MultiLineMsgMode == 4 ) ) { // log headline in mode 3 if ( MultiLineMsgMode == 3 ) { logBuf._( FmtMultiLineMsgHeadline ); AutoSizes.ActualIndex= qtyTabStops; logText( domain, verbosity, logBuf, scope, 0 ); } // remember zero as offset lbLenBeforeMsgPart=0; } // blank out meta information? (do this exactly in 2nd line once) if ( MultiLineMsgMode == 2 ) { if (lineNo != 0 ) { logBuf.Clear()._( ESC.EOMETA ); AutoSizes.ActualIndex= qtyTabStops + qtyESCTabsWritten; } } // reset logBuf length to marked position else { logBuf.SetLength_NC( lbLenBeforeMsgPart ); AutoSizes.ActualIndex= qtyTabStops; } // append message logBuf._( FmtMultiLinePrefix ); logBuf._NC( msgBuf, actStart, actEnd - actStart ); logBuf._( FmtMultiLineSuffix ); logText( domain, verbosity, logBuf, scope, lineNo ); // next actStart= actEnd + delimLen; lineNo++; } // signal end of multi line log if ( lineNo > 0 ) { notifyMultiLineOp( Phase.End ); if (usesStdStreams) ALIB.StdOutputStreamsLock.Release(); } }
/** ******************************************************************************************** * Processes the next command found in the format string, by writing formatted information * into the given buffer. * The given Substring holds the next command. When method returns, the command is cut * from the front. * * @param logger The logger that we are embedded in. * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param scope Information about the scope of the <em>Log Statement</em>.. * @param dest The buffer to write meta information into. * @param variable The variable to read (may have more characters appended) * * @return The number of tab sequences that were written (by adding ESC::TAB to the buffer). **********************************************************************************************/ protected virtual int processVariable( TextLogger logger, Domain domain, Verbosity verbosity, ScopeInfo scope, AString dest, Substring variable ) { // process commands char c2; switch ( variable.Consume() ) { // scope info case 'S': { // read sub command AString val; switch( c2= variable.Consume() ) { case 'P': // SP: full path { int length; String path= scope.GetFullPath( out length ); if ( length > 0 ) { dest._( path, 0, length ); return 0; } val= NoSourceFileInfo; } break; case 'p': // Sp: trimmed path { val= scope.GetTrimmedPath(); if ( val.IsEmpty() ) val= NoSourceFileInfo; } break; case 'F': // file name { val= scope.GetFileName(); if ( val.IsEmpty() ) val= NoSourceFileInfo; } break; case 'f': // file name without extension { val= scope.GetFileNameWithoutExtension(); if ( val.IsEmpty() ) val= NoSourceFileInfo; } break; case 'M': // method name { String method= scope.GetMethod(); if ( method.Length == 0 ) dest._( NoMethodInfo ); else dest._( method ); return 0; } case 'L': // line number { dest._( scope.GetLineNumber() ); return 0; } default: { if( !warnedOnce ) { warnedOnce= true; ALIB.WARNING( "Unknown format variable '%S" + c2 + "\' (only one warning)" ); } dest._( "%ERROR" ); return 0; } } dest._( val ); return 0; } // %Tx: Time case 'T': { // read sub command c2= variable.Consume(); // %TD: Date if ( c2 == 'D' ) { // get time stamp as DateTime once if ( callerDateTime == null ) callerDateTime= scope.GetTimeStamp().InDotNetDateTime(); // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!) // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow. if ( DateFormat.Equals( "yyyy-MM-dd" ) ) { dest ._( callerDateTime.Value.Year, 4 )._( '-' ) ._( callerDateTime.Value.Month, 2 )._( '-' ) ._( callerDateTime.Value.Day, 2 ); } // support user defined standards else { // detect changes of format string since last log if ( detectDateFormatChanges != DateFormat ) { detectDateFormatChanges= DateFormat; dateFormatString= "{0:" + DateFormat + "}"; } // get date string from system and append to log buffer formatSB.Clear(); formatSB.AppendFormat( CultureInfo.InvariantCulture, dateFormatString, callerDateTime ); dest._( formatSB ); } } // %TT: Time of Day else if ( c2 == 'T' ) { // get time stamp as DateTime once if ( callerDateTime == null ) callerDateTime= scope.GetTimeStamp().InDotNetDateTime(); // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!) // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow. if ( TimeOfDayFormat.Equals( "HH:mm:ss" ) ) { dest ._( callerDateTime.Value.Hour, 2 )._( ':' ) ._( callerDateTime.Value.Minute, 2 )._( ':' ) ._( callerDateTime.Value.Second, 2 ); } // support user defined standards else { // detect changes of format string since last log if ( detectTimeOfDayFormatChanges != TimeOfDayFormat ) { detectTimeOfDayFormatChanges= TimeOfDayFormat; timeOfDayFormatString= "{0:" + TimeOfDayFormat + "}"; } // get time string from system and append to log buffer formatSB.Clear(); formatSB.AppendFormat( CultureInfo.InvariantCulture, timeOfDayFormatString, callerDateTime); dest ._( formatSB ); } } // %TC: Time elapsed since created else if ( c2 == 'C' ) { elapsedTime.Set( scope.GetTimeStamp() ); elapsedTime.Sub( logger.TimeOfCreation ); if( MaxElapsedTime.Raw() < elapsedTime.Raw() ) MaxElapsedTime.Set( elapsedTime ); long maxElapsedSecs= MaxElapsedTime.InSeconds(); TimeSpan elapsed= new TimeSpan( elapsedTime.Raw() ); if ( maxElapsedSecs >= 60*60*24 ) dest._( elapsed.Days )._NC( TimeElapsedDays ); if ( maxElapsedSecs >= 60*60 ) dest._( elapsed.Hours , maxElapsedSecs >= 60*60*10 ? 2 : 1 )._( ':' ); if ( maxElapsedSecs >= 60 ) dest._( elapsed.Minutes, maxElapsedSecs >= 10*60 ? 2 : 1 )._( ':' ); dest._( elapsed.Seconds, maxElapsedSecs > 9 ? 2 : 1 )._NC( '.' ); dest._( elapsed.Milliseconds, 3 ); } // %TL: Time elapsed since last log call else if ( c2 == 'L' ) writeTimeDiff( dest, scope.GetTimeStamp().Since(logger.TimeOfLastLog).InNanos() ); else { if( !warnedOnce ) { warnedOnce= true; ALIB.WARNING( "Unknown format variable '%T" + c2 + "\' (only one warning)" ); } dest._( "%ERROR" ); } return 0; } // thread name / ID case 't': { c2= variable.Consume(); if ( c2 == 'N' ) { dest.Field() ._( scope.GetThreadName() ) .Field( logger.AutoSizes.Next( scope.GetThreadName().Length(), 0 ), Alignment.Center ); } else if ( c2 == 'I' ) { tmpAString._()._( scope.GetThreadID() ); dest.Field() ._( tmpAString ) .Field( logger.AutoSizes.Next( tmpAString .Length(), 0 ), Alignment.Center ); } else { if( !warnedOnce ) { warnedOnce= true; ALIB.WARNING( "Unknown format variable '%t" + c2 + "\' (only one warning)" ); } dest._( "%ERROR" ); } return 0; } case 'L': { c2= variable.Consume(); if ( c2 == 'G' ) dest._NC( logger.GetName() ); else if ( c2 == 'X' ) dest._NC( scope.GetLoxName() ); else { if( !warnedOnce ) { warnedOnce= true; ALIB.WARNING( "Unknown format variable '%L" + c2 + "\' (only one warning)" ); } dest._( "%ERROR" ); return 0; } return 0; } case 'P': { dest._NC( Util.GetProcessName() ); return 0; } case 'V': dest._ ( verbosity == Verbosity.Error ? VerbosityError : verbosity == Verbosity.Warning ? VerbosityWarning : verbosity == Verbosity.Info ? VerbosityInfo : VerbosityVerbose ); return 0; case 'D': { dest.Field()._( domain.FullPath ).Field( logger.AutoSizes.Next( domain.FullPath.Length(), 0 ), Alignment.Left ); return 0; } case '#': dest._( logger.CntLogs, LogNumberMinDigits ); return 0; // A: Auto tab case 'A': { // read extra space from format string int oldStart= variable.Start; int extraSpace; variable.ConsumeInteger( out extraSpace ); if ( oldStart == variable.Start ) extraSpace= 1; // insert ESC code to jump to next tab level extraSpace= Math.Min( extraSpace, 10 + ('Z'-'A') ); char escNo= extraSpace < 10 ? (char) ( '0' + extraSpace ) : (char) ( 'A' + extraSpace ); dest._( "\x1Bt" )._( escNo ); return 1; } default: { if( !warnedOnce ) { warnedOnce= true; ALIB.WARNING( "Unknown format variable \'" + variable.Buf[variable.Start - 1] + "\'" ); } dest._( "%ERROR" ); } return 0; } }
/** ******************************************************************************************** * Internal method used by State() to recursively (DFS) log <em>Prefix Logables</em> bound to * <em>Log Domains</em> * * @param domain The actual domain to check. * @param indentSpaces The number of spaces to write before each line. * @param target The target string. **********************************************************************************************/ protected void logStateCollectPrefixes( Domain domain, int indentSpaces, AString target ) { foreach ( Domain.PL pfl in domain.PrefixLogables ) { target.InsertChars( ' ', indentSpaces ); target._('\"'); int idx= target.Length(); target._NC( pfl.Logable ); ESC.ReplaceToReadable( target, idx ); target._('\"'); if ( pfl.IncludeOtherPLs == Inclusion.Exclude ) target._NC( " (Excl.)" ); target.Tab( 25 ); target._NC( "<domain> [" )._NC( domain.FullPath )._(']').NewLine(); } foreach( Domain it in domain.SubDomains ) logStateCollectPrefixes( it, indentSpaces, target ); }
/** **************************************************************************************** * Reads a prefix string from the ALib configuration system. * This internal method is used when a new domain is created, * * @param dom The domain to set the verbosity for. ******************************************************************************************/ protected void getDomainPrefixFromConfig( Domain dom ) { Variable variable= new Variable( ALox.PREFIXES, GetName() ); if( 0 == variable.Load() ) return; Tokenizer prefixTok= new Tokenizer(); Tokenizer prefixTokInner= new Tokenizer(); Substring domainStr= new Substring(); AString domainStrBuf= new AString(); Substring prefixStr= new Substring(); for( int varNo= 0; varNo< variable.Size(); varNo++ ) { prefixTok.Set( variable.GetString( varNo ), '=' ); domainStr.Set( prefixTok.Next() ); if ( domainStr.StartsWith( "INTERNAL_DOMAINS", DomainSensitivity ) ) { domainStrBuf._()._( domainStr.Buf, domainStr.Start + 16, domainStr.Length() -16 ); while ( domainStrBuf.CharAtStart() == '/' ) domainStrBuf.DeleteStart( 1 ); domainStrBuf.InsertAt( ALox.InternalDomains, 0 ); domainStr.Set( domainStrBuf ); } prefixTokInner.Set( prefixTok.Next(), ',' ); prefixStr.Set( prefixTokInner.Next() ); if ( prefixStr.IsEmpty() ) continue; if ( prefixStr.Consume( '\"' ) ) prefixStr.ConsumeFromEnd( '\"' ); Inclusion otherPLs= Inclusion.Include; prefixTokInner.Next(); if ( prefixTokInner.Actual.IsNotEmpty() ) otherPLs= ALIB.ReadInclusion( prefixTokInner.Actual ); int searchMode= 0; if ( domainStr.Consume ( '*' ) ) searchMode+= 2; if ( domainStr.ConsumeFromEnd( '*' ) ) searchMode+= 1; if( ( searchMode == 0 && dom.FullPath.Equals ( domainStr, DomainSensitivity ) ) || ( searchMode == 1 && dom.FullPath.StartsWith ( domainStr, DomainSensitivity ) ) || ( searchMode == 2 && dom.FullPath.EndsWith ( domainStr, DomainSensitivity ) ) || ( searchMode == 3 && dom.FullPath.IndexOf ( domainStr, 0, DomainSensitivity ) >=0 ) ) { dom.PrefixLogables.Add( new Domain.PL( new AString( prefixStr ), otherPLs ) ); // log info on this intMsg._()._NC( "String \"" )._NC( prefixStr )._NC ( "\" added as prefix logable for domain \'" ) ._NC( dom.FullPath ) ._NC( "\'. (Retrieved from variable" ) ._NC( variable.Fullname )._( ".)" ); logInternal( Verbosity.Info, "PFX", intMsg ); } } }
/** **************************************************************************************** * Reads the verbosity for the given logger and domain from the ALib configuration system. * This internal method is used when a new logger is added. * Walks recursively for all existing domains. * * @param logger The logger to set the verbosity for. * @param dom The domain to set the verbosity for. * @param cfgResult The result of the search for the variable to set verbosities from. ******************************************************************************************/ protected void getAllVerbosities ( Logger logger, Domain dom, Variable cfgResult ) { // get verbosity for us getVerbosityFromConfig( logger, dom, cfgResult ); // loop over all sub domains (recursion) foreach ( Domain subDomain in dom.SubDomains ) getAllVerbosities( logger, subDomain, cfgResult ); }
/** **************************************************************************************** * This method is looping over the \e Loggers, checking their verbosity against the given * one, and, if they match, invoke the log method of the \e Logger. * With the first logger identified to be active, the <em>Prefix Objects</em> get * collected from the scope store. * @param dom The domain to log on * @param verbosity The verbosity. * @param logable The object to log. * @param prefixes Denotes if prefixes should be included or not. ******************************************************************************************/ protected void log( Domain dom, Verbosity verbosity, Object logable, Inclusion prefixes ) { // OK, this is a little crude, but the simplest solution: As class ScopeStore sees // null objects as nothing and won't return them in a walk, we replace null by // an object (we choose the store itself) and fix this in the loop back to null if ( logable == null ) logable= scopePrefixes; dom.CntLogCalls++; logObjects.Clear(); for ( int i= 0; i < dom.CountLoggers() ; i++ ) if( dom.IsActive( i, verbosity ) ) { // lazily collect objects once if ( logObjects.Count == 0 ) { scopePrefixes.InitWalk( Scope.ThreadInner, logable ); Object next; while( (next= scopePrefixes.Walk() ) != null ) { if ( prefixes == Inclusion.Include || next == logable) logObjects.Insert( 0, next != scopePrefixes ? next : null ); // was this the actual? then insert domain-associated logables now bool excludeOthers= false; if( next == logable ) { int qtyThreadInner= logObjects.Count -1; Domain pflDom= dom; while ( pflDom != null ) { for( int ii= pflDom.PrefixLogables.Count -1 ; ii >= 0 ; ii-- ) { Domain.PL pl= pflDom.PrefixLogables[ii]; logObjects.Insert( 0, pl.Logable ); if ( pl.IncludeOtherPLs == Inclusion.Exclude ) { excludeOthers= true; break; } } pflDom= excludeOthers ? null : pflDom.Parent; } // found a stoppable one? remove those from thread inner and break if (excludeOthers) { for ( int ii= 0; ii < qtyThreadInner ; ii++ ) logObjects.RemoveAt( logObjects.Count - 1 ); break; } } } } Logger logger= dom.GetLogger(i); logger.Acquire(); logger.CntLogs++; logger.Log( dom, verbosity, logObjects, scopeInfo ); logger.TimeOfLastLog.Set(); logger.Release(); } }
/** **************************************************************************************** * Reads the verbosity for the given logger and domain from the ALib configuration system. * This internal method is used in two occasions: * - when a new logger is added: recursively for all existing domains (\p configStr is * given) * - when a new domain is created on the fly(\p configStr is not given) * * @param logger The logger to set the verbosity for. * @param dom The domain to set the verbosity for. * @param variable The (already read) variable to set verbosities from. ******************************************************************************************/ protected void getVerbosityFromConfig( Logger logger, Domain dom, Variable variable ) { // get logger number. It may happen that the logger is not existent in this domain tree. int loggerNo= dom.GetLoggerNo( logger ) ; if ( loggerNo < 0 ) return; Tokenizer verbosityTknzr= new Tokenizer(); Substring domainStr= new Substring(); AString domainStrBuf= new AString(); for( int varNo= 0; varNo< variable.Size(); varNo++ ) { verbosityTknzr.Set( variable.GetString( varNo ), '=' ); domainStr.Set( verbosityTknzr.Next() ); if ( domainStr.StartsWith( "INTERNAL_DOMAINS", DomainSensitivity ) ) { domainStrBuf._()._( domainStr.Buf, domainStr.Start + 16, domainStr.Length() -16 ); while ( domainStrBuf.CharAtStart() == '/' ) domainStrBuf.DeleteStart( 1 ); domainStrBuf.InsertAt( ALox.InternalDomains, 0 ); domainStr.Set( domainStrBuf ); } Substring verbosityStr= verbosityTknzr.Next(); if ( verbosityStr.IsEmpty() ) continue; int searchMode= 0; if ( domainStr.Consume ( '*' ) ) searchMode+= 2; if ( domainStr.ConsumeFromEnd( '*' ) ) searchMode+= 1; if( ( searchMode == 0 && dom.FullPath.Equals ( domainStr, DomainSensitivity ) ) || ( searchMode == 1 && dom.FullPath.StartsWith ( domainStr, DomainSensitivity ) ) || ( searchMode == 2 && dom.FullPath.EndsWith ( domainStr, DomainSensitivity ) ) || ( searchMode == 3 && dom.FullPath.IndexOf ( domainStr, 0, DomainSensitivity ) >=0 ) ) { Verbosity verbosity= ALox.ReadVerbosity( verbosityStr); dom.SetVerbosity( loggerNo, verbosity, variable.Priority ); // log info on this intMsg._()._NC( "Logger \"" )._NC( logger.GetName() ) ._NC( "\":" ).Tab(11 + maxLoggerNameLength) ._( '\'' )._NC( dom.FullPath ) ._( '\'' ).InsertChars(' ', maxDomainPathLength - dom.FullPath.Length() + 1 ) ._("= Verbosity." ); ALox.ToString( verbosity, dom.GetPriority(loggerNo), intMsg ).TrimEnd() ._('.'); logInternal( Verbosity.Info, "LGR", intMsg ); } } }
/** **************************************************************************************** * Invokes \b Find on the given domain and logs internal message when the domain was * not known before. * * @param domainSystem The domain system. Either the standard or the internal one. * @param domainPath The domain path. * @return The resulting \ref cs::aworx::lox::core::Domain "Domain". ******************************************************************************************/ Domain findDomain( Domain domainSystem, AString domainPath ) { AString substPath= domainSystem == domains ? tmpSubstitutionPath : tmpSubstitutionPathInternalDomains; int maxSubstitutions= 10; for(;;) { // loop for creating domains, one by one Domain dom= null; for(;;) { bool wasCreated= false; dom= domainSystem.Find( domainPath, DomainSensitivity, 1, ref wasCreated ); if ( wasCreated ) { // get maximum domain path length (for nicer State output only...) if ( maxDomainPathLength < dom.FullPath.Length() ) maxDomainPathLength= dom.FullPath.Length(); // log info on new domain intMsg._()._('\'')._NC( dom.FullPath )._NC("' registered."); logInternal( Verbosity.Info, "DMN", intMsg ); } // read domain from Config if ( !dom.ConfigurationRead ) { dom.ConfigurationRead= true; Variable variable= new Variable(); for ( int i= 0; i < dom.CountLoggers(); ++i ) { Logger logger= dom.GetLogger(i); if ( 0 != variable.Define( ALox.VERBOSITY, GetName(), logger.GetName() ).Load() ) getVerbosityFromConfig( logger, dom, variable ); } getDomainPrefixFromConfig( dom ); } // log inherited setting for each logger if ( wasCreated ) { if ( domainSystem.CountLoggers() == 0 ) logInternal( Verbosity.Verbose, "DMN", intMsg._()._NC("No loggers set, yet.") ); else for ( int i= 0; i < domainSystem.CountLoggers(); i++ ) { intMsg._()._(" \"")._( dom.GetLogger(i).GetName() )._NC("\":"); intMsg.InsertChars( ' ', maxLoggerNameLength + 6 - intMsg.Length() ); intMsg._NC( dom.FullPath ) ._NC( " = " ); ALox.ToString( dom.GetVerbosity( i ), dom.GetPriority( i), intMsg ); logInternal( Verbosity.Verbose, "DMN", intMsg ); } } else break; } // apply domain substitutions if( domainSubstitutions.Count > 0 ) { substPath.Clear(); while( maxSubstitutions-- > 0 ) { // loop over rules bool substituted= false; foreach( DomainSubstitutionRule rule in domainSubstitutions ) { switch( rule.type ) { case DomainSubstitutionRule.Type.StartsWith: if( substPath.IsEmpty() ) { if ( dom.FullPath.StartsWith( rule.Search, DomainSensitivity ) ) { substPath._( rule.Replacement )._( dom.FullPath, rule.Search.Length() ); substituted= true; continue; //next rule } } else { if ( substPath.StartsWith( rule.Search, DomainSensitivity ) ) { substPath.ReplaceSubstring( rule.Replacement, 0, rule.Search.Length() ); substituted= true; continue; //next rule } } break; case DomainSubstitutionRule.Type.EndsWith: if( substPath.IsEmpty() ) { if ( dom.FullPath.EndsWith( rule.Search, DomainSensitivity ) ) { substPath._( dom.FullPath, 0, dom.FullPath.Length() - rule.Search.Length() )._( rule.Replacement ); substituted= true; continue; } } else { if ( substPath.EndsWith( rule.Search, DomainSensitivity ) ) { substPath.DeleteEnd( rule.Search.Length() )._( rule.Replacement ); substituted= true; continue; } } break; case DomainSubstitutionRule.Type.Substring: if( substPath.IsEmpty() ) { int idx= dom.FullPath.IndexOf( rule.Search, 0, DomainSensitivity ); if ( idx >= 0 ) { substPath._( dom.FullPath, 0, idx )._( rule.Replacement)._( dom.FullPath, idx + rule.Search.Length() ); substituted= true; continue; //next rule } } else { int idx= substPath.IndexOf( rule.Search, 0, DomainSensitivity ); if ( idx >= 0 ) { substPath.ReplaceSubstring( rule.Replacement, idx, rule.Search.Length() ); substituted= true; continue; //next rule } } break; case DomainSubstitutionRule.Type.Exact: { if( substPath.IsEmpty() ) { if ( dom.FullPath.Equals( rule.Search ) ) { substPath._( rule.Replacement); substituted= true; continue; //next rule } } else { if ( substPath.Equals( rule.Search) ) { substPath._()._( rule.Replacement ); substituted= true; continue; //next rule } } } break; } // switch rule type }//rules loop // stop if non was found if( !substituted ) break; } // too many substitutions? if ( maxSubstitutions <= 0 && !oneTimeWarningCircularDS ) { oneTimeWarningCircularDS= true; intMsg._()._( "The Limit of 10 domain substitutions was reached. Circular substitution assumed!" + " (This error is only reported once!)" ); logInternal( Verbosity.Error, "DMN", intMsg ); } // anything substituted? if( substPath.Length() > 0 ) { domainPath= substPath; continue; } } return dom; } }
// ############################################################################################# // Constructors // ############################################################################################# /** **************************************************************************************** * Constructs a new, empty Lox with the given \p name. * The name is immutable and all \b %Lox objects registered with ALox must be unique. * The name \c "Log" is reserved for the internal default singleton used for debug-logging. * In addition, name \c "GLOBAL" is not allowed. * * If parameter \p register is \c true (the default), static method * \ref cs::aworx::lox::ALox::Register "ALox.Register" is invoked and the object will be * retrievable with static method * \ref cs::aworx::lox::ALox::Get "ALox.Get". In some situations, such 'registration' * may not be wanted. * @param name The name of the Lox. Will be converted to upper case. * @param doRegister If \c true, this object is registered with static class * \ref cs::aworx::lox::ALox "ALox". * Optional and defaults to \c true. ******************************************************************************************/ public Lox( String name, bool doRegister = true ) : base() { // set recursion warning of log buffer lock to 1. Warnings are logged if recursively // acquired more than once #if ALOX_DBG_LOG || ALOX_REL_LOG logBufLock.RecursionWarningThreshold= 1; scopeInfo= new ScopeInfo( name, threadDictionary ); scopeDomains= new ScopeStore<AString >( scopeInfo, false ); scopePrefixes= new ScopeStore<Object >( scopeInfo, false ); scopeLogData= new ScopeStore<Dictionary<AString, LogData>>( scopeInfo, true ); scopeLogOnce= new ScopeStore<Dictionary<AString, int[]> >( scopeInfo, true ); // create domain trees domains = new Domain( null, new AString( "") ); internalDomains = new Domain( null, new AString( ALox.InternalDomains, 0, ALox.InternalDomains.Length - 1) ); // create internal sub-domains bool wasCreated= false; String[] internalDomainList= {"LGR", "DMN", "PFX", "THR", "LGD", "VAR" }; foreach ( String it in internalDomainList ) { resDomainInternal._()._NC( it ); internalDomains.Find( resDomainInternal, Case.Sensitive, 1, ref wasCreated ); } maxDomainPathLength= ALox.InternalDomains.Length + 3; // register with ALox if ( doRegister ) ALox.Register( this, ContainerOp.Insert ); // read domain substitution rules from configuration Variable variable= new Variable( ALox.DOMAIN_SUBSTITUTION, GetName() ); if ( variable.Load() != 0 ) { for( int ruleNo= 0; ruleNo< variable.Size(); ruleNo++ ) { AString rule= variable.GetString( ruleNo ); int idx= rule.IndexOf( "->" ); if ( idx > 0 ) { String domainPath= rule.ToString( 0, idx ).Trim(); String replacement= rule.ToString( idx + 2 ).Trim(); SetDomainSubstitutionRule( domainPath, replacement ); } else { // using alib warning here as we can't do internal logging in the constructor ALIB.WARNING( "Syntax error in variable \"" + variable.Fullname + "\"." ); } } } #endif }
/** ******************************************************************************************** * Internal method used by State() to recursively (DFS) collect Domains of Logger that have * a different verbosity than their parent. * * @param domain The actual domain to check. * @param loggerNo The logger to collect domains for. * @param results The result list. **********************************************************************************************/ protected void logStateDomsWithDiffVerb( Domain domain, int loggerNo, List<Domain> results ) { if ( domain.Parent == null || domain.Parent.GetVerbosity(loggerNo) != domain.GetVerbosity(loggerNo) ) results.Add( domain ); foreach( Domain it in domain.SubDomains ) logStateDomsWithDiffVerb( it, loggerNo, results ); }
// ############################################################################################# // Internals // ############################################################################################# /** **************************************************************************************** * Internal, recursive helper of #Find. * * @param domainPath Path to search. * @param sensitivity Denotes if domain name search is treated case sensitive or not. * @param maxCreate The maximum number of sub domains that are created if not * found at the end of the path. * @param[out] wasCreated Output parameter that is set \c true if domain was not found * and hence created. * @return The domain found or created. ******************************************************************************************/ protected Domain findRecursive( Substring domainPath, Case sensitivity, int maxCreate, ref bool wasCreated ) { //--- get act sub-name and rest of path domainPath.Consume( Separator ); int endSubName= domainPath.IndexOf( Separator ); ALIB.ASSERT_ERROR( endSubName != 0, "Internal Error" ); // find end of actual domain name and save rest Substring restOfDomainPath= tSubstring2; restOfDomainPath.SetNull(); if ( endSubName > 0 ) domainPath.Split( endSubName, restOfDomainPath, 1 ); // search sub-domain Domain subDomain= null; // "." if( domainPath.Equals( "." ) ) subDomain= this; // ".." else if( domainPath.Equals( ".." ) ) subDomain= Parent != null ? Parent : this; // search in sub-domain else { int i; bool fixedOnce= false; for(;;) { for( i= 0; i< SubDomains.Count; i++ ) { int comparison= SubDomains[i].Name.CompareTo( domainPath, sensitivity ); if( comparison >= 0 ) { if ( comparison == 0 ) subDomain= SubDomains[i]; break; } } // domain found? if ( subDomain != null ) break; // try and fix name if( !fixedOnce ) { fixedOnce= true; bool illegalCharacterFound= false; for( int cp= 0; cp< domainPath.Length() ; ++cp ) { char c= domainPath.CharAt(cp); if ( c < '-' || c > 'z' || c == '<' || c == '>' || c == '[' || c == ']' || c == '=' || c == '?' || c == ';' || c == ':' || c == '\\'|| c == '\''|| c == '.' || c == ',' ) { illegalCharacterFound= true; domainPath.Buf[domainPath.Start + cp]= '#'; } } if ( illegalCharacterFound ) continue; } // create if ( maxCreate == 0 ) return null; wasCreated= true; SubDomains.Insert( i, subDomain= new Domain( this, new AString( domainPath ) ) ); maxCreate--; if ( maxCreate == 0 ) return subDomain; break; } } // recursion? if ( restOfDomainPath.IsNotEmpty() ) { domainPath.Set( restOfDomainPath ); return subDomain.findRecursive( domainPath, sensitivity, maxCreate, ref wasCreated ); } // that's it return subDomain; }
/** **************************************************************************************** * Helper method of #dumpStateOnLoggerRemoval to recursively collect domain settings. * @param domain The actual domain. * @param loggerNo The number of the logger * @param variable The AString to collect the information. ******************************************************************************************/ #if ALOX_DBG_LOG || ALOX_REL_LOG protected void verbositySettingToVariable( Domain domain, int loggerNo, Variable variable ) { variable.AddString()._( domain.FullPath ) ._('=') ._( domain.GetVerbosity( loggerNo ).ToString() ); // loop over all sub domains (recursion) foreach( Domain subDomain in domain.SubDomains ) verbositySettingToVariable( subDomain, loggerNo, variable ); }
/** ******************************************************************************************** * Parses the #Format string and logs meta information into the log buffer. For each * variable found, method #processVariable is invoked. Hence, to add new variables, * the latter method can be overwritten by descendants. Overwriting this method is * recommended for formatter classes that do not rely on format strings. * @param logger The logger that we are embedded in. * @param buffer The buffer to write meta information into. * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param scope Information about the scope of the <em>Log Statement</em>.. * * @return The number of tab sequences that were written (by adding ESC::TAB to the buffer). **********************************************************************************************/ public virtual int Write( TextLogger logger, AString buffer, Domain domain, Verbosity verbosity, ScopeInfo scope ) { int qtyTabStops= 0; // check if ( Format == null || Format.IsEmpty() ) return 0; // clear DateTime singleton callerDateTime= null ; // loop/switch over content specified in configuration array tTok.Set( Format, '%' ); while ( true ) { // get next and log substring between commands if ( tTok.Next(Whitespaces.Keep).IsNotEmpty() ) buffer._( tTok.Actual ); // previous next did not find a delimiter if ( !tTok.HasNext() ) break; // process the found variable qtyTabStops+= processVariable( logger, domain, verbosity, scope, buffer, tTok.Rest ); } return qtyTabStops; }
/** ******************************************************************************************** * The implementation of the abstract method of parent class TextLogger. * Loops over the log text, removes or ignores ESC sequences (all but ESC.TAB) and invokes * abstract methods of descendants as follows: * - \ref notifyLogOp "notifyLogOp(true)" * - #logSubstring() * - ... * - \ref notifyLogOp "notifyLogOp(Phase.End)" * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this stage * and is provided to be able to be logged out only. * @param msg The log message. * @param scope Information about the scope of the <em>Log Statement</em>.. * @param lineNumber The line number of a multi-line message, starting with 0. For single * line messages this is -1. **********************************************************************************************/ override protected void logText( Domain domain, Verbosity verbosity, AString msg, ScopeInfo scope, int lineNumber) { if ( !notifyLogOp( Phase.Begin ) ) return; // loop over message, print the parts between the escape sequences int msgLength= msg.Length(); int start= 0; int end; int column= 0; while( start < msgLength ) { bool foundESC= true; end= msg.IndexOf( '\x1B', start ); if( end < 0 ) { foundESC= false; end= msgLength ; } if ( end > start ) { if (!logSubstring( msg, start, end - start ) ) return; column+= end - start; } // interpret escape sequence if ( foundESC ) { char c= msg[++end]; // auto tab or end of meta info part if ( c == 't' || c == 'A') { end++; c= msg[end++]; int extraSpace= c >= '0' && c <= '9' ? (int) ( c - '0' ) : (int) ( c - 'A' ) + 10; int tabStop= AutoSizes.Next( column, extraSpace ); if ( tabStop > column ) { AString spaces= Util.GetSpaces(); int spacesLength= spaces.Length(); int qty= tabStop - column; while ( qty > 0 ) { int size= qty < spacesLength ? qty : spacesLength; if(!logSubstring( spaces, 0, size ) ) return; qty-= size; } column= tabStop; } } // prune or ignore all others else { if ( !PruneESCSequences ) if(!logSubstring( msg, end - 1, 3 )) return; end+= 2; } } // next start= end; } // write loop ALIB.ASSERT_WARNING( start == msgLength, "Loop error when pruning ESC codes" ); notifyLogOp( Phase.End); }
// ############################################################################################# // logText // ############################################################################################# /** ******************************************************************************************** * * The implementation of the abstract method of parent class TextLogger. Logs messages to the * application console and/or the VStudio output window. * * @param domain The <em>Log Domain</em>. * @param verbosity The verbosity. This has been checked to be active already on this * stage and is provided to be able to be logged out only. * @param msg The log message * @param scope Information about the scope of the <em>Log Statement</em>.. * @param lineNumber The line number of a multi-line message, starting with 0. For * single line messages this is -1. **********************************************************************************************/ override protected void logText( Domain domain, Verbosity verbosity, AString msg, ScopeInfo scope, int lineNumber) { // loop over message, print the parts between the escape sequences Tokenizer msgParts= new Tokenizer( msg, '\x001B' ); Substring actual= msgParts.Actual; Substring rest= msgParts.Rest; int column= 0; for(;;) { msgParts.Next( Whitespaces.Keep ); // check if this is an ANSI sequence already if ( rest.CharAtStart() == '[' ) { // read the 'm' int idx= rest.IndexOf( 'm' ); if ( idx < 0 ) // unknown ANSI Code { ALIB.WARNING( "Unknown ANSI ESC Code " ); textWriter.Write( actual.Buf, actual.Start, actual.Length() ); continue; } column+= actual.Length(); actual.End= rest.Start + idx ; rest.Start+= idx + 1; textWriter.Write( actual.Buf, actual.Start, actual.Length() ); continue; } if ( actual.IsNotEmpty() ) { textWriter.Write( actual.Buf, actual.Start, actual.Length() ); column+= actual.Length(); } // end of loop? if ( !msgParts.HasNext() ) break; // found an ESC sequence char c= rest.Consume(); // Colors bool isForeGround= true; if( c == 'C' || c == 'c' ) { isForeGround= c== 'c'; c= rest.Consume(); int colNo= c - '0'; ALIB.ASSERT_WARNING( colNo >=0 && colNo <=9, "Unknown ESC-c code" ); // add bg colNo+= isForeGround ? 0 : 10; // add light colNo+= (isForeGround ? !IsBackgroundLight : IsBackgroundLight ) ? 20 : 0; textWriter.Write( ansiCols[ colNo ] ); } // Styles else if ( c == 's' ) { // bold/italics style not supported in Windows console // reset all if ( rest.Consume() == 'a' ) { textWriter.Write( ANSI_RESET ); } } // auto tab / end of meta else if ( c == 't' || c == 'A') { bool endOfMeta= c == 'A'; c= rest.Consume(); int extraSpace= c >= '0' && c <= '9' ? (int) ( c - '0' ) : (int) ( c - 'A' ) + 10; int tabStop= AutoSizes.Next( column, extraSpace ); Util.WriteSpaces( textWriter, tabStop - column ); column= tabStop; if ( endOfMeta ) { String msgPrefix; switch ( verbosity ) { case lox.Verbosity.Verbose: msgPrefix= MsgPrefixVerbose; break; case lox.Verbosity.Info: msgPrefix= MsgPrefixInfo; break; case lox.Verbosity.Warning: msgPrefix= MsgPrefixWarning; break; case lox.Verbosity.Error: msgPrefix= MsgPrefixError; break; default: msgPrefix= ""; break; } textWriter.Write( msgPrefix ); } } // Link (we just colorize links here) else if ( c == 'l' ) { textWriter.Write( rest.Consume() == 'S' ? ( IsBackgroundLight ? ANSI_LIGHT_BLUE : ANSI_LIGHT_BLUE ) : ANSI_STD_COL ); } else { ALIB.WARNING( "Unknown ESC code" ); } } // write loop textWriter.WriteLine( MsgSuffix ); }
/** ******************************************************************************************** * Internal method used by State() to recursively log Domain instances. * * @param domain The Domain instance to log out. * @param buf The buffer to log to. **********************************************************************************************/ protected void logStateDomainRecursive( Domain domain, AString buf ) { int reference= buf.Length(); buf._NC(" ") ._NC( domain ); int idx= buf.IndexOf( '[', reference ); buf.InsertChars( ' ', maxDomainPathLength + 5 - idx + reference , idx); buf.NewLine(); // loop over all sub domains (recursion) foreach ( Domain subDomain in domain.SubDomains ) logStateDomainRecursive( subDomain, buf); }