Git Product home page Git Product logo

python-logging-rabbitmq's Introduction

python-logging-rabbitmq

Package License PyPI - Version PyPI - Wheel Build Status

Logging handler to ships logs to RabbitMQ. Compatible with Django.

Installation

Install using pip.

pip install python_logging_rabbitmq

Versions

Version Dependency
>= 2.x Pika == 0.13
<= 1.1.1 Pika <= 0.10

Handlers

This package has two built-in handlers that you can import as follows:

from python_logging_rabbitmq import RabbitMQHandler

or (thanks to @wallezhang)

from python_logging_rabbitmq import RabbitMQHandlerOneWay
Handler Description
RabbitMQHandler Basic handler for sending logs to RabbitMQ. Every record will be delivered directly to RabbitMQ using the exchange configured.
RabbitMQHandlerOneWay High throughput handler. Initializes an internal queue where logs are stored temporarily. A thread is used to deliver the logs to RabbitMQ using the exchange configured. Your app doesn't need to wait until the log is delivered. Notice that if the main thread dies you might lose logs.

Standalone python

To use with python first create a logger for your app, then create an instance of the handler and add it to the logger created.

import logging
from python_logging_rabbitmq import RabbitMQHandler

logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)

rabbit = RabbitMQHandler(host='localhost', port=5672)
logger.addHandler(rabbit)

logger.debug('test debug')

As result, a similar message as follows will be sent to RabbitMQ:

{
	"relativeCreated":280.61580657958984,
	"process":13105,
	"args":[],
	"module":"test",
	"funcName":"<module>",
	"host":"albertomr86-laptop",
	"exc_text":null,
	"name":"myapp",
	"thread":140032818181888,
	"created":1482290387.454017,
	"threadName":"MainThread",
	"msecs":454.01692390441895,
	"filename":"test.py",
	"levelno":10,
	"processName":"MainProcess",
	"pathname":"test.py",
	"lineno":11,
	"msg":"test debug",
	"exc_info":null,
	"levelname":"DEBUG"
}

Sending logs

By default, logs will be sent to RabbitMQ using the exchange 'log', this should be of type topic. The routing key used is formed by concatenating the logger name and the log level. For example:

import logging
from python_logging_rabbitmq import RabbitMQHandler

logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
logger.addHandler(RabbitMQHandler(host='localhost', port=5672))

logger.info('test info')
logger.debug('test debug')
logger.warning('test warning')

The messages will be sent using the following routing keys:

  • myapp.INFO
  • myapp.DEBUG
  • myapp.WARNING

For an explanation about topics and routing keys go to https://www.rabbitmq.com/tutorials/tutorial-five-python.html

When create the handler, you're able to specify different parameters in order to connect to RabbitMQ or configure the handler behavior.

Overriding routing-key creation

If you wish to override routing-key format entirely, you can pass routing_key_formatter function which takes LogRecord objects and returns routing-key. For example:

RabbitMQHandler(
	host='localhost',
	port=5672,
	routing_key_formatter=lambda r: (
		'some_exchange_prefix.{}'.format(r.levelname.lower())
	)
)

Configuration

These are the configuration allowed:

Parameter Description Default
host RabbitMQ Server hostname or ip address. localhost
port RabbitMQ Server port. 5672
username Username for authentication. None
password Provide a password for the username. None
exchange Name of the exchange to publish the logs. This exchange is considered of type topic. log
declare_exchange Whether or not to declare the exchange. False
remove_request If True (default), remove request & exc info. True
routing_key_format Customize how messages are routed to the queues. {name}.{level}
routing_key_formatter Customize how routing-key is constructed. None
connection_params Allow extra params to connect with RabbitMQ. None
formatter Use custom formatter for the logs. python_logging_rabbitmq.JSONFormatter
close_after_emit Close the active connection after send a log. A new connection is open for the next log. False
fields Dict to add as a field in each logs send to RabbitMQ. This is useful when you want fields in each log but without pass them every time. None
fields_under_root When is True, each key in parameter 'fields' will be added as an entry in the log, otherwise they will be logged under the key 'fields'. True
message_headers A dictionary of headers to be published with the message. None
record_fields A set of attributes that should be preserved from the record object. None
exclude_record_fields A set of attributes that should be ignored from the record object. None
heartbeat Lower bound for heartbeat timeout. 60
content_type The format of the message sent to the queue. text/plain

