ATLAS Offline Software
Loading...
Searching...
No Matches
check_log.py
Go to the documentation of this file.
1#!/usr/bin/env python
2#
3# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
4#
5"""Tool to check for error messages in a log file.
6
7By default ERROR, FATAL and CRITICAL messages are considered.
8The config file may be used to provide patterns of lines to exclude from this check
9(known problems or false positives). If no config file is provided, all errors will be shown."""
10
11import re
12import argparse
13import sys
14import os
15
16# Error keywords
17regexMap = {}
18regexMap['error/fatal'] = [
19 r'^ERROR ', '^ERROR:', ' ERROR ', ' FATAL ', 'CRITICAL ', 'ABORT_CHAIN',
20 r'^Exception\:',
21 r'^Caught signal',
22 r'^Core dump',
23 r'tcmalloc\: allocation failed',
24 r'athenaHLT.py\: error',
25 r'athenaEF.py\: error',
26 r'HLTMPPU.*Child Issue',
27 r'HLTMPPU.*Configuration Issue',
28 r'There was a crash',
29 r'illegal instruction',
30 r'failure loading library',
31 r'Cannot allocate memory',
32 r'Attempt to free invalid pointer',
33 r'CUDA error',
34]
35
36regexMap['prohibited'] = [
37 r'inconsistent use of tabs and spaces in indentation',
38 r'glibc detected',
39 r'in state: CONTROLREADY$',
40 r'(^\s*|^\d\d:\d\d:\d\d\s*)missing data: ',
41 r'(^\s*|^\d\d:\d\d:\d\d\s*)missing conditions data: ',
42 r'(^\s*|^\d\d:\d\d:\d\d\s*)can be produced by alg\‍(s\‍): ',
43 r'(^\s*|^\d\d:\d\d:\d\d\s*)required by tool: ',
44 r'pure virtual method called',
45 r'Selected dynamic Aux atribute.*not found in the registry',
46]
47
48regexMap['fpe'] = [
49 r'FPEAuditor.*WARNING FPE',
50]
51
52# Add list of all builtin Python errors
53import builtins
54builtins = dir(builtins)
55builtinErrors = [b for b in builtins if 'Error' in b]
56regexMap['python error'] = builtinErrors
57
58# Traceback keywords
59backtrace = [
60 r'Traceback',
61 r'Shortened traceback',
62 r'stack trace',
63 r'^Algorithm stack',
64 r'^#\d+\s*0x\w+ in ',
65]
66regexMap['backtrace'] = backtrace
67
68# FPEAuditor traceback keywords
69fpeTracebackStart = [r'FPEAuditor.*INFO FPE stacktrace']
70fpeTracebackCont = [
71 ' in function : ',
72 ' included from : ',
73 ' in library : ',
74]
75regexMap['fpe'].extend(fpeTracebackStart)
76
77# Warning keywords
78regexMap['warning'] = ['WARNING ']
79
80for key,exprlist in regexMap.items():
81 if not exprlist:
82 raise RuntimeError(f'Empty regex list for category \'{key}\' -- will match everything!')
83 sys.exit(1)
84
86 parser = argparse.ArgumentParser(description=__doc__, formatter_class=
87 lambda prog : argparse.HelpFormatter(
88 prog, max_help_position=40, width=100))
89
90 parser.add_argument('logfile', metavar='<logfile>', nargs='+',
91 help='log file(s) to scan')
92 parser.add_argument('--config', metavar='<file>',
93 help='specify config file')
94 parser.add_argument('--showexcludestats', action='store_true',
95 help='print summary table with number of matches for each exclude pattern')
96 parser.add_argument('--printpatterns', action='store_true',
97 help='print the list of warning/error patterns being searched for')
98 parser.add_argument('--warnings', action = 'store_true',
99 help='check for WARNING messages')
100 parser.add_argument('--errors', action = 'store_true',
101 help='check for ERROR messages')
102
103 return parser
104
105
106def main():
107 parser = get_parser()
108
109 args = parser.parse_args()
110 if not (args.errors or args.warnings):
111 parser.error('at least one of --errors or --warnings must be enabled')
112
113 ignorePattern = parseConfig(args) if args.config else []
114 rc = 0
115 for i, lf in enumerate(args.logfile):
116 if i>0:
117 print()
118 rc += scanLogfile(args, lf, ignorePattern)
119
120 return rc
121
122
123def parseConfig(args):
124 """Parses the config file provided into a list (ignorePattern)"""
125 ignorePattern = []
126
127 os.system(f"get_files -data -symlink {args.config} > /dev/null")
128 with open(args.config) as f:
129 print('Ignoring warnings/error patterns defined in ' + args.config)
130 for aline in f:
131 if 'ignore' in aline:
132 line = aline.strip('ignore').strip()
133 if line.startswith('\'') and line.endswith('\''):
134 line = line[1:-1]
135 ignorePattern.append(line)
136 return ignorePattern
137
138
139def scanLogfile(args, logfile, ignorePattern=[]):
140 """Scan one log file and print report"""
141 tPattern = re.compile('|'.join(backtrace))
142 fpeStartPattern = re.compile('|'.join(fpeTracebackStart))
143 fpeContPattern = re.compile('|'.join(fpeTracebackCont))
144 ignoreDict = None
145
146 categories = []
147 if args.warnings is True:
148 categories += ['warning']
149 if args.errors is True:
150 categories += ['error/fatal', 'prohibited', 'python error', 'fpe', 'backtrace']
151
152 # if ignorePattern is empty, igLevels.search would match anything, protect by adding a pattern that matches nothing
153 if not ignorePattern:
154 ignorePattern.append('(?!)')
155
156 igLevels = re.compile('|'.join(ignorePattern))
157
158 patterns = {
159 cat: re.compile('|'.join(regexMap[cat])) for cat in categories
160 }
161 resultsA = {cat:[] for cat in categories}
162 with open(logfile, encoding='utf-8') as f:
163 tracing = False
164 fpeTracing = False
165
166 try:
167 for line in f:
168 # First check if we need to start or continue following a trace
169 # Tracing only makes sense for errors
170 if args.errors:
171 if tPattern.search(line) and not igLevels.search(line):
172 tracing = True
173 elif fpeStartPattern.search(line) and not igLevels.search(line):
174 fpeTracing = True
175 elif line =='\n':
176 tracing = False
177 fpeTracing = False
178
179 if tracing:
180 # Save all lines after a backtrace even if they don't belong to backtrace
181 resultsA['backtrace'].append(line)
182 elif fpeTracing:
183 # Continue following FPE so long as recognised
184 if fpeStartPattern.search(line) or fpeContPattern.search(line):
185 resultsA['fpe'].append(line)
186 else:
187 fpeTracing = False
188 else:
189 for cat in categories:
190 if patterns[cat].search(line):
191 resultsA[cat].append(line)
192 except UnicodeDecodeError as e:
193 print(f'ERROR: Exception raised processing log file: {e}\n')
194
195 ignoreDict = {}
196 results = {cat:[] for cat in categories}
197 if args.config is None:
198 results = resultsA
199 else:
200 if args.showexcludestats:
201 separateIgnoreRegex = [re.compile(line) for line in ignorePattern]
202 ignoreDict = {line:0 for line in ignorePattern} # stores counts of ignored errors/warnings
203
204 # Filter messages
205 for cat, messages in resultsA.items():
206 for res in messages:
207 if not igLevels.search(res):
208 results[cat].append(res)
209 elif args.showexcludestats:
210 for i in range(len(separateIgnoreRegex)):
211 if separateIgnoreRegex[i].search(res):
212 ignoreDict[ignorePattern[i]] += 1
213
214
215 # Report results
216 found_bad_message = False
217 for cat in categories:
218
219 if args.printpatterns:
220 print(f'check_log.py - Checking for {cat} messages with pattern: {str(patterns[cat])} in '+logfile+'\n')
221 if len(results[cat]) > 0:
222 print(f'Found {len(results[cat])} {cat} message(s) in {logfile}:')
223 for msg in results[cat]: print(msg.strip('\n'))
224 found_bad_message = True
225
226 if ignoreDict:
227 print('Ignored:')
228 for s in ignoreDict:
229 if ignoreDict[s] > 0:
230 print(str(ignoreDict[s]) + "x " + s)
231 print('\n')
232
233 if found_bad_message:
234 print(f'FAILURE : problematic message found in {logfile}')
235 return 1
236
237 print(f'No error/warning messages found in {logfile}')
238 return 0
239
240
241if __name__ == "__main__":
242 sys.exit(main())
void print(char *figname, TCanvas *c1)
void search(TDirectory *td, const std::string &s, std::string cwd, node *n)
recursive directory search for TH1 and TH2 and TProfiles
Definition hcg.cxx:743
parseConfig(args)
Definition check_log.py:123
scanLogfile(args, logfile, ignorePattern=[])
Definition check_log.py:139