Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / znc / znc_logs.py @ a7139bca

Historique | Voir | Annoter | Télécharger (7,43 ko)

1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
'''
4
=head1 NAME
5

6
znc_logs
7

8
=head1 DESCRIPTION
9

10
Shows lines/minute in today's znc-logs
11

12
=head2 CONFIGURATION
13

14
 [znc_logs]
15
 user znc                              # or any other user/group that can read the znclog-folder
16
 group znc
17
 env.logdir /var/lib/znc/moddata/log/  # path to the GLOBAL log-folder with a "/" at the end
18
 env.expire 0                          # Keep channel names forever  - OR -
19
 env.expire 1                          # Forget channel names from last run
20

21
=head1 COPYRIGHT
22

23
GNU General Public License v3.0 only
24

25
SPDX-License-Identifier: GPL-3.0-only
26

27
=head1 AUTHOR
28

29
Thor77 <thor77[at]thor77.org>
30

31
=cut
32
'''
33
import json
34
import os, sys, time
35
import re
36
import stat
37
import traceback
38

    
39
logdir = os.environ.get('logdir')
40
expire = os.environ.get('expire', 0)
41

    
42
if not logdir:
43
    raise Exception('You have to set the logdir with env.logdir <path to log> in the plugin-conf!')
44

    
45
date = time.strftime('%Y%m%d')
46
longdate = time.strftime('%Y-%m-%d')
47
last_values_file = os.environ['MUNIN_PLUGSTATE'] + '/znc_logs_last'
48

    
49
def get_last():
50
    try:
51
        d = {}
52
        with open(last_values_file, 'r') as f:
53
            d = json.load(f)
54
        return d
55
    except FileNotFoundError:
56
        return {}
57

    
58
def tail_open(filename, position=0):
59
    # Based on tail_open from perls' Munin::Plugin
60
    filereset = 0
61
    size = os.stat(filename)[stat.ST_SIZE]
62
    if size is None:
63
        return (undef, undef)
64
    f = open(filename, 'r', encoding='utf-8', errors='replace')
65
    if position > size:
66
        filereset = 1
67
    else:
68
        f.seek(position, 0)
69
        newpos = f.tell()
70
        if newpos != position:
71
            raise Exception
72
    return (f, filereset)
73

    
74
def tail_close(fh):
75
    position = fh.tell()
76
    fh.close()
77
    return position
78

    
79

    
80
last = get_last()
81
if "users" in last:
82
    user_list = last["users"]
83
else:
84
    user_list = {}
85
if "channels" in last:
86
    channel_list = last["channels"]
87
else:
88
    channel_list = {}
89
if "log_pos" in last:
90
    log_pos = last["log_pos"]
91
else:
92
    log_pos = {}
93

    
94
channel_stats = {}
95
user_stats = {}
96

    
97
def read_data(savestate=True):
98
    # Version 1.6 will change to directory-based filing, so walk recursively
99
    for (dirpath, dirnames, filenames) in os.walk(logdir):
100
        for filename in filenames:
101
            filename_ = filename.replace('.log', '')
102

    
103
            user, network, channel, file_date = (None, None, None, None)
104

    
105
            try:
106
                if len(dirpath) > len(logdir):
107
                    # We're below the log path, so this is a 1.6-style log
108
                    reldir = dirpath.replace(logdir + "/", '', 1)
109
                    try:
110
                        network, channel = reldir.split(os.sep)
111
                    except ValueError as e:
112
                        user, network, channel = reldir.split(os.sep)
113
                    file_date = filename_
114
                else:
115
                    try:
116
                        network, channel, file_date = filename_.split('_')
117
                    except ValueError as e:
118
                        user, network, channel, file_date = filename_.split('_')
119
            except ValueError as e:
120
                continue
121
            network_channel = '{}@{}'.format(channel, network)
122
            if network.lower() not in channel_list:
123
                channel_list[network.lower()] = {}
124
            if channel.startswith('#'):
125
                channel_list[network.lower()][channel.lower()] = network_channel
126
            user_list[user.lower()] = user
127
            # check if log is from today
128
            if (file_date == date or file_date == longdate):
129
                # current lines in the file
