pax_global_header00006660000000000000000000000064123530712210014506gustar00rootroot0000000000000052 comment=db78ecbb8c8c5613bd4e37c35dba6efadb87f27f python-netsyslog/000077500000000000000000000000001235307122100144205ustar00rootroot00000000000000python-netsyslog/.gitignore000066400000000000000000000000061235307122100164040ustar00rootroot00000000000000*.pyc python-netsyslog/AUTHORS000066400000000000000000000000561235307122100154710ustar00rootroot00000000000000Graham Ashton python-netsyslog/INSTALL000066400000000000000000000014521235307122100154530ustar00rootroot00000000000000INSTALL ======= Installation is very straightforward. Simply run the following command from a terminal in which you have administrator privileges: # python setup.py install To test the installation ensure your syslog server is listening for incoming connections, then run an interactive Python interpreter and try the following code. $ python Python 2.4.1 (#2, Mar 30 2005, 21:51:10) [GCC 3.3.5 (Debian 1:3.3.5-8ubuntu2)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import netsyslog >>> import syslog >>> logger = netsyslog.Logger() >>> logger.add_host("myhost") >>> logger.log(syslog.LOG_USER, syslog.LOG_INFO, "Test", pid=True) You should see a log message appear in one of your syslog files (probably /var/log/messages). python-netsyslog/MANIFEST000066400000000000000000000000771235307122100155550ustar00rootroot00000000000000AUTHORS INSTALL README netsyslog.py netsyslog_test.py setup.py python-netsyslog/README000066400000000000000000000016501235307122100153020ustar00rootroot00000000000000README ====== netsyslog enables you to construct syslog messages and send them (via UDP) to a remote syslog server directly from Python. Unlike other syslog modules it allows you to set the metadata (e.g. time, host name, program name, etc.) yourself, giving you full control over the contents of the UDP packets that it creates. netsyslog was initially developed for the Hack Saw project, where it was used to read log messages from a file and inject them into a network of syslog servers, whilst maintaining the times and hostnames recorded in the original messages. The module also allows you to send log messages that contain the current time, local hostname and calling program name (i.e. the typical requirement of a logging package) to one or more syslog servers. The format of the UDP packets sent by netsyslog adheres closely to that defined in RFC 3164. For more information see http://hacksaw.sourceforge.net/netsyslog/ python-netsyslog/TODO000066400000000000000000000002051235307122100151050ustar00rootroot00000000000000TODO ==== - Move all automatic behaviour into Logger class. - Get rid of copious use of DEFAULT_FOO vars that obfuscate the tests. python-netsyslog/netsyslog.py000066400000000000000000000515141235307122100170270ustar00rootroot00000000000000# Copyright (C) 2005 Graham Ashton # Copyright (C) 2010 Daniel Pocock http://danielpocock.com # # This module is free software, and you may redistribute it and/or modify # it under the same terms as Python itself, so long as this copyright message # and disclaimer are retained in their original form. # # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # # $Id: netsyslog.py,v 1.9 2005/11/22 16:35:40 ashtong Exp $ """netsyslog enables you to construct syslog messages and send them (via UDP) to a remote syslog server directly from Python. You can send log messages that contain the current time, local hostname and calling program name (i.e. the typical requirement of a logging package) to one or more syslog servers. Unlike other syslog modules netsyslog also allows you to set the metadata (e.g. time, host name, program name, etc.) yourself, giving you full control over the contents of the UDP packets that it creates. See L{Logger.log} and L{Logger.send_packet} for a synopsis of these two techniques. The format of the UDP packets sent by netsyslog adheres closely to that defined in U{RFC 3164}. Much of the terminology used in the RFC has been incorporated into the names of the classes and properties and is used throughout this documentation. Further information and support can be found from the U{netsyslog home page}. """ __version__ = "0.1.0" import os import logging import socket import SocketServer import sys import time class Error(Exception): """Base class for exceptions in this module.""" pass class ParseError(Error): """Exception raised for errors parsing frames from the wire. Attributes: field -- input expression in which the error occurred msg -- explanation of the error """ def __init__(self, field, msg): print field + ", " + msg self.field = field self.msg = msg class PriPart(object): """The PRI part of the packet. Though never printed in the output from a syslog server, the PRI part is a crucial part of the packet. It encodes both the facility and severity of the packet, both of which are defined in terms of numerical constants from the standard syslog module. See Section 4.1.1 of RFC 3164 for details. """ def __init__(self, facility, severity): """Initialise the object, specifying facility and severity. Specify the arguments using constants from the syslog module (e.g. syslog.LOG_USER, syslog.LOG_INFO). """ assert facility is not None assert severity is not None self.facility = facility self.severity = severity @classmethod def fromWire(cls, pri_text): """Initialise the object, specifying a numerical priority from the wire. """ assert pri_text is not None try: pri_n = int(pri_text) except ValueError: raise ParseError("priority", "not numeric") facility = pri_n & 0xf8 severity = pri_n & 0x07 return cls(facility, severity) def __str__(self): value = self.facility + self.severity return "<%s>" % value class HeaderPart(object): """The HEADER part of the message. The HEADER contains a timestamp and a hostname. It is the first component of the log message that is displayed in the output of syslog. See Section 4.1.2 of RFC 3164 for details. """ def __init__(self, timestamp=None, hostname=None): """Initialise the object, specifying timestamp and hostname. The timestamp represents the local time when the log message was created. If the timestamp is not set the current local time will be used. See the L{HeaderPart.timestamp} property for a note on the format. The hostname should be set to the hostname of the computer that originally generated the log message. If the hostname is not set the hostname of the local computer will be used. See the L{HeaderPart.hostname} property for a note on the format. """ self.timestamp = timestamp self.hostname = hostname @classmethod def fromWire(cls, header_text): """Initialise the object, specifying text from the wire. """ assert header_text is not None # timestamp (15 bytes), space (1 byte), hostname (at least one byte) if len(header_text) < 17: raise ParseError("header", "should be at least 17 bytes") # timestamp is fixed length if header_text[15] != " ": raise ParseError("header", "16th byte should be a space") timestamp = header_text[0:15] if not cls._timestamp_is_valid(timestamp): raise ParseError("header/timestamp", "invalid timestamp: '%s'" % timestamp) hostname = header_text[16:] return cls(timestamp, hostname) def __str__(self): return "%s %s" % (self.timestamp, self.hostname) def _get_timestamp(self): return self._timestamp def parse_timestamp(self): """Parses the syslog timestamp string into a struct_time object. """ # syslog RFC3164 timestamps don't include a year value so # we must substitute it manually localtime = time.localtime() year = localtime.tm_year full_ts = "%d %s" % (year, self._timestamp) result = time.strptime(full_ts, "%Y %b %d %H:%M:%S") # In the first day of a year (tm_mon==1) we may still # receive some values from the last day of the previous year if result.tm_mon == 12 and localtime.tm_mon == 1: year = year - 1 full_ts = "%d %s" % (year, self._timestamp) result = time.strptime(full_ts, "%Y %b %d %H:%M:%S") return result def _format_timestamp_rfc3164(self, _timestamp): day = time.strftime("%d", _timestamp) if day[0] == "0": day = " " + day[1:] value = time.strftime("%b %%s %H:%M:%S", _timestamp) return value % day def _calculate_current_timestamp(self): localtime = time.localtime() return self._format_timestamp_rfc3164(localtime) @classmethod def _timestamp_is_valid(self, value): if value is None: return False for char in value: if ord(char) < 32 or ord(char) > 126: return False return True def _set_timestamp(self, value): if not self._timestamp_is_valid(value): value = self._calculate_current_timestamp() self._timestamp = value timestamp = property(_get_timestamp, _set_timestamp, None, """The local time when the message was written. Must follow the format 'Mmm DD HH:MM:SS'. If the day of the month is less than 10, then it MUST be represented as a space and then the number. """) def _get_hostname(self): return self._hostname def _set_hostname(self, value): if value is None: value = socket.gethostname() self._hostname = value hostname = property(_get_hostname, _set_hostname, None, """The hostname where the log message was created. Should be the first part of the hostname, or an IP address. Should NOT be set to a fully qualified domain name. """) class MsgPart(object): """Represents the MSG part of a syslog packet. The MSG part of the packet consists of the TAG and CONTENT. The TAG and the CONTENT fields must be separated by a non-alphanumeric character. Unless you ensure that the CONTENT field begins with such a character a seperator of a colon and space will be inserted between them when the C{MsgPart} object is converted into a UDP packet. See Section 4.1.3 of RFC 3164 for details. """ MAX_TAG_LEN = 32 def __init__(self, tag=None, content="", pid=None): """Initialise the object, specifying tag and content. See the documentation for the L{MsgPart.tag} and L{MsgPart.content} properties for further documentation. If the pid is set it will be prepended to the content in square brackets when the packet is created. """ self.tag = tag self.content = content self.pid = pid @classmethod def fromWire(cls, message_text): """Initialise the object, specifying text from the wire.""" assert message_text is not None # look for the tag[PID] text _colon = message_text.find(":") if _colon < 0: raise ParseError("message", "missing colon to separate tag from message") tag_text = message_text[0:_colon] begin_pid = tag_text.find("[") end_pid = tag_text.find("]") _pid = None if begin_pid > -1: if end_pid < 0: # not a valid message raise ParseError("message", "missing ']' in tag/pid section") _tag = tag_text[0:begin_pid] _pid = tag_text[begin_pid+1:end_pid] else: _tag = tag_text _pid = None _content = message_text[_colon+2:] return cls(_tag, _content, _pid) def __str__(self): content = self._prepend_seperator(self.content) if self.pid is not None: content = "[%s]" % self.pid + content return self.tag + content def _get_tag(self): return self._tag def _set_tag(self, value): if value is None: value = sys.argv[0] self._tag = value[:self.MAX_TAG_LEN] tag = property(_get_tag, _set_tag, None, """The name of the program that generated the log message. The tag can only contain alphanumeric characters. If the tag is longer than %d characters it will be truncated automatically. """ % MAX_TAG_LEN) def _get_content(self): return self._content def _prepend_seperator(self, value): try: first_char = value[0] except IndexError: pass else: if first_char.isalnum(): value = ": " + value return value def _set_content(self, value): self._content = value content = property(_get_content, _set_content, None, """The main component of the log message. The content field is a freeform field that often begins with the process ID (pid) of the program that created the message. """) class Packet(object): """Combines the PRI, HEADER and MSG into a packet. If the packet is longer than L{MAX_LEN} bytes in length it is automatically truncated prior to sending; any extraneous bytes are lost. """ MAX_LEN = 1024 def __init__(self, pri, header, msg): """Initialise the object. The three arguments must be instances of the L{PriPart}, L{HeaderPart} and L{MsgPart} classes. """ self.pri = pri self.header = header self.msg = msg @classmethod def fromWire(cls, packet_text): """Initialise the object, specifying packet text from the wire.""" assert packet_text is not None if len(packet_text) < 6: # not long enough raise ParseError("frame", "too short") if packet_text[0] != "<": # not correct syntax raise ParseError("frame", "should begin with '<'") gt = packet_text.index(">", 1) pri_text = packet_text[1:gt] # skip the next space and the timestamp sp = gt + 1 + 15 # now skip the hostname sp = packet_text.index(" ", sp + 1) header_text = packet_text[gt+1:sp] msg_text = packet_text[sp+1:] pri = PriPart.fromWire(pri_text) header = HeaderPart.fromWire(header_text) msg = MsgPart.fromWire(msg_text) return cls(pri, header, msg) def __str__(self): message = "%s%s %s" % (self.pri, self.header, self.msg) return message[:self.MAX_LEN] class Logger(object): """Send log messages to syslog servers. The Logger class provides two different methods for sending log messages. The first approach (the L{log} method) is suitable for creating new log messages from within a normal application. The second (the L{send_packet} method) is designed for use in circumstances where you need full control over the contents of the syslog packet. """ PORT = 514 def __init__(self): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._hostnames = {} def add_host(self, hostname): """Add hostname to the list of hosts that will receive packets. Can be a hostname or an IP address. Note that if the hostname cannot be resolved calls to L{log} or L{send_packet} will take a long time to return. """ self._hostnames[hostname] = 1 def remove_host(self, hostname): """Remove hostname from the list of hosts that will receive packets.""" del self._hostnames[hostname] def _send_packet_to_hosts(self, packet): for hostname in self._hostnames: self._sock.sendto(str(packet), (hostname, self.PORT)) def log(self, facility, level, text, pid=False): """Send the message text to all registered hosts. The facility and level will be used to create the packet's PRI part. The HEADER will be automatically determined from the current time and hostname. The MSG will be set from the running program's name and the text parameter. This is the simplest way to use netsyslog, creating log messages containing the current time, hostname, program name, etc. This is how you do it:: logger = netsyslog.Logger() logger.add_host("localhost") logger.log(syslog.LOG_USER, syslog.LOG_INFO, "Hello World") If pid is True the process ID will be prepended to the text parameter, enclosed in square brackets and followed by a colon. """ pri = PriPart(facility, level) header = HeaderPart() if pid: msg = MsgPart(content=text, pid=os.getpid()) else: msg = MsgPart(content=text) packet = Packet(pri, header, msg) self._send_packet_to_hosts(packet) def send_packet(self, packet): """Send a L{Packet} object to all registered hosts. This method requires more effort than L{log} as you need to construct your own L{Packet} object beforehand, but it does give you full control over the contents of the packet:: pri = netsyslog.PriPart(syslog.LOG_USER, syslog.LOG_INFO) header = netsyslog.HeaderPart("Jun 1 18:34:03", "myhost") msg = netsyslog.MsgPart("myprog", "Hello World", mypid) packet = netsyslog.Packet(pri, header, msg) logger = netsyslog.Logger() logger.add_host("localhost") logger.send_packet(packet) """ self._send_packet_to_hosts(packet) class SyslogTCPHandler(SocketServer.BaseRequestHandler): BUF_SIZE = 2048 MAX_CACHED = 4096 MAX_FRAME = 2048 TERM_CHAR = "\n" def setup(self): """Setup variables used by this instance.""" self.logger = logging.getLogger(__name__) self.cached = None self.frame_size = None self.logger.info("incoming TCP connection accepted") def handle(self): """Handle the incoming bytes, try to resolve them to frames.""" data = self.request.recv(self.BUF_SIZE) while len(data) > 0: if self.cached is None: self.cached = data else: if (len(self.cached) + len(data)) > self.MAX_CACHED: # too many bytes self.logger.warning("too much data") self.request.close() return self.cached = self.cached + data if len(self.cached) > 8: if self.frame_size is None: if self.cached[0] == "<": # non-transparent framing self.frame_size = -1 else: # octet counting sp = self.cached.find(" ") if sp < 0: # didn't find frame length terminated by a space self.logger.warning("suspected octet-framing, but frame length not terminated by a space") self.request.close() return try: self.frame_size = int(self.cached[0:sp]) except ValueError: # frame length is not a number self.logger.warning("frame length is not a number") self.request.close() return if self.frame_size < 1 or self.frame_size > self.MAX_FRAME: # specified frame size too small/big self.logger.warning("specified frame size is too big or too small") self.request.close() return # now we parsed the size, trim the frame size string from the # beginning of the frame self.cached = self.cached[sp+1:] try: if self.frame_size > 0: if len(self.cached) >= self.frame_size: self.handle_frame_text(self.frame_size, self.frame_size) else: term_idx = self.cached.find(self.TERM_CHAR) if term_idx >= 0: # do not consider the TERM_CHAR as part of the frame self.handle_frame_text(term_idx, term_idx + 1) except Exception as e: self.logger.warning("exception occurred parsing/handling a frame: " + str(e)) self.request.close() return # loop again data = self.request.recv(self.BUF_SIZE) # we get here if the received data size == 0 (connection closed) self.request.close() def handle_frame_text(self, frame_len, skip_len): """Handle the frame text, convert to L{Packet}.""" # extract the frame itself frame_text = self.cached[0:frame_len] # manage the buffer, there may be more data available if len(self.cached) > skip_len: self.cached = self.cached[skip_len:] else: self.cached = None self.frame_size = None # parse the frame try: frame = Packet.fromWire(frame_text) except ParseError: # these are errors we noticed raise except Exception: # these are errors the parser didn't correctly detect, should # analyze them and improve the parser raise ParseError("frame", "unexpected parsing error") try: self.handle_message(frame) except Exception: # the application (subclass) raised some exception raise def handle_message(self, frame): """Handle parsed Syslog frames. Applications should override this method. This default implementation prints some data from each frame. """ pass class ThreadedSyslogServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass class Collector(object): """Accept log messages from Syslog clients. Accept Syslog messages over the network and pass them to the application. """ def __init__(self, port=514, handler=SyslogTCPHandler): address = ("0.0.0.0", port) ThreadedSyslogServer.daemon_threads = True ThreadedSyslogServer.allow_reuse_address = True self.server = ThreadedSyslogServer(address, handler) def run(self): self.server.serve_forever() python-netsyslog/netsyslog_test.py000066400000000000000000000253751235307122100200740ustar00rootroot00000000000000# Copyright (C) 2005 Graham Ashton # Copyright (C) 2010 Daniel Pocock http://danielpocock.com # # This module is free software, and you may redistribute it and/or modify # it under the same terms as Python itself, so long as this copyright message # and disclaimer are retained in their original form. # # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # # $Id: netsyslog_test.py,v 1.4 2005/07/15 09:49:29 ashtong Exp $ import copy import os import socket import sys import syslog import time import unittest from pmock import * import netsyslog class PriPartTest(unittest.TestCase): def test_priority_format(self): """Check PRI is correctly formatted""" pri = netsyslog.PriPart(syslog.LOG_LOCAL4, syslog.LOG_NOTICE) self.assertEqual(str(pri), "<165>") DEFAULT_TIMESTAMP = "Jun 7 09:00:00" DEFAULT_HOSTNAME = "myhost" DEFAULT_HEADER = "%s %s" % (DEFAULT_TIMESTAMP, DEFAULT_HOSTNAME) class MockHeaderTest(unittest.TestCase): def mock_localtime(self): return (2005, 6, 7, 9, 0, 0, 1, 158, 1) # see DEFAULT_TIMESTAMP def mock_gethostname(self): return "myhost" def setUp(self): self.real_localtime = time.localtime time.localtime = self.mock_localtime self.real_gethostname = socket.gethostname socket.gethostname = self.mock_gethostname def tearDown(self): time.localtime = self.real_localtime socket.gethostname = self.real_gethostname class HeaderPartTest(MockHeaderTest): def test_automatic_timestamp(self): """Check HEADER is automatically calculated if not set""" header = netsyslog.HeaderPart() self.assertEqual(str(header), " ".join((DEFAULT_TIMESTAMP, DEFAULT_HOSTNAME))) def test_incorrect_characters_disallowed(self): """Check only valid characters are used in the HEADER""" # Only allowed characters are ABNF VCHAR values and space. # Basically, if ord() returns between 32 and 126 inclusive # it's okay. bad_char = u"\x1f" # printable, ord() returns 31 header = netsyslog.HeaderPart() header.timestamp = header.timestamp[:-1] + bad_char self.assertEqual(str(header), " ".join((DEFAULT_TIMESTAMP, DEFAULT_HOSTNAME))) def test_set_timestamp_manually(self): """Check it is possible to set the timestamp in HEADER manually""" timestamp = "Jan 31 18:12:34" header = netsyslog.HeaderPart(timestamp=timestamp) self.assertEqual(str(header), "%s %s" % (timestamp, DEFAULT_HOSTNAME)) def test_set_hostname_manually(self): """Check it is possible to set the hostname in HEADER manually""" hostname = "otherhost" header = netsyslog.HeaderPart(hostname=hostname) self.assertEqual(str(header), "%s %s" % (DEFAULT_TIMESTAMP, hostname)) # check format of time and hostname, set automatically if incorrect # - time is "Mmm dd hh:mm:ss" where dd has leading space, hh leading 0 # - single space between time and hostname # - no space in hostname # - if using hostname, not IP, no dots allowed # print message to stderr if badly formatted message encountered DEFAULT_TAG = "program" MOCK_PID = 1234 class MockMsgTest(unittest.TestCase): def mock_getpid(self): return MOCK_PID def setUp(self): self.real_argv = sys.argv sys.argv = [DEFAULT_TAG] self.real_getpid = os.getpid os.getpid = self.mock_getpid def tearDown(self): sys.argv = self.real_argv os.getpid = self.real_getpid class MsgPartTest(MockMsgTest): def test_tag_defaults_to_progname(self): """Check TAG defaults to program name""" msg = netsyslog.MsgPart() self.assertEqual(msg.tag, DEFAULT_TAG) def test_override_tag(self): """Check TAG can be set manually""" msg = netsyslog.MsgPart(tag="mytag") self.assertEqual(msg.tag, "mytag") def test_tag_trimmed_if_too_long(self): """Check long TAGs are trimmed to 32 characters""" tag = "abcd" * 10 msg = netsyslog.MsgPart(tag=tag) self.assertEqual(msg.tag, tag[:32]) def test_space_prefixed_to_content(self): """Check single space inserted infront of CONTENT if necessary""" msg = netsyslog.MsgPart("program", content="hello") self.assertEqual(str(msg), "program: hello") def test_space_only_added_if_necessary(self): """Check space only added to CONTENT if necessary""" msg = netsyslog.MsgPart("program", content=" hello") self.assertEqual(str(msg), "program hello") def test_include_pid(self): """Check the program's pid can be included in CONTENT""" msg = netsyslog.MsgPart("program", "hello", pid=MOCK_PID) self.assertEqual(str(msg), "program[%d]: hello" % (MOCK_PID)) DEFAULT_PRI = netsyslog.PriPart(syslog.LOG_LOCAL4, syslog.LOG_NOTICE) DEFAULT_HEADER = netsyslog.HeaderPart(DEFAULT_TIMESTAMP, DEFAULT_HOSTNAME) DEFAULT_MSG = netsyslog.MsgPart(DEFAULT_TAG, "hello") class PacketTest(unittest.TestCase): def test_message_format(self): """Check syslog message is correctly constructed""" packet = netsyslog.Packet(DEFAULT_PRI, DEFAULT_HEADER, DEFAULT_MSG) header = " ".join((DEFAULT_TIMESTAMP, DEFAULT_HOSTNAME)) start_of_packet = "<165>%s %s" % (header, DEFAULT_TAG) self.assert_(str(packet).startswith(start_of_packet)) def test_max_length(self): """Check that no syslog packet is longer than 1024 bytes""" message = "a" * 2048 packet = netsyslog.Packet(DEFAULT_PRI, DEFAULT_HEADER, message) self.assertEqual(len(str(packet)), netsyslog.Packet.MAX_LEN) def test_parse(self): """Check that we can parse a message from the wire""" packet = netsyslog.Packet.fromWire("<142>Mar 16 08:58:41 alpha1 foobar[1234]: testing") self.assertEqual(packet.pri.severity, syslog.LOG_INFO) self.assertEqual(packet.pri.facility, syslog.LOG_LOCAL1) self.assertEqual(packet.header.hostname, "alpha1") self.assertEqual(packet.msg.tag, "foobar") self.assertEqual(packet.msg.pid, "1234") self.assertEqual(packet.msg.content, "testing") parsed_ts = packet.header.parse_timestamp() self.assertEqual(parsed_ts.tm_mon, 3) self.assertEqual(parsed_ts.tm_mday, 16) self.assertEqual(parsed_ts.tm_hour, 8) # now try with an unusual date (single digit for day of month): packet = netsyslog.Packet.fromWire("<142>Mar 6 08:58:41 alpha1 foobar[1234]: testing") self.assertEqual(packet.header.hostname, "alpha1") try: # just too short packet = netsyslog.Packet.fromWire("<2>") self.assert_(true) except netsyslog.ParseError: pass try: # timestamp single digit for day of month: packet = netsyslog.Packet.fromWire("<142>Mar 6 08:58:41 alpha1 foobar[1234]: testing") self.assert_(true) except netsyslog.ParseError: pass class LoggerTest(MockHeaderTest, MockMsgTest): def mock_socket(self, family, proto): return self.mock_sock def setUp(self): MockHeaderTest.setUp(self) MockMsgTest.setUp(self) self.mock_sock = Mock() self.real_socket = socket.socket socket.socket = self.mock_socket def tearDown(self): socket.socket = self.real_socket MockMsgTest.tearDown(self) MockHeaderTest.tearDown(self) def test_send_message(self): """Check we can send a message via UDP""" logger = netsyslog.Logger() packet = netsyslog.Packet(DEFAULT_PRI, DEFAULT_HEADER, DEFAULT_MSG) hostname = "localhost" address = (hostname, netsyslog.Logger.PORT) self.mock_sock.expects(once()).sendto(eq(str(packet)), eq(address)) logger.add_host(hostname) hostname = "remotehost" address = (hostname, netsyslog.Logger.PORT) self.mock_sock.expects(once()).sendto(eq(str(packet)), eq(address)) logger.add_host(hostname) logger.log(syslog.LOG_LOCAL4, syslog.LOG_NOTICE, "hello") self.mock_sock.verify() def test_remove_host(self): """Check host can be removed from list of those receiving messages""" hostname = "localhost" logger = netsyslog.Logger() logger.add_host(hostname) logger.remove_host(hostname) logger.log(syslog.LOG_LOCAL4, syslog.LOG_NOTICE, "hello") self.mock_sock.verify() def test_pid_not_included_by_default(self): """Check the program's pid is not included by default""" packet = "<165>%s myhost program: hello" % DEFAULT_TIMESTAMP hostname = "localhost" address = (hostname, netsyslog.Logger.PORT) self.mock_sock.expects(once()).sendto(eq(packet), eq(address)) logger = netsyslog.Logger() logger.add_host(hostname) logger.log(syslog.LOG_LOCAL4, syslog.LOG_NOTICE, "hello") self.mock_sock.verify() def test_include_pid(self): """Check the program's pid can be included in a log message""" packet = "<165>%s myhost program[1234]: hello" % DEFAULT_TIMESTAMP hostname = "localhost" address = (hostname, netsyslog.Logger.PORT) self.mock_sock.expects(once()).sendto(eq(packet), eq(address)) logger = netsyslog.Logger() logger.add_host(hostname) logger.log(syslog.LOG_LOCAL4, syslog.LOG_NOTICE, "hello", pid=True) self.mock_sock.verify() def test_send_packets_by_hand(self): """Check we can send a hand crafted log packet""" hostname = "localhost" address = (hostname, netsyslog.Logger.PORT) packet_text = "<165>Jan 1 10:00:00 myhost myprog: hello" self.mock_sock.expects(once()).sendto(eq(packet_text), eq(address)) pri = netsyslog.PriPart(syslog.LOG_LOCAL4, syslog.LOG_NOTICE) header = netsyslog.HeaderPart("Jan 1 10:00:00", "myhost") msg = netsyslog.MsgPart("myprog", "hello") packet = netsyslog.Packet(pri, header, msg) logger = netsyslog.Logger() logger.add_host(hostname) logger.send_packet(packet) self.mock_sock.verify() if __name__ == "__main__": unittest.main() python-netsyslog/setup.py000066400000000000000000000016601235307122100161350ustar00rootroot00000000000000# Copyright (C) 2005 Graham Ashton # # $Id: setup.py,v 1.3 2006/02/13 01:53:56 ashtong Exp $ from distutils.core import setup import netsyslog if __name__ == "__main__": setup(py_modules=["netsyslog"], name="netsyslog", version=netsyslog.__version__, author="Graham Ashton", author_email="ashtong@users.sourceforge.net", url="http://hacksaw.sourceforge.net/netsyslog/", description="Send log messages to remote syslog servers", long_description="""netsyslog is a Python module that enables you to construct syslog messages and send them (via UDP) to a remote syslog server. Unlike other syslog modules it allows you to set the metadata (e.g. time, host name, program name, etc.) yourself, giving you full control over the contents of the UDP packets that it creates.""", ) python-netsyslog/simple_server.py000066400000000000000000000035751235307122100176630ustar00rootroot00000000000000 # # This is a very trivial demo of how to write a syslog collector # that receives messages from the wire and prints them on # the screen. # # To run it, specify the port number as the first command line # argument, e.g. # # python simple_server.py 10514 # import logging import netsyslog import sys class MyHandler(netsyslog.SyslogTCPHandler): def handle_message(self, frame): """Handle parsed Syslog frames. Applications should override this method. This default implementation prints some data from each frame. """ print "severity: " + str(frame.pri.severity) print "facility: " + str(frame.pri.facility) print "tag: " + str(frame.msg.tag) print "pid: " + str(frame.msg.pid) print "content: " + str(frame.msg.content) print "host: " + str(frame.header.hostname) print "ts: " + str(frame.header.timestamp) print "" if __name__ == '__main__': logging.basicConfig(level=logging.INFO) c = netsyslog.Collector(int(sys.argv[1]), MyHandler) c.run() # Copyright (C) 2010, Daniel Pocock http://danielpocock.com # # This module is free software, and you may redistribute it and/or modify # it under the same terms as Python itself, so long as this copyright message # and disclaimer are retained in their original form. # # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.