Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redirect logs to files #320

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion deployment/.template.env
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ EMAIL_USE_TLS=
EMAIL_USE_SSL=
DEFAULT_FROM_EMAIL=

# -------------------------------
# ---------- LOG ROTATE ------------
# log rotate max files
LOGROTATE_COPIES=14
# log rotate max size per file
LOGROTATE_SIZE=100M
# log rotate interval check
LOGROTATE_INTERVAL=daily

# --------------------------------
# ---------- APP DOMAIN ----------
# Required for tenant configurations
Expand All @@ -59,4 +68,4 @@ APP_DOMAIN=localhost
# - reference_dataset
# - machine_info_fetcher
PLUGINS=
# -----------------------------
# -----------------------------
20 changes: 19 additions & 1 deletion deployment/docker-compose.override.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ services:
dbbackups:
volumes:
- ./volumes/backups:/backups
redis:
volumes:
# fix redis permission issue because it's using user redis (1001)
# sudo chown -R 1001:1001 ./volumes/tmp_data/redis
- ./volumes/tmp_data/redis:/tmp
- ./redis/redis.conf:/opt/bitnami/redis/mounted-etc/overrides.conf

django:
build:
context: ../
Expand All @@ -30,6 +37,8 @@ services:
- ./volumes/static:/home/web/static
- ./volumes/media:/home/web/media
- ./volumes/backups:/backups
- ./volumes/tmp_data/django:/tmp
- ./volumes/tmp_data:/home/web/logs
worker:
build:
context: ../
Expand All @@ -38,6 +47,7 @@ services:
- ../django_project:/home/web/django_project
- ./volumes/static:/home/web/static
- ./volumes/media:/home/web/media
- ./volumes/tmp_data/worker:/tmp
celery_beat:
build:
context: ../
Expand All @@ -46,6 +56,7 @@ services:
- ../django_project:/home/web/django_project
- ./volumes/static:/home/web/static
- ./volumes/media:/home/web/media
- ./volumes/tmp_data/celery_beat:/tmp
nginx:
build:
context: ../
Expand All @@ -54,6 +65,7 @@ services:
- ./nginx/sites-enabled:/etc/nginx/conf.d
- ./volumes/static:/home/web/static:ro
- ./volumes/media:/home/web/media:ro
- ./volumes/tmp_data/nginx:/tmp
ports:
- "${HTTP_PORT:-80}:8080"
- "${HTTPS_PORT:-443}:443"
Expand All @@ -72,6 +84,7 @@ services:
- ./volumes/static:/home/web/static
- ./volumes/media:/home/web/media
- ./volumes/backups:/backups
- ./volumes/tmp_data:/home/web/logs
environment:
# editable in .env
- DATABASE_TEMP_NAME=${DATABASE_TEMP_NAME:-temp}
Expand Down Expand Up @@ -113,4 +126,9 @@ services:
links:
- db
- redis
- celery_beat
- celery_beat

logrotate:
volumes:
- ./volumes/tmp_data:/logs
- ./volumes/tmp_data/logrotate:/tmp
24 changes: 22 additions & 2 deletions deployment/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ volumes:
backups-data:
rabbitmq:
redis-data:
redis-conf:
tmp-data:
tmp-logrotate:

x-common-django:
&default-common-django
Expand Down Expand Up @@ -66,6 +69,7 @@ x-common-django:
volumes:
- static-data:/home/web/static
- media-data:/home/web/media
- tmp-data:/tmp
restart: on-failure

services:
Expand All @@ -76,6 +80,8 @@ services:
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_password}
volumes:
- redis-data:/bitnami/redis/data
- tmp-data:/tmp
- redis-conf:/opt/bitnami/redis/mounted-etc/overrides.conf

db:
image: kartoza/postgis:13.0
Expand Down Expand Up @@ -121,7 +127,7 @@ services:
worker:
<<: *default-common-django
container_name: "geosight_worker"
command: 'celery -A core worker -l info'
command: 'celery -A core worker -l info --logfile=/tmp/worker.log'
entrypoint: [ ]
links:
- db
Expand All @@ -132,7 +138,7 @@ services:
<<: *default-common-django
container_name: "geosight_celery_beat"
entrypoint: [ ]
command: 'celery -A core beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler'
command: 'celery -A core beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler --logfile=/tmp/celery_beat.log'
links:
- db
- redis
Expand All @@ -144,6 +150,20 @@ services:
volumes:
- static-data:/home/web/static:ro
- media-data:/home/web/media:ro
- tmp-data:/tmp
ports:
- "${HTTP_PORT:-80}:8080"
restart: on-failure:5

logrotate:
image: blacklabelops/logrotate:1.3
container_name: "geosight_logrotate"
volumes:
- tmp-data:/logs
- tmp-logrotate:/tmp
environment:
- LOGS_DIRECTORIES=/logs
- LOGROTATE_STATUSFILE=/tmp/logrotate.status
- LOGROTATE_COPIES=${LOGROTATE_COPIES:-14}
- LOGROTATE_SIZE=${LOGROTATE_SIZE:-100M}
- LOGROTATE_INTERVAL=${LOGROTATE_INTERVAL:-daily}
3 changes: 2 additions & 1 deletion deployment/docker/uwsgi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ cheaper = 2
env = DJANGO_SETTINGS_MODULE=core.settings.prod
# disabled so we run in the foreground for docker
# daemonize = /tmp/django.log
logto = /tmp/django.log
# reload-os-env
# uid = 1000
# gid = 1000
memory-report = true
harakiri = 2400
buffer-size = 8192
disable-logging = True
# disable-logging = True
6 changes: 3 additions & 3 deletions deployment/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
error_log /tmp/error.log notice;
pid /tmp/nginx.pid;

