Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4Extract LVAlert message JSON from ``lvalert_listen`` log files. Reads the log
5file contents from STDIN and writes the JSON packets to STDOUT as a
6pretty-printed JSON list of message objects. Errors are logged to STDERR. Keeps
7reading from STDIN, so in principle you can do something like ``tail -f
8lvalert_listen.log`` to keep track of new messages in real-time (while also
9logging them).
10"""
12import sys
13import json
14import argparse
15from subprocess import Popen, PIPE
18def get_parser():
19 """Get CLI parser."""
20 parser = argparse.ArgumentParser(prog='llama dev log lvalert',
21 description=__doc__)
22 arg = parser.add_argument
23 arg("-s", "--script", default=None, help="""
24 A script that should be used as an LVAlert handler for all well-defined
25 LVALerts found in the log read in from STDIN. The LVAlert Node will be
26 passed as the first command line argument to this script; the FAR
27 (False Alarm Rate) will be passed as the second argument. The LVAlert
28 JSON payload will be passed to this script through STDIN. If this
29 argument is provided, then nothing will be printed to STDOUT, and
30 instead, the script defined in this argument will be invoked once in
31 the described manner for each parsed LVAlert. Only one instance of the
32 script runs at a time. STDOUT and STDERR from the invoked scripts will
33 be forwarded to STDOUT and STDERR for this calling script.""")
34 return parser
37INDENT_SPACES = 2
38# this is the string that terminates the line preceding the JSON payload
39JSON_LOAD_ANNOUNCEMENT_SUFFIX = "INFO lvalert_handler - <module>: JSON:"
40NODE_DEFINITION_SUFFIX = "INFO lvalert_handler - <module>: LVALERT NODE:"
41FAR_DEFINITION_SUFFIX = "INFO lvalert_handler - <module>: FAR:"
44def main():
45 """Run the CLI."""
46 # print the start of the list (unless we are using the --script argument to
47 # handle each parsed LVAlert)
48 # default values for node and FAR
49 node = far = None
52 parser = get_parser()
53 args = parser.parse_args()
54 if args.script is None:
55 sys.stdout.write('[')
57 a_message_has_been_parsed = False
58 parse_next_line = False
59 for line in sys.stdin:
60 if parse_next_line:
61 try:
62 message_json = line[line.find('{'):]
63 message = json.loads(message_json)
64 # if we are running each LVAlert through a handler script, then run
65 # that script on that message. otherwise, parse it, reformat it,
66 # and print it to STDOUT.
67 if args.script is None:
68 # indent an extra block since this is all being wrapped in a
69 # list; strip leading spaces from the indent block so that the
70 # end of the last message and the start of the next can be on
71 # the same line
72 message_str = json.dumps(
73 message,
74 indent=INDENT_SPACES
75 ).strip().replace('\n', '\n'+INDENT_SPACES*' ')
76 if a_message_has_been_parsed:
77 message_str = ', ' + message_str
78 else:
79 message_str = '\n' + INDENT_SPACES*' ' + message_str
80 sys.stdout.write(message_str)
81 a_message_has_been_parsed = True
82 # FIXME. this doesn't make sense. we haven't set it to the
83 # value for the next message yet...
84 elif node is not None:
85 # we only want to run the LVAlert handler --script if node and
86 # far can be parsed from a given LVAlert.
87 try:
88 far = (far if far is not None else
89 str(message['object']['far']))
90 proc = Popen([args.script, node, far], stdin=PIPE)
91 proc.communicate(input=message_json)
92 if proc.returncode:
93 sys.stderr.write((
94 "LVAlert handling script {} failed "
95 "with script {} and payload: {}\n"
96 ).format(args.script, proc.returncode, message_json))
97 except KeyError:
98 sys.stderr.write((
99 "Could not parse a far from this LVAlert's logs nor "
100 "payload: {}\n"
101 ).format(message_json))
102 else:
103 sys.stderr.write((
104 "Could not parse the LVAlert node from this LVAlert's "
105 "logs: {}\n"
106 ).format(message_json))
107 except ValueError:
108 sys.stderr.write("Can't decode JSON: {}\n".format(line))
109 parse_next_line = False
110 # reset the parsed node and far values after any JSON-parsing attempt
111 node = far = None
112 else:
113 parse_next_line = line.strip().endswith(JSON_LOAD_ANNOUNCEMENT_SUFFIX)
114 # see if we can parse LVAlert node or far from this line
115 node_ind = line.find(NODE_DEFINITION_SUFFIX)
116 if node_ind != -1:
117 node = line[node_ind:].strip()
118 far_ind = line.find(FAR_DEFINITION_SUFFIX)
119 if far_ind != -1:
120 node = line[far_ind:].strip()
122 # print the end of the list (unless we are using the --script argument to
123 # handle each parsed LVAlert)
124 if args.script is None:
125 sys.stdout.write(('\n' if a_message_has_been_parsed else '') + ']')
128if __name__ == "__main__":
129 main()