├── tests.py ├── example ├── example │ ├── __init__.py │ ├── urls.py │ ├── wsgi.py │ ├── views.py │ └── settings.py ├── requirements.txt ├── manage.py └── templates │ └── index.html ├── requirements.txt ├── django_fluentd ├── __init__.py └── handler.py ├── setup.py └── README.rst /tests.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fluent-logger 2 | -------------------------------------------------------------------------------- /django_fluentd/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'J' 2 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | django-fluentd -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | from django.contrib import admin 4 | from .views import IndexView 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^$', IndexView.as_view(), name='home'), 9 | # url(r'^blog/', include('blog.urls')), 10 | ) 11 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /example/example/views.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.views.generic import TemplateView 4 | 5 | logger = logging.getLogger("django") 6 | 7 | class IndexView(TemplateView): 8 | 9 | template_name = "index.html" 10 | 11 | def dispatch(self, request, *args, **kwargs): 12 | #print "foo" 13 | #logging.debug("debug message") 14 | #logging.info("info message") 15 | #logging.warning("warning message") 16 | #logging.error("error message") 17 | #logging.critical("fatal message") 18 | foo = 1/0 19 | return super(IndexView, self).dispatch(request, *args, **kwargs) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read() 5 | 6 | # allow setup.py to be run from any path 7 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 8 | 9 | setup( 10 | name='django-fluentd', 11 | version='0.1', 12 | packages=['django_fluentd'], 13 | include_package_data=True, 14 | license='MIT', 15 | description='Fluentd bindings for django', 16 | long_description=README, 17 | url='http://www.github.com/jayfk/django-fluentd', 18 | author='Jannis Gebauer', 19 | author_email='ja.geb@me.com', 20 | classifiers=[ 21 | 'Environment :: Web Environment', 22 | 'Framework :: Django', 23 | 'Intended Audience :: Developers', 24 | 'License :: MIT', 25 | 'Operating System :: OS Independent', 26 | 'Programming Language :: Python', 27 | 'Programming Language :: Python :: 2.6', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Topic :: Internet :: WWW/HTTP', 30 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 31 | ], 32 | ) -------------------------------------------------------------------------------- /example/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bootstrap 101 Template 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 |

Hello, world!

