ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
textlogger.cpp
1// #################################################################################################
2// alib::lox::detail - ALox Logging Library
3//
4// Copyright 2013-2024 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
8
9#if !defined(ALIB_DOX)
10# if !defined (HPP_ALOX_DETAIL_TEXTLOGGER_TEXTLOGGER)
12# endif
13# define HPP_ALIB_LOX_PROPPERINCLUDE
14# if !defined (HPP_ALOX_DETAIL_DOMAIN)
16# endif
17# if !defined (HPP_ALOX_DETAIL_SCOPEINFO)
19# endif
20# undef HPP_ALIB_LOX_PROPPERINCLUDE
21
22# if !defined (HPP_ALIB_CAMP_PROCESSINFO)
24# endif
25
26# if !defined (HPP_ALIB_ALOXMODULE)
28# endif
29# if !defined (HPP_ALIB_STRINGS_FORMAT)
31# endif
32# if !defined(HPP_ALIB_CAMP_MESSAGE_REPORT)
34# endif
35# if !defined (HPP_ALIB_LANG_CAMP_INLINES)
37# endif
38#endif // !defined(ALIB_DOX)
39
40
41namespace alib { namespace lox { namespace detail { namespace textlogger {
42
43// #################################################################################################
44// StandardConverter
45// #################################################################################################
47{
48 auto* firstLevelFormatter= new FormatterPythonStyle();
49 firstLevelFormatter->Next.reset( new FormatterJavaStyle() );
50 #if ALIB_THREADS
51 firstLevelFormatter->SetSafeness( lang::Safeness::Unsafe );
52 firstLevelFormatter->Next->SetSafeness( lang::Safeness::Unsafe );
53 firstLevelFormatter->Acquire( ALIB_CALLER_PRUNED );
54 #endif
55
56 Formatters.emplace_back( firstLevelFormatter );
57 cntRecursionx= -1;
58}
59
61{
62 ALIB_ASSERT_ERROR( cntRecursionx == -1, "ALOX",
63 "ALox object converter recursion counter > 0.\n"
64 "Note: This error indicates, that a previous format operation (log statement) contained\n"
65 " corrupt format values, which caused the formatter to behave undefined, including\n"
66 " the corruption of the execution stack of ALox logging." )
67 for( auto* elem : Formatters )
68 {
69 ALIB_IF_THREADS( elem->Release(); )
70 delete elem;
71 }
72}
73
75{
77
78 ALIB_ASSERT_WARNING( cntRecursionx < 5, "ALOX", "Logging recursion depth >= 5" )
79
80 // get a formatter. We use a clone per recursion depth!
81 // So, did we have this depth already? If not, create a new set of formatters formatter
82 if( static_cast<size_t>( cntRecursionx ) >= Formatters.size() )
83 {
84 // create a pair of recursion formatters
85 Formatter* recursionFormatter= new FormatterPythonStyle();
86 recursionFormatter->Next.reset( new FormatterJavaStyle() );
87
88 #if ALIB_THREADS
89 recursionFormatter ->SetSafeness( lang::Safeness::Unsafe );
90 recursionFormatter->Next->SetSafeness( lang::Safeness::Unsafe );
91 recursionFormatter->Acquire( ALIB_CALLER_PRUNED );
92 #endif
93
94 recursionFormatter->CloneSettings( *Formatters[0] );
95
96 Formatters.emplace_back( recursionFormatter );
97 }
98
99 Formatter* formatter= Formatters[static_cast<size_t>( cntRecursionx )];
100
101 try
102 {
103 formatter->FormatArgs( target, logables );
104 }
105 catch(Exception& e )
106 {
107 target << ALOX.GetResource("TLFmtExc");
108 e.Format( target );
109 }
110
111 --cntRecursionx;
112}
113
114AutoSizes* StandardConverter::GetAutoSizes()
115{
116 FormatterPythonStyle* fmtPS= dynamic_cast<FormatterPythonStyle*>( Formatters[0] );
117 if (fmtPS != nullptr )
118 return &fmtPS->Sizes;
119 return nullptr;
120}
121
122void StandardConverter::ResetAutoSizes()
123{
124 FormatterPythonStyle* fmtPS;
125 for( auto* elem : Formatters )
126 if ( (fmtPS= dynamic_cast<FormatterPythonStyle*>( elem )) != nullptr )
127 fmtPS->Sizes.Reset();
128}
129
130// #################################################################################################
131// MetaInfo
132// #################################################################################################
133void MetaInfo::Write( TextLogger& logger, AString& buf, detail::Domain& domain, Verbosity verbosity, ScopeInfo& scope )
134{
135 // check
136 if ( Format.IsEmpty() )
137 return;
138
139 // clear DateTime singleton
140 callerDateTime.Year= (std::numeric_limits<int>::min)();
141
142 Substring format( Format );
143 for(;;)
144 {
145 // get next and log sub-string between commands
146 integer idx= format.IndexOf( '%' );
147 if ( idx >= 0 )
148 {
149 format.ConsumeChars<false, lang::CurrentData::Keep>( idx, buf, 1 );
150 processVariable( logger, domain, verbosity, scope, buf, format );
151 }
152 else
153 {
154 buf._<false>( format );
155 break;
156 }
157 }
158}
159
160void MetaInfo::processVariable( TextLogger& logger,
161 detail::Domain& domain,
162 Verbosity verbosity,
163 ScopeInfo& scope,
164 AString& dest,
165 Substring& variable )
166
167{
168 // process commands
169 character c2;
170 switch ( variable.ConsumeChar() )
171 {
172 // scope info
173 case 'S':
174 {
175 // read sub command
176 NString val;
177 switch( c2= variable.ConsumeChar() )
178 {
179 case 'P': // SP: full path
180 {
181 val= scope.GetFullPath();
182 if ( val.IsEmpty() )
183 val= NoSourceFileInfo;
184 } break;
185
186 case 'p': // Sp: trimmed path
187 {
188 integer previousLength= dest.Length();
189 scope.GetTrimmedPath( dest );
190 if( dest.Length() != previousLength )
191 return;
192 val= NoSourceFileInfo;
193 } break;
194
195 case 'F': // file name
196 {
197 val= scope.GetFileName();
198 if ( val.IsEmpty() )
199 val= NoSourceFileInfo;
200 } break;
201
202 case 'f': // file name without extension
203 {
204 val= scope.GetFileNameWithoutExtension();
205 if ( val.IsEmpty() )
206 val= NoSourceFileInfo;
207 } break;
208
209
210 case 'M': // method name
211 {
212 val= scope.GetMethod();
213 if ( val.IsEmpty() )
214 val= NoMethodInfo;
215 } break;
216
217 case 'L': // line number
218 {
219 dest._<false>( scope.GetLineNumber() );
220 return;
221 }
222
223 default:
224 {
225 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
226 "Unknown format variable '%S{}' (only one warning)", c2 )
227 ALIB_DBG( FormatWarningOnce= true; )
228 val= "%ERROR";
229 } break;
230 }
231 dest._( val );
232 return;
233 }
234
235 // %Tx: Time
236 case 'T':
237 {
238 c2= variable.ConsumeChar();
239
240 // %TD: Date
241 if ( c2 == 'D' )
242 {
243 // get time stamp as CalendarDateTime once
244 if ( callerDateTime.Year == (std::numeric_limits<int>::min)() )
245 callerDateTime.Set( DateConverter.ToDateTime( scope.GetTimeStamp() ) );
246
247 // if standard format, just write it out
248 if ( DateFormat.Equals<false>( A_CHAR("yyyy-MM-dd") ) )
249 {
250 dest._<false>( alib::Format( callerDateTime.Year, 4 ) )._<false>( '-' )
251 ._<false>( alib::Format( callerDateTime.Month, 2 ) )._<false>( '-' )
252 ._<false>( alib::Format( callerDateTime.Day, 2 ) );
253 }
254 // user defined format
255 else
256 callerDateTime.Format( DateFormat, dest );
257
258 return;
259 }
260
261
262 // %TT: Time of Day
263 if ( c2 == 'T' )
264 {
265 // get time stamp as CalendarDateTime once
266 if ( callerDateTime.Year == (std::numeric_limits<int>::min)() )
267 callerDateTime.Set( DateConverter.ToDateTime( scope.GetTimeStamp() ) );
268
269 // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!)
270 // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow.
271 if ( TimeOfDayFormat.Equals<false>( A_CHAR("HH:mm:ss") ) )
272 {
273 dest._<false>( alib::Format(callerDateTime.Hour, 2) )._<false>( ':' )
274 ._<false>( alib::Format(callerDateTime.Minute, 2) )._<false>( ':' )
275 ._<false>( alib::Format(callerDateTime.Second, 2) );
276 }
277
278 // user defined format
279 else
280 callerDateTime.Format( TimeOfDayFormat, dest );
281 }
282
283 // %TC: Time elapsed since created
284 else if ( c2 == 'C' )
285 {
286 auto elapsedTime= scope.GetTimeStamp() - logger.TimeOfCreation;
287
288 if( MaxElapsedTime < elapsedTime )
289 MaxElapsedTime= elapsedTime;
290
291 auto maxElapsedSecs= MaxElapsedTime.InAbsoluteSeconds();
292 CalendarDuration elapsed( elapsedTime );
293
294 if ( maxElapsedSecs >= 60*60*24 ) dest._<false>( elapsed.Days )._<false>( TimeElapsedDays );
295 if ( maxElapsedSecs >= 60*60 ) dest._<false>( alib::Format(elapsed.Hours , maxElapsedSecs >= 60*60*10 ? 2 : 1 ) )._<false>( ':' );
296 if ( maxElapsedSecs >= 60 ) dest._<false>( alib::Format(elapsed.Minutes, maxElapsedSecs >= 10*60 ? 2 : 1 ) )._<false>( ':' );
297 dest._<false>( alib::Format(elapsed.Seconds, maxElapsedSecs > 9 ? 2 : 1) )._<false>( '.' );
298 dest._<false>( alib::Format(elapsed.Milliseconds, 3) );
299 }
300
301 // %TL: Time elapsed since last log call
302 else if ( c2 == 'L' )
303 writeTimeDiff( dest, scope.GetTimeStamp().Since( logger.TimeOfLastLog ).InNanoseconds() );
304
305 else
306 {
307 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
308 "Unknown format variable '%T{}' (only one warning)", c2 )
309 ALIB_DBG( FormatWarningOnce= true; )
310 }
311 return;
312 }
313
314
315 // Thread
316 case 't':
317 {
318 c2= variable.ConsumeChar();
319
320 if ( c2 == 'N' ) // %tN: thread name
321 {
322 #if ALIB_THREADS
323 const String& threadName= scope.GetThreadNameAndID(nullptr);
324 #else
325 String msg( A_CHAR("SINGLE_THREADED") );
326 const String& threadName= msg;
327 #endif
328 dest._<false>( Format::Field( threadName, logger.AutoSizes.Next( AutoSizes::Types::Field, threadName.Length(), 0 ), lang::Alignment::Center ) );
329 }
330 else if ( c2 == 'I' ) // %tI: thread ID
331 {
332 String32 threadID;
333 #if ALIB_THREADS
334 threadID._( scope.GetThreadID() );
335 #else
336 threadID << "-1";
337 #endif
338 dest._<false>( Format::Field( threadID, logger.AutoSizes.Next( AutoSizes::Types::Field, threadID .Length(), 0 ), lang::Alignment::Center ) );
339 }
340 else
341 {
342 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
343 "Unknown format variable '%t{}' (only one warning)", c2 )
344 ALIB_DBG( FormatWarningOnce= true; )
345 }
346 return;
347 }
348
349 case 'L':
350 {
351 c2= variable.ConsumeChar();
352 if ( c2 == 'G' ) dest._<false>( logger.GetName() );
353 else if ( c2 == 'X' ) dest._<false>( scope.GetLoxName() );
354 else
355 {
356 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
357 "Unknown format variable '%L{}' (only one warning)", c2 )
358 ALIB_DBG( FormatWarningOnce= true; )
359 }
360 return;
361 }
362
363 case 'P':
364 {
365 dest._<false>( ProcessInfo::Current().Name );
366 return;
367 }
368
369 case 'V':
370 dest._<false>( verbosity == Verbosity::Error ? VerbosityError
371 : verbosity == Verbosity::Warning ? VerbosityWarning
372 : verbosity == Verbosity::Info ? VerbosityInfo
373 : VerbosityVerbose );
374 return;
375
376 case 'D':
377 {
378 dest._( Format::Field( domain.FullPath, logger.AutoSizes.Next( AutoSizes::Types::Field, domain.FullPath.Length(), 0 ), lang::Alignment::Left ) );
379 return;
380 }
381
382 case '#':
383 dest._<false>( alib::Format( logger.CntLogs, LogNumberMinDigits ) );
384 return;
385
386 // A: Auto tab
387 case 'A':
388 {
389 // read extra space from format string
390 integer extraSpace;
391 if( !variable.ConsumeDecDigits( extraSpace ) )
392 extraSpace= 1;
393 integer currentLength= dest.WStringLength();
394 integer tabPos= logger.AutoSizes.Next( AutoSizes::Types::Tabstop, currentLength, extraSpace );
395 dest.InsertChars(' ', tabPos - currentLength );
396
397 return;
398 }
399
400 case 'N':
401 dest._<false>( logger.GetName() );
402 return;
403
404 default:
405 ALIB_WARNINGS_ALLOW_UNSAFE_BUFFER_USAGE
406 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
407 "Unknown format character {!Q'} (only one warning)",
408 *( variable.Buffer() -1 ) )
409 ALIB_DBG( FormatWarningOnce= true; )
410 ALIB_WARNINGS_RESTORE
411 return;
412 }// switch
413}
414
415void MetaInfo::writeTimeDiff( AString& buf, int64_t diffNanos )
416{
417 // unmeasurable?
418 if ( diffNanos < TimeDiffMinimum )
419 {
420 buf._<false>( TimeDiffNone );
421 return;
422 }
423
424 if ( diffNanos < 1000 )
425 {
426 buf._<false>( alib::Format( diffNanos, 3 ) )._<false>( TimeDiffNanos );
427 return;
428 }
429
430 // we continue with micros
431 int64_t diffMicros= diffNanos / 1000L;
432
433 // below 1000 microseconds?
434 if ( diffMicros < 1000 )
435 {
436 buf._<false>( alib::Format( diffMicros, 3 ) );
437 buf._<false>( TimeDiffMicros );
438 return;
439 }
440
441 // below 1000 ms?
442 if ( diffMicros < 1000000 )
443 {
444 buf._<false>( alib::Format( (diffMicros / 1000), 3 ) )._<false>( TimeDiffMillis );
445 return;
446 }
447
448
449 // below 10 secs (rounded) ?
450 if ( diffMicros < 9995000 )
451 {
452 // convert to hundredth of secs
453 int64_t hundredthSecs= ((diffMicros / 1000) + 5) / 10;
454
455 // print two digits after dot x.xx
456 buf._<false>( alib::Format( (hundredthSecs / 100), 1 ) )
457 ._<false>( '.' )
458 ._<false>( alib::Format( (hundredthSecs % 100), 2 ) )
459 ._<false>( TimeDiffSecs );
460 return;
461 }
462
463 // convert to tenth of secs
464 int64_t tenthSecs= ((diffMicros / 10000) + 5) / 10 ;
465
466 // below 100 secs ?
467 if ( tenthSecs < 1000 )
468 {
469 // print one digits after dot xx.x (round value by adding 5 hundredth)
470 buf._<false>( alib::Format( ( tenthSecs / 10 ), 2 ) )
471 ._<false>( '.' )
472 ._<false>( alib::Format( ( tenthSecs % 10 ), 1 ) )
473 ._<false>( TimeDiffSecs );
474 return;
475 }
476
477 // below 10 mins ?
478 if ( tenthSecs < 6000 )
479 {
480 // convert to hundredth of minutes
481 int64_t hundredthMins= tenthSecs / 6;
482
483 // print two digits after dot x.xx
484 buf._<false>( alib::Format( (hundredthMins / 100), 1 ) )
485 ._<false>( '.' )
486 ._<false>( alib::Format( (hundredthMins % 100), 2 ) )
487 ._<false>( TimeDiffMins );
488 return;
489 }
490
491 // convert to tenth of minutes
492 int64_t tenthMins= tenthSecs / 60;
493
494 // below 100 mins ?
495 if ( tenthMins < 1000 )
496 {
497 // print one digits after dot xx.x (round value by adding 5 hundredth)
498 buf._<false>( alib::Format( (tenthMins / 10), 2 ) )
499 ._<false>( '.' )
500 ._<false>( alib::Format( (tenthMins % 10), 1 ) )
501 ._<false>( TimeDiffMins );
502 return;
503 }
504
505 // below ten hours?
506 if ( tenthMins < 6000 )
507 {
508 // convert to hundredth of hours
509 int64_t hundredthHours= tenthMins / 6;
510
511 // print two digits after dot x.xx
512 buf._<false>( alib::Format( (hundredthHours / 100), 1 ) )
513 ._<false>( '.' )
514 ._<false>( alib::Format( (hundredthHours % 100), 2 ))
515 ._<false>( TimeDiffHours );
516 return;
517 }
518
519 // convert to tenth of minutes
520 int64_t tenthHours= tenthMins / 60;
521
522 // below 10 hours ?
523 if ( tenthHours < 1000 )
524 {
525 // print two digits after dot x.xx
526 buf._<false>( alib::Format( (tenthHours / 10), 2 ) )
527 ._<false>( '.' )
528 ._<false>( alib::Format( (tenthHours % 10), 1 ) )
529 ._<false>( TimeDiffHours );
530 return;
531 }
532
533 // below 100 hours ?
534 if ( tenthHours < 1000 )
535 {
536 // print one digits after dot xx.x (round value by adding 5 hundredth)
537 buf._<false>( alib::Format( (tenthHours / 10), 2 ) )
538 ._<false>( '.' )
539 ._<false>( alib::Format( ((tenthHours / 10) % 10), 1 ) )
540 ._<false>( TimeDiffHours );
541 return;
542 }
543
544 // convert to hundredth of days
545 int64_t hundredthDays= tenthHours * 10 / 24;
546
547 // below 10 days ?
548 if ( hundredthDays < 1000 )
549 {
550 // print two digits after dot x.xx
551 buf._<false>( alib::Format( (hundredthDays / 100), 1 ) )
552 ._<false>( '.' )
553 ._<false>( alib::Format( (hundredthDays % 100), 2 ) )
554 ._<false>( TimeDiffDays );
555 return;
556 }
557
558 // 10 days or more (print days plus one digit after the comma)
559 // print one digits after dot xx.x (round value by adding 5 hundredth)
560 buf ._<false>( alib::Format( (hundredthDays / 100), 2 ) )
561 ._<false>( '.' )
562 ._<false>( alib::Format( ((hundredthDays / 10) % 10), 1 ) )
563 ._<false>( TimeDiffDays );
564}
565
566
567// #################################################################################################
568// TextLogger
569// #################################################################################################
570
571TextLogger::TextLogger( const NString& pName, const NString& typeName, bool pUsesStdStreams )
572: Logger( pName, typeName )
573, usesStdStreams( pUsesStdStreams )
574{
575 logBuf.SetBuffer( 256 );
576 msgBuf.SetBuffer( 256 );
577 MetaInfo= new textlogger::MetaInfo();
578}
579
580TextLogger::~TextLogger()
581{
582 delete MetaInfo;
583 if (Converter)
584 delete Converter;
585 ALIB_ASSERT( msgBuf.IsEmpty() )
586}
587
588void TextLogger::AcknowledgeLox( LoxImpl* lox, lang::ContainerOp op )
589{
590 ALIB_IFN_THREADS( (void) lox; )
591
592 Variable cfgVar;
593
594 // --------------- insert ------------------
595 if( op == lang::ContainerOp::Insert )
596 {
597 if ( Converter == nullptr )
598 Converter= new textlogger::StandardConverter();
599
600 // register with ALIB lockers (if not done yet)
601 if ( usesStdStreams )
602 {
603 #if ALIB_THREADS
604 int registrationCounter;
605 {
606 ALIB_LOCK_WITH( lock )
607 registrationCounter= stdStreamLockRegistrationCounter++;
608 }
609 if ( registrationCounter == 0 )
610 SmartLock::StdOutputStreams.AddAcquirer( this );
611 #endif
612 }
613
614 // Variable AUTO_SIZES: use last session's values
615 cfgVar.Declare( Variables::AUTO_SIZES, GetName() );
616 if ( ALOX.GetConfig().Load( cfgVar ) != Priorities::NONE )
617 {
618 Substring importMI= cfgVar.GetString();
619 Substring importLog;
620 integer sepPos= importMI.IndexOf(';');
621 if( sepPos >= 0 )
622 importMI.Split( sepPos, importLog, 1 );
623 AutoSizes.Import( importMI );
624
625 strings::util::AutoSizes* autoSizesLog= Converter->GetAutoSizes();
626 if( autoSizesLog != nullptr && importLog.IsNotEmpty() )
627 {
628 autoSizesLog->Import( importLog );
629 }
630 }
631
632
633 // Variable MAX_ELAPSED_TIME: use last session's values
634 cfgVar.Declare( Variables::MAX_ELAPSED_TIME, GetName());
635 if ( ALOX.GetConfig().Load( cfgVar ) != Priorities::NONE )
636 {
637 int maxInSecs= static_cast<int>( cfgVar.GetInteger() );
638 Substring attrValue;
639 if ( cfgVar.GetAttribute( A_CHAR( "limit"), attrValue ) )
640 {
641 integer maxMax;
642 attrValue.ConsumeInt( maxMax );
643 if ( maxInSecs > maxMax )
644 maxInSecs= static_cast<int>( maxMax );
645 }
646 MetaInfo->MaxElapsedTime= Ticks::Duration::FromSeconds( maxInSecs );
647 }
648
649
650 // Variable <name>_FORMAT / <typeName>_FORMAT:
651 VariableDecl variableDecl( Variables::FORMAT );
652 ALIB_ASSERT_WARNING( variableDecl.DefaultValue.IsNull(), "ALOX",
653 "Default value of variable FORMAT should be kept null" )
654
655 if( ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetName() ) ) == Priorities::NONE
656 && ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetTypeName() ) ) == Priorities::NONE )
657 {
658 // no variable created, yet. Let's create a 'personal' one on our name
659 cfgVar.Declare( Variables::FORMAT, GetName() );
660 cfgVar.Add( MetaInfo->Format );
661 cfgVar.Add( MetaInfo->VerbosityError );
662 cfgVar.Add( MetaInfo->VerbosityWarning );
663 cfgVar.Add( MetaInfo->VerbosityInfo );
664 cfgVar.Add( MetaInfo->VerbosityVerbose );
665 cfgVar.Add( FmtMsgSuffix );
666 ALOX.GetConfig().Store( cfgVar );
667 }
668 else
669 {
670 MetaInfo->Format .Reset( cfgVar.GetString( 0) );
671 if( cfgVar.Size() >= 2 ) MetaInfo->VerbosityError .Reset( cfgVar.GetString( 1) );
672 if( cfgVar.Size() >= 3 ) MetaInfo->VerbosityWarning.Reset( cfgVar.GetString( 2) );
673 if( cfgVar.Size() >= 4 ) MetaInfo->VerbosityInfo .Reset( cfgVar.GetString( 3) );
674 if( cfgVar.Size() >= 5 ) MetaInfo->VerbosityVerbose.Reset( cfgVar.GetString( 4) );
675 if( cfgVar.Size() >= 6 ) FmtMsgSuffix .Reset( cfgVar.GetString( 5) );
676 }
677
678 // Variable <name>_FORMAT_DATE_TIME / <typeName>_FORMAT_DATE_TIME:
680 ALIB_ASSERT_WARNING( variableDecl.DefaultValue.IsNull(), "ALOX",
681 "Default value of variable FORMAT_DATE_TIME should be kept null" )
682 if( ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetName() ) ) == Priorities::NONE
683 && ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetTypeName() ) ) == Priorities::NONE )
684 {
685 // no variable created, yet. Let's create a 'personal' one on our name
686 cfgVar.Declare( Variables::FORMAT_DATE_TIME, GetName() );
687 cfgVar.Add( MetaInfo->DateFormat );
688 cfgVar.Add( MetaInfo->TimeOfDayFormat );
689 cfgVar.Add( MetaInfo->TimeElapsedDays );
690 ALOX.GetConfig().Store( cfgVar );
691 }
692 else
693 {
694 MetaInfo->DateFormat .Reset( cfgVar.GetString( 0) );
695 if( cfgVar.Size() >= 2 ) MetaInfo->TimeOfDayFormat .Reset( cfgVar.GetString( 1) );
696 if( cfgVar.Size() >= 3 ) MetaInfo->TimeElapsedDays .Reset( cfgVar.GetString( 2) );
697 }
698
699 // Variable <name>FORMAT_TIME_DIFF / <typeName>FORMAT_TIME_DIFF:
701 ALIB_ASSERT_WARNING( variableDecl.DefaultValue.IsNull(), "ALOX",
702 "Default value of variable FORMAT_TIME_DIFF should be kept null" )
703 if( ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetName() )) == Priorities::NONE
704 && ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetTypeName() )) == Priorities::NONE )
705 {
706 // no variable created, yet. Let's create a 'personal' one on our name
707 cfgVar.Declare( Variables::FORMAT_TIME_DIFF, GetName() );
708 cfgVar.Add( MetaInfo->TimeDiffMinimum);
709 cfgVar.Add( MetaInfo->TimeDiffNone );
710 cfgVar.Add( MetaInfo->TimeDiffNanos );
711 cfgVar.Add( MetaInfo->TimeDiffMicros );
712 cfgVar.Add( MetaInfo->TimeDiffMillis );
713 cfgVar.Add( MetaInfo->TimeDiffSecs );
714 cfgVar.Add( MetaInfo->TimeDiffMins );
715 cfgVar.Add( MetaInfo->TimeDiffHours );
716 cfgVar.Add( MetaInfo->TimeDiffDays );
717 ALOX.GetConfig().Store( cfgVar );
718 }
719 else
720 {
721 MetaInfo->TimeDiffMinimum= cfgVar.GetInteger ( 0) ;
722 if( cfgVar.Size() >= 2 ) MetaInfo->TimeDiffNone .Reset( cfgVar.GetString( 1) );
723 if( cfgVar.Size() >= 3 ) MetaInfo->TimeDiffNanos .Reset( cfgVar.GetString( 2) );
724 if( cfgVar.Size() >= 4 ) MetaInfo->TimeDiffMicros .Reset( cfgVar.GetString( 3) );
725 if( cfgVar.Size() >= 5 ) MetaInfo->TimeDiffMillis .Reset( cfgVar.GetString( 4) );
726 if( cfgVar.Size() >= 6 ) MetaInfo->TimeDiffSecs .Reset( cfgVar.GetString( 5) );
727 if( cfgVar.Size() >= 7 ) MetaInfo->TimeDiffMins .Reset( cfgVar.GetString( 6) );
728 if( cfgVar.Size() >= 8 ) MetaInfo->TimeDiffHours .Reset( cfgVar.GetString( 7) );
729 if( cfgVar.Size() >= 9 ) MetaInfo->TimeDiffDays .Reset( cfgVar.GetString( 8) );
730 }
731
732 // Variable <name>FORMAT_MULTILINE / <typeName>FORMAT_MULTILINE:
734 ALIB_ASSERT_WARNING( variableDecl.DefaultValue.IsNull(), "ALOX",
735 "Default value of variable FORMAT_MULTILINE should be kept null" )
736 if( ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetName() )) == Priorities::NONE
737 && ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetTypeName() )) == Priorities::NONE )
738 {
739 // no variable created, yet. Let's create a 'personal' one on our name
740 cfgVar.Declare( Variables::FORMAT_MULTILINE, GetName() );
741 cfgVar.Add( MultiLineMsgMode );
742 cfgVar.Add( FmtMultiLineMsgHeadline );
743 cfgVar.Add( FmtMultiLinePrefix );
744 cfgVar.Add( FmtMultiLineSuffix );
745 ALOX.GetConfig().Store( cfgVar );
746 }
747 else
748 {
749 MultiLineMsgMode= static_cast<int>( cfgVar.GetInteger( 0) ) ;
750 if( cfgVar.Size() >= 2 ) FmtMultiLineMsgHeadline.Reset( cfgVar.GetString( 1) );
751 if( cfgVar.Size() >= 3 ) FmtMultiLinePrefix .Reset( cfgVar.GetString( 2) );
752 if( cfgVar.Size() >= 4 ) FmtMultiLineSuffix .Reset( cfgVar.GetString( 3) );
753 if( cfgVar.Size() >= 5 ) { if (cfgVar.GetString( 4).Equals<false, lang::Case::Ignore>( A_CHAR( "nulled") ) )
754 MultiLineDelimiter.SetNull();
755 else
756 MultiLineDelimiter.Reset( cfgVar.GetString( 4) );
757 }
758 if( cfgVar.Size() >= 6 ) MultiLineDelimiterRepl .Reset( cfgVar.GetString( 5) );
759 }
760
761 // Variable <name>FORMAT_REPLACEMENTS / <typeName>FORMAT_REPLACEMENTS:
762 variableDecl= VariableDecl( Variables::REPLACEMENTS );
763 ALIB_ASSERT_WARNING( variableDecl.DefaultValue.IsNull(), "ALOX",
764 "Default value of variable REPLACEMENTS should be kept null" )
765 if( ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetName() )) == Priorities::NONE
766 || ALOX.GetConfig().Load( cfgVar.Declare( variableDecl, GetTypeName() )) == Priorities::NONE )
767 {
768 for( int i= 0; i + 1 < cfgVar.Size() ; i+= 2 )
769 {
770 String searchString= cfgVar.GetString( i );
771 String replaceString= cfgVar.GetString( i + 1);
772 SetReplacement( searchString, replaceString );
773 }
774 }
775
776 // register the lox' lock with us (being a smartlock)
778
779 return;
780 }
781
782 // --------------- remove ------------------
783
784 // de-register with ALIB lockers (if not done yet)
785 if ( usesStdStreams )
786 {
787 #if ALIB_THREADS
788 int registrationCounter;
789 {
790 ALIB_LOCK_WITH( lock )
791 registrationCounter= --this->stdStreamLockRegistrationCounter;
792 }
793
794 if ( registrationCounter == 0 )
796 #endif
797 }
798
799 // export auto sizes to configuration
800 cfgVar.Declare( Variables::AUTO_SIZES, Name );
801 String256 exportString; exportString.DbgDisableBufferReplacementWarning();
802 AutoSizes.Export( exportString );
803 strings::util::AutoSizes* autoSizesLog= Converter->GetAutoSizes();
804 if( autoSizesLog != nullptr )
805 {
806 exportString._( ';' );
807 autoSizesLog->Export( exportString );
808 }
809 cfgVar.Add( exportString );
810 ALOX.GetConfig().Store( cfgVar );
811
812
813 // export "max elapsed time" to configuration
814 String128 destVal( MetaInfo->MaxElapsedTime.InAbsoluteSeconds() );
815 cfgVar.Declare( Variables::MAX_ELAPSED_TIME, Name );
816 if( ALOX.GetConfig().Load( cfgVar ) != Priorities::NONE )
817 cfgVar.ReplaceValue(0, destVal);
818 else
819 cfgVar.Add(destVal);
820
821 ALOX.GetConfig().Store( cfgVar );
822
823 // de-register the lox' lock from us (being a smartlock)
825}
826
827
828void TextLogger::SetReplacement( const String& searched, const String& replacement )
829{
830 // if exists, replace replacement
831 for( auto it= replacements.begin(); it < replacements.end(); it+= 2)
832 if ( it->Equals<false>( searched ) )
833 {
834 if ( replacement.IsNotNull() )
835 {
836 ++it;
837 (*it).Reset( replacement );
838 return;
839 }
840
841 replacements.erase( it );
842 replacements.erase( it );
843 return;
844 }
845
846 // append at the end
847 if ( replacement.IsNotNull() )
848 {
849 replacements.insert( replacements.end(), AString(searched ) );
850 replacements.insert( replacements.end(), AString(replacement) );
851 }
852}
853
855{
856 replacements.clear();
857}
858
860{
861 Converter->ResetAutoSizes();
862}
863
864
865void TextLogger::Log( Domain& domain, Verbosity verbosity, Boxes& logables, ScopeInfo& scope )
866{
867 // we store the current msgBuf length and reset the buffer to this length when exiting.
868 // This allows recursive calls! Recursion might happen with the evaluation of the
869 // logables (in the next line).
870 StringLengthResetter msgBufResetter(msgBuf);
871 Converter->ConvertObjects( msgBuf, logables );
872
873 // replace strings in message
874 for ( size_t i= 0; i < replacements.size() ; i+= 2 )
875 msgBuf.SearchAndReplace( replacements[i],
876 replacements[i + 1], msgBufResetter.OriginalLength() );
877
878 // clear log buffer and write meta info
879 logBuf.Reset();
881 MetaInfo->Write( *this, logBuf, domain, verbosity, scope );
882 logBuf._<false>( ESC::EOMETA );
883
884 // check for empty messages
885 if ( msgBuf.Length() == msgBufResetter.OriginalLength() )
886 {
887 // log empty msg and quit
888 logBuf._<false>( FmtMsgSuffix );
889 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Acquire(ALIB_CALLER_PRUNED); )
890 logText( domain, verbosity, logBuf, scope, -1 );
891 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Release(); )
892 return;
893 }
894
895 // single line output
896 if ( MultiLineMsgMode == 0 )
897 {
898 // replace line separators
899 integer cntReplacements=0;
900 if ( MultiLineDelimiter.IsNotNull() )
901 cntReplacements+= msgBuf.SearchAndReplace( MultiLineDelimiter, MultiLineDelimiterRepl, msgBufResetter.OriginalLength() );
902 else
903 {
904 String& replacement= MultiLineDelimiterRepl;
905 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\r\n"), replacement, msgBufResetter.OriginalLength() );
906 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\r"), replacement, msgBufResetter.OriginalLength() );
907 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\n"), replacement, msgBufResetter.OriginalLength() );
908 }
909
910 // append msg to logBuf
911 if ( cntReplacements == 0 )
912 {
913 logBuf._<false>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
914 }
915 else
916 {
917 logBuf._<false>( FmtMultiLinePrefix );
918 logBuf._<false>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
919 logBuf._<false>( FmtMultiLineSuffix );
920 }
921 logBuf._<false>( FmtMsgSuffix );
922
923 // now do the logging by calling our derived class's logText
924 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Acquire(ALIB_CALLER_PRUNED); )
925 logText( domain, verbosity, logBuf, scope, -1 );
926 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Release(); )
927 }
928
929 // multiple line output
930 size_t qtyTabStops= AutoSizes.ActualIndex;
931 integer actStart= msgBufResetter.OriginalLength();
932 int lineNo= 0;
933 integer lbLenBeforeMsgPart= logBuf.Length();
934
935 // loop over lines in msg
936 while ( actStart < msgBuf.Length() )
937 {
938 // find next end
939 integer delimLen;
940 integer actEnd;
941
942 // no delimiter given: search '\n' and then see if it is "\r\n" in fact
943 if ( MultiLineDelimiter.IsEmpty() )
944 {
945 delimLen= 1;
946
947 actEnd= msgBuf.IndexOf<false>( '\n', actStart );
948 if( actEnd > actStart )
949 {
950 if( msgBuf.CharAt<false>(actEnd - 1) == '\r' )
951 {
952 --actEnd;
953 delimLen= 2;
954 }
955 }
956 }
957 else
958 {
959 delimLen= MultiLineDelimiter.Length();
960 actEnd= msgBuf.IndexOf<false>( MultiLineDelimiter, actStart );
961 }
962
963 // not found a delimiter? - log the rest
964 if ( actEnd < 0 )
965 {
966 // single line?
967 if ( lineNo == 0 )
968 {
969 logBuf._<false>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
970 logBuf._<false>( FmtMsgSuffix );
971
973 logText( domain, verbosity, logBuf, scope, -1 );
974 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Release(); )
975
976 // stop here
977 return;
978 }
979
980 // store actual end
981 actEnd= msgBuf.Length();
982 }
983
984 // found a delimiter
985
986 // signal start of multi line log
987 if ( lineNo == 0 )
988 {
989 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Acquire(ALIB_CALLER_PRUNED); )
990 notifyMultiLineOp( lang::Phase::Begin );
991 }
992
993 // in mode 3, 4, meta info is deleted
994 if ( lineNo == 0 && ( MultiLineMsgMode == 3 || MultiLineMsgMode == 4 ) )
995 {
996 // log headline in mode 3
997 if ( MultiLineMsgMode == 3 )
998 {
999 logBuf._<false>( FmtMultiLineMsgHeadline );
1000 AutoSizes.ActualIndex= qtyTabStops;
1001 logText( domain, verbosity, logBuf, scope, 0 );
1002 }
1003
1004 // remember zero as offset
1005 lbLenBeforeMsgPart= 0;
1006 }
1007
1008 // clear meta information?
1009 if ( MultiLineMsgMode == 2 )
1010 {
1011 if (lineNo != 0 )
1012 {
1013 logBuf.Reset( ESC::EOMETA );
1014 AutoSizes.ActualIndex= qtyTabStops;
1015 }
1016 }
1017 // reset logBuf length to marked position
1018 else
1019 {
1020 logBuf.ShortenTo( lbLenBeforeMsgPart );
1021 AutoSizes.ActualIndex= qtyTabStops;
1022 }
1023
1024 // append message and do the log
1025 logBuf._<false>( FmtMultiLinePrefix );
1026 logBuf._<false>( msgBuf, actStart, actEnd - actStart );
1027 logBuf._<false>( FmtMultiLineSuffix );
1028 actStart= actEnd + delimLen;
1029 if ( actStart >= msgBuf.Length() )
1030 logBuf._<false>( FmtMsgSuffix );
1031 logText( domain, verbosity, logBuf, scope, lineNo );
1032
1033 // next
1034 ++lineNo;
1035 }
1036
1037 // signal end of multi line log
1038 if ( lineNo > 0 )
1039 {
1040 notifyMultiLineOp( lang::Phase::End );
1041 ALIB_IF_THREADS( if (usesStdStreams) SmartLock::StdOutputStreams.Release(); )
1042 }
1043}
1044
1045}}}} // namespace [alib::lox::detail::textlogger]
ALIB_API Priorities Load(Variable &variable)
ALIB_API Priorities Store(Variable &variable, const String &externalizedValue=nullptr)
config::Configuration & GetConfig()
Definition camp.hpp:231
static constexpr character EOMETA[4]
End of meta information in log string.
virtual ALIB_API void Write(TextLogger &logger, AString &buffer, detail::Domain &domain, Verbosity verbosity, ScopeInfo &scope)
virtual ALIB_API void ConvertObjects(AString &target, Boxes &logables) override
virtual ALIB_API ~StandardConverter() override
virtual ALIB_API void ClearReplacements()
virtual ALIB_API void Log(Domain &domain, Verbosity verbosity, Boxes &logables, ScopeInfo &scope) override
virtual ALIB_API void SetReplacement(const String &searched, const String &replacement)
integer IndexOf(TChar needle, integer startIdx=0) const
Definition string.hpp:889
constexpr bool IsNotEmpty() const
Definition string.hpp:420
constexpr bool IsNotNull() const
Definition string.hpp:402
TSubstring & Split(integer position, TSubstring &target, integer separatorWidth=0, bool trim=false)
bool ConsumeInt(TIntegral &result, TNumberFormat< TChar > *numberFormat=nullptr)
ALIB_API void Import(const String &source, lang::CurrentData session=lang::CurrentData::Clear)
Definition autosizes.cpp:69
ALIB_API void Export(AString &target)
Definition autosizes.cpp:55
ALIB_API int RemoveAcquirer(ThreadLock *acquirer)
Definition smartlock.cpp:92
ALIB_API int AddAcquirer(ThreadLock *newAcquirer)
Definition smartlock.cpp:29
static ALIB_API SmartLock StdOutputStreams
#define A_CHAR(STR)
#define ALIB_IF_THREADS(...)
Definition alib.hpp:303
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:984
#define ALIB_ASSERT_WARNING(cond,...)
Definition alib.hpp:985
#define ALIB_CALLER_PRUNED
Definition alib.hpp:845
#define ALIB_LOCK_WITH(lock)
@ Unsafe
Omit checks or perform unsafe operations.
@ Begin
The start of a transaction.
@ End
The end of a transaction.
Definition alib.cpp:57
lang::format::FormatterPythonStyle FormatterPythonStyle
Type alias in namespace alib.
lox::ALox ALOX
lang::format::FormatterJavaStyle FormatterJavaStyle
Type alias in namespace alib.
LocalString< 256 > String256
Type alias name for TLocalString<character,256> .
strings::TAString< character > AString
Type alias in namespace alib.
characters::character character
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
config::VariableDecl VariableDecl
Type alias in namespace alib.
alib::config::Priorities Priorities
Type alias in namespace alib.
strings::util::AutoSizes AutoSizes
Type alias in namespace alib.
LocalString< 128 > String128
Type alias name for TLocalString<character,128> .
lang::integer integer
Type alias in namespace alib.
Definition integers.hpp:286
static ALIB_API threads::ThreadLock & getLock(LoxImpl *impl)
Definition loxpimpl.cpp:302