events {
worker_connections 1024;
Expand All @@ -21,7 +21,7 @@ http {
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main if=$logme;
access_log /tmp/access.log main if=$logme;

sendfile on;
#tcp_nopush on;
Expand Down
1 change: 1 addition & 0 deletions deployment/redis/redis.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logfile /tmp/redis.log
1 change: 1 addition & 0 deletions django_project/core/settings/apps/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
'geosight.georepo',
'geosight.permission',
'geosight.importer',
'geosight.log',
'frontend'
]
7 changes: 7 additions & 0 deletions django_project/core/settings/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,10 @@
INSTALLED_APPS = [
app for app in INSTALLED_APPS if app != 'django.contrib.admin'
]

# ----------------------------------------
# Logs Directory
# ----------------------------------------
LOGS_DIRECTORY = os.environ.get(
'LOGS_DIRECTORY', '/home/web/logs'
)
28 changes: 28 additions & 0 deletions django_project/geosight/log/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding=utf-8
"""
GeoSight is UNICEF's geospatial web-based business intelligence platform.

Contact : [email protected]

.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

"""
__author__ = '[email protected]'
__date__ = '13/11/2024'
__copyright__ = ('Copyright 2023, Unicef')

from django.apps import AppConfig


class Config(AppConfig):
"""Machine log app."""

label = 'geosight_log'
name = 'geosight.log'
verbose_name = "GeoSight Log"


default_app_config = 'geosight.log.Config'
125 changes: 125 additions & 0 deletions django_project/geosight/log/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# coding=utf-8
"""
GeoSight is UNICEF's geospatial web-based business intelligence platform.

Contact : [email protected]

.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

"""
__author__ = '[email protected]'
__date__ = '22/10/2024'
__copyright__ = ('Copyright 2023, Unicef')

import mimetypes
import os
from datetime import datetime

from django.conf import settings
from django.contrib import admin, messages
from django.http import (
HttpResponseRedirect,
HttpResponse,
Http404,
FileResponse
)
from django.urls import re_path, reverse
from django.utils.html import format_html

from geosight.log.models import LogFile


def list_log_files(parent_dir):
"""Get list of log files from parent_dir."""
log_files = []
for root, dirs, files in os.walk(parent_dir):
for file in files:
if ".log" in file:
file_path = os.path.join(root, file)
file_size = os.path.getsize(file_path)
created_on = datetime.fromtimestamp(
os.path.getctime(file_path)
).strftime('%Y-%m-%d %H:%M:%S')
log_files.append((file_path, file_size, created_on))
return log_files


@admin.register(LogFile)
class LogFileAdmin(admin.ModelAdmin):
"""Class that represents LogFile Admin."""

list_display = (
'filename', 'size', 'created_on', 'download_link')
actions = ['refresh_log_files']

def download_link(self, obj):
"""Get download link for LogFile."""
return format_html(
'<a href="{}">Download</a>',
reverse(
'admin:dashboard_download_log_file',
args=[obj.pk]
)
)

download_link.short_description = 'Download Log File'

def get_urls(self):
"""Add url for download link LogFile."""
urls = super().get_urls()
custom_urls = [
re_path(
r'^download-log-file/(?P<pk>\d+)$',
self.download_log_file,
name='dashboard_download_log_file'
),
]
return custom_urls + urls

def download_log_file(self, request, pk):
"""Download log file action."""
try:
log_file = LogFile.objects.get(pk=pk)
file_path = log_file.path
file_handle = open(file_path, 'rb')

# Use FileResponse for efficient streaming
response = FileResponse(
file_handle,
content_type=mimetypes.guess_type(file_path)[0]
)
response['Content-Disposition'] = (
f'attachment; filename="{log_file.filename()}"'
)
return response
except LogFile.DoesNotExist:
raise Http404("Log file not found")
except Exception as e:
return HttpResponse(f"Error: {e}", status=500)

def refresh_log_files(self, request, queryset):
"""Refresh log file list."""
LogFile.objects.all().delete()

# Insert new log files
new_log_files = list_log_files(settings.LOGS_DIRECTORY)
log_entries = [
LogFile(path=path, size=size, created_on=created_on)
for path, size, created_on in new_log_files
]
LogFile.objects.bulk_create(log_entries)

# Send a success message
self.message_user(
request,
"Log files have been refreshed.",
level=messages.SUCCESS
)

# Redirect back to the log file list page
return HttpResponseRedirect(request.get_full_path())

refresh_log_files.short_description = "Refresh log files"
23 changes: 23 additions & 0 deletions django_project/geosight/log/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2024-11-13 02:54

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='LogFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(max_length=500, unique=True)),
('size', models.PositiveBigIntegerField(blank=True, editable=False, null=True)),
('created_on', models.DateTimeField(auto_now_add=True)),
],
),
]
Empty file.
Loading
Loading