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