summaryrefslogtreecommitdiffhomepage
path: root/libs/apprise/plugins/NotifySyslog.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/apprise/plugins/NotifySyslog.py')
-rw-r--r--libs/apprise/plugins/NotifySyslog.py207
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