iT邦幫忙

2023 iThome 鐵人賽

0
自我挑戰組

Django系列 第 25

Day25~Django 漫漫長路-使用者日誌建立

  • 分享至 

  • xImage
  •  

大家好,我是Leo
今天來介紹的是如果要建立使用者的操作日誌,將資料存儲於db/images/emoticon/emoticon30.gif
OK~~~ Let's go now!!!


models/sys_model.py

from django.db import models
from website.models.users_model import Users

class Sys_Event_Code(models.Model):
    event_id = models.CharField(max_length=10, primary_key=True)
    name = models.CharField(max_length=50, null=False, blank=False)

    def __str__(self):
        return f"{self.event_id} - {self.name}"


class Sys_Subject(models.Model):
    subject_id = models.CharField(max_length=10, primary_key=True)
    category = models.CharField(max_length=50, null=False, blank=False)
    item = models.CharField(max_length=50, null=True)

    def __str__(self):
        return f"{self.subject_id}#{self.category}#{self.item}"

class Sys_Event_Log(models.Model):
    id = models.AutoField(primary_key=True)
    # log_at = models.CharField(max_length=30, blank=False, null=False)
    log_at = models.DateTimeField(blank=False, null=False)
    user_id = models.ForeignKey(Users, on_delete=models.PROTECT, blank=False, null=True, db_column='user_id')
    level = models.CharField(max_length=5)
    event_id = models.ForeignKey(Sys_Event_Code, on_delete=models.PROTECT, default='999999', db_column='event_id')
    subject_id = models.ForeignKey(Sys_Subject, on_delete=models.PROTECT, default='999999', db_column='subject_id')
    message1 = models.CharField(max_length=200, blank=True, null=True)
    message2 = models.CharField(max_length=200, blank=True, null=True)
    info1 = models.CharField(max_length=200, blank=True, null=True)

    def __str__(self):
        return f"{self.user_id}#{self.log_at}#{self.level}"

Sys_Event_Code DB Example

  • A:動作 (C(create)/U(update)/D(delete))
  • I:資訊
  • E:錯誤
  • R:讀取
event_id   name
999999    未定義
ASU001    使用者登入
ESU001    使用者登入失敗
ISU001    使用者登入成功

Sys_Subject DB Example

subject_id   category   item
SYS_USER     使用者狀態  登入

Sys_Event_Log DB Example

id            log_at          level    message1    message2  info   event_id subject_id user_id
1	2023-xx-xx xx:xx:xx.xxxxxx	A	使用者登入,username=xxxxxx			 ASU001	 SYS_USER1
2	2023-xx-xx xx:xx:xx.xxxxxx	I	使用者登入,username=xxxxxx			 ISU001	 SYS_USER1

views/login.py

class LoginView(views.APIView):
    permission_classes = (permissions.AllowAny,)
    authentication_classes = []


    @method_decorator(logtodb(event_id='ASU001',subject_id='SYS_USER1',
            message1='使用者登入,username=%s',keys=[('data','username')] ))
    def post(self, request, format=None):
        try:
            serializer = LoginSerializer(data=request.data, context={'request': self.request})
            fail_msg = {'success': False, 'message': 'register not finish.'}
            if serializer.is_valid():
                user = serializer.validated_data['user']
                token = Token.objects.get(user=user)
                try:
                    user_profile = Users.objects.get(auth_user__username=user)
                except:
                    user_profile = None
                    return Response(fail_msg, status=status.HTTP_400_BAD_REQUEST)    
                # -----
                # 新增session資訊
                # -----
                request.session['user_id'] = user_profile.id
                request.session['auth_user_id'] = user_profile.auth_user.id
                request.session['is_login'] = True
                msg = {
                    'token': token.key,
                    'success': True,
                    'message': "Validation success",
                    'username': user.username,
                    'id': user_profile.id
                }
                return Response(msg, status=status.HTTP_202_ACCEPTED)

        except:
            logger.error(traceback.format_exc())

        return Response({'success': False,'message': 'Wrong username or password',},
                        status=status.HTTP_400_BAD_REQUEST)

