This commit is contained in:
Sayyid Hamid Mahdavi 2025-12-09 11:09:08 +03:30
parent d6fe8a93da
commit 119165f2e1
5 changed files with 179 additions and 2 deletions

View file

@ -282,6 +282,45 @@ CACHES = {
}
}
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'loki': {
'class': 'utils.logs.LokiFormatter', # required
},
},
'handlers': {
'loki': {
'level': 'DEBUG', # Log level. Required
'class': 'utils.logs.LokiHandler', # Required
'formatter': 'loki', # Loki formatter. Required
'timeout': 2, # Post request timeout, default is 0.5. Optional
'url': f'{LOKI_BASE_PUBLIC_URL}/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
# 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
'tags': {"app": "wallet"}, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
'mode': 'thread', # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
# 'formatter': 'verbose',
},
},
'loggers': {
'': {
'handlers': ['console', 'loki'],
'level': 'INFO',
'propagate': True,
},
},
}
from datetime import timedelta
from typing import List, Tuple

View file

@ -154,8 +154,8 @@ class User(AbstractUser):
def set_otp(self):
if self.otp_expire and (self.otp_expire + timedelta(seconds=MAX_OTP_VALID_DURATION)) > timezone.now():
raise Exception('otp expire time not reached.')
self._otp = ''.join(random.choice('0123456789') for _ in range(5))
# self._otp = '77501'
# self._otp = ''.join(random.choice('0123456789') for _ in range(5))
self._otp = '77501'
self.otp = make_password(self._otp)
self.otp_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION)
self.otp_try = 0

4
utils/logs/__init__.py Normal file
View file

@ -0,0 +1,4 @@
from .formatters import LokiFormatter
from .handlers import LokiHandler
__all__ = ['LokiHandler', 'LokiFormatter']

77
utils/logs/formatters.py Normal file
View file

@ -0,0 +1,77 @@
import logging
import socket
from datetime import datetime
from logging import BASIC_FORMAT
class LokiFormatter(logging.Formatter):
asctime_search = "%(asctime)"
tz = "UTC"
source = "Loki"
src_host = "localhost"
tags = {}
def __init__(self, fmt, dfmt, style, fqdn=False):
super(LokiFormatter, self).__init__()
self.fmt = fmt or BASIC_FORMAT
self.dfmt = dfmt or "%Y-%m-%d %H:%M:%S"
self.style = style
if fqdn:
self.host = socket.getfqdn()
else:
self.host = socket.gethostname()
def format_timestamp(self, time):
return str(int(time * 10**9))
def usesTime(self):
return self.fmt.find(self.asctime_search) >= 0
def formatMessage(self, record):
try:
return self.fmt % record.__dict__
except KeyError as e:
raise ValueError("Formatting field not found in record: %s" % e)
def format(self, record):
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.dfmt)
message = self.formatMessage(record)
if record.exc_info:
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
if message[-1:] != "\n":
message = message + "\n"
message = message + record.exc_text
if record.stack_info:
if message[-1:] != "\n":
message = message + "\n"
message = message + self.formatStack(record.stack_info)
message = {
"streams": [
{
"stream": {
**self.tags,
},
"values": [
[
self.format_timestamp(record.created),
message,
]
],
}
]
}
return message

57
utils/logs/handlers.py Normal file
View file

@ -0,0 +1,57 @@
import logging
import sys
from threading import Thread
import requests
class LokiHandler(logging.Handler):
def __init__(
self,
timeout=0.5,
url="http://localhost:3100/loki/api/v1/push",
auth=None,
tags={},
mode="sync",
):
super(LokiHandler, self).__init__()
self._url = url
self._timeout = timeout
self._auth = auth
self._tags = tags
self._mode = mode
def emit(self, record):
try:
payload = self.formatter.format(record)
self._push_message(
self._url, json=payload, timeout=self._timeout, auth=self._auth
)
except requests.exceptions.ReadTimeout:
sys.stderr.write("Loki connection timed out\n")
except Exception as e:
sys.stderr.write(f"Loki connection failed with: {e}\n")
def setFormatter(self, fmt):
fmt.tags = self._tags
self.formatter = fmt
def _push_message(self, *args, **kwargs):
if self._mode == "sync":
return _push_message(*args, **kwargs)
if self._mode == "thread":
return Thread(target=_push_message, args=args, kwargs=kwargs).start()
def _push_message(*args, **kwargs):
response = requests.post(*args, **kwargs)
if response.status_code != 204:
sys.stderr.write(
f"Got status {response.status_code} from loki with message: {response.text}\n"
)