├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs
├── index.rst
├── installation.rst
├── requirements.rst
└── run-and-demo.rst
├── log_reader
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── settings.py
├── static
│ └── log_reader
│ │ ├── css
│ │ └── log_reader.css
│ │ └── js
│ │ └── log_reader.js
├── templates
│ └── log_reader
│ │ └── admin
│ │ └── change_list.html
├── tests.py
├── utils.py
└── views.py
├── screenshots
└── django_log_reader.png
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 | .idea/
54 | .DS_Store
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
97 | __pypackages__/
98 |
99 | # Celery stuff
100 | celerybeat-schedule
101 | celerybeat.pid
102 |
103 | # SageMath parsed files
104 | *.sage.py
105 |
106 | # Environments
107 | .env
108 | .venv
109 | env/
110 | venv/
111 | ENV/
112 | env.bak/
113 | venv.bak/
114 |
115 | # Spyder project settings
116 | .spyderproject
117 | .spyproject
118 |
119 | # Rope project settings
120 | .ropeproject
121 |
122 | # mkdocs documentation
123 | /site
124 |
125 | # mypy
126 | .mypy_cache/
127 | .dmypy.json
128 | dmypy.json
129 |
130 | # Pyre type checker
131 | .pyre/
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Iman Karimi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | recursive-include log_reader/static *
4 | recursive-include log_reader/templates *
5 | recursive-include docs *
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django Log Reader
2 | **Django Log Reader** allows you to read & download log files on the admin page.
3 |
4 | > This version designed for the Linux operating system and uses Linux commands to read files faster.
5 |
6 |
7 |
8 | ## Why Django Log Reader?
9 |
10 | - Reading files based on Linux commands speeds up the display of file content
11 | - Search in files based on Linux commands
12 | - Download the result of the content
13 | - Display all files according to the pattern defined in the `settings.py`
14 | - Simple interface
15 | - Easy integration
16 |
17 |
18 |
19 | 
20 |
21 |
22 |
23 |
24 | ## How to use it
25 |
26 |
27 |
28 | * Download and install latest version of Django Log Reader:
29 |
30 | ```bash
31 | $ pip install django-log-reader
32 | # or
33 | $ easy_install django-log-reader
34 | ```
35 |
36 |
37 |
38 | * Add `log_reader` application to the `INSTALLED_APPS` setting of your Django project `settings.py` file:
39 |
40 | ```python
41 | INSTALLED_APPS = (
42 | # ...
43 | "log_reader.apps.LogReaderConfig",
44 | )
45 | ```
46 |
47 |
48 |
49 | * You can Add the following value In your `settings.py` file:
50 |
51 | ```python
52 | # This value specifies the folder for the files. The default value is 'logs'
53 | LOG_READER_DIR_PATH = 'logs'
54 |
55 | # This value specifies the file extensions. The default value is '*.log'
56 | LOG_READER_FILES_PATTERN = '*.log'
57 |
58 | # This value specifies the default file. If there is no filter, the system reads the default file.
59 | LOG_READER_DEFAULT_FILE = 'django.log'
60 |
61 | # The contents of the files are separated based on this pattern.
62 | LOG_READER_SPLIT_PATTERN = "\\n"
63 |
64 | # This value indicates the number of lines of content in the file. Set the number of lines you want to read to this value.
65 | LOG_READER_MAX_READ_LINES = 1000
66 |
67 | # You can exclude files with this value.
68 | LOG_READER_EXCLUDE_FILES = []
69 | ```
70 |
71 |
72 |
73 | * Collect static if you are in production environment:
74 | ```bash
75 | $ python manage.py collectstatic
76 | ```
77 |
78 | * Clear your browser cache
79 |
80 |
81 |
82 | ## Start the app
83 |
84 | ```bash
85 | # Set up the database
86 | $ python manage.py makemigrations
87 | $ python manage.py migrate
88 |
89 | # Create the superuser
90 | $ python manage.py createsuperuser
91 |
92 | # Start the application (development mode)
93 | $ python manage.py runserver # default port 8000
94 | ```
95 |
96 | * Access the `admin` section in the browser: `http://127.0.0.1:8000/`
97 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Django Log Reader Documentation
2 | ##########################################
3 |
4 | Django Log Reader allows you to read and download log files on the admin page.
5 |
6 | .. note::
7 | This version designed for the Linux operating system and uses Linux commands to read files faster.
8 |
9 |
10 | Why Django Log Reader?
11 | =========================
12 |
13 | * Reading files based on Linux commands speeds up the display of file content
14 | * Search in files based on Linux commands
15 | * Download the result of the content
16 | * Display all files according to the pattern defined in the ``settings.py``
17 | * Simple interface
18 | * Easy integration
19 |
20 | .. image:: https://raw.githubusercontent.com/imankarimi/django-log-reader/main/screenshots/django_log_reader.png
21 | :alt: Django Log Reader
22 |
23 | Contents:
24 | =========
25 |
26 | .. toctree::
27 | :maxdepth: 2
28 |
29 | requirements
30 | installation
31 | run-and-demo
32 |
33 | I would love to hear your feedback on this application. If you run into
34 | problems, please file an issue on GitHub_, or contribute to the project by
35 | forking the repository and sending some pull requests.
36 |
37 | .. _GitHub: https://github.com/imankarimi/django-log-reader/issues
38 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | * Download and install latest version of `Django Log Reader`_:
5 |
6 | .. code-block:: console
7 |
8 | $ pip install django-log-reader
9 |
10 | Setup
11 | -------
12 |
13 | * Add ``log_reader`` application to the ``INSTALLED_APPS`` setting of your Django project ``settings.py`` file:
14 |
15 | .. code-block:: python
16 |
17 | INSTALLED_APPS = (
18 | ...
19 | "log_reader.apps.LogReaderConfig",
20 | )
21 |
22 | * You can Add the following value In your ``settings.py`` file:
23 |
24 | .. code-block:: python
25 |
26 | # This value specifies the folder for the files. The default value is 'logs'
27 | LOG_READER_DIR_PATH = 'logs'
28 |
29 | # This value specifies the file extensions. The default value is '*.log'
30 | LOG_READER_FILES_PATTERN = '*.log'
31 |
32 | # This value specifies the default file. If there is no filter, the system reads the default file.
33 | LOG_READER_DEFAULT_FILE = 'django.log'
34 |
35 | # The contents of the files are separated based on this pattern.
36 | LOG_READER_SPLIT_PATTERN = "\\n"
37 |
38 | # This value indicates the number of lines of content in the file. Set the number of lines you want to read to this value.
39 | LOG_READER_MAX_READ_LINES = 1000
40 |
41 | # You can exclude files with this value.
42 | LOG_READER_EXCLUDE_FILES = []
43 |
44 |
45 | * Collect static if you are in production environment:
46 |
47 | .. code-block:: console
48 |
49 | $ python manage.py collectstatic
50 |
51 | * Clear your browser cache
52 |
53 |
54 | .. _Django Log Reader: https://pypi.org/project/django-admin-two-factor/
55 |
--------------------------------------------------------------------------------
/docs/requirements.rst:
--------------------------------------------------------------------------------
1 | Requirements
2 | ============
3 |
4 | Django
5 | ------
6 | Modern Django versions are supported. Currently this list includes Django 2.*, and 3.2
7 |
8 | Python
9 | ------
10 | The following Python versions are supported: 3.5, 3.6, 3.7 and 3.8 with a
11 | limit to what Django itself supports. As support for older Django versions is
12 | dropped, the minimum version might be raised. See also `What Python version can
13 | I use with Django?`_.
14 |
15 |
16 | .. _What Python version can I use with Django?:
17 | https://docs.djangoproject.com/en/stable/faq/install/#what-python-version-can-i-use-with-django
18 |
--------------------------------------------------------------------------------
/docs/run-and-demo.rst:
--------------------------------------------------------------------------------
1 | Run & Demo
2 | ##############
3 |
4 | Run
5 | -----
6 |
7 | .. code-block:: console
8 |
9 | # Set up the database
10 | $ python manage.py makemigrations
11 | $ python manage.py migrate
12 |
13 | # Create the superuser
14 | $ python manage.py createsuperuser
15 |
16 | # Start the application (development mode)
17 | $ python manage.py runserver # default port 8000
18 |
19 | Access the ``admin`` section in the browser: ``http://127.0.0.1:8000/``
20 |
21 | Demo
22 | ------
23 |
24 | .. image:: https://raw.githubusercontent.com/imankarimi/django-log-reader/main/screenshots/django_log_reader.png
25 | :alt: Demo
26 |
--------------------------------------------------------------------------------
/log_reader/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imankarimi/django-log-reader/e37fbd07a3420a51e641e23c440e3396509b0d3f/log_reader/__init__.py
--------------------------------------------------------------------------------
/log_reader/admin.py:
--------------------------------------------------------------------------------
1 | import django
2 | from django.contrib import admin, messages
3 | from django.template.response import TemplateResponse
4 | from django.urls import path
5 |
6 | from log_reader import settings
7 | from log_reader.models import FileLogReader
8 | from log_reader.utils import get_log_files, read_file_lines
9 |
10 |
11 | @admin.register(FileLogReader)
12 | class FileLogReaderAdmin(admin.ModelAdmin):
13 | list_filter = ['id']
14 |
15 | def get_urls(self):
16 | info = self.model._meta.app_label, self.model._meta.module_name
17 | return [
18 | path(r'', self.admin_site.admin_view(self.changelist_view), name='%s_%s_changelist' % info),
19 | ]
20 |
21 | def changelist_view(self, request, extra_context=None):
22 | filename = request.GET.get('file_name', settings.LOG_READER_DEFAULT_FILE)
23 | search = request.GET.get('q', None) or None
24 | is_valid, file_contents = read_file_lines(file_name=filename, search=search)
25 | if not is_valid:
26 | self.message_user(request, file_contents, level=messages.ERROR)
27 | log_files = get_log_files(settings.LOG_READER_DIR_PATH)
28 |
29 | context = dict(
30 | self.admin_site.each_context(request),
31 | log_files=log_files,
32 | file_contents=file_contents if is_valid else [],
33 | file_name=filename,
34 | django_version=django.get_version()
35 | )
36 | return TemplateResponse(request, "log_reader/admin/change_list.html", context=context)
37 |
--------------------------------------------------------------------------------
/log_reader/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class LogReaderConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'log_reader'
7 | verbose_name = 'log reader'
8 |
--------------------------------------------------------------------------------
/log_reader/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imankarimi/django-log-reader/e37fbd07a3420a51e641e23c440e3396509b0d3f/log_reader/migrations/__init__.py
--------------------------------------------------------------------------------
/log_reader/models.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | if django.get_version() >= '4':
4 | from django.utils.translation import gettext_lazy as _
5 | else:
6 | from django.utils.translation import ugettext_lazy as _
7 |
8 |
9 | class FileLogReader(object):
10 | class Meta(object):
11 | app_label = 'log_reader'
12 | object_name = 'file_log_readers'
13 | model_name = module_name = 'file_log_readers'
14 | verbose_name = _('file log')
15 | verbose_name_plural = _('file logs')
16 | abstract = False
17 | swapped = False
18 | app_config = ""
19 |
20 | _meta = Meta()
21 |
--------------------------------------------------------------------------------
/log_reader/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | """ log reader config """
5 |
6 | LOG_READER_DIR_PATH = getattr(settings, 'LOG_READER_DIR_PATH', 'logs')
7 | LOG_READER_FILES_PATTERN = getattr(settings, 'LOG_READER_FILES_PATTERN', '*.log')
8 | LOG_READER_DEFAULT_FILE = getattr(settings, 'LOG_READER_DEFAULT_FILE', 'django.log')
9 | LOG_READER_SPLIT_PATTERN = getattr(settings, 'LOG_READER_SPLIT_PATTERN', "\\n")
10 | # The contents of the files are separated based on this regex. if it couldn't split correctly with both pattern.
11 | # The default value is '[(?i)[0-9]{4}-[0-9]{2}-[0-9]{2}\\s(?:[0-9]{2}:){2}[0-9]{2}.+?(?=[0-9]{4}-[0-9]{2}-[0-9]{2}\\s(?:[0-9]{2}:){2}[0-9]{2}|$)'
12 | LOG_READER_REGEX_SPLIT_PATTERN = '[(?i)[0-9]{4}-[0-9]{2}-[0-9]{2}\\s(?:[0-9]{2}:){2}[0-9]{2}.+?(?=[0-9]{4}-[0-9]{2}-[0-9]{2}\\s(?:[0-9]{2}:){2}[0-9]{2}|$)'
13 | LOG_READER_MAX_READ_LINES = getattr(settings, 'LOG_READER_MAX_READ_LINES', 1000)
14 | LOG_READER_EXCLUDE_FILES = getattr(settings, 'LOG_READER_EXCLUDE_FILES', [])
15 |
16 |
--------------------------------------------------------------------------------
/log_reader/static/log_reader/css/log_reader.css:
--------------------------------------------------------------------------------
1 | #DownloadLogFile {
2 | padding: 6px;
3 | border-radius: 5px;
4 | background-color: #79aec8;
5 | color: #ffffff;
6 | cursor: pointer;
7 | border: 1px solid #d0d0d0;
8 | position: absolute;
9 | right: 315px;
10 | top: 156px;
11 | }
--------------------------------------------------------------------------------
/log_reader/static/log_reader/js/log_reader.js:
--------------------------------------------------------------------------------
1 | function download(filename, text) {
2 | var element = document.createElement('a');
3 | element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
4 | element.setAttribute('download', filename);
5 |
6 | element.style.display = 'none';
7 | document.body.appendChild(element);
8 |
9 | element.click();
10 |
11 | document.body.removeChild(element);
12 | }
--------------------------------------------------------------------------------
/log_reader/templates/log_reader/admin/change_list.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base_site.html" %}
2 | {% load i18n static %}
3 |
4 | {% block title %}Log Reader{% endblock %}
5 |
6 | {% block extrastyle %}
7 | {{ block.super }}
8 |
9 |
10 |
11 |
12 | {% endblock %}
13 |
14 | {% block breadcrumbs %}
15 |
20 | {% endblock %}
21 |
22 | {% block pretitle %}
23 | File Logs
24 | {% endblock %}
25 |
26 | {% block content %}
27 |
28 |
29 | {% if django_version >= '3' %}
{% endif %}
83 |
84 | {% block filters %}
85 | {{ block.super }}
86 |
87 |
{% trans 'Filter' %}
88 |
By File
89 |
90 | {% for file_name in log_files %}
91 | - {{ file_name }}
92 | {% endfor %}
93 |
94 |
95 | {% endblock %}
96 |
97 |
98 |
99 |
112 | {% endblock %}
--------------------------------------------------------------------------------
/log_reader/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/log_reader/utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import django
4 | import os
5 | # import re
6 | import subprocess
7 | from fnmatch import fnmatch
8 | from subprocess import PIPE
9 |
10 | if django.get_version() >= '4':
11 | from django.utils.translation import gettext_lazy as _
12 | else:
13 | from django.utils.translation import ugettext_lazy as _
14 |
15 | from log_reader import settings
16 |
17 |
18 | def get_log_files(directory):
19 | for (dir_path, dir_names, filenames) in os.walk(directory):
20 | log_files = []
21 | all_files = list(filter(lambda x: x.find('~') == -1, filenames))
22 | log_files.extend([x for x in all_files if fnmatch(x, settings.LOG_READER_FILES_PATTERN) and x not in settings.LOG_READER_EXCLUDE_FILES])
23 | return list(set(log_files))
24 | return []
25 |
26 |
27 | def read_file_lines(file_name, search=None):
28 | if file_name not in get_log_files(settings.LOG_READER_DIR_PATH):
29 | return False, _("%s file, not found. Please try again." % file_name)
30 |
31 | try:
32 | file_path = '%s/%s' % (settings.LOG_READER_DIR_PATH, file_name)
33 |
34 | if search:
35 | result = subprocess.run(
36 | ['grep', '-m %s' % settings.LOG_READER_MAX_READ_LINES, search, file_path],
37 | stdout=PIPE,
38 | stderr=PIPE,
39 | encoding="utf8",
40 | )
41 | else:
42 | result = subprocess.run(
43 | ['tail', '-%s' % settings.LOG_READER_MAX_READ_LINES, file_path],
44 | stdout=PIPE,
45 | stderr=PIPE,
46 | encoding="utf8",
47 | )
48 | content = repr(result.stdout) if result.stdout else None
49 | except Exception as e:
50 | return False, str(e)
51 |
52 | return True, split_file_content(content)
53 |
54 |
55 | def split_file_content(content):
56 | data = content.split(settings.LOG_READER_SPLIT_PATTERN) if content else []
57 | # if content and len(res) == 1:
58 | # res = re.findall(settings.LOG_READER_REGEX_SPLIT_PATTERN, content) if content else []
59 | res = [x for x in data if len(x) > 5]
60 | return res
61 |
62 |
--------------------------------------------------------------------------------
/log_reader/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/screenshots/django_log_reader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imankarimi/django-log-reader/e37fbd07a3420a51e641e23c440e3396509b0d3f/screenshots/django_log_reader.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from setuptools import find_packages, setup
4 |
5 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
6 | README = readme.read()
7 |
8 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
9 |
10 | setup(
11 | name='django-log-reader',
12 | version='1.1.9',
13 | zip_safe=False,
14 | packages=find_packages(),
15 | include_package_data=True,
16 | description='Read & Download log files on the admin page',
17 | long_description=README,
18 | long_description_content_type="text/markdown",
19 | url='https://github.com/imankarimi/django-log-reader',
20 | author='Iman Karimi',
21 | author_email='imankarimi.mail@gmail.com',
22 | license='MIT License',
23 | classifiers=[
24 | 'Environment :: Web Environment',
25 | 'Framework :: Django',
26 | 'Framework :: Django :: 3.2',
27 | 'Intended Audience :: Developers',
28 | 'License :: OSI Approved :: MIT License',
29 | 'Programming Language :: Python',
30 | 'Programming Language :: Python :: 2.6',
31 | 'Programming Language :: Python :: 2.7',
32 | 'Programming Language :: Python :: 3.2',
33 | 'Programming Language :: Python :: 3.3',
34 | 'Programming Language :: Python :: 3.4',
35 | 'Programming Language :: Python :: 3.5',
36 | 'Programming Language :: Python :: 3.6',
37 | 'Programming Language :: Python :: 3.7',
38 | 'Programming Language :: Python :: 3.8',
39 | 'Environment :: Web Environment',
40 | 'Topic :: Software Development',
41 | ],
42 | )
43 |
--------------------------------------------------------------------------------