建立一個logger/django/init.py

from .logger import LogManager

建立一個logger/django/logtodb.py

這邊主要是如果當event_id and subject_id 沒給時會變成未定義狀態
如果有給時進程序,抓取views requests.data 傳給的參數
將相對應參數帶回logtodb
執行成功,新增寫入sys_event_log , 動作(A)執行後訊息
成功I
失敗E
記錄在Sys_Event_Log DB
如上圖

import traceback, datetime, json, base64
from functools import wraps
from website.models.sys_model import Sys_Event_Log
from logger import LogManager
from website.models.sys_model import Sys_Event_Log,Sys_Event_Code,Sys_Subject
from website.models.users_model import Users
from website.serializers.sys_ser import SysEventCodeSerializer, SysSubjectSerializer

logger = LogManager().getLogger('view')
sys_event_code_data = Sys_Event_Code.objects.values_list('event_id', flat=True)
sys_subject_data = Sys_Subject.objects.values_list('subject_id', flat=True)

# event_id: view_func執行前和後的log紀錄,A:前A後IorE / I:前I / E:不合理 / R:讀取(查詢)
# keys: ["session.key", "path.key", "param.key", "data,key"]
    # path : kwargs[key], path路徑
    # data : request.data[key], post內容
    # param : request.GET[key], ?後面的attribute
    # session : request.session[key], 取seesion內容, 如user_id
