mirror of
https://github.com/xcat2/confluent.git
synced 2026-08-28 17:46:42 +00:00
5caf0451fd
Every entry already says which log it came from, so -s narrows the output to the ones asked for and "-s list" names what a node offers. Some of what a platform keeps is noise: an AMI MegaRAC's event log is a list of redfish sessions being opened and closed, while its useful records are elsewhere. A selection cannot be cleared, since the platforms offer no such thing and clearing more than was asked for is not something to do quietly.
223 lines
8.3 KiB
Python
Executable File
223 lines
8.3 KiB
Python
Executable File
#!/usr/bin/python2
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2015-2019 Lenovo
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import codecs
|
|
from datetime import datetime as dt
|
|
from datetime import timedelta
|
|
import optparse
|
|
import os
|
|
import signal
|
|
import sys
|
|
|
|
try:
|
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
except AttributeError:
|
|
pass
|
|
path = os.path.dirname(os.path.realpath(__file__))
|
|
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
|
if path.startswith('/opt'):
|
|
sys.path.append(path)
|
|
|
|
import confluent.client as client
|
|
|
|
if sys.version_info[0] < 3:
|
|
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
|
|
|
argparser = optparse.OptionParser(
|
|
usage="Usage: %prog [options] <noderange> [clear]")
|
|
argparser.add_option('-m', '--maxnodes', type='int',
|
|
help='Specify a maximum number of '
|
|
'nodes to clear if clearing log, '
|
|
'prompting if over the threshold')
|
|
argparser.add_option('-l', '--lines', type='int',
|
|
help='return the last <n> entries '
|
|
'for each node in the eventlog. '
|
|
)
|
|
argparser.add_option('-t', '--timeframe', type='string',
|
|
help='return entries within a specified timeframe '
|
|
'for each node in the eventlog. This will return '
|
|
'entries from the last hours or days. '
|
|
'1h would be one hour, 4d would be four days. '
|
|
'format <num>h or <num>d'
|
|
)
|
|
|
|
|
|
argparser.add_option('-s', '--source', type='string',
|
|
help='only show entries from the named logs, comma '
|
|
'delimited. "-s list" names the logs this node '
|
|
'offers instead of showing entries')
|
|
(options, args) = argparser.parse_args()
|
|
try:
|
|
noderange = args[0]
|
|
except IndexError:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
client.check_globbing(noderange)
|
|
deletemode = False
|
|
if len(args) > 2:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
if len(args) == 2:
|
|
if args[1] == 'clear':
|
|
deletemode = True
|
|
else:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
|
|
listmode = bool(options.source) and options.source.lower() == 'list'
|
|
wantsources = None
|
|
if options.source and not listmode:
|
|
wantsources = set(x.strip().lower() for x in options.source.split(',')
|
|
if x.strip())
|
|
if options.source and deletemode:
|
|
# Clearing a subset is not something the platforms offer, and clearing more
|
|
# than was asked for is not something to do quietly
|
|
sys.stderr.write(
|
|
'Clearing a selection of logs is not supported, so --source cannot be '
|
|
'combined with clear\n')
|
|
sys.exit(1)
|
|
|
|
session = client.Command()
|
|
exitcode = 0
|
|
|
|
def format_event(evt):
|
|
retparts = []
|
|
if 'timestamp' in evt and evt['timestamp'] is not None:
|
|
try:
|
|
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
|
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
|
|
except ValueError:
|
|
display = ''
|
|
dscparts = []
|
|
if evt.get('log_id', None):
|
|
retparts.append(evt['log_id'] + ':')
|
|
if 'component_type' in evt and evt['component_type'] is not None:
|
|
dscparts.append(evt['component_type'])
|
|
if 'component' in evt and evt['component'] is not None:
|
|
dscparts.append(evt['component'])
|
|
if 'event' in evt and evt['event'] and evt['event'] is not None:
|
|
evttext = evt['event']
|
|
try:
|
|
if evttext.startswith(evt['component'] + ' - '):
|
|
evttext = evt['event'].replace(evt['component'] + ' - ', '')
|
|
except (KeyError, TypeError):
|
|
pass
|
|
dscparts.append(evttext)
|
|
retparts.append(' - '.join(dscparts))
|
|
msg = evt.get('message')
|
|
if not msg:
|
|
msg = ''
|
|
return ' '.join(retparts) + msg
|
|
|
|
if deletemode:
|
|
func = session.delete
|
|
session.stop_if_noderange_over(noderange, options.maxnodes)
|
|
else:
|
|
func = session.read
|
|
|
|
if options.timeframe:
|
|
try:
|
|
delta = int(options.timeframe[:-1])
|
|
except ValueError:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
if options.timeframe[-1].lower() == 'd':
|
|
tdelta = timedelta(days=delta)
|
|
elif options.timeframe[-1].lower() == 'h':
|
|
tdelta = timedelta(hours=delta)
|
|
else:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
timeframe = dt.now() - tdelta
|
|
|
|
sources_seen = {}
|
|
event_dict = {}
|
|
nodes = []
|
|
for res in session.read('/noderange/{0}/nodes/'.format(args[0])):
|
|
node = res.get('item', {}).get('href', '/').replace('/', '')
|
|
nodes.append(node)
|
|
event_dict[node] = []
|
|
|
|
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
|
if 'error' in rsp:
|
|
sys.stderr.write(rsp['error'] + '\n')
|
|
exitcode |= rsp['errorcode']
|
|
if 'databynode' in rsp:
|
|
nodedata = rsp['databynode']
|
|
for node in nodedata:
|
|
thisdata = nodedata[node]
|
|
if 'error' in thisdata:
|
|
sys.stderr.write('{0}: {1}\n'.format(node, thisdata['error']))
|
|
exitcode |= 1
|
|
if 'events' in thisdata:
|
|
evtdata = thisdata['events']
|
|
for evt in evtdata:
|
|
if evt.get('log_id', None):
|
|
sources_seen.setdefault(node, set()).add(evt['log_id'])
|
|
if listmode:
|
|
continue
|
|
if wantsources is not None:
|
|
# Filter before any tail is taken, or asking for the last
|
|
# few entries of one log would answer with fewer
|
|
evtdata = [x for x in evtdata if (x.get('log_id', '')
|
|
or '').lower()
|
|
in wantsources]
|
|
if options.lines:
|
|
event_dict[node].extend(evtdata)
|
|
else:
|
|
for evt in evtdata:
|
|
if options.timeframe:
|
|
# check if line is in timeframe
|
|
if 'timestamp' in evt and evt['timestamp'] is not None:
|
|
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
|
if display > timeframe:
|
|
print('{0}: {1}'.format(node, format_event(evt)))
|
|
else:
|
|
print('{0}: {1}'.format(node, format_event(evt)))
|
|
|
|
if listmode:
|
|
for node in nodes:
|
|
found = sorted(sources_seen.get(node, ()))
|
|
if found:
|
|
print('{0}: {1}'.format(node, ','.join(found)))
|
|
else:
|
|
sys.stderr.write(
|
|
'No event log sources reported for "{0}"\n'.format(node))
|
|
elif wantsources is not None:
|
|
for node in nodes:
|
|
found = set(x.lower() for x in sources_seen.get(node, ()))
|
|
missing = wantsources - found
|
|
if missing and found:
|
|
sys.stderr.write('{0}: no such log source: {1}, this node has {2}\n'
|
|
.format(node, ','.join(sorted(missing)),
|
|
','.join(sorted(sources_seen[node]))))
|
|
|
|
if options.lines and not listmode:
|
|
for node in nodes:
|
|
evtdata_list = event_dict[node]
|
|
if len(evtdata_list) != 0:
|
|
if len(evtdata_list) > options.lines:
|
|
evtdata_list = evtdata_list[-abs(options.lines):]
|
|
for evt in evtdata_list:
|
|
if options.timeframe:
|
|
if 'timestamp' in evt and evt['timestamp'] is not None:
|
|
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
|
if display > timeframe:
|
|
print('{0}: {1}'.format(node, format_event(evt)))
|
|
else:
|
|
print('{0}: {1}'.format(node, format_event(evt)))
|