├── 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)
--------------------------------------------------------------------------------