ALib C++ Library
Library Version: 2510 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
textlogger.cpp
1// #################################################################################################
2// alib::lox::detail - ALox Logging Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14// ====================================== Global Fragment ======================================
16
17// =========================================== Module ==========================================
18#if ALIB_C20_MODULES
19 module ALib.ALox.Impl;
20 import ALib.Lang;
21 import ALib.EnumOps;
22 import ALib.Strings;
23 import ALib.Boxing;
24 import ALib.EnumRecords;
28 import ALib.Variables;
29 import ALib.Camp;
30 import ALib.Camp.Base;
31#else
32# include "ALib.Lang.H"
33# include "ALib.Strings.H"
34# include "ALib.Boxing.H"
38# include "ALib.Variables.H"
39# include "ALib.Camp.H"
40# include "ALib.Camp.Base.H"
41# include "ALib.ALox.H"
42# include "ALib.ALox.Impl.H"
43#endif
44// ====================================== Implementation =======================================
45namespace alib::lox::textlogger {
46
47// #################################################################################################
48// StandardConverter
49// #################################################################################################
51{
52 auto* firstLevelFormatter= new FormatterPythonStyle();
53 firstLevelFormatter->Next.InsertDerived<FormatterJavaStyle>();
54
55 Formatters.emplace_back( firstLevelFormatter );
56 cntRecursion= -1;
57}
58
60{
61 ALIB_ASSERT_ERROR( cntRecursion == -1, "ALOX",
62 "ALox object converter recursion counter > 0.\n"
63 "Note: This error indicates, that a previous format operation (log statement) contained\n"
64 " corrupt format values, which caused the formatter to behave undefined, including\n"
65 " the corruption of the execution stack of ALox logging." )
66 for( auto* elem : Formatters )
67 delete elem;
68}
69
71{
73
75
76 // get a formatter. We use a clone per recursion depth!
77 // So, did we have this depth already before? If not, create a new set of formatters formatter
78 if( size_t( cntRecursion ) >= Formatters.size() )
79 {
80 // create a pair of recursion formatters
81 Formatter* recursionFormatter= new FormatterPythonStyle();
82 recursionFormatter->Next.InsertDerived<FormatterJavaStyle>();
83 recursionFormatter->CloneSettings( *Formatters[0] );
84 Formatters.emplace_back( recursionFormatter );
85 }
86
87 Formatter* formatter= Formatters[size_t( cntRecursion )];
88
89 try
90 {
91 formatter->FormatArgs( target, logables );
92 }
93 catch(Exception& e )
94 {
95 target << ALOX.GetResource("TLFmtExc");
96 ALIB_LOCK_RECURSIVE_WITH( format::Formatter::DefaultLock )
97 e.Format( target );
98 }
99
100 --cntRecursion;
101}
102
103void StandardConverter::SetAutoSizes( AutoSizes* autoSizes )
104{
105 FormatterPythonStyle* fmtPS= dynamic_cast<FormatterPythonStyle*>( Formatters[0] );
106 if (fmtPS != nullptr )
107 fmtPS->Sizes= autoSizes;
108}
109
110AutoSizes* StandardConverter::GetAutoSizes()
111{
112 FormatterPythonStyle* fmtPS= dynamic_cast<FormatterPythonStyle*>( Formatters[0] );
113 if (fmtPS != nullptr )
114 return fmtPS->Sizes;
115 return nullptr;
116}
117
118void StandardConverter::ResetAutoSizes()
119{
120 FormatterPythonStyle* fmtPS;
121 for( auto* elem : Formatters )
122 if ( (fmtPS= dynamic_cast<FormatterPythonStyle*>( elem )) != nullptr )
123 fmtPS->Sizes->Reset();
124}
125
126// #################################################################################################
127// MetaInfo
128// #################################################################################################
129void TextLogger::writeMetaInfo( AString& buf, detail::Domain& domain, Verbosity verbosity,
130 detail::ScopeInfo& scope )
131{
132 // check
133 auto& fmt= varFormatMetaInfo.Get<FormatMetaInfo>();
134 if ( fmt.Format.IsEmpty() )
135 return;
136
137 // clear DateTime singleton
138 callerDateTime.Year= (std::numeric_limits<int>::min)();
139
140 Substring format( fmt.Format );
141 for(;;)
142 {
143 // get next and log substring between commands
144 integer idx= format.IndexOf( '%' );
145 if ( idx >= 0 )
146 {
147 format.ConsumeChars<NC, lang::CurrentData::Keep>( idx, buf, 1 );
148 processVariable( domain.FullPath, verbosity, scope, buf, format );
149 }
150 else
151 {
152 buf._<NC>( format );
153 break;
154 }
155 }
156}
157
158void TextLogger::processVariable( const NString& domainPath,
159 Verbosity verbosity,
160 detail::ScopeInfo& scope,
161 AString& dest,
162 Substring& variable )
163
164{
165 // process commands
166 auto& fmt= varFormatMetaInfo .Get<FormatMetaInfo>();
167 auto& autoSizes= varFormatAutoSizes.Get<FormatAutoSizes>();
168 character c2;
169 switch ( variable.ConsumeChar() )
170 {
171 // scope info
172 case 'S':
173 {
174 // read sub command
175 NString val;
176 switch( c2= variable.ConsumeChar() )
177 {
178 case 'P': // SP: full path
179 {
180 val= scope.GetFullPath();
181 if ( val.IsEmpty() )
182 val= GetFormatOther().NoSourceFileInfo;
183 } break;
184
185 case 'p': // Sp: trimmed path
186 {
187 integer previousLength= dest.Length();
188 scope.GetTrimmedPath( dest );
189 if( dest.Length() != previousLength )
190 return;
191 val= GetFormatOther().NoSourceFileInfo;
192 } break;
193
194 case 'F': // file name
195 {
196 val= scope.GetFileName();
197 if ( val.IsEmpty() )
198 val= GetFormatOther().NoSourceFileInfo;
199 } break;
200
201 case 'f': // file name without extension
202 {
203 val= scope.GetFileNameWithoutExtension();
204 if ( val.IsEmpty() )
205 val= GetFormatOther().NoSourceFileInfo;
206 } break;
207
208
209 case 'M': // method name
210 {
211 val= scope.GetMethod();
212 if ( val.IsEmpty() )
213 val= GetFormatOther().NoMethodInfo;
214 } break;
215
216 case 'L': // line number
217 {
218 dest._<NC>( scope.GetLineNumber() );
219 return;
220 }
221
222 default:
223 {
224 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
225 "Unknown format variable '%S{}' (only one warning)", c2 )
226 ALIB_DBG( FormatWarningOnce= true; )
227 val= "%ERROR";
228 } break;
229 }
230 dest._( val );
231 return;
232 }
233
234 // %Tx: Time
235 case 'T':
236 {
237 c2= variable.ConsumeChar();
238
239 // %TD: Date
240 if ( c2 == 'D' )
241 {
242 // get time stamp as CalendarDateTime once
243 if ( callerDateTime.Year == (std::numeric_limits<int>::min)() )
244 callerDateTime.Set( DateConverter.ToDateTime( scope.GetTimeStamp() ) );
245
246 // if standard format, just write it out
247 if ( GetFormatDate().Date.Equals<NC>( A_CHAR("yyyy-MM-dd") ) )
248 {
249 dest._<NC>( alib::Dec( callerDateTime.Year, 4 ) )._<NC>( '-' )
250 ._<NC>( alib::Dec( callerDateTime.Month, 2 ) )._<NC>( '-' )
251 ._<NC>( alib::Dec( callerDateTime.Day, 2 ) );
252 }
253 // user-defined format
254 else
255 callerDateTime.Format( GetFormatDate().Date, dest );
256
257 return;
258 }
259
260
261 // %TT: Time of Day
262 if ( c2 == 'T' )
263 {
264 // get time stamp as CalendarDateTime once
265 if ( callerDateTime.Year == (std::numeric_limits<int>::min)() )
266 callerDateTime.Set( DateConverter.ToDateTime( scope.GetTimeStamp() ) );
267
268 // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!)
269 // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow.
270 if ( GetFormatDate().TimeOfDay.Equals<NC>( A_CHAR("HH:mm:ss") ) )
271 {
272 dest._<NC>( alib::Dec(callerDateTime.Hour, 2) )._<NC>( ':' )
273 ._<NC>( alib::Dec(callerDateTime.Minute, 2) )._<NC>( ':' )
274 ._<NC>( alib::Dec(callerDateTime.Second, 2) );
275 }
276
277 // user-defined format
278 else
279 callerDateTime.Format( GetFormatDate().TimeOfDay, dest );
280 }
281
282 // %TC: Time elapsed since created
283 else if ( c2 == 'C' )
284 {
285 auto elapsedTime= scope.GetTimeStamp() - TimeOfCreation;
286 auto elapsedSecs= elapsedTime.InAbsoluteSeconds();
287 CalendarDuration elapsed( elapsedTime );
288
289 // determine number of segments to write and match this to recent (autosizes) value
290 int timeSize= elapsedSecs >= 24*3600 ? 6
291 : elapsedSecs >= 10*3600 ? 5
292 : elapsedSecs >= 3600 ? 4
293 : elapsedSecs >= 10*60 ? 3
294 : elapsedSecs >= 60 ? 2
295 : elapsedSecs >= 9 ? 1
296 : 0;
297 timeSize= int(autoSizes.Main.Next( AutoSizes::Types::Field, timeSize, 0 ));
298
299
300 if ( timeSize >= 4 ) dest._<NC>( elapsed.Days )._<NC>( GetFormatDate().ElapsedDays );
301 if ( timeSize >= 3 ) dest._<NC>( alib::Dec(elapsed.Hours , timeSize >= 5 ? 2 : 1 ) )._<NC>( ':' );
302 if ( timeSize >= 2 ) dest._<NC>( alib::Dec(elapsed.Minutes, timeSize >= 3 ? 2 : 1 ) )._<NC>( ':' );
303 dest._<NC>( alib::Dec(elapsed.Seconds, timeSize >= 1 ? 2 : 1) )._<NC>( '.' );
304 dest._<NC>( alib::Dec(elapsed.Milliseconds, 3) );
305 }
306
307 // %TL: Time elapsed since last log call
308 else if ( c2 == 'L' )
309 writeTimeDiff( dest, scope.GetTimeStamp().Since( TimeOfLastLog ).InNanoseconds() );
310
311 else
312 {
313 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
314 "Unknown format variable '%T{}' (only one warning)", c2 )
315 ALIB_DBG( FormatWarningOnce= true; )
316 }
317 return;
318 }
319
320
321 // Thread
322 case 't':
323 {
324 c2= variable.ConsumeChar();
325
326 if ( c2 == 'N' ) // %tN: thread name
327 {
328 #if !ALIB_SINGLE_THREADED
329 const String& threadName= scope.GetThreadNameAndID(nullptr);
330 #else
331 String msg( A_CHAR("SINGLE_THREADED") );
332 const String& threadName= msg;
333 #endif
334 dest._<NC>( Field( threadName,
335 autoSizes.Main.Next(
336 AutoSizes::Types::Field, threadName.Length(), 0),
337 lang::Alignment::Center ) );
338 }
339 else if ( c2 == 'I' ) // %tI: thread ID
340 {
341 String32 threadID;
342 #if !ALIB_SINGLE_THREADED
343 threadID._( scope.GetThreadID() );
344 #else
345 threadID << "-1";
346 #endif
347 dest._<NC>( Field( threadID,
348 autoSizes.Main.Next(
349 AutoSizes::Types::Field, threadID .Length(), 0),
350 lang::Alignment::Center ) );
351 }
352 else
353 {
354 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
355 "Unknown format variable '%t{}' (only one warning)", c2 )
356 ALIB_DBG( FormatWarningOnce= true; )
357 }
358 return;
359 }
360
361 case 'L':
362 {
363 c2= variable.ConsumeChar();
364 if ( c2 == 'G' ) dest._<NC>( GetName() );
365 else if ( c2 == 'X' ) dest._<NC>( scope.GetLoxName() );
366 else
367 {
368 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
369 "Unknown format variable '%L{}' (only one warning)", c2 )
370 ALIB_DBG( FormatWarningOnce= true; )
371 }
372 return;
373 }
374
375 case 'P':
376 {
377 dest._<NC>( ProcessInfo::Current().Name );
378 return;
379 }
380
381 case 'V':
382 dest._<NC>( verbosity == Verbosity::Error ? fmt.VerbosityError
383 : verbosity == Verbosity::Warning ? fmt.VerbosityWarning
384 : verbosity == Verbosity::Info ? fmt.VerbosityInfo
385 : fmt.VerbosityVerbose );
386 return;
387
388 case 'D':
389 {
390 dest._( Field( domainPath,
391 autoSizes.Main.Next(
392 AutoSizes::Types::Field, domainPath.Length(), 0 ),
393 lang::Alignment::Left ) );
394 return;
395 }
396
397 case '#':
398 dest._<NC>( alib::Dec( CntLogs, GetFormatOther().LogNumberMinDigits ) );
399 return;
400
401 // A: Auto tab
402 case 'A':
403 {
404 // read extra space from format string
405 integer extraSpace;
406 if( !variable.ConsumeDecDigits( extraSpace ) )
407 extraSpace= 1;
408 integer currentLength= dest.WStringLength();
409 integer tabPos= autoSizes.Main.Next(
410 AutoSizes::Types::Tabstop, currentLength, extraSpace);
411 dest.InsertChars(' ', tabPos - currentLength );
412
413 return;
414 }
415
416 case 'N':
417 dest._<NC>( GetName() );
418 return;
419
420 default:
421 ALIB_ASSERT_WARNING( FormatWarningOnce, "ALOX",
422 "Unknown format character '{}' (only one warning)", *( variable.Buffer() -1 ) )
423 ALIB_DBG( FormatWarningOnce= true; )
424 return;
425 }// switch
426}
427
428void TextLogger::writeTimeDiff( AString& buf, int64_t diffNanos )
429{
430 auto& td= GetFormatTimeDiff();
431
432 // unmeasurable?
433 if ( diffNanos < td.Minimum )
434 {
435 buf._<NC>( td.None );
436 return;
437 }
438
439 if ( diffNanos < 1000 )
440 {
441 buf._<NC>( alib::Dec( diffNanos, 3 ) )._<NC>( td.Nanos );
442 return;
443 }
444
445 // we continue with micros
446 int64_t diffMicros= diffNanos / 1000L;
447
448 // below 1000 microseconds?
449 if ( diffMicros < 1000 )
450 {
451 buf._<NC>( alib::Dec( diffMicros, 3 ) );
452 buf._<NC>( td.Micros );
453 return;
454 }
455
456 // below 1000 ms?
457 if ( diffMicros < 1000000 )
458 {
459 buf._<NC>( alib::Dec( (diffMicros / 1000), 3 ) )._<NC>( td.Millis );
460 return;
461 }
462
463
464 // below 10 secs (rounded) ?
465 if ( diffMicros < 9995000 )
466 {
467 // convert to hundredth of secs
468 int64_t hundredthSecs= ((diffMicros / 1000) + 5) / 10;
469
470 // print two digits after dot x.xx
471 buf._<NC>( alib::Dec( (hundredthSecs / 100), 1 ) )
472 ._<NC>( '.' )
473 ._<NC>( alib::Dec( (hundredthSecs % 100), 2 ) )
474 ._<NC>( td.Secs );
475 return;
476 }
477
478 // convert to tenth of secs
479 int64_t tenthSecs= ((diffMicros / 10000) + 5) / 10 ;
480
481 // below 100 secs ?
482 if ( tenthSecs < 1000 )
483 {
484 // print one digits after dot xx.x (round value by adding 5 hundredth)
485 buf._<NC>( alib::Dec( ( tenthSecs / 10 ), 2 ) )
486 ._<NC>( '.' )
487 ._<NC>( alib::Dec( ( tenthSecs % 10 ), 1 ) )
488 ._<NC>( td.Secs );
489 return;
490 }
491
492 // below 10 mins ?
493 if ( tenthSecs < 6000 )
494 {
495 // convert to hundredth of minutes
496 int64_t hundredthMins= tenthSecs / 6;
497
498 // print two digits after dot x.xx
499 buf._<NC>( alib::Dec( (hundredthMins / 100), 1 ) )
500 ._<NC>( '.' )
501 ._<NC>( alib::Dec( (hundredthMins % 100), 2 ) )
502 ._<NC>( td.Mins );
503 return;
504 }
505
506 // convert to tenth of minutes
507 int64_t tenthMins= tenthSecs / 60;
508
509 // below 100 mins ?
510 if ( tenthMins < 1000 )
511 {
512 // print one digits after dot xx.x (round value by adding 5 hundredth)
513 buf._<NC>( alib::Dec( (tenthMins / 10), 2 ) )
514 ._<NC>( '.' )
515 ._<NC>( alib::Dec( (tenthMins % 10), 1 ) )
516 ._<NC>( td.Mins );
517 return;
518 }
519
520 // below ten hours?
521 if ( tenthMins < 6000 )
522 {
523 // convert to hundredth of hours
524 int64_t hundredthHours= tenthMins / 6;
525
526 // print two digits after dot x.xx
527 buf._<NC>( alib::Dec( (hundredthHours / 100), 1 ) )
528 ._<NC>( '.' )
529 ._<NC>( alib::Dec( (hundredthHours % 100), 2 ))
530 ._<NC>( td.Hours );
531 return;
532 }
533
534 // convert to tenth of minutes
535 int64_t tenthHours= tenthMins / 60;
536
537 // below 10 hours ?
538 if ( tenthHours < 1000 )
539 {
540 // print two digits after dot x.xx
541 buf._<NC>( alib::Dec( (tenthHours / 10), 2 ) )
542 ._<NC>( '.' )
543 ._<NC>( alib::Dec( (tenthHours % 10), 1 ) )
544 ._<NC>( td.Hours );
545 return;
546 }
547
548 // below 100 hours ?
549 if ( tenthHours < 1000 )
550 {
551 // print one digits after dot xx.x (round value by adding 5 hundredth)
552 buf._<NC>( alib::Dec( (tenthHours / 10), 2 ) )
553 ._<NC>( '.' )
554 ._<NC>( alib::Dec( ((tenthHours / 10) % 10), 1 ) )
555 ._<NC>( td.Hours );
556 return;
557 }
558
559 // convert to hundredth of days
560 int64_t hundredthDays= tenthHours * 10 / 24;
561
562 // below 10 days ?
563 if ( hundredthDays < 1000 )
564 {
565 // print two digits after dot x.xx
566 buf._<NC>( alib::Dec( (hundredthDays / 100), 1 ) )
567 ._<NC>( '.' )
568 ._<NC>( alib::Dec( (hundredthDays % 100), 2 ) )
569 ._<NC>( td.Days );
570 return;
571 }
572
573 // 10 days or more (print days plus one digit after the comma)
574 // print one digits after dot xx.x (round value by adding 5 hundredth)
575 buf ._<NC>( alib::Dec( (hundredthDays / 100), 2 ) )
576 ._<NC>( '.' )
577 ._<NC>( alib::Dec( ((hundredthDays / 10) % 10), 1 ) )
578 ._<NC>( td.Days );
579}
580
581
582// #################################################################################################
583// TextLogger
584// #################################################################################################
585TextLogger::TextLogger( const NString& pName, const NString& typeName, bool pUsesStdStreams )
586: Logger( pName, typeName )
587, usesStdStreams( pUsesStdStreams )
588, varFormatMetaInfo (variables::CampVariable(alib::ALOX))
589, varFormatDateTime (variables::CampVariable(alib::ALOX))
590, varFormatTimeDiff (variables::CampVariable(alib::ALOX))
591, varFormatMultiLine(variables::CampVariable(alib::ALOX))
592, varFormatOther (variables::CampVariable(alib::ALOX))
593, varFormatAutoSizes(variables::CampVariable(alib::ALOX))
594, varReplacements (variables::CampVariable(alib::ALOX))
595{
596 logBuf.SetBuffer( 256 );
597 msgBuf.SetBuffer( 256 );
598}
599
600TextLogger::~TextLogger()
601{
602 if (Converter)
603 delete Converter;
604 ALIB_ASSERT( msgBuf.IsEmpty(), "ALOX" )
605}
606
607void TextLogger::AcknowledgeLox( detail::LoxImpl* , lang::ContainerOp op )
608{
609 // --------------- insert ------------------
610 if( op == lang::ContainerOp::Insert )
611 {
612 if ( Converter == nullptr )
613 Converter= new textlogger::StandardConverter();
614
615 // Variable AUTO_SIZES: use last session's values
616 {ALIB_LOCK_WITH(ALOX.GetConfig())
617 varFormatAutoSizes.Declare(Variables::AUTO_SIZES, Name );
618 (void) varFormatAutoSizes.Define();
619 Converter->SetAutoSizes( &varFormatAutoSizes.Get<FormatAutoSizes>().LogMessage );
620 }
621
622 // Variable <name>_FORMAT / <typeName>_FORMAT:
623 {ALIB_LOCK_WITH(ALOX.GetConfig())
624 const Declaration* variableDecl= Declaration::Get( Variables::FORMAT );
625 const Declaration* privateDecl= ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
626
627 if( !varFormatMetaInfo.Try( privateDecl )
628 && !varFormatMetaInfo.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName() ) ) )
629 {
630 varFormatMetaInfo.Declare( privateDecl );
631 ALIB_ASSERT_ERROR(varFormatMetaInfo.IsDefined(), "ALOX",
632 "Mandatory (usually resourced) default value is missing for variable \"{}\".",
634 }
635 }
636
637 // Variable <name>_FORMAT_DATE_TIME / <typeName>_FORMAT_DATE_TIME:
638 {ALIB_LOCK_WITH(ALOX.GetConfig())
639 auto* variableDecl= Declaration::Get( Variables::FORMAT_DATE_TIME );
640 auto* privateDecl= ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
641 if( !varFormatDateTime.Try( privateDecl )
642 && !varFormatDateTime.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName() ) ) )
643 {
644 varFormatDateTime.Declare( privateDecl );
645
646 ALIB_ASSERT_ERROR(varFormatDateTime.IsDefined(), "ALOX",
647 "Mandatory (usually resourced) default value is missing for variable \"{}\".",
648 varFormatDateTime)
649 }
650 }
651
652 // Variable <name>FORMAT_TIME_DIFF / <typeName>FORMAT_TIME_DIFF:
654 auto* variableDecl= Declaration::Get( Variables::FORMAT_TIME_DIFF );
655 auto* privateDecl= ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
656 if( !varFormatTimeDiff.Try( privateDecl )
657 && !varFormatTimeDiff.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName() ) ) )
658 {
659 varFormatTimeDiff.Declare( privateDecl );
660 ALIB_ASSERT_ERROR(varFormatTimeDiff.IsDefined(), "ALOX",
661 "Mandatory (usually resourced) default value is missing for variable \"{}\".",
662 varFormatTimeDiff)
663 }
664 }
665
666 // Variable <name>FORMAT_MULTILINE / <typeName>FORMAT_MULTILINE:
668 auto* variableDecl= Declaration::Get( Variables::FORMAT_MULTILINE );
669 auto* privateDecl= ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
670 if( !varFormatMultiLine.Try( privateDecl )
671 && !varFormatMultiLine.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName() ) ) )
672 {
673 varFormatMultiLine.Declare( privateDecl );
674 ALIB_ASSERT_ERROR(varFormatMultiLine.IsDefined(), "ALOX",
675 "Mandatory (usually resourced) default value is missing for variable \"{}\".",
676 varFormatMultiLine)
677 }
678 }
679
680 // Variable <name>FORMAT_OTHER / <typeName>FORMAT_OTHER:
682 auto* variableDecl= Declaration::Get( Variables::FORMAT_OTHER );
683 auto* privateDecl= ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
684 if( !varFormatOther.Try( privateDecl )
685 && !varFormatOther.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName() ) ) )
686 {
687 varFormatOther.Declare( privateDecl );
688 ALIB_ASSERT_ERROR(varFormatOther.IsDefined(), "ALOX",
689 "Mandatory (usually resourced) default value is missing for variable \"{}\".",
690 varFormatOther )
691 }
692 }
693
694 // Variable <name>FORMAT_REPLACEMENTS / <typeName>FORMAT_REPLACEMENTS:
696 auto* variableDecl= Declaration::Get( Variables::REPLACEMENTS );
697 auto* privateDecl = ALOX.GetConfig()->StoreDeclaration(variableDecl, GetName() );
698 if( !varReplacements.Try( privateDecl)
699 && !varReplacements.Try( ALOX.GetConfig()->StoreDeclaration(variableDecl, GetTypeName())) )
700 {
701 varReplacements.Declare(privateDecl);
702 }
703
704 // if not defined, create the empty variable. For one, this way it is not needed to be
705 // checked before access, and furthermore this allows it to appear in config files.
706 if( !varReplacements.IsDefined() )
707 (void) varReplacements.Define(Priority::DefaultValues - 1);
708 }
709 }
710}
711
712
713void TextLogger::SetReplacement( const String& searched, const String& replacement )
714{
715 auto& replacements= GetReplacements().Pairs;
716 // if exists, replace replacement
717 for( auto it= replacements.begin(); it < replacements.end(); it+= 2)
718 if ( it->Equals<NC>( searched ) )
719 {
720 if ( replacement.IsNotNull() )
721 {
722 ++it;
723 (*it).Reset( replacement );
724 return;
725 }
726
727 replacements.erase( it );
728 replacements.erase( it );
729 return;
730 }
731
732 // append at the end
733 if ( replacement.IsNotNull() )
734 {
735 replacements.insert( replacements.end(), AStringPA(replacements.get_allocator().GetAllocator()) );
736 replacements.back() << searched;
737 replacements.insert( replacements.end(), AStringPA(replacements.get_allocator().GetAllocator()) );
738 replacements.back() << replacement;
739 }
740}
741
743{
744 GetReplacements().Pairs.clear();
745}
746
748{
749 Converter->ResetAutoSizes();
750}
751
752
753void TextLogger::Log( detail::Domain& domain, Verbosity verbosity, BoxesMA& logables,
754 detail::ScopeInfo& scope )
755{
756 // we store the current msgBuf length and reset the buffer to this length when exiting.
757 // This allows recursive calls! Recursion might happen with the evaluation of the
758 // logables (in the next line).
759 StringLengthResetter msgBufResetter(msgBuf);
760 Converter->ConvertObjects( msgBuf, logables );
761
762 // replace strings in message
763 auto& replacements= GetReplacements().Pairs;
764 for ( size_t i= 0; i < replacements.size() ; i+= 2 )
765 msgBuf.SearchAndReplace( replacements[i],
766 replacements[i + 1], msgBufResetter.OriginalLength() );
767
768 // get auto-sizes and set write protected in case someone wrote the variable with higher
769 // priority than we do.
770 auto& autoSizes= varFormatAutoSizes.Get<FormatAutoSizes>();
771 if( varFormatAutoSizes.GetPriority() > Priority::Session )
772 autoSizes.Main .WriteProtected=
773 autoSizes.LogMessage.WriteProtected= true;
774
775 // clear log buffer and write meta info
776 logBuf.Reset();
777 autoSizes.Main.Restart();
778 writeMetaInfo( logBuf, domain, verbosity, scope );
779 logBuf._<NC>( ESC::EOMETA );
780
781 // check for empty messages
782 auto& fmt= varFormatMetaInfo.Get<FormatMetaInfo>();
783 if ( msgBuf.Length() == msgBufResetter.OriginalLength() )
784 {
785 // log empty msg and quit
786 logBuf._<NC>( fmt.MsgSuffix );
787 if ( usesStdStreams )
789 logText( domain, verbosity, logBuf, scope, -1 );
790 }
791 else
792 logText( domain, verbosity, logBuf, scope, -1 );
793 return;
794 }
795
796 // single line output
797 auto& multiLine= varFormatMultiLine.Get<FormatMultiLine>();
798 if ( multiLine.Mode == 0 )
799 {
800 // replace line separators
801 integer cntReplacements=0;
802 if ( multiLine.Delimiter.IsNotNull() )
803 cntReplacements+= msgBuf.SearchAndReplace( multiLine.Delimiter, multiLine.DelimiterReplacement, msgBufResetter.OriginalLength() );
804 else
805 {
806 String replacement= multiLine.DelimiterReplacement;
807 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\r\n"), replacement, msgBufResetter.OriginalLength() );
808 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\r"), replacement, msgBufResetter.OriginalLength() );
809 cntReplacements+= msgBuf.SearchAndReplace( A_CHAR("\n"), replacement, msgBufResetter.OriginalLength() );
810 }
811
812 // append msg to logBuf
813 if ( cntReplacements == 0 )
814 {
815 logBuf._<NC>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
816 }
817 else
818 {
819 logBuf._<NC>( multiLine.Prefix );
820 logBuf._<NC>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
821 logBuf._<NC>( multiLine.Suffix );
822 }
823 logBuf._<NC>( fmt.MsgSuffix );
824
825 // now do the logging by calling our derived class's logText
826 if ( usesStdStreams )
828 logText( domain, verbosity, logBuf, scope, -1 );
829 }
830 else
831 logText( domain, verbosity, logBuf, scope, -1 );
832 }
833
834 // multiple line output
835 auto prevIndex= autoSizes.Main.ActualIndex;
836 integer actStart= msgBufResetter.OriginalLength();
837 int lineNo= 0;
838 integer lbLenBeforeMsgPart= logBuf.Length();
839
840 // loop over lines in msg
841 while ( actStart < msgBuf.Length() )
842 {
843 // find next end
844 integer delimLen;
845 integer actEnd;
846
847 // no delimiter given: search '\n' and then see if it is "\r\n" in fact
848 if (multiLine.Delimiter.IsEmpty() )
849 {
850 delimLen= 1;
851
852 actEnd= msgBuf.IndexOf<NC>( '\n', actStart );
853 if( actEnd > actStart )
854 {
855 if( msgBuf.CharAt<NC>(actEnd - 1) == '\r' )
856 {
857 --actEnd;
858 delimLen= 2;
859 }
860 }
861 }
862 else
863 {
864 delimLen= multiLine.Delimiter.Length();
865 actEnd= msgBuf.IndexOf<NC>( multiLine.Delimiter, actStart );
866 }
867
868 // not found a delimiter? - log the rest
869 if ( actEnd < 0 )
870 {
871 // single line?
872 if ( lineNo == 0 )
873 {
874 logBuf._<NC>( msgBuf, msgBufResetter.OriginalLength(), msgBuf.Length() - msgBufResetter.OriginalLength() );
875 logBuf._<NC>( fmt.MsgSuffix );
876
877 if ( usesStdStreams )
879 logText( domain, verbosity, logBuf, scope, -1 );
880 }
881 else
882 logText( domain, verbosity, logBuf, scope, -1 );
883
884 // stop here
885 goto LOG_END;
886 }
887
888 // store actual end
889 actEnd= msgBuf.Length();
890 }
891
892 // found a delimiter
893
894 // signal start of multi line log
895 if ( lineNo == 0 )
896 {
897 if ( usesStdStreams)
900 }
901 else
903 }
904
905 // in mode 3, 4, meta info is deleted
906 if ( lineNo == 0 && ( multiLine.Mode == 3 || multiLine.Mode == 4 ) )
907 {
908 // log headline in mode 3
909 if ( multiLine.Mode == 3 )
910 {
911 logBuf._<NC>( multiLine.Headline );
912 autoSizes.Main.ActualIndex= prevIndex;
913 logText( domain, verbosity, logBuf, scope, 0 );
914 }
915
916 // remember zero as offset
917 lbLenBeforeMsgPart= 0;
918 }
919
920 // clear meta-information?
921 if ( multiLine.Mode == 2 )
922 {
923 if (lineNo != 0 )
924 {
925 logBuf.Reset(ESC::EOMETA);
926 autoSizes.Main.ActualIndex= prevIndex;
927 }
928 }
929 // reset logBuf length to marked position
930 else
931 {
932 logBuf.ShortenTo( lbLenBeforeMsgPart );
933 autoSizes.Main.ActualIndex= prevIndex;
934 }
935
936 // append message and do the log
937 logBuf._<NC>( multiLine.Prefix );
938 logBuf._<NC>( msgBuf, actStart, actEnd - actStart );
939 logBuf._<NC>( multiLine.Suffix );
940 actStart= actEnd + delimLen;
941 if ( actStart >= msgBuf.Length() )
942 logBuf._<NC>( fmt.MsgSuffix );
943 logText( domain, verbosity, logBuf, scope, lineNo );
944
945 // next
946 ++lineNo;
947 }
948
949 // signal end of multi line log
950 if ( lineNo > 0 )
951 {
952 if ( usesStdStreams)
955 }
956 else
958 }
959
960 LOG_END:
961 // In case of changes, re-define the auto-sizes variable.
962 // This might trigger a listener that monitors this session-type variable
963 if( autoSizes.Main .IsChanged()
964 || autoSizes.LogMessage.IsChanged() )
965 (void) varFormatAutoSizes.Define( Priority::Session );
966} // TextLogger::Log()
967
968} // namespace [alib::lox::textlogger]
969
970
SharedConfiguration & GetConfig()
Definition camp.inl:215
static constexpr character EOMETA[4]
End of meta-information in log string.
ALIB_DLL StandardConverter()
Constructor.
virtual ALIB_DLL void ConvertObjects(AString &target, BoxesMA &logables) override
virtual ALIB_DLL ~StandardConverter() override
Virtual destructor.
std::vector< Formatter * > Formatters
int cntRecursion
A counter to detect recursive calls.
virtual ALIB_DLL void writeMetaInfo(AString &buffer, detail::Domain &domain, Verbosity verbosity, detail::ScopeInfo &scope)
virtual ALIB_DLL void SetReplacement(const String &searched, const String &replacement)
virtual ALIB_DLL void Log(detail::Domain &domain, Verbosity verbosity, BoxesMA &logables, detail::ScopeInfo &scope) override
virtual ALIB_DLL void ResetAutoSizes()
AString logBuf
The internal log Buffer.
AString msgBuf
The buffers for converting the logables.
virtual void logText(detail::Domain &domain, Verbosity verbosity, AString &msg, detail::ScopeInfo &scope, int lineNumber)=0
virtual ALIB_DLL void ClearReplacements()
Removes all pairs of searched strings and their replacement value.
virtual void notifyMultiLineOp(lang::Phase phase)=0
constexpr bool IsNotNull() const
Definition string.inl:357
static const Declaration * Get(TEnum element)
#define A_CHAR(STR)
#define ALIB_ASSERT_WARNING(cond, domain,...)
Definition alib.inl:1050
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1049
#define ALIB_LOCK_WITH(lock)
Definition alib.inl:1322
@ Begin
The start of a transaction.
@ End
The end of a transaction.
This namespaces defines class TextLogger and its helpers.
Definition loxpimpl.inl:13
@ FORMAT_TIME_DIFF
Denotes configuration variable ALOX/LOGGERNAME/FORMAT_TIME_DIFF used by class TextLogger.
Definition aloxcamp.inl:58
@ REPLACEMENTS
Denotes configuration variable ALOX/LOGGERNAME/REPLACEMENTS used by class TextLogger.
Definition aloxcamp.inl:64
@ FORMAT_DATE_TIME
Denotes configuration variable ALOX/LOGGERNAME/FORMAT_DATE_TIME used by class TextLogger.
Definition aloxcamp.inl:52
@ FORMAT_MULTILINE
Denotes configuration variable ALOX/LOGGERNAME/FORMAT_MULTILINE used by class TextLogger.
Definition aloxcamp.inl:55
@ FORMAT_OTHER
Denotes configuration variable ALOX/LOGGERNAME/FORMAT_OTHER used by class TextLogger.
Definition aloxcamp.inl:61
ALIB_DLL Lock STD_IOSTREAMS_LOCK
Definition locks.cpp:44
strings::TStringLengthResetter< character,lang::HeapAllocator > StringLengthResetter
Type alias in namespace alib.
format::FormatterJavaStyle FormatterJavaStyle
Type alias in namespace alib.
variables::Declaration Declaration
Type alias in namespace alib.
format::FormatterPythonStyle FormatterPythonStyle
Type alias in namespace alib.
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
lox::ALoxCamp ALOX
The singleton instance of ALib Camp class ALoxCamp.
Definition aloxcamp.cpp:53
boxing::TBoxes< MonoAllocator > BoxesMA
Type alias in namespace alib.
Definition boxes.inl:245
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2381
strings::TAString< character, PoolAllocator > AStringPA
Type alias in namespace alib.
characters::character character
Type alias in namespace alib.
AutoSizes Main
The instance used with the meta info format string.