root / plugins / mysql / mysql_audit @ 852aa41a
Historique | Voir | Annoter | Télécharger (4,41 ko)
| 1 |
#!/usr/bin/env python3 |
|---|---|
| 2 |
# -*- python -*- |
| 3 |
|
| 4 |
""" |
| 5 |
=head1 INTRODUCTION |
| 6 |
|
| 7 |
Plugin to monitor the MySQL audit log connection count |
| 8 |
|
| 9 |
=head1 APPLICABLE SYSTEMS |
| 10 |
|
| 11 |
MySQL needs to be configured manually in order to enable the audit log. This |
| 12 |
can be done as follows: |
| 13 |
|
| 14 |
=over 2 |
| 15 |
|
| 16 |
plugin-load = server_audit=server_audit.so |
| 17 |
server_audit_events = connect |
| 18 |
server_audit_output_type=file |
| 19 |
server_audit_file_path = /var/log/mysql/audit.log |
| 20 |
server_audit_file_rotate_size = 512M |
| 21 |
server_audit_logging = ON |
| 22 |
server_audit_file_rotations = 5 |
| 23 |
|
| 24 |
=back |
| 25 |
|
| 26 |
=head1 INSTALLATION |
| 27 |
|
| 28 |
Place in /etc/munin/plugins/ (or link it there using ln -s) |
| 29 |
|
| 30 |
=head1 CONFIGURATION |
| 31 |
|
| 32 |
Add this to your /etc/munin/plugin-conf.d/munin-node: |
| 33 |
|
| 34 |
=over 2 |
| 35 |
|
| 36 |
[mysql_audit] |
| 37 |
user mysql |
| 38 |
env.logfile /var/log/mysql/audit.log |
| 39 |
env.sorted 1 # sort output (if not: unsorted) |
| 40 |
env.toplist 10 # only the top 10 (if unset: all of them). implies sorted |
| 41 |
|
| 42 |
=back |
| 43 |
|
| 44 |
=head1 HISTORY |
| 45 |
|
| 46 |
2017-11-03: v 1.0 Bert Van de Poel <bert@bhack.net>: created |
| 47 |
2020-07-19: v 1.1 pcy <pcy@ulyssis.org>: added config options |
| 48 |
|
| 49 |
=head1 USAGE |
| 50 |
|
| 51 |
Parameters understood: |
| 52 |
|
| 53 |
config (required) |
| 54 |
autoconf (optional - used by munin-config) |
| 55 |
|
| 56 |
=head1 MAGIC MARKERS |
| 57 |
|
| 58 |
#%# family=auto |
| 59 |
#%# capabilities=autoconf |
| 60 |
""" |
| 61 |
|
| 62 |
|
| 63 |
from datetime import datetime, timedelta, timezone |
| 64 |
import operator |
| 65 |
import os |
| 66 |
import struct |
| 67 |
import sys |
| 68 |
|
| 69 |
|
| 70 |
def weakbool(x): |
| 71 |
return x.lower().strip() in {'true', 'yes', 'y', '1'}
|
| 72 |
|
| 73 |
|
| 74 |
logfile = os.getenv('logfile', '/var/log/mysql/audit.log')
|
| 75 |
toplist = int(os.getenv('toplist', '0'))
|
| 76 |
sortlist = weakbool(os.getenv('sorted', 'N')) or toplist > 0
|
| 77 |
|
| 78 |
STATEFILE = os.getenv('MUNIN_STATEFILE')
|
| 79 |
|
| 80 |
|
| 81 |
def loadstate(): |
| 82 |
if not os.path.isfile(STATEFILE): |
| 83 |
return None |
| 84 |
|
| 85 |
with open(STATEFILE, 'rb') as f: |
| 86 |
tstamp = struct.unpack('d', f.read())[0]
|
| 87 |
return datetime.fromtimestamp(tstamp, tz=timezone.utc) |
| 88 |
|
| 89 |
|
| 90 |
def savestate(state): |
| 91 |
with open(STATEFILE, 'wb') as f: |
| 92 |
f.write(struct.pack('d', state.timestamp()))
|
| 93 |
|
| 94 |
|
| 95 |
def reverse_lines(filename, BUFSIZE=4096): |
| 96 |
with open(filename, "r") as f: |
| 97 |
f.seek(0, 2) |
| 98 |
p = f.tell() |
| 99 |
remainder = "" |
| 100 |
while True: |
| 101 |
sz = min(BUFSIZE, p) |
| 102 |
p -= sz |
| 103 |
f.seek(p) |
| 104 |
buf = f.read(sz) + remainder |
| 105 |
if '\n' not in buf: |
| 106 |
remainder = buf |
| 107 |
else: |
| 108 |
i = buf.index('\n')
|
| 109 |
for L in buf[i + 1:].split("\n")[::-1]:
|
| 110 |
yield L |
| 111 |
remainder = buf[:i] |
| 112 |
if p == 0: |
| 113 |
break |
| 114 |
yield remainder |
| 115 |
|
| 116 |
|
| 117 |
def get_data(do_save=True): |
| 118 |
occurrences = {}
|
| 119 |
begin = datetime.now(timezone.utc) |
| 120 |
begin_local = datetime.now() |
| 121 |
|
| 122 |
state = loadstate() |
| 123 |
if state is None: |
| 124 |
# need to do something here to prevent reading indefinitely |
| 125 |
state = begin - timedelta(minutes=5) |
| 126 |
|
| 127 |
for line in reverse_lines(logfile): |
| 128 |
if ',CONNECT,' not in line: |
| 129 |
continue |
| 130 |
|
| 131 |
splitted = line.split(',')
|
| 132 |
key = splitted[2] |
| 133 |
date = datetime.strptime(splitted[0], '%Y%m%d %H:%M:%S') |
| 134 |
# hack to add timezone data to the datetime |
| 135 |
date = begin + (date - begin_local) |
| 136 |
|
| 137 |
if date < state: |
| 138 |
break |
| 139 |
|
| 140 |
occurrences[key] = occurrences.get(key, 0) + 1 |
| 141 |
|
| 142 |
if do_save: |
| 143 |
savestate(begin) |
| 144 |
|
| 145 |
return occurrences |
| 146 |
|
| 147 |
|
| 148 |
def autoconf(): |
| 149 |
print("no (logfile not found)" if os.path.isfile(logfile) else "yes")
|
| 150 |
|
| 151 |
|
| 152 |
def configure(): |
| 153 |
print('graph_title MySQL Audit connect count')
|
| 154 |
print('graph_vlabel Connections')
|
| 155 |
print('graph_category mysql')
|
| 156 |
|
| 157 |
occurrences = get_data(False) |
| 158 |
occitems = occurrences.items() |
| 159 |
occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0), |
| 160 |
reverse=sortlist) |
| 161 |
if toplist > 0: |
| 162 |
occitems = occitems[:toplist] |
| 163 |
|
| 164 |
for key, value in occitems: |
| 165 |
print('{}.label {}'.format(key.lower(), key))
|
| 166 |
print('{}.type GAUGE'.format(key.lower()))
|
| 167 |
print('{}.draw AREASTACK'.format(key.lower()))
|
| 168 |
|
| 169 |
|
| 170 |
def fetch(): |
| 171 |
occurrences = get_data() |
| 172 |
occitems = occurrences.items() |
| 173 |
occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0), |
| 174 |
reverse=sortlist) |
| 175 |
if toplist > 0: |
| 176 |
occitems = occitems[:toplist] |
| 177 |
|
| 178 |
for key, value in occitems: |
| 179 |
print('{}.value {}'.format(key, value))
|
| 180 |
|
| 181 |
|
| 182 |
if len(sys.argv) == 2 and sys.argv[1] == "autoconf": |
| 183 |
autoconf() |
| 184 |
elif len(sys.argv) == 2 and sys.argv[1] == "config": |
| 185 |
configure() |
| 186 |
else: |
| 187 |
fetch() |
