1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2011 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "log_report.h"
23 #include "nel/misc/common.h"
24 #include "nel/misc/displayer.h"
25 #include "nel/misc/file.h"
26 #include "nel/misc/path.h"
27 #include "nel/misc/variable.h"
29 using namespace NLMISC
;
33 CVariable
<string
> LogPath( "LogReport","LogPath", "Path of the log files", ".", 0, true );
35 const uint MAX_LOG_LINE_SIZE
= 1024;
36 //nlctassert(MAX_LOG_LINE_SIZE>0);
38 enum TLogLineHeader
{ LHDate
, LHTime
, LHType
, LHThread
, LHService
, LHCodeFile
, LHCodeLine
, LHSeparator
, LH_NB_FIELDS
};
42 bool isLogFile( const std::string
& filename
)
44 uint len
= (uint
)filename
.size();
45 return (len
>= 4 ) && (filename
.substr( len
-4 ) == ".log");
49 inline bool isNumberChar( char c
)
51 return (c
>= '0') && (c
<= '9');
55 void sortLogFiles( vector
<std::string
>& filenames
)
58 for ( i
=0; i
!=filenames
.size(); ++i
)
60 // Ensure that a log file without number comes *after* the ones with a number
61 string name
= string(filenames
[i
]);
62 string::size_type dotpos
= name
.find_last_of('.');
63 if ( (dotpos
!=string::npos
) && (dotpos
> 2) )
65 if ( ! (isNumberChar(name
[dotpos
-1]) && isNumberChar(name
[dotpos
-2]) && isNumberChar(name
[dotpos
-3])) )
67 name
= name
.substr( 0, dotpos
) + "ZZZ" + name
.substr( dotpos
);
68 filenames
[i
] = name
.c_str();
72 sort( filenames
.begin(), filenames
.end() );
73 for ( i
=0; i
!=filenames
.size(); ++i
)
75 // Set the original names back
76 string name
= filenames
[i
];
77 string::size_type tokenpos
= name
.find( "ZZZ." );
78 if ( tokenpos
!= string::npos
)
80 name
= name
.substr( 0, tokenpos
) + name
.substr( tokenpos
+ 3 );
81 filenames
[i
] = name
.c_str();
86 void CMakeLogTask::setLogPath(const std::string
& logPath
)
88 _LogPaths
.resize( 1 );
89 _LogPaths
[0] = logPath
;
92 void CMakeLogTask::setLogPaths(const std::vector
<std::string
>& logPaths
)
97 void CMakeLogTask::setLogPathToDefault()
99 setLogPath( LogPath
.get() );
105 CMakeLogTask::~CMakeLogTask()
107 if ( _Thread
) // implies && _OutputLogReport
122 void CMakeLogTask::start()
133 _Thread
= NLMISC::IThread::create( this );
134 _OutputLogReport
= new CLogReport();
142 void CMakeLogTask::clear()
149 if (_OutputLogReport
)
151 delete _OutputLogReport
;
152 _OutputLogReport
= NULL
;
159 void CMakeLogTask::terminateTask()
161 if (!_Thread
) // _Thread _implies _OutputLogReport
171 bool isOfLogDotLogFamily( const std::string
& filename
)
173 return ((filename
== "log.log") ||
174 ((filename
.size() == 10) &&
175 (filename
.substr( 0, 3 ) == "log") &&
176 isNumberChar(filename
[3]) && isNumberChar(filename
[4]) && isNumberChar(filename
[5]) &&
177 (filename
.substr( 6, 4 ) == ".log")) );
181 enum TVersionTargetMode
{ TTMAll
, TTMMatchAllV
, TTMMatchExactV
, TTMMatchGreaterV
, TTMMatchLowerV
} targetMode
;
182 const uint CurrentVersion
= std::numeric_limits
<uint
>::max();
184 // Return true and logVersion, or false if not a log with version
185 bool getLogVersion( const std::string
& filename
, uint
& logVersion
)
187 uint len
= (uint
)filename
.size();
188 if ( (len
> 4) && (filename
.substr( len
-4 ) == ".log") )
190 if ( filename
.substr(0, 3) == "log" )
193 ((len
== 10) && (isNumberChar(filename
[3]) && isNumberChar(filename
[4]) && isNumberChar(filename
[5]))) )
195 logVersion
= CurrentVersion
;
199 else if ( filename
[0] == 'v' )
201 string::size_type p
= filename
.find( "_", 1 );
202 if ( p
!= string::npos
)
204 if ( (len
== p
+ 8) ||
205 ((len
== p
+ 11) && (isNumberChar(filename
[p
+4]) && isNumberChar(filename
[p
+5]) && isNumberChar(filename
[p
+6]))) )
207 NLMISC::fromString( filename
.substr( 1, p
-1 ), logVersion
);
216 // Assumes filename is .log file
217 bool matchLogTarget( const std::string
& filename
, TVersionTargetMode targetMode
, uint targetVersion
)
219 if ( targetMode
== TTMAll
)
224 // Get version or exclude non-standard log files
225 if ( ! getLogVersion( filename
, version
) )
228 // Exclude non-matching version
229 switch ( targetMode
)
232 return (version
== targetVersion
); // break;
233 case TTMMatchGreaterV
:
234 return (version
>= targetVersion
); // break;
236 return (version
<= targetVersion
); // break;
237 default: // TTMMatchAllV
245 void CMakeLogTask::run()
248 uint targetVersion
= CurrentVersion
;
249 uint lts
= (uint
)_LogTarget
.size();
250 if ( _LogTarget
.empty() || (_LogTarget
== "v") )
252 targetMode
= TTMMatchExactV
;
254 else if ( _LogTarget
== "v*" )
256 targetMode
= TTMMatchAllV
;
258 else if ( _LogTarget
== "*" )
262 else if ( (lts
> 1) && (_LogTarget
[0] == 'v') )
264 uint additionalChars
= 1;
265 if ( _LogTarget
[lts
-1] == '+' )
266 targetMode
= TTMMatchGreaterV
;
267 else if ( _LogTarget
[lts
-1] == '-' )
268 targetMode
= TTMMatchLowerV
;
271 targetMode
= TTMMatchExactV
;
275 NLMISC::fromString( _LogTarget
.substr( 1, lts
-additionalChars
-1 ), targetVersion
);
279 nlwarning( "Invalid log target argument: %s", _LogTarget
.c_str() );
284 // Get log files and sort them
285 vector
<string
> filenames
;
286 vector
<string
> filenamesOfPath
;
287 for ( vector
<string
>::const_iterator ilf
=_LogPaths
.begin(); ilf
!=_LogPaths
.end(); ++ilf
)
289 string path
= (*ilf
);
290 if ( (! path
.empty()) && (path
[path
.size()-1]!='/') )
292 filenamesOfPath
.clear();
293 CPath::getPathContent( path
, false, false, true, filenamesOfPath
, NULL
, true );
294 vector
<string
>::iterator ilf2
= partition( filenamesOfPath
.begin(), filenamesOfPath
.end(), isLogFile
);
295 filenamesOfPath
.erase( ilf2
, filenamesOfPath
.end() );
296 sortLogFiles( filenamesOfPath
);
297 filenames
.insert( filenames
.end(), filenamesOfPath
.begin(), filenamesOfPath
.end() );
301 _OutputLogReport
->reset();
303 char line
[MAX_LOG_LINE_SIZE
];
305 uint nbSkippedFiles
= 0;
306 for ( vector
<string
>::const_iterator ilf
=filenames
.begin(); ilf
!=filenames
.end(); ++ilf
)
308 string shortname
= CFile::getFilename( *ilf
);
310 // Filter log files based on filename before opening them
311 if ( ! matchLogTarget( shortname
, targetMode
, targetVersion
) )
317 nlinfo( "Processing %s (%u/%u)", (*ilf
).c_str(), ilf
-filenames
.begin(), filenames
.size() );
319 if ( logfile
.open( *ilf
, true ) )
321 _OutputLogReport
->setProgress( (uint
)(ilf
-filenames
.begin()), (uint
)filenames
.size() );
322 while ( ! logfile
.eof() )
324 logfile
.getline( line
, MAX_LOG_LINE_SIZE
);
325 line
[MAX_LOG_LINE_SIZE
-1] = '\0'; // force valid end of line
326 _OutputLogReport
->pushLine( line
);
334 nlinfo( "%u lines processed", nbLines
);
335 if ( nbSkippedFiles
!= 0 )
336 nlinfo( "%u log files skipped, not matching target %s", nbSkippedFiles
, _LogTarget
.c_str() );
342 * Add a log line to the report tree
344 void CLogReport::pushLine( const std::string
& line
, NLMISC::CLog::TLogType onlyType
, bool countOtherTypes
)
346 // Ignore session title
347 if ( (line
.size() > 14) && (line
.substr( 0, 14 ) == "Log Starting [") )
350 // Decode standard log line
351 vector
<string
> lineTokens
;
352 explode( line
, string(" "), lineTokens
);
354 if ( lineTokens
.size() < LH_NB_FIELDS
)
358 if ( onlyType
!= CLog::LOG_UNKNOWN
)
360 if ( lineTokens
[LHType
] != IDisplayer::logTypeToString( onlyType
) )
362 if ( countOtherTypes
)
363 storeLine( lineTokens
, true );
369 storeLine( lineTokens
, false );
376 void CLogReportNode::storeLine( const std::vector
<std::string
>& lineTokens
, bool mainCountOnly
)
378 // Get service name from "[machine/]serviceName-serviceId"
379 string service
= lineTokens
[LHService
];
380 string::size_type p
= service
.find( '/' );
381 if ( p
!= string::npos
)
382 service
= service
.substr( p
+1 );
383 p
= service
.find( '-' );
384 if ( p
!= string::npos
)
385 service
= service
.substr( 0, p
);
387 // Store to appropriate child
388 CLogReportLeaf
*child
= getChild( service
);
390 child
= addChild( service
);
391 child
->storeLine( lineTokens
, mainCountOnly
);
398 void CLogReportLeaf::storeLine( const std::vector
<std::string
>& lineTokens
, bool mainCountOnly
)
400 if ( ! mainCountOnly
)
402 // Build key from "codeFile codeLine"
403 string key
= lineTokens
[LHCodeFile
] + ":" + lineTokens
[LHCodeLine
];
404 _LogLineInfo
[key
].addAnOccurence( lineTokens
);
406 ++_Counts
[lineTokens
[LHType
]];
414 void CLogLineInfo::addAnOccurence( const std::vector
<std::string
>& lineTokens
)
416 if ( NbOccurences
== 0 )
418 for ( uint i
=LH_NB_FIELDS
; i
<lineTokens
.size(); ++i
)
420 if ( i
!= LH_NB_FIELDS
)
421 SampleLogText
+= " ";
422 SampleLogText
+= lineTokens
[i
];
432 uint
CLogReportLeaf::getNbTotalLines( NLMISC::CLog::TLogType logType
)
434 return (logType
==NLMISC::CLog::LOG_UNKNOWN
) ? _TotalLines
: _Counts
[NLMISC::IDisplayer::logTypeToString( logType
)];
439 * Get results for a service
441 void CLogReport::reportByService( const std::string
& service
, NLMISC::CLog
*targetLog
)
443 ILogReport
*child
= getChild( service
);
446 child
->report( targetLog
, true );
450 targetLog
->displayNL( "Nothing found for service %s", service
.c_str() );
456 * Get results for a service (all distinct lines, sorted by occurence)
458 void CLogReportLeaf::report( NLMISC::CLog
*targetLog
, bool )
461 typedef multimap
< uint
, pair
< string
, const CLogLineInfo
* >, std::greater
<uint
> > CSortedByOccurenceLogLineInfoMap
;
462 CSortedByOccurenceLogLineInfoMap sortedByOccurence
;
463 for ( CLogLineInfoMap::const_iterator it
=_LogLineInfo
.begin(); it
!=_LogLineInfo
.end(); ++it
)
465 const string
&key
= (*it
).first
;
466 const CLogLineInfo
& info
= (*it
).second
;
467 sortedByOccurence
.insert( make_pair( info
.NbOccurences
, make_pair( key
, &info
) ) );
471 for ( CSortedByOccurenceLogLineInfoMap::const_iterator iso
=sortedByOccurence
.begin(); iso
!=sortedByOccurence
.end(); ++iso
)
473 const string
&key
= (*iso
).second
.first
;
474 const CLogLineInfo
& info
= *((*iso
).second
.second
);
475 targetLog
->displayRawNL( "%s %6u %s : %s", _Service
.c_str(), info
.NbOccurences
, key
.c_str(), info
.SampleLogText
.c_str() );
481 * Return the number of lines displayed
483 uint
CLogReportLeaf::reportPart( uint beginIndex
, uint maxNbLines
, NLMISC::CLog
*targetLog
)
486 CLogLineInfoMap::const_iterator it
;
487 for ( it
=_LogLineInfo
.begin(); it
!=_LogLineInfo
.end(); ++it
)
489 if ( i
>= beginIndex
)
491 if ( i
>= maxNbLines
)
492 return i
- beginIndex
;
494 const string
&key
= (*it
).first
;
495 const CLogLineInfo
& info
= (*it
).second
;
496 targetLog
->displayRawNL( "%s %6u %s : %s", _Service
.c_str(), info
.NbOccurences
, key
.c_str(), info
.SampleLogText
.c_str() );
500 return i
- beginIndex
;
505 * Get summary of results
507 void CLogReportNode::report( NLMISC::CLog
*targetLog
, bool displayDetailsPerService
)
509 uint nb1Sum
=0, nb2Sum
=0;
510 for ( std::vector
<CLogReportLeaf
*>::const_iterator it
=_Children
.begin(); it
!=_Children
.end(); ++it
)
512 CLogReportLeaf
*pt
= (*it
);
514 // Get distinct warnings
515 uint nb1
= pt
->getNbDistinctLines();
518 // Get total warnings, info... but filter out lines with no header
519 uint sumTotalLinesNotUnknown
= 0;
520 uint nbTotalLines
[CLog::LOG_UNKNOWN
];
521 for ( uint i
=CLog::LOG_ERROR
; i
!=CLog::LOG_UNKNOWN
; ++i
)
523 nbTotalLines
[i
] = pt
->getNbTotalLines( (CLog::TLogType
)i
);
524 sumTotalLinesNotUnknown
+= nbTotalLines
[i
];
526 if ( sumTotalLinesNotUnknown
!= 0 )
528 targetLog
->displayRawNL( "%s: \t%u distinct WRN, %u total WRN, %u INF, %u DBG, %u STT, %u AST, %u ERR, %u TOTAL",
529 pt
->service().c_str(), nb1
, nbTotalLines
[CLog::LOG_WARNING
],
530 nbTotalLines
[CLog::LOG_INFO
], nbTotalLines
[CLog::LOG_DEBUG
],
531 nbTotalLines
[CLog::LOG_STAT
], nbTotalLines
[CLog::LOG_ASSERT
],
532 nbTotalLines
[CLog::LOG_ERROR
], pt
->getNbTotalLines( CLog::LOG_UNKNOWN
) );
533 nb2Sum
+= nbTotalLines
[CLog::LOG_WARNING
];
536 targetLog
->displayRawNL( "=> %u distinct, %u total WRN (%u pages)", nb1Sum
, nb2Sum
, nb1Sum
/ NB_LINES_PER_PAGE
+ 1 );
538 if ( displayDetailsPerService
)
540 for ( std::vector
<CLogReportLeaf
*>::const_iterator it
=_Children
.begin(); it
!=_Children
.end(); ++it
)
542 (*it
)->report( targetLog
, true );
549 * Get partial results (pageNum>=1)
551 void CLogReportNode::reportPage( uint pageNum
, NLMISC::CLog
*targetLog
)
553 if ( _Children
.empty() )
555 targetLog
->displayRawNL( "[END OF LOG]" );
559 uint beginIndex
= pageNum
* NB_LINES_PER_PAGE
;
560 uint lineCounter
= 0, prevLineCounter
;
561 for ( std::vector
<CLogReportLeaf
*>::const_iterator it
=_Children
.begin(); it
!=_Children
.end(); ++it
)
563 CLogReportLeaf
*pt
= (*it
);
564 prevLineCounter
= lineCounter
;
565 lineCounter
+= pt
->getNbDistinctLines();
566 if ( lineCounter
>= beginIndex
)
568 uint remainingLines
= pageNum
- (lineCounter
- beginIndex
);
569 pt
->reportPart( beginIndex
- prevLineCounter
, remainingLines
, targetLog
); //
570 while ( remainingLines
!= 0 )
573 if ( it
== _Children
.end() )
575 targetLog
->displayRawNL( "[END OF LOG]" );
579 remainingLines
-= pt
->reportPart( 0, remainingLines
, targetLog
);