diff options
author | morpheus65535 <[email protected]> | 2021-12-01 21:19:18 -0500 |
---|---|---|
committer | morpheus65535 <[email protected]> | 2021-12-01 21:19:18 -0500 |
commit | d51dc68ebb3910ca09bb40c33814d43b93d916b8 (patch) | |
tree | 569807abb70b41eb98dded56c17bd504e793da5b /libs/apprise/plugins/NotifySyslog.py | |
parent | 402c82d84f7bd51353348bea7d1a876ad9ecc5b1 (diff) | |
download | bazarr-d51dc68ebb3910ca09bb40c33814d43b93d916b8.tar.gz bazarr-d51dc68ebb3910ca09bb40c33814d43b93d916b8.zip |
Updated Apprise notification module to the latest providers.v1.0.2-beta.2
Diffstat (limited to 'libs/apprise/plugins/NotifySyslog.py')
-rw-r--r-- | libs/apprise/plugins/NotifySyslog.py | 207 |
1 files changed, 182 insertions, 25 deletions
diff --git a/libs/apprise/plugins/NotifySyslog.py b/libs/apprise/plugins/NotifySyslog.py index 2457410e2..4fa9d915f 100644 --- a/libs/apprise/plugins/NotifySyslog.py +++ b/libs/apprise/plugins/NotifySyslog.py @@ -22,12 +22,15 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - +import os +import six import syslog +import socket from .NotifyBase import NotifyBase from ..common import NotifyType from ..utils import parse_bool +from ..utils import is_hostname from ..AppriseLocale import gettext_lazy as _ @@ -98,6 +101,21 @@ SYSLOG_FACILITY_RMAP = { } +class SyslogMode(object): + # A local query + LOCAL = "local" + + # A remote query + REMOTE = "remote" + + +# webhook modes are placed ito this list for validation purposes +SYSLOG_MODES = ( + SyslogMode.LOCAL, + SyslogMode.REMOTE, +) + + class NotifySyslog(NotifyBase): """ A wrapper for Syslog Notifications @@ -119,13 +137,14 @@ class NotifySyslog(NotifyBase): # local anyway request_rate_per_sec = 0 - # Title to be added to body if present - title_maxlen = 0 - # Define object templates templates = ( '{schema}://', '{schema}://{facility}', + '{schema}://{host}', + '{schema}://{host}:{port}', + '{schema}://{host}/{facility}', + '{schema}://{host}:{port}/{facility}', ) # Define our template tokens @@ -136,6 +155,18 @@ class NotifySyslog(NotifyBase): 'values': [k for k in SYSLOG_FACILITY_MAP.keys()], 'default': SyslogFacility.USER, }, + 'host': { + 'name': _('Hostname'), + 'type': 'string', + 'required': True, + }, + 'port': { + 'name': _('Port'), + 'type': 'int', + 'min': 1, + 'max': 65535, + 'default': 514, + }, }) # Define our template arguments @@ -144,6 +175,12 @@ class NotifySyslog(NotifyBase): # We map back to the same element defined in template_tokens 'alias_of': 'facility', }, + 'mode': { + 'name': _('Syslog Mode'), + 'type': 'choice:string', + 'values': SYSLOG_MODES, + 'default': SyslogMode.LOCAL, + }, 'logpid': { 'name': _('Log PID'), 'type': 'bool', @@ -158,8 +195,8 @@ class NotifySyslog(NotifyBase): }, }) - def __init__(self, facility=None, log_pid=True, log_perror=False, - **kwargs): + def __init__(self, facility=None, mode=None, log_pid=True, + log_perror=False, **kwargs): """ Initialize Syslog Object """ @@ -179,6 +216,14 @@ class NotifySyslog(NotifyBase): SYSLOG_FACILITY_MAP[ self.template_tokens['facility']['default']] + self.mode = self.template_args['mode']['default'] \ + if not isinstance(mode, six.string_types) else mode.lower() + + if self.mode not in SYSLOG_MODES: + msg = 'The mode specified ({}) is invalid.'.format(mode) + self.logger.warning(msg) + raise TypeError(msg) + # Logging Options self.logoptions = 0 @@ -214,17 +259,76 @@ class NotifySyslog(NotifyBase): NotifyType.WARNING: syslog.LOG_WARNING, } + if title: + # Format title + body = '{}: {}'.format(title, body) + # Always call throttle before any remote server i/o is made self.throttle() - try: - syslog.syslog(_pmap[notify_type], body) + if self.mode == SyslogMode.LOCAL: + try: + syslog.syslog(_pmap[notify_type], body) - except KeyError: - # An invalid notification type was specified - self.logger.warning( - 'An invalid notification type ' - '({}) was specified.'.format(notify_type)) - return False + except KeyError: + # An invalid notification type was specified + self.logger.warning( + 'An invalid notification type ' + '({}) was specified.'.format(notify_type)) + return False + + else: # SyslogMode.REMOTE + + host = self.host + port = self.port if self.port \ + else self.template_tokens['port']['default'] + if self.log_pid: + payload = '<%d>- %d - %s' % ( + _pmap[notify_type] + self.facility * 8, os.getpid(), body) + + else: + payload = '<%d>- %s' % ( + _pmap[notify_type] + self.facility * 8, body) + + # send UDP packet to upstream server + self.logger.debug( + 'Syslog Host: %s:%d/%s', + host, port, SYSLOG_FACILITY_RMAP[self.facility]) + self.logger.debug('Syslog Payload: %s' % str(payload)) + + # our sent bytes + sent = 0 + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(self.socket_connect_timeout) + sent = sock.sendto(payload.encode('utf-8'), (host, port)) + sock.close() + + except socket.gaierror as e: + self.logger.warning( + 'A connection error occurred sending Syslog ' + 'notification to %s:%d/%s', host, port, + SYSLOG_FACILITY_RMAP[self.facility] + ) + self.logger.debug('Socket Exception: %s' % str(e)) + return False + + except socket.timeout as e: + self.logger.warning( + 'A connection timeout occurred sending Syslog ' + 'notification to %s:%d/%s', host, port, + SYSLOG_FACILITY_RMAP[self.facility] + ) + self.logger.debug('Socket Exception: %s' % str(e)) + return False + + if sent < len(payload): + self.logger.warning( + 'Syslog sent %d byte(s) but intended to send %d byte(s)', + sent, len(payload)) + return False + + self.logger.info('Sent Syslog (%s) notification.', self.mode) return True @@ -237,16 +341,31 @@ class NotifySyslog(NotifyBase): params = { 'logperror': 'yes' if self.log_perror else 'no', 'logpid': 'yes' if self.log_pid else 'no', + 'mode': self.mode, } # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - return '{schema}://{facility}/?{params}'.format( + if self.mode == SyslogMode.LOCAL: + return '{schema}://{facility}/?{params}'.format( + facility=self.template_tokens['facility']['default'] + if self.facility not in SYSLOG_FACILITY_RMAP + else SYSLOG_FACILITY_RMAP[self.facility], + schema=self.secure_protocol, + params=NotifySyslog.urlencode(params), + ) + + # Remote mode: + return '{schema}://{hostname}{port}/{facility}/?{params}'.format( + schema=self.secure_protocol, + hostname=NotifySyslog.quote(self.host, safe=''), + port='' if self.port is None + or self.port == self.template_tokens['port']['default'] + else ':{}'.format(self.port), facility=self.template_tokens['facility']['default'] if self.facility not in SYSLOG_FACILITY_RMAP else SYSLOG_FACILITY_RMAP[self.facility], - schema=self.secure_protocol, params=NotifySyslog.urlencode(params), ) @@ -262,9 +381,28 @@ class NotifySyslog(NotifyBase): # We're done early as we couldn't load the results return results - # if specified; save hostname into facility - facility = None if not results['host'] \ - else NotifySyslog.unquote(results['host']) + tokens = [] + if results['host']: + tokens.append(NotifySyslog.unquote(results['host'])) + + # Get our path values + tokens.extend(NotifySyslog.split_path(results['fullpath'])) + + facility = None + if len(tokens) > 1 and is_hostname(tokens[0]): + # syslog://hostname/facility + results['mode'] = SyslogMode.REMOTE + + # Store our facility as the first path entry + facility = tokens[-1] + + elif tokens: + # This is a bit ambigious... it could be either: + # syslog://facility -or- syslog://hostname + + # First lets test it as a facility; we'll correct this + # later on if nessisary + facility = tokens[-1] # However if specified on the URL, that will over-ride what was # identified @@ -280,15 +418,34 @@ class NotifySyslog(NotifyBase): facility = next((f for f in SYSLOG_FACILITY_MAP.keys() if f.startswith(facility)), facility) - # Save facility - results['facility'] = facility + # Attempt to solve our ambiguity + if len(tokens) == 1 and is_hostname(tokens[0]) and ( + results['port'] or facility not in SYSLOG_FACILITY_MAP): + + # facility is likely hostname; update our guessed mode + results['mode'] = SyslogMode.REMOTE + + # Reset our facility value + facility = None + + # Set mode if not otherwise set + if 'mode' in results['qsd'] and len(results['qsd']['mode']): + results['mode'] = NotifySyslog.unquote(results['qsd']['mode']) + + # Save facility if set + if facility: + results['facility'] = facility # Include PID as part of the message logged - results['log_pid'] = \ - parse_bool(results['qsd'].get('logpid', True)) + results['log_pid'] = parse_bool( + results['qsd'].get( + 'logpid', + NotifySyslog.template_args['logpid']['default'])) # Print to stderr as well. - results['log_perror'] = \ - parse_bool(results['qsd'].get('logperror', False)) + results['log_perror'] = parse_bool( + results['qsd'].get( + 'logperror', + NotifySyslog.template_args['logperror']['default'])) return results |