From 6b18f9f1930c64c31aaffafdb1b34ebee46a8f91 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Wed, 1 Jan 2014 22:53:26 -0600 Subject: [PATCH 1/5] No need for fancy timezone handling, just send a UTC timestamp. Also, due to limitations in the JodaTime library that logstash uses it is limited to millisecond accuracy. Some versions (1.3.2 in particular) will choke if given anything other than a millisecond timestamp. --- salt/log/handlers/logstash_mod.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/salt/log/handlers/logstash_mod.py b/salt/log/handlers/logstash_mod.py index 1981f6ef5b03..9269678cba62 100644 --- a/salt/log/handlers/logstash_mod.py +++ b/salt/log/handlers/logstash_mod.py @@ -123,13 +123,6 @@ from salt.log.setup import LOG_LEVELS from salt.log.mixins import NewStyleClassMixIn -# Import 3rd-party libs -try: - from pytz import utc as _UTC - HAS_PYTZ = True -except ImportError: - HAS_PYTZ = False - log = logging.getLogger(__name__) # Define the module's virtual name @@ -221,10 +214,7 @@ def __init__(self, msg_type='logstash', msg_path='logstash'): super(LogstashFormatter, self).__init__(fmt=None, datefmt=None) def formatTime(self, record, datefmt=None): - timestamp = datetime.datetime.utcfromtimestamp(record.created) - if HAS_PYTZ: - return _UTC.localize(timestamp).isoformat() - return '{0}+00:00'.format(timestamp.isoformat()) + return datetime.datetime.utcfromtimestamp(record.created).isoformat()[:-3] + 'Z' def format(self, record): host = socket.getfqdn() From c63ced6500f50169404169278dd5a19e88a6af06 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Wed, 1 Jan 2014 22:57:15 -0600 Subject: [PATCH 2/5] Add 'salt' to the list of tags. --- salt/log/handlers/logstash_mod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/log/handlers/logstash_mod.py b/salt/log/handlers/logstash_mod.py index 9269678cba62..f9f92eedf5de 100644 --- a/salt/log/handlers/logstash_mod.py +++ b/salt/log/handlers/logstash_mod.py @@ -237,7 +237,7 @@ def format(self, record): ), '@source_host': host, '@source_path': self.msg_path, - '@tags': [], + '@tags': ['salt'], '@timestamp': self.formatTime(record), '@type': self.msg_type, } From 3535dbce7d4c6ff9719005e4a32310619ca415b2 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 2 Jan 2014 09:01:59 -0600 Subject: [PATCH 3/5] add a formatter for v1 logstash events --- salt/log/handlers/logstash_mod.py | 65 +++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/salt/log/handlers/logstash_mod.py b/salt/log/handlers/logstash_mod.py index f9f92eedf5de..b7d8bb3441e8 100644 --- a/salt/log/handlers/logstash_mod.py +++ b/salt/log/handlers/logstash_mod.py @@ -144,11 +144,12 @@ def __virtual__(): def setup_handlers(): host = port = address = None - logstash_formatter = LogstashFormatter() if 'logstash_udp_handler' in __opts__: host = __opts__['logstash_udp_handler'].get('host', None) port = __opts__['logstash_udp_handler'].get('port', None) + version = __opts__['logstash_udp_handler'].get('version', 0) + if host is None and port is None: log.debug( 'The required \'logstash_udp_handler\' configuration keys, ' @@ -156,6 +157,7 @@ def setup_handlers(): 'configuring the logstash UDP logging handler.' ) else: + logstash_formatter = LogstashFormatter(version = version) udp_handler = DatagramLogstashHandler(host, port) udp_handler.setFormatter(logstash_formatter) udp_handler.setLevel( @@ -177,6 +179,7 @@ def setup_handlers(): if 'logstash_zmq_handler' in __opts__: address = __opts__['logstash_zmq_handler'].get('address', None) zmq_hwm = __opts__['logstash_zmq_handler'].get('hwm', 1000) + version = __opts__['logstash_zmq_handler'].get('version', 0) if address is None: log.debug( @@ -185,6 +188,7 @@ def setup_handlers(): 'configuring the logstash ZMQ logging handler.' ) else: + logstash_formatter = LogstashFormatter(version = version) zmq_handler = ZMQLogstashHander(address, zmq_hwm=zmq_hwm) zmq_handler.setFormatter(logstash_formatter) zmq_handler.setLevel( @@ -208,17 +212,20 @@ def setup_handlers(): class LogstashFormatter(logging.Formatter, NewStyleClassMixIn): - def __init__(self, msg_type='logstash', msg_path='logstash'): + def __init__(self, msg_type='logstash', msg_path='logstash', version=0): self.msg_path = msg_path self.msg_type = msg_type + self.version = version + self.format = getattr(self, 'format_v{0}'.format(version)) super(LogstashFormatter, self).__init__(fmt=None, datefmt=None) def formatTime(self, record, datefmt=None): return datetime.datetime.utcfromtimestamp(record.created).isoformat()[:-3] + 'Z' - def format(self, record): + def format_v0(self, record): host = socket.getfqdn() message_dict = { + '@timestamp': self.formatTime(record), '@fields': { 'levelname': record.levelname, 'logger': record.name, @@ -238,7 +245,6 @@ def format(self, record): '@source_host': host, '@source_path': self.msg_path, '@tags': ['salt'], - '@timestamp': self.formatTime(record), '@type': self.msg_type, } @@ -268,6 +274,57 @@ def format(self, record): message_dict['@fields'][key] = repr(value) return json.dumps(message_dict) + def format_v1(self, record): + host = socket.getfqdn() + message_dict = { + '@version': 1, + '@timestamp': self.formatTime(record), + 'levelname': record.levelname, + 'logger': record.name, + 'lineno': record.lineno, + 'pathname': record.pathname, + 'process': record.process, + 'threadName': record.threadName, + 'funcName': record.funcName, + 'processName': record.processName, + 'message': record.getMessage(), + 'source': '{0}://{1}/{2}'.format( + self.msg_type, + host, + self.msg_path + ), + 'source_host': host, + 'source_path': self.msg_path, + 'tags': ['salt'], + 'type': self.msg_type, + } + + if record.exc_info: + message_dict['exc_info'] = self.formatException( + record.exc_info + ) + + # Add any extra attributes to the message field + for key, value in record.__dict__.items(): + if key in ('args', 'asctime', 'created', 'exc_info', 'exc_text', + 'filename', 'funcName', 'id', 'levelname', 'levelno', + 'lineno', 'module', 'msecs', 'msecs', 'message', 'msg', + 'name', 'pathname', 'process', 'processName', + 'relativeCreated', 'thread', 'threadName'): + # These are already handled above or not handled at all + continue + + if value is None: + message_dict[key] = value + continue + + if isinstance(value, (string_types, bool, dict, float, int, list)): + message_dict[key] = value + continue + + message_dict[key] = repr(value) + return json.dumps(message_dict) + class DatagramLogstashHandler(logging.handlers.DatagramHandler): ''' From 3a88b54d5323d27070763c67fd7c62de8c7448af Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 2 Jan 2014 15:50:36 -0600 Subject: [PATCH 4/5] update fields for v1 --- salt/log/handlers/logstash_mod.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/salt/log/handlers/logstash_mod.py b/salt/log/handlers/logstash_mod.py index b7d8bb3441e8..5e1516cf41d9 100644 --- a/salt/log/handlers/logstash_mod.py +++ b/salt/log/handlers/logstash_mod.py @@ -275,10 +275,10 @@ def format_v0(self, record): return json.dumps(message_dict) def format_v1(self, record): - host = socket.getfqdn() message_dict = { '@version': 1, '@timestamp': self.formatTime(record), + 'host': socket.getfqdn(), 'levelname': record.levelname, 'logger': record.name, 'lineno': record.lineno, @@ -288,15 +288,8 @@ def format_v1(self, record): 'funcName': record.funcName, 'processName': record.processName, 'message': record.getMessage(), - 'source': '{0}://{1}/{2}'.format( - self.msg_type, - host, - self.msg_path - ), - 'source_host': host, - 'source_path': self.msg_path, 'tags': ['salt'], - 'type': self.msg_type, + 'type': self.msg_type } if record.exc_info: From d87a4be0b67688c813f345038fba17e3098eeb3b Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 2 Jan 2014 15:51:07 -0600 Subject: [PATCH 5/5] update logstash docs --- salt/log/handlers/logstash_mod.py | 60 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/salt/log/handlers/logstash_mod.py b/salt/log/handlers/logstash_mod.py index 5e1516cf41d9..5ec5758ded86 100644 --- a/salt/log/handlers/logstash_mod.py +++ b/salt/log/handlers/logstash_mod.py @@ -11,17 +11,18 @@ UDP Logging Handler ------------------- - In order to setup the datagram handler for `Logstash`_, please define on - the salt configuration file: + For versions of `Logstash`_ before 1.2.0: + + In the salt configuration file: .. code-block:: yaml logstash_udp_handler: host: 127.0.0.1 port: 9999 + version: 0 - - On the `Logstash`_ configuration file you need something like: + In the `Logstash`_ configuration file: .. code-block:: text @@ -32,6 +33,27 @@ } } + For version 1.2.0 of `Logstash`_ and newer: + + In the salt configuration file: + + .. code-block:: yaml + + logstash_udp_handler: + host: 127.0.0.1 + port: 9999 + version: 1 + + In the `Logstash`_ configuration file: + + .. code-block:: text + + input { + udp { + port => 9999 + codec => json + } + } Please read the `UDP input`_ configuration page for additional information. @@ -39,16 +61,17 @@ ZeroMQ Logging Handler ---------------------- - In order to setup the ZMQ handler for `Logstash`_, please define on the - salt configuration file: + For versions of `Logstash`_ before 1.2.0: + + In the salt configuration file: .. code-block:: yaml logstash_zmq_handler: address: tcp://127.0.0.1:2021 + version: 0 - - On the `Logstash`_ configuration file you need something like: + In the `Logstash`_ configuration file: .. code-block:: text @@ -63,6 +86,27 @@ } } + For version 1.2.0 of `Logstash`_ and newer: + + In the salt configuration file: + + .. code-block:: yaml + + logstash_zmq_handler: + address: tcp://127.0.0.1:2021 + version: 1 + + In the `Logstash`_ configuration file: + + .. code-block:: text + + input { + zeromq { + topology => "pubsub" + address => "tcp://0.0.0.0:2021" + codec => json + } + } Please read the `ZeroMQ input`_ configuration page for additional information.