21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | django-fluentd 3 | ============ 4 | 5 | django-fluentd allows you to use django's logging framework to log directly to a fluentd server of your choice. 6 | 7 | Please consider the package as unstable and don't use it for production, yet. 8 | 9 | Installation 10 | ============ 11 | 12 | with pip:: 13 | 14 | pip install django-fluentd 15 | 16 | **or** 17 | 18 | with setup.py:: 19 | 20 | git clone https://github.com/jayfk/django-fluentd.git 21 | cd django-fluentd 22 | python setup.py install 23 | 24 | Configuration 25 | ============ 26 | 27 | Add the following to your settings.py:: 28 | 29 | DJANGO_FLUENTD_SERVER = "10.10.10.10" 30 | DJANGO_FLUENTD_PORT = 24224 #no string 31 | DJANGO_FLUENTD_TAG = "your_fluentd_tag" 32 | 33 | Add the fluentd handler to your LOGGING dict in your settings.py and add this handler to one of your loggers:: 34 | 35 | LOGGING = { 36 | 37 | ... 38 | 39 | 'handlers':{ 40 | 'fluentd': { 41 | 'level': 'DEBUG', 42 | 'class': 'django_fluentd.handler.FluentdHandler', 43 | }, 44 | } 45 | 46 | ... 47 | 48 | 'loggers': { 49 | 'django': { 50 | 'handlers': ['fluentd',], 51 | 'level': 'DEBUG', 52 | 'propagate': False, 53 | }, 54 | }, 55 | } 56 | 57 | If you want to capture all logging messages using fluentd, you can add a root handler:: 58 | 59 | LOGGING = { 60 | ... 61 | 62 | 'root': { 63 | 'level': 'DEBUG', 64 | 'handlers': ['fluentd'], 65 | }, 66 | 67 | ... 68 | } 69 | 70 | Further information on how to use django's logging framework can be found here: https://docs.djangoproject.com/en/dev/topics/logging/ 71 | 72 | Fluentd Server Setup 73 | ============ 74 | 75 | Your Fluentd Server should listen on the ip and the port you specified in ``DJANGO_FLUENTD_SERVER`` and ``DJANGO_FLUENTD_PORT``:: 76 | 77 | 78 | type forward 79 | port 24224 80 | bind 10.10.10.10 81 | 82 | 83 | Please not that you currently can't use fluentd's secure_forward. If you want to send encrypted or authenticated messages 84 | to another fluentd server on the net, you'll have to add a local fluentd server that accepts unencrypted messages and forwards 85 | them using secure_forward:: 86 | 87 | 88 | type forward 89 | port 24224 90 | bind 10.10.10.10 91 | 92 | 93 | 94 | type secure_forward 95 | shared_key foobar 96 | self_hostname example.org 97 | send_timeout 60s 98 | recover_wait 10s 99 | heartbeat_interval 1s 100 | phi_threshold 8 101 | hard_timeout 60s 102 | 103 | 104 | name remote_server_name 105 | host 10.10.10.11 106 | port 24224 107 | username your_username 108 | password your_password 109 | 110 | 111 | 112 | 113 | Further information on how to use fluentd can be found here: http://fluentd.org/ 114 | 115 | 116 | -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | from logging import StreamHandler 13 | import os 14 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 15 | 16 | 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = '-h*6fgbfpbux64eas*^_ibemmd(9=7w#*8ibbtp4n3qost#en1' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | TEMPLATE_DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = ( 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ) 51 | 52 | ROOT_URLCONF = 'example.urls' 53 | 54 | WSGI_APPLICATION = 'example.wsgi.application' 55 | 56 | 57 | # Database 58 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 59 | 60 | DATABASES = { 61 | 'default': { 62 | 'ENGINE': 'django.db.backends.sqlite3', 63 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 64 | } 65 | } 66 | 67 | # Internationalization 68 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 69 | 70 | LANGUAGE_CODE = 'en-us' 71 | 72 | TIME_ZONE = 'UTC' 73 | 74 | USE_I18N = True 75 | 76 | USE_L10N = True 77 | 78 | USE_TZ = True 79 | 80 | TEMPLATE_DIRS = (os.path.join(BASE_DIR, "templates"),) 81 | 82 | DJANGO_FLUENTD_SERVER = "10.0.1.7" 83 | DJANGO_FLUENTD_PORT = 24224 84 | DJANGO_FLUENTD_TAG = "example" 85 | 86 | DJANGO_FLUENTD_TIMEOUT = 2.0 87 | DJANGO_FLUENTD_VERBOSE = False 88 | 89 | # Static files (CSS, JavaScript, Images) 90 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 91 | 92 | STATIC_URL = '/static/' 93 | 94 | LOGGING = { 95 | 'version': 1, 96 | 'disable_existing_loggers': True, 97 | 'root': { 98 | 'level': 'DEBUG', 99 | 'handlers': ['fluentd'], 100 | }, 101 | 'formatters': { 102 | 'verbose': { 103 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 104 | }, 105 | 'simple': { 106 | 'format': '%(levelname)s %(message)s' 107 | }, 108 | }, 109 | 'handlers': { 110 | 'fluentd': { 111 | 'level': 'DEBUG', 112 | 'class': 'django_fluentd.handler.FluentdHandler', 113 | }, 114 | "console":{ 115 | "level": "DEBUG", 116 | "class": "logging.StreamHandler", 117 | "formatter": "verbose", 118 | } 119 | }, 120 | 121 | 'loggers': { 122 | 'django': { 123 | 'handlers': [ 124 | 'fluentd', 125 | "console" 126 | ], 127 | 'level': 'DEBUG', 128 | 'propagate': False, 129 | }, 130 | }, 131 | } 132 | -------------------------------------------------------------------------------- /django_fluentd/handler.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import traceback 4 | 5 | from fluent import handler 6 | from django.conf import settings 7 | from django.core.exceptions import ImproperlyConfigured 8 | 9 | 10 | class FluentdHandler(handler.FluentHandler): 11 | """An log handler that sends all incoming logs to a fluentd instance""" 12 | 13 | def __init__(self, *args, **kwargs): 14 | self.config = self._load_settings() 15 | 16 | super(FluentdHandler, self).__init__( 17 | tag=self.config["tag"], host=self.config["server"], port=self.config["port"] 18 | ) 19 | 20 | def _load_settings(self): 21 | """load configuration from settings.py and return a dict""" 22 | print("loading config") 23 | config = { 24 | "server": self._load_settings_option("DJANGO_FLUENTD_SERVER"), 25 | "port": self._load_settings_option("DJANGO_FLUENTD_PORT"), 26 | "tag": self._load_settings_option("DJANGO_FLUENTD_TAG") 27 | } 28 | 29 | return config 30 | 31 | def _load_settings_option(self, var, default=None): 32 | """loads a variable from djangos settings. If the variable is not defined, and a default value is passed 33 | over with the call, default value is returned. If no default value is specified, the variable is required 34 | and a ImproperlyConfigured Exception is raised""" 35 | 36 | try: 37 | #load and return 38 | return getattr(settings, var) 39 | except AttributeError: 40 | #setting is not defined 41 | if default is not None: 42 | print("returning default") 43 | #but we have a default value that is not None. return it 44 | return default 45 | #setting is required but could not be loaded, print to stderr 46 | raise ImproperlyConfigured("Missing %s in settings. FluentdHandler could not be loaded." % var) 47 | 48 | def has_string_message(self, record): 49 | """checks if the record has a massage with type str. This is true when the logger is called like this: 50 | logger.debug("foo").""" 51 | if isinstance(record.msg, str): 52 | return True 53 | return False 54 | 55 | def add_string_to_record(self, record): 56 | """adds the string in msg.record to a dictionary""" 57 | record.msg = {"message": record.msg} 58 | return record 59 | 60 | def has_exception(self, record): 61 | """returns True if the record has a exc_info""" 62 | if record.exc_info: 63 | return True 64 | return False 65 | 66 | def add_exception_to_record(self, record): 67 | """adds a traceback (and a message if it is not defined) to record.msg""" 68 | tb = traceback.format_exception(*record.exc_info) 69 | 70 | if isinstance(record.msg, dict): 71 | record.msg.update("traceback", tb) 72 | elif self.has_string_message(record): 73 | record = self.add_string_to_record(record) 74 | record.msg.update("traceback", tb) 75 | else: 76 | record.msg = { 77 | "message": record.getMessage(), 78 | "traceback": tb 79 | } 80 | 81 | return record 82 | 83 | def emit(self, record): 84 | #As of fluent-logger v 0.3.3 logged exceptions have no information. Make sure to add a message 85 | #and to add traceback information if available 86 | if self.has_exception(record): 87 | record = self.add_exception_to_record(record) 88 | 89 | #As of fluent-logger v 0.3.3 logs with a plain string as message don't get converted. 90 | #That's a problem, because logs in the format of logger.debug("foobar") just have no message. 91 | #convert record.msg to a dict containing the message 92 | if self.has_string_message(record): 93 | record = self.add_string_to_record(record) 94 | 95 | return super(FluentdHandler, self).emit(record) --------------------------------------------------------------------------------