# message1: 使用%s、%f等字串格式自行handle
def logtodb(event_id:str='999999',subject_id:str='999999',
            message1:str=None, message2:str=None, keys:dict=None):
    def my_decorator(view_func):
        @wraps(view_func)
        def wrapper(request, *args, **kwargs):
            keep_message1 = ''; keep_message2 = ''
            origin_event_id = event_id ; origin_subject_id = subject_id
            response = None
            # 統一時間戳記
            # log_at = datetime.datetime.now().strftime('%Y%m%d-%H:%M:%S.%f')
            log_at = datetime.datetime.now()
            # 如果是登入行為,則user_id會是None, 從username去取(post data)
            if '/login' in request.get_full_path():
                user_id = None
            else:
                user_id = request.session['user_id'] if 'user_id' in request.session else None
                auth_user_id = request.session['auth_user_id'] if 'auth_user_id' in request.session else None
            try:
                # ------
                # 動作執行前寫入sys_event_log, 如果發生問題不應該影響動作繼續執行
                # ------
                my_param = None ; info1 = None
                try:
                    keep_message2 = message2
                    keep_event_id = event_id if (event_id in sys_event_code_data and event_id[0] != 'E') else '999999'
                    keep_subject_id = subject_id if subject_id in sys_subject_data else '999999'
                    my_param = tuple()
                    if keys is not None:
                        for i in keys:
                            try:
                                if i[0] == 'session':
                                    my_param += (request.session[i[1]],)
                                elif i[0] == 'param':
                                    my_param += (request.GET[i[1]],)
                                elif i[0] == 'data':
                                    my_param += (request.data[i[1]],)
                                else:
                                    my_param += (kwargs[i[1]],)
                            except:
                                my_param += ('',)                       

                        # my_param = tuple((request.session[i[1]] if i[0]=='session' 
                        #             else request.GET[i[1]] if i[0]=='param' 
                        #             else request.data[i[1]] if i[0]=='data'
                        #             else kwargs[i[1]] for i in keys))
                    try:
                        keep_message1 = message1 %my_param if my_param else message1
                    except:
                        keep_message1 = message1                        
                    # info1 = None if my_param else str(keys)
                    level = keep_event_id[0] if keep_event_id[0] in ['I','A','E', 'R'] else '-'
                    
                    sel_obj = Sys_Event_Log(log_at=log_at, level=level, message1=keep_message1,
                                        message2=keep_message2, info1=info1,
                                        event_id=Sys_Event_Code.objects.get(pk=keep_event_id),
                                        subject_id=Sys_Subject.objects.get(pk=keep_subject_id), 
                                        user_id=Users.objects.get(pk=user_id) if user_id else None)
                    sel_obj.save()
                except:
                    info1 = 'event_id:%s, subject_id:%s, key:%s' %(origin_event_id, origin_subject_id ,str(keys))
                    sel_obj = Sys_Event_Log(log_at=log_at, level='E', message1=message1,
                                        message2=message2, info1=info1,
                                        event_id=Sys_Event_Code.objects.get(pk='999999'),
                                        subject_id=Sys_Subject.objects.get(pk='999999'), 
                                        user_id=Users.objects.get(pk=user_id) if user_id else None)
                    sel_obj.save()
                    logger.error('sys_event_log 參數解析異常. 將訊息寫到info1')
                    logger.error(traceback.format_exc())
                # ------
                # 執行view
                # ------
                response = view_func(request, *args, **kwargs)
                if str(response.status_code)[0] in ['4','5']:
                    raise ValueError('Response:4xx/5xx請求失敗,請參考sys_event_log table.')
                
                # ------
                # 如果是登入行為則在執行完view才會將user_id寫入session
                # 如果user_id仍然是None則可能原因是什麼???(瀏覽器禁用cookie?)
                # ------
                idvalue = json.loads(base64.b64decode(request.headers['X-Userinfo'])) \
                          if 'X-Userinfo' in request.headers else None
                user_id = request.session['user_id'] if 'user_id' in request.session \
                          else idvalue['ext']['id'] if 'ext' in idvalue else None
                if user_id is None:
                    raise ValueError('user_id 不存在, 未知的錯誤.')

                # ------
                # 執行成功,新增寫入sys_event_log , 動作執行後訊息
                # event_id為"A"開頭才有完成log
                # ------
                try:
                    if keep_event_id[0] == 'A':
                        check_event_id = 'I' + keep_event_id[1:]
                        check_event_id = check_event_id if check_event_id in sys_event_code_data else '999999'
                        sel_obj = Sys_Event_Log(log_at=log_at, level='I', message1=keep_message1,
                                message2=keep_message2, info1=info1,
                                event_id=Sys_Event_Code.objects.get(pk=check_event_id),
                                subject_id=Sys_Subject.objects.get(pk=keep_subject_id), 
                                user_id=Users.objects.get(pk=user_id) if user_id else None)
                        sel_obj.save()
                except:
                    sel_obj = Sys_Event_Log(log_at=log_at, level='E', message1=message1,
                                message2=message2, info1=info1,
                                event_id=Sys_Event_Code.objects.get(pk='999999'),
                                subject_id=Sys_Subject.objects.get(pk='999999'), 
                                user_id=Users.objects.get(pk=user_id) if user_id else None)
                    sel_obj.save()
                    logger.error('sys_event_log 參數解析異常. 將訊息寫到info1')
                    logger.error(traceback.format_exc())

            except:
                # 當view_func發生except或status_code='4xx'
                # TODO 寫入sys_event_log, 是否回500?
                check_event_id = 'E' + keep_event_id[1:]
                check_event_id = check_event_id if check_event_id in sys_event_code_data else '999999'
                sel_obj = Sys_Event_Log(log_at=log_at, level='E', message1=keep_message1,
                                        message2=keep_message2, info1='失敗原因:api執行異常或log寫入失敗. 相關參數:%s' %info1,
                                        event_id=Sys_Event_Code.objects.get(pk=check_event_id),
                                        subject_id=Sys_Subject.objects.get(pk=keep_subject_id), 
                                        user_id=Users.objects.get(pk=user_id) if user_id else None)
                sel_obj.save()
                logger.error('發生錯誤,寫錯誤代碼到sys_event_log')
                logger.error(traceback.format_exc())
            return response
        return wrapper
    return my_decorator

今天主要介紹使用者的日誌建立,明天來講解,windows IIS的建設
我們明天見,各位掰掰~~~/images/emoticon/emoticon29.gif


上一篇
Day24~Django 漫漫長路- 快速找尋問題 logs檔的建立
下一篇
Day26~Django 漫漫長路-踏上windows IIS_01
系列文
Django30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言