Examples

RabbitMQ Connection

rabbit = RabbitMQHandler(
	host='localhost',
	port=5672,
	username='guest',
	password='guest',
	connection_params={
		'virtual_host': '/',
		'connection_attempts': 3,
		'socket_timeout': 5000
	}
)

Custom fields

rabbit = RabbitMQHandler(
	host='localhost',
	port=5672,
	fields={
		'source': 'MyApp',
		'env': 'production'
	},
	fields_under_root=True
)

Custom formatter

By default, python_logging_rabbitmq implements a custom JSONFormatter; but if you prefer to format your own message you could do it as follow:

import logging
from python_logging_rabbitmq import RabbitMQHandler

FORMAT = '%(asctime)-15s %(message)s'
formatter = logging.Formatter(fmt=FORMAT)
rabbit = RabbitMQHandler(formatter=formatter)

For a custom JSON Formatter take a look at https://github.com/madzak/python-json-logger

Django

To use with Django add the handler in the logging config.

LOGGING = {
	'version': 1,
	'disable_existing_loggers': False,
	'handlers': {
		'rabbit': {
			'level': 'DEBUG',
			'class': 'python_logging_rabbitmq.RabbitMQHandler',
			'host': 'localhost'
		}
	},
	'loggers': {
		'myapp': {
			'handlers': ['rabbit'],
			'level': 'DEBUG',
			'propagate': False
		}
	}
}

Configuration

Same as when use it with standalone python, you could configure the handle directly when declaring it in the config:

LOGGING = {
	'version': 1,
	'disable_existing_loggers': False,
	'handlers': {
		'rabbit': {
			'level': 'DEBUG',
			'class': 'python_logging_rabbitmq.RabbitMQHandler',
			'host': 'localhost',
			'port': 5672,
			'username': 'guest',
			'password': 'guest',
			'exchange': 'log',
			'declare_exchange': False,
			'connection_params': {
				'virtual_host': '/',
				'connection_attempts': 3,
				'socket_timeout': 5000
			},
			'fields': {
				'source': 'MainAPI',
				'env': 'production'
			},
			'fields_under_root': True
		}
	},
	'loggers': {
		'myapp': {
			'handlers': ['rabbit'],
			'level': 'DEBUG',
			'propagate': False
		}
	}
}

Custom formatter

LOGGING = {
	'version': 1,
	'disable_existing_loggers': False,
	'formatters': {
		'standard': {
			'format': '%(levelname)-8s [%(asctime)s]: %(message)s'
		}
	},
	'handlers': {
		'rabbit': {
			'level': 'DEBUG',
			'class': 'python_logging_rabbitmq.RabbitMQHandler',
			'host': 'localhost',
			'formatter': 'standard'
		}
	},
	'loggers': {
		'myapp': {
			'handlers': ['rabbit'],
			'level': 'DEBUG',
			'propagate': False
		}
	}
}

JSON formatter

pip install python-json-logger
LOGGING = {
	'version': 1,
	'disable_existing_loggers': False,
	'formatters': {
		'json': {
			'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
			'fmt': '%(name)s %(levelname) %(asctime)s %(message)s'
		}
	},
	'handlers': {
		'rabbit': {
			'level': 'DEBUG',
			'class': 'python_logging_rabbitmq.RabbitMQHandler',
			'host': 'localhost',
			'formatter': 'json'
		}
	},
	'loggers': {
		'myapp': {
			'handlers': ['rabbit'],
			'level': 'DEBUG',
			'propagate': False
		}
	}
}

Releases

CHANGELOG

Similar efforts

python-logging-rabbitmq's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

