Extending syslog-ng in Python: Best of both worlds
CC Attribution 2.0 Belgium:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Czanik, Peter
The syslog-ng application collects logs from many different sources, performs real-time log analysis by processing and filtering them, and finally it stores the logs, or forwards them for further analysis. It was originally developed in C due to its low footprint and high performance. Recently Python language bindings were added, so you can collect, process, and store log messages using embedded Python code. Learn how to customize your log processing infrastructure in Python using syslog-ng, integrate your logs to local and cloud services, and the implementation details and benefits of such solutions. The syslog-ng application collects logs from many different sources, performs real-time log analysis by processing and filtering them, and finally it stores the logs, or forwards them for further analysis. It was originally developed in C due to its low footprint and high performance. Recently Python language bindings were added, so you can collect, process and store log messages using embedded Python code. Python bindings always have two parts: a configuration part – where you configure a source, parser, or destination – and the actual Python code part. You can store the code in-line in the syslog-ng configuration file, or in external files, if you have a larger project. The first Python binding added to syslog-ng was the Python destination. Many databases and cloud services have an easy to use Python API, while a C implementation of the API is either missing or difficult to implement. The syslog-ng application handles log messages and parsed data as name-value pairs. You can pass name-values to the Python destination code in mulitple ways: an object which contains all of the name-value pairs, or as a dict, which contains only name-value pairs configured in syslog-ng.conf. Recent releases also support the use of templates. Next the Python parser was implemented. Originally it was intended as an easy-to-code parser for messages not supported by the built-in parsers, like PatternDB or csv-parser. But you can also use it to enrich log messages from external databases. For example, you can use it to resolve host names from IP addresses. Recently support for Python source was added. You can use it to fetch data or implement a server in Python, for example, to collect messages over HTTP or Kafka protocols. Obviously the Python code is slower than native C code. On the other hand, extending syslog-ng in Python offers a lot more flexibility than development in C. You do not need a development environment other than a text editor and the Python module installed. Neither do you need you to compile the code. Just write it and use it. It is easy to get started, as there is no need for extra setup. You can get proof-of-concept minimal setup working with a single method quickly. You can extend it later to have proper initialization and connection control for added robustness. Learn how to customize your log processing infrastructure in Python using syslog-ng, integrate your logs to local and cloud services, and the implementation details and benefits of such solutions.
 Hi, I'm Peter, and today I will talk about extending CystogenG in Python. I'm coming from Hungary. I'm evangelist at one identity. CystogenG is upstream developer. I'm doing CystogenG packaging support and advocacy. First, I will give you a quick introduction. I will show you how to configure CystogenG for Python and a few simple source code examples, how you can code in Python for CystogenG. So, what is logging? It's a recording of events, like an SSH login. You can see many of these messages in the world directory. CystogenG is a strong focus on portability and high performance central log collection. It was originally developed in C. So, why Python? Python is actually slower than C. On the other hand, it makes development a lot more easy and flexible. You don't need a dedicated development environment like with C. It's just a text editor and you are ready to go. Even a sysadmin like myself can extend CystogenG with new functionality. CystogenG is collecting messages, filtering them and storing them either locally or to another destination. The first tool is data collection. There are a wide variety of platform-specific sources that support it, like devlog, journal, and central log collector. It knows the legacy and the new C-strock protocol, where UDP, TCP and encrypted connections. But the absolute jolichoker source is the Python source. This way, you can add easily just about anything, like an HTTP server to collect log messages or fetch messages from Amazon CloudVetch using the Python API or adding a Kafka source without really too much effort. The next tool is processing. You can classify, normalize, and structure log messages with built-in parsers. You can divide log messages and you don't have to think about falsifying messages here, but for example, anonymization is required by many different compliance regulations. You can reformat messages. For example, if your destination meets JSON, you can turn your messages to JSON. You can enrich messages using GIP or add additional fields on message content. The Python parser can do practically all of the above I mentioned. You can also use it to enrich log messages from external databases and also do filtering, which brings us immediately to our next topic, filtering, which has two main uses. One is discarding surplus log messages and the other one is message routing. So you can make sure that, for example, all authentication-related messages reach your log analysis systems. It can be based on many possibilities like message content, parameters, different macros, using comparisons, write cards, and filtering functions. And best of all, any of these can be combined using good operators to make really complex filtering. Finally, you have to store your log messages somewhere. Traditionally, if Syslog messages were stored locally into flat files or sent to a central location using the Syslog protocol and saved to flat files over there. Over the times, many different destinations were added like SQL, different big data destinations like Hadoop, Elasticsearch, and now we also have Python destinations, so you can easily write support for just about anything. Personally, I did an MQTT destination, which I already used in production in many places. Let's talk a few words about log messages. If you look at a typical log message, you see that it's a date, a host name, and some text after it, like this SSH login message. So look at the text part, you see that it's an almost complete English sentence with some variable parts in it. It's pretty easy to be read by a human, but you will have quite tough time if you want to create alerts and reports automatically from these kind of messages. The solution for this is to use structured logging, in which case events are represented as name value pairs. For example, the SSH login I've shown you in the previous slide can be described as an application name, a user name, and a source IP address. And Cisco Gen.G has name value pairs right from the beginning, the date, facility, priority, and so on. And parsers can turn many unstructured and some structured data also into name value pairs. And the Python bindings of SSH support name value pairs as well. So before going deeper into Python, a tricky question, without looking into the next slide, what do you think, which is the most used SSH version? Some pointers, our most popular distribution has 3.5, the current version is 3.19. What do you think? Well, close. It's 1.6, I don't think there are any other computers running in this number, so it's 1.6, and over 100,000 Kindle devices. So back to more serious topics, configuring Cisco Gen.G. So my initial advice is don't panic. Configuring Cisco Gen.G looks difficult task at first, but it's really simple. It has a PyPy model where there are many different building blocks like sources, where you collect messages, destinations, where you store messages, filters, parsers, and so on. And these building blocks are connected together using log statements. Here you can see a quick example. It starts with a version number. You can include extra other configurations. You can comment your configuration, define some global options, so you don't have to specify each time how many lines to write together, but configure it once, and you can override it later on. Then here are the different building blocks I mentioned, a source, a destination, a filter, these defined Warlock messages. And you connect all of these together in log statements, the source, the filter, and the destination. So how Python is coming here? The Python bindings always have two parts, a configuration part and the code part. Usually the configuration part has just a single mandatory option, the class name, what you call from Cystogengy. And you can also pass many parameters and also pass parameters to your Python code. So you don't have to hard code everything into Python, but have generic Python code, which you parameter from your Cystogengy configuration. The code itself can be stored in the Cystogengy configuration in a Python block or stored in external files. Like you can put it just anywhere in the script and call it or create the proper Python module and use that. What, which Python versions are supported, it really depends on, at the time, compiling Cystogengy. So it supports one version at a time. It can be compiled with 2.7 or 3.4 or later. And in any packages I maintain, I compile it with Python 3. Let's start with the Python destination, even if it's logically at the end of the chain, but this was the first one implemented within Cystogengy. On the Python side, the only method you have to implement is send, which is actually sending your message to somewhere. And you can pass name value pairs from Cystogengy to Python in two ways. If you don't define what to send to Python in the configuration, then there is an object containing all of the different name value pairs from Cystogengy, or you can define what values to send to the destination. And in that case, all those name value pairs are in a dict. There are many optional possibilities configuring the Python destination. You can define things like this buffer, so no messages are lost if the Python destination is busy. And if you want to write a real robust code, then you don't use only a single send method, but do proper initialization, then it, which are called when Cystogengy started or reloaded, or use open and close, which are called from Cystogengy when Cystogengy started, reloaded, or when sending the message failed. Here is a very simple Python destination. It's practically a five destination, re-inflammated in Python. As you can see, here is just a single class option in the Python destination, the same as here in the Python code. We have a log statement which connects the default source to the Python destination, and here is the Python code inline. The Cystogengy configuration, as you can see, the class name is the same as defined up there, and we have a single send method which has all of the code, the five-name hardcoded, picking a single name value pair through the message object, writing it into the disk, and closing the file. Of course, if you want to do something productionally, then you better use all of the optional methods I mentioned on the previous slide, but this also works, and good something to get started with. The next one is the Python parser, where in the Python code, only the parse method is mandatory, and here name value pairs are available only through an object, and here you can create new name value pairs as well using this object. In the sample code on the next slide, I will parse this log message. This is generated by loggan. That's a tool for Cystogengy for testing and benchmarking, and it has this beautiful message format. Here you can see the configuration part for the Python parser. Here is the parser defined. As you can see, the class name is here, and here we also have an optional thing. The regular expression parsing the message on the previous slide is passed here as an object, passed here as an option to the Python code. Here is a log statement, which starts with a network collector, the Python parser, and the destination file, where we also use a message template with the name value pairs extracted from the previous log message by using the Python code. Here is the actual Python code using this, importing the regular expression library and the class we defined also in the configuration. Here we also use the init method, where we initialize the regular expression parser and also start the counter. And showing this as the Python code is started together with Cystogengy, and it's running constantly as long as Cystogengy is running for reloaded. So you can create variables, create a counter like this, and you can refer to it one later, log messages. So if Python is not started each time a new message arrived. And here is the code part. The init method is usually empty, but I put it here, and here is the parser part. So we pick the message from the log message object, run the regular expression parser init, create new name value pairs to the log message object, create a counter name value pair, and increase the counter here. Finally, we also have a Python source. This was released just recently, probably two months ago or three. It has very few optional configuration possibilities like time zone handling, name value pairs are handled here also through an object, and it has two modes. You can write a complete server application for receiving, collecting messages, or you can use it as a fetcher where Cystogengy itself handles the event loop. In my example, I used the server part, and the server has two mandatory methods, the run method and the request exit. The later one is necessary to quit from our own event loop. Here is the configuration part of the Python, a simple Python source. Here we define the request name and pass a few dummy options, file destination and log statement which combines the two together, so the Python source and the destination file. And here is the actual Python code for the Python source. This source simply generates log messages like crazy as long as Cystogengy is running, so it's an easy way to fill up your hard drive quickly. I tested just for numbers. Cystogengy, on my laptop, Cystogengy can handle about 90,000 messages, and with this Python source, it handled about 300,000 messages. So it's still quite a nice number. To use the Python source, you need to import a few classes from Cystogengy, and in the class, the class needs to descend it from the log source class. In the initialization part, we just simply print the options I defined and set the exit variable to false. This is used by the request exit method, and here in the event loop of my Python code to exit once Cystogengy is reloaded or stopped. And here in this part, we generate log messages, just a simple text message and send it to Cystogengy. And here is a preview which was merged just recently into Cystogengy, and will be available in the next series. This way, debugging Python code in Cystogengy will be easier. By using the logger method of Cystogengy, you can create log messages from the Python code for Cystogengy to the appearing on the internal source. So you can follow what's going on in your Python code as a log message within Cystogengy, or if you start it in the foreground, then these messages are printed on your terminal window. These were quite simple examples, but there are more complex examples on my blog, an MQTT destination, a few parsers, and then HTTP source. You can check these out for more complex examples. If you want to learn more about Cystogengy and try it out, either just Cystogengy or the Python bindings, you can find more information on Cystogengy.org. The source code is on GitHub, and we also have an issue tracker there if you run into any problems, and you can ask questions on our mailing list or on JITAR. And we have probably two minutes for questions. If you have any. Can you send messages to Kinesis? Sorry. Can you send messages to Kinesis from this log in JIT? What I mentioned here is a collector, so it can pull messages from there. I guess if it has an API, then it's not yet implemented, but it shouldn't be difficult to implement it. It's open source. The earlier Python bindings are already in distributions, but if it's too early Cystogengy in the distros, then there are third-party repositories with up-to-date packages with Python bindings enabled. Any questions? Okay, thank you. Thank you. Thank you.
 Hi, I'm Peter, and today I will talk about extending CystogenG in Python.  I'm coming from Hungary. I'm evangelist at one identity. CystogenG is upstream developer.  I'm doing CystogenG packaging support and advocacy. First, I will give you a quick introduction.  I will show you how to configure CystogenG for Python and a few simple source code examples,  how you can code in Python for CystogenG. So, what is logging? It's a recording of events,  like an SSH login. You can see many of these messages in the world directory.  CystogenG is a strong focus on portability and high performance central log collection.  It was originally developed in C. So, why Python? Python is actually slower than C.  On the other hand, it makes development a lot more easy and flexible.  You don't need a dedicated development environment like with C. It's just a text editor and you are ready to go.  Even a sysadmin like myself can extend CystogenG with new functionality.  CystogenG is collecting messages, filtering them and storing them either locally or to another destination.  The first tool is data collection. There are a wide variety of platform-specific sources that support it,  like devlog, journal, and central log collector. It knows the legacy and the new C-strock protocol,  where UDP, TCP and encrypted connections. But the absolute jolichoker source is the Python source.  This way, you can add easily just about anything, like an HTTP server to collect log messages or fetch messages from Amazon CloudVetch  using the Python API or adding a Kafka source without really too much effort.  The next tool is processing. You can classify, normalize, and structure log messages with built-in parsers.  You can divide log messages and you don't have to think about falsifying messages here,  but for example, anonymization is required by many different compliance regulations.  You can reformat messages. For example, if your destination meets JSON, you can turn your messages to JSON.  You can enrich messages using GIP or add additional fields on message content.  The Python parser can do practically all of the above I mentioned.  You can also use it to enrich log messages from external databases and also do filtering,  which brings us immediately to our next topic, filtering, which has two main uses.  One is discarding surplus log messages and the other one is message routing.  So you can make sure that, for example, all authentication-related messages reach your log analysis systems.  It can be based on many possibilities like message content, parameters, different macros,  using comparisons, write cards, and filtering functions.  And best of all, any of these can be combined using good operators to make really complex filtering.  Finally, you have to store your log messages somewhere.  Traditionally, if Syslog messages were stored locally into flat files or sent to a central location  using the Syslog protocol and saved to flat files over there.  Over the times, many different destinations were added like SQL, different big data destinations  like Hadoop, Elasticsearch, and now we also have Python destinations,  so you can easily write support for just about anything.  Personally, I did an MQTT destination, which I already used in production in many places.  Let's talk a few words about log messages.  If you look at a typical log message, you see that it's a date, a host name, and some text after it,  like this SSH login message.  So look at the text part, you see that it's an almost complete English sentence with some variable parts in it.  It's pretty easy to be read by a human, but you will have quite tough time if you want to create alerts  and reports automatically from these kind of messages.  The solution for this is to use structured logging, in which case events are represented as name value pairs.  For example, the SSH login I've shown you in the previous slide can be described as an application name,  a user name, and a source IP address.  And Cisco Gen.G has name value pairs right from the beginning, the date, facility, priority, and so on.  And parsers can turn many unstructured and some structured data also into name value pairs.  And the Python bindings of SSH support name value pairs as well.  So before going deeper into Python, a tricky question, without looking into the next slide,  what do you think, which is the most used SSH version?  Some pointers, our most popular distribution has 3.5, the current version is 3.19.  What do you think?  Well, close.  It's 1.6, I don't think there are any other computers running in this number, so it's 1.6,  and over 100,000 Kindle devices.  So back to more serious topics, configuring Cisco Gen.G.  So my initial advice is don't panic.  Configuring Cisco Gen.G looks difficult task at first, but it's really simple.  It has a PyPy model where there are many different building blocks like sources,  where you collect messages, destinations, where you store messages, filters, parsers,  and so on.  And these building blocks are connected together using log statements.  Here you can see a quick example.  It starts with a version number.  You can include extra other configurations.  You can comment your configuration, define some global options,  so you don't have to specify each time how many lines to write together,  but configure it once, and you can override it later on.  Then here are the different building blocks I mentioned, a source, a destination,  a filter, these defined Warlock messages.  And you connect all of these together in log statements, the source, the filter,  and the destination.  So how Python is coming here?  The Python bindings always have two parts, a configuration part and the code part.  Usually the configuration part has just a single mandatory option, the class name,  what you call from Cystogengy.  And you can also pass many parameters and also pass parameters to your Python code.  So you don't have to hard code everything into Python, but have generic Python code,  which you parameter from your Cystogengy configuration.  The code itself can be stored in the Cystogengy configuration in a Python block  or stored in external files.  Like you can put it just anywhere in the script and call it or create the proper Python module and use that.  What, which Python versions are supported, it really depends on, at the time,  compiling Cystogengy.  So it supports one version at a time.  It can be compiled with 2.7 or 3.4 or later.  And in any packages I maintain, I compile it with Python 3.  Let's start with the Python destination, even if it's logically at the end of the chain,  but this was the first one implemented within Cystogengy.  On the Python side, the only method you have to implement is send,  which is actually sending your message to somewhere.  And you can pass name value pairs from Cystogengy to Python in two ways.  If you don't define what to send to Python in the configuration,  then there is an object containing all of the different name value pairs from Cystogengy,  or you can define what values to send to the destination.  And in that case, all those name value pairs are in a dict.  There are many optional possibilities configuring the Python destination.  You can define things like this buffer, so no messages are lost if the Python destination is busy.  And if you want to write a real robust code, then you don't use only a single send method,  but do proper initialization, then it, which are called when Cystogengy started or reloaded,  or use open and close, which are called from Cystogengy when Cystogengy started, reloaded,  or when sending the message failed.  Here is a very simple Python destination.  It's practically a five destination, re-inflammated in Python.  As you can see, here is just a single class option in the Python destination,  the same as here in the Python code.  We have a log statement which connects the default source to the Python destination,  and here is the Python code inline.  The Cystogengy configuration, as you can see, the class name is the same as defined up there,  and we have a single send method which has all of the code, the five-name hardcoded,  picking a single name value pair through the message object, writing it into the disk,  and closing the file.  Of course, if you want to do something productionally, then you better use all of the optional methods  I mentioned on the previous slide, but this also works, and good something to get started with.  The next one is the Python parser, where in the Python code, only the parse method is mandatory,  and here name value pairs are available only through an object,  and here you can create new name value pairs as well using this object.  In the sample code on the next slide, I will parse this log message.  This is generated by loggan.  That's a tool for Cystogengy for testing and benchmarking,  and it has this beautiful message format.  Here you can see the configuration part for the Python parser.  Here is the parser defined.  As you can see, the class name is here, and here we also have an optional thing.  The regular expression parsing the message on the previous slide is passed here as an object,  passed here as an option to the Python code.  Here is a log statement, which starts with a network collector, the Python parser,  and the destination file, where we also use a message template with the name value pairs  extracted from the previous log message by using the Python code.  Here is the actual Python code using this,  importing the regular expression library and the class we defined also in the configuration.  Here we also use the init method, where we initialize the regular expression parser  and also start the counter.  And showing this as the Python code is started together with Cystogengy,  and it's running constantly as long as Cystogengy is running for reloaded.  So you can create variables, create a counter like this,  and you can refer to it one later, log messages.  So if Python is not started each time a new message arrived.  And here is the code part.  The init method is usually empty, but I put it here, and here is the parser part.  So we pick the message from the log message object, run the regular expression parser init,  create new name value pairs to the log message object,  create a counter name value pair, and increase the counter here.  Finally, we also have a Python source.  This was released just recently, probably two months ago or three.  It has very few optional configuration possibilities like time zone handling,  name value pairs are handled here also through an object, and it has two modes.  You can write a complete server application for receiving, collecting messages,  or you can use it as a fetcher where Cystogengy itself handles the event loop.  In my example, I used the server part, and the server has two mandatory methods,  the run method and the request exit.  The later one is necessary to quit from our own event loop.  Here is the configuration part of the Python, a simple Python source.  Here we define the request name and pass a few dummy options,  file destination and log statement which combines the two together,  so the Python source and the destination file.  And here is the actual Python code for the Python source.  This source simply generates log messages like crazy as long as Cystogengy is running,  so it's an easy way to fill up your hard drive quickly.  I tested just for numbers.  Cystogengy, on my laptop, Cystogengy can handle about 90,000 messages,  and with this Python source, it handled about 300,000 messages.  So it's still quite a nice number.  To use the Python source, you need to import a few classes from Cystogengy,  and in the class, the class needs to descend it from the log source class.  In the initialization part, we just simply print the options I defined  and set the exit variable to false.  This is used by the request exit method,  and here in the event loop of my Python code to exit once Cystogengy is reloaded or stopped.  And here in this part, we generate log messages,  just a simple text message and send it to Cystogengy.  And here is a preview which was merged just recently into Cystogengy,  and will be available in the next series.  This way, debugging Python code in Cystogengy will be easier.  By using the logger method of Cystogengy, you can create log messages  from the Python code for Cystogengy to the appearing on the internal source.  So you can follow what's going on in your Python code as a log message within Cystogengy,  or if you start it in the foreground, then these messages are printed on your terminal window.  These were quite simple examples,  but there are more complex examples on my blog,  an MQTT destination, a few parsers, and then HTTP source.  You can check these out for more complex examples.  If you want to learn more about Cystogengy and try it out,  either just Cystogengy or the Python bindings,  you can find more information on Cystogengy.org.  The source code is on GitHub, and we also have an issue tracker there if you run into any problems,  and you can ask questions on our mailing list or on JITAR.  And we have probably two minutes for questions.  If you have any.  Can you send messages to Kinesis?  Sorry.  Can you send messages to Kinesis from this log in JIT?  What I mentioned here is a collector, so it can pull messages from there.  I guess if it has an API, then it's not yet implemented,  but it shouldn't be difficult to implement it.  It's open source.  The earlier Python bindings are already in distributions,  but if it's too early Cystogengy in the distros,  then there are third-party repositories with up-to-date packages  with Python bindings enabled.  Any questions?  Okay, thank you.  Thank you.  Thank you.
Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_0_47.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_48_95.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_96_143.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_144_611.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_612_1079.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_1080_1151.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_1152_1283.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_1284_1775.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_1776_3203.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_3204_3491.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_3492_4715.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_4716_6095.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_6096_7103.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_7104_8279.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_8280_9203.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_9204_10283.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_10284_11051.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_11052_11099.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_11100_11147.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_11148_11243.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_11244_11375.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_11376_12275.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_12276_12359.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_12360_12419.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_12420_13511.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_13512_15959.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_15960_17519.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_17520_19043.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_19044_20999.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_21000_22163.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_22164_23615.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_23616_25151.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_25152_26267.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_26268_27983.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_27984_28319.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_28320_28811.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_28812_31559.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_31560_33023.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_33024_33695.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_33696_34523.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_34524_37631.png Extending_syslog-ng_in_Python_Best_of_both_worlds_4J7-r_eBLi8_37632_37754.png