-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnotify.py
executable file
·170 lines (133 loc) · 5.73 KB
/
notify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# coding=utf-8
# Author: Maxime Petazzoni
# maxime.petazzoni@bulix.org
#
# This file is part of pTFTPd.
#
# pTFTPd is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pTFTPd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pTFTPd. If not, see <http://www.gnu.org/licenses/>.
"""Notification and logging system for the pTFTPd tool suite.
This module implements a notification engine for the pTFTPd tools using
Python's logging module. Its main feature include easily configurable
notifications from various TFTP events such as start and end of a file
transfer.
These event notifications can be routed to several destinations by adding one
or more engines to the notification chain.
"""
import logging
import sys
# Transfer states
TRANSFER_STARTED = 1
TRANSFER_COMPLETED = 2
TRANSFER_FAILED = 3
_STATE_NAMES = {
TRANSFER_STARTED: 'STARTED',
TRANSFER_COMPLETED: 'COMPLETED',
TRANSFER_FAILED: 'FAILED',
}
class NullEngine(logging.Handler):
"""A no-op notification engine. Simply results in no logging messages being
outputted anywhere."""
def emit(self, record):
pass
@staticmethod
def install(logger):
logger.addHandler(NullEngine())
class StreamEngine(logging.StreamHandler):
"""Simple stream log handler, similar to what you get using
logging.basicConfig."""
def __init__(self, stream, loglevel, fmt):
"""Creates a new notification engine that simply logs to the given
stream.
Args:
stream (stream): logging stream.
loglevel (logging.loglevel): minimum level of messages to be
outputted.
fmt (string format): default format string to apply
on log messages.
"""
logging.StreamHandler.__init__(self, stream)
self.setFormatter(logging.Formatter(fmt))
self.setLevel(loglevel)
@staticmethod
def install(logger, stream=sys.stderr, loglevel=logging.WARNING,
fmt='%(message)s'):
handler = StreamEngine(stream, loglevel, fmt)
logger.addHandler(handler)
class DetailFilter(logging.Filter):
"""This log filter filters log records that don't possess the extra
information we require for advanced notifications (host, port, file name
and transfer state)."""
def filter(self, record):
r = record.__dict__
return all(['host' in r, 'port' in r, 'file' in r, 'state' in r])
class DetailledStreamEngine(StreamEngine):
"""The DetailledStreamEngine is a extension of the StreamEngine define
above designed to log detailed notifications. Pertinent log records are
filtered using the DetailFilter, as set up by the install() method."""
def emit(self, record):
"""Emits the given LogRecord object, first replacing the transfer state
by its string representation. The original state numeric value is
restored afterwards for other log handlers down the chain."""
state = record.state
record.state = _STATE_NAMES[record.state]
# Emit the log entry using the parent's method
StreamEngine.emit(self, record)
record.state = state
@staticmethod
def install(logger, stream=sys.stderr, loglevel=logging.INFO,
fmt='%(message)s (%(host)s:%(port)d#%(file)s %(state)s)'):
handler = DetailledStreamEngine(stream, loglevel, fmt)
handler.addFilter(DetailFilter())
logger.addHandler(handler)
class CallbackEngine(logging.Handler):
"""The CallbackEngine is another notification engine, using a callback
mechanism. When a LogRecord is received by this handler and makes it
through the DetailFilter, a callback function matchin the transfer state
provided in the LogRecord object is called, passing the detail information
along."""
def __init__(self, callbacks):
"""Creates a callback notification engine using the provided callback
mapping. This handler has a default loglevel of logging.DEBUG, hence
making sure all messages that pass the filter will be processed and
result in a callback call."""
logging.Handler.__init__(self, logging.DEBUG)
self.callbacks = callbacks
def _nop(self, **kwargs):
"""Define a no-op callback to use as a default when no callback is
provided for a transfer state."""
pass
def emit(self, record):
"""Call the defined callback for the transfer state found in the log
record."""
callback = self.callbacks.get(record.state, self._nop)
callback(host=record.host,
port=record.port,
file=record.file,
state=record.state)
@staticmethod
def install(logger, callbacks=None):
if callbacks is None:
callbacks = {}
handler = CallbackEngine(callbacks)
handler.addFilter(DetailFilter())
logger.addHandler(handler)
def getLogger(name):
"""Return a named logger usable with the notification engines defined in
this module. This logger is set with a loglevel of logging.DEBUG to make
sure all log messages are processed by the logger and handed over to the
attached handlers. It is then the responsibility of the handlers to define
their desired logging level."""
l = logging.getLogger(name)
l.setLevel(logging.DEBUG)
return l