python-logging-rabbitmq's Issues

Unconfigurable Routing Key Format

I need to able to change the routing key format in my system, so i prefered that this file, python_loggin_rabbitmq/handlers.py:

line 115:

            routing_key ="{name}.{level}".format(name=record.name, level=record.levelname)

to be changed to:

line 14:

            ROUTING_KEY_FORMAT = "{name}.{level}"

line 115:

            routing_key = self.ROUTING_KEY_FORMAT.format(name=record.name, level=record.levelname)

so it will be configurable
thank you

ImportError: No module named 'compat'

When I use the library I see an Exception:

File "/usr/local/lib/python3.4/dist-packages/python_logging_rabbitmq/init.py", line 2, in
from .formatters import JSONFormatter # noqa: F401
File "/usr/local/lib/python3.4/dist-packages/python_logging_rabbitmq/formatters.py", line 4, in
from compat import json
ImportError: No module named 'compat'

are some wrong in ini?

Regards and thank you for your library.

SSL configuration isn't working automatically

As a workaround I initilize to following:

SSLOptions(ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2))

and pass it as connection_params under ssl_options

Without a workaround I get a connection reset error.

[django] AttributeError: 'socket' object has no attribute 'POST'

i got this error:

--- Logging error ---
Traceback (most recent call last):
NoneNone  File "D:\projects\dtp\env\lib\site-packages\python_logging_rabbitmq\handlers.py", line 159, in emit
    no_exc_record.traceback = reporter.get_traceback_text()
  File "D:\projects\dtp\env\lib\site-packages\django\views\debug.py", line 403, in get_traceback_text
    c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
  File "D:\projects\dtp\env\lib\site-packages\django\views\debug.py", line 364, in get_traceback_data
    self.filter.get_post_parameters(self.request).items()
  File "D:\projects\dtp\env\lib\site-packages\django\views\debug.py", line 209, in get_post_parameters
    return request.POST