130
                (fh, r) = tail_open(os.path.join(dirpath,filename), log_pos.get(os.path.join(dirpath, filename), 0))
131
                current_value = 0
132
                while True:
133
                    where = fh.tell()
134
                    line = fh.readline()
135
                    if line.endswith('\n'):
136
                        current_value += 1
137
                    else:
138
                        # Incomplete last line
139
                        fh.seek(where, 0)
140
                        log_pos[os.path.join(dirpath, filename)] = tail_close(fh)
141
                        break
142

    
143
                if network_channel.lower() in channel_stats and channel.startswith('#'):
144
                    channel_stats[network_channel.lower()] += current_value
145
                else:
146
                    channel_stats[network_channel.lower()] = current_value
147

    
148
                if user is not None and user.lower() in user_stats:
149
                    user_stats[user.lower()] += current_value
150
                else:
151
                    user_stats[user.lower()] = current_value
152
    if savestate:
153
        savedata = {}
154
        if int(expire) == 0:
155
            savedata["users"] = user_list
156
            savedata["channels"] = channel_list
157
        savedata["log_pos"] = log_pos
158
        with open(last_values_file, 'w') as f:
159
            json.dump(savedata,f)
160

    
161

    
162
def emit_config():
163
    print('graph_title Lines in the ZNC-log')
164
    print('graph_category chat')
165
    print('graph_vlabel lines / ${graph_period}')
166
    print('graph_scale no')
167
    print('graph_args --base 1000 --lower-limit 0')
168
    print('graph_period minute')
169
    graph_order = []
170

    
171
    if os.getenv('MUNIN_CAP_DIRTYCONFIG') == "1":
172
        read_data(1)
173
    else:
174
        read_data(0)
175

    
176
    for network in channel_list.keys():
177
        for channel in channel_list[network].keys():
178

    
179
            # print things to munin
180
            network_channel = "{}_{}".format(network,channel).replace('.', '').replace('#', '').replace('@','_')
181
            print('{network_channel}.label {label}'.format(network_channel=network_channel, label=channel_list[network][channel]))
182
            print('{network_channel}.type  ABSOLUTE'.format(network_channel=network_channel))
183
            print('{network_channel}.min   0'.format(network_channel=network_channel))
184
            print('{network_channel}.draw  AREASTACK'.format(network_channel=network_channel))
185

    
186
            graph_order.append(network_channel)
187
    for user in user_list.keys():
188
            fuser = re.sub(r'^[^A-Za-z_]', '_', user)
189
            fuser = re.sub(r'[^A-Za-z0-9_]', '_', fuser)
190
            print('{fuser}.label User {user}'.format(fuser=fuser, user=user))
191
            print('{fuser}.type  ABSOLUTE'.format(fuser=fuser))
192
            print('{fuser}.min   0'.format(fuser=fuser))
193
            print('{fuser}.draw  LINE1'.format(fuser=fuser))
194

    
195
    print('graph_order {}'.format(" ".join(sorted(graph_order, key=str.lower))))
196

    
197
def emit_values():
198
    read_data(1)
199
    for network in channel_list.keys():
200
        for channel in channel_list[network].keys():
201

    
202
            # print things to munin
203
            key = channel_list[network][channel]
204
            network_channel = "{}_{}".format(network,channel).replace('.', '').replace('#', '').replace('@','_')
205
            if key.lower() in channel_stats:
206
                print('{network_channel}.value {value}'.format(network_channel=network_channel, value=channel_stats[key.lower()]))
207
            else:
208
                print('{network_channel}.value U'.format(network_channel=network_channel))
209
    for user in user_list.keys():
210
        fuser = re.sub(r'^[^A-Za-z_]', '_', user)
211
        fuser = re.sub(r'[^A-Za-z0-9_]', '_', fuser)
212
        if user.lower() in user_stats:
213
            print('{fuser}.value {value}'.format(fuser=fuser, value=user_stats[user.lower()]))
214
        else:
215
            print('{fuser}.value U'.format(fuser=fuser))
216

    
217

    
218
if len(sys.argv) > 1 and sys.argv[1] == 'config':
219
    emit_config()
220
    sys.exit(0)
221

    
222
emit_values()