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