AttributeError: 'socket' object has no attribute 'POST'
Call stack:
  File "c:\sdk\Anaconda3\lib\threading.py", line 930, in _bootstrap
    self._bootstrap_inner()
  File "c:\sdk\Anaconda3\lib\threading.py", line 973, in _bootstrap_inner
    self.run()
  File "c:\sdk\Anaconda3\lib\threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "c:\sdk\Anaconda3\lib\socketserver.py", line 683, in process_request_thread
    self.finish_request(request, client_address)
  File "c:\sdk\Anaconda3\lib\socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "c:\sdk\Anaconda3\lib\socketserver.py", line 747, in __init__
    self.handle()
  File "D:\projects\dtp\env\lib\site-packages\django\core\servers\basehttp.py", line 204, in handle
    self.handle_one_request()
  File "D:\projects\dtp\env\lib\site-packages\django\core\servers\basehttp.py", line 227, in handle_one_request
    handler.run(self.server.get_app())
  File "c:\sdk\Anaconda3\lib\wsgiref\handlers.py", line 138, in run
    self.finish_response()
  File "c:\sdk\Anaconda3\lib\wsgiref\handlers.py", line 196, in finish_response
    self.close()
  File "D:\projects\dtp\env\lib\site-packages\django\core\servers\basehttp.py", line 148, in close
    super().close()
  File "c:\sdk\Anaconda3\lib\wsgiref\simple_server.py", line 34, in close
    self.request_handler.log_request(
  File "c:\sdk\Anaconda3\lib\http\server.py", line 542, in log_request
    self.log_message('"%s" %s %s',
  File "D:\projects\dtp\env\lib\site-packages\django\core\servers\basehttp.py", line 187, in log_message
    level(format, *args, extra=extra)
  File "c:\sdk\Anaconda3\lib\logging\__init__.py", line 1458, in warning
    self._log(WARNING, msg, args, **kwargs)
  File "c:\sdk\Anaconda3\lib\logging\__init__.py", line 1589, in _log
    self.handle(record)
  File "c:\sdk\Anaconda3\lib\logging\__init__.py", line 1599, in handle
    self.callHandlers(record)
  File "c:\sdk\Anaconda3\lib\logging\__init__.py", line 1661, in callHandlers
    hdlr.handle(record)
  File "c:\sdk\Anaconda3\lib\logging\__init__.py", line 952, in handle
    self.emit(record)
  File "D:\projects\dtp\env\lib\site-packages\python_logging_rabbitmq\handlers.py", line 178, in emit
    self.handleError(record)
Message: '"%s" %s %s'
Arguments: ('GET /favicon.ico HTTP/1.1', '404', '3919')

this my config:

LOGGING = {
   ...
   'rabbit': {
            'level': 'DEBUG',
            'class': 'python_logging_rabbitmq.RabbitMQHandler',
            'host': '192.168.0.9',
            'port': 5672,
            'username': 'syslog',
            'password': 'mypassword',
            'exchange': 'django',
            'declare_exchange': False,
            'connection_params': {
                'virtual_host': '/',
                    'connection_attempts': 3,
                    'socket_timeout': 5000
                    },
            'fields': {
                'source': 'DTP_REMOTE',
                    'env': 'development'
                    },
            'fields_under_root': True
        },
    ...

Standalone not working

Hello everybody,

I'm trying to implement your lib in my python app. We're not using Django and we have this error raised :

Traceback (most recent call last): File "/home/vgaugry/darwin/sms_v2_tools/sms_v2_tools/custom_logger/test.py", line 1, in <module> import DarwinLogger File "/home/vgaugry/darwin/sms_v2_tools/sms_v2_tools/custom_logger/DarwinLogger.py", line 4, in <module> from python_logging_rabbitmq import RabbitMQHandlerOneWay File "/home/vgaugry/.virtualenvs/sms-v2_env/local/lib/python2.7/site-packages/python_logging_rabbitmq/__init__.py", line 2, in <module> from .formatters import JSONFormatter # noqa: F401 File "/home/vgaugry/.virtualenvs/sms-v2_env/local/lib/python2.7/site-packages/python_logging_rabbitmq/formatters.py", line 5, in <module> from django.core.serializers.json import DjangoJSONEncoder ImportError: No module named django.core.serializers.json

I simply followed the "standalone" part of the readme.
Is this normal ? Or Am I doing something wrong ?

Thx !

RabbitMQ server closes the connection because not receiving heartbeat

Hi Albert,
Similar to issue pika/pika#1104.
After digging into Pika and RabbitMQ, I find with BlockedConnection, pika will not automatically send out the heartbeat. The heartbeat event will only be handled/sent in "start_consuming" and "process_data_events".
For consumer, we will use "start_consuming", there will not be such issue. But for producer, normally we won't call "process_data_events" specifically, it will only be called when we call "basic_publish".
Let's say we set "heartbeat" to 20s, if we don't log any message within 3x10s, the server would close the connection. (Different version of RabbitMQ might have different behaviors, some might take 3x20s)
I didn't see anyone report this issue or talk this on the internet, so I'm not sure if my understanding is correct. Look forward to your response.
Thanks in advance.

self.queue.task_done() can be called when no message was got due to continue executing finally block anyway leading to ValueError exception

The changes in version 2.2 for fix #25 in python_logging_rabbitmq/handlers_oneway.py may have introduced an issue. Before the Queue.Empty exception was never raised because record, routing_key = self.queue.get() had no timeout. Now when the exception is raised if no messages arrives within 10s, the exception handler will call "continue" but still the "finally" block is executed anyway - and queue.task_done() could be called more times than put() and it will lead to a ValueError exception.

queue.task_done() should be called in a inner "try..finally" block after a message has been dequeued actually, for example:

record, routing_key = self.queue.get(block=True, timeout=10)
try:
#Actually got a message
... try to send the message ...
finally:
queue.task_done()

Moreover when is_stopping is set the loop is exited before queue.task_done() is called, and messages still in the queue are not processed. If on the other side of the queue something attempts to call queue.join() it could never return.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.