23b64aee by Gruel

logging config & common app

1 parent a90b24be
1 certifi==2016.2.28 1 certifi==2016.2.28
2 Django==2.1 2 Django==2.1
3 djangorestframework==3.9.0
4 djangorestframework-jwt==1.11.0
5 PyJWT==1.7.1
6 PyMuPDF==1.17.0
3 pytz==2020.1 7 pytz==2020.1
4 # simple-config @ http://gitlab.situdata.com/zhouweiqi/simple_config/repository/archive.tar.gz?ref=master 8 # simple-config @ http://gitlab.situdata.com/zhouweiqi/simple_config/repository/archive.tar.gz?ref=master
9 # situlogger @ http://gitlab.situdata.com/zhouweiqi/situlogger/repository/archive.tar.gz?ref=master
10 six==1.14.0
......
1 http://gitlab.situdata.com/zhouweiqi/simple_config/repository/archive.tar.gz?ref=master
...\ No newline at end of file ...\ No newline at end of file
1 http://gitlab.situdata.com/zhouweiqi/simple_config/repository/archive.tar.gz?ref=master
2 http://gitlab.situdata.com/zhouweiqi/situlogger/repository/archive.tar.gz?ref=master
...\ No newline at end of file ...\ No newline at end of file
......
1 from django.contrib import admin
2
3 # Register your models here.
1 from django.apps import AppConfig
2
3
4 class AccountConfig(AppConfig):
5 name = 'account'
1 from django.db import models
2
3 # Create your models here.
1 from rest_framework.permissions import DjangoModelPermissions
2
3
4 class Permissions(DjangoModelPermissions):
5 """
6 copy from rest_framework/permissions.py
7 """
8
9 def has_permission(self, request, view):
10 # Workaround to ensure DjangoModelPermissions are not applied
11 # to the root view when using DefaultRouter.
12 if getattr(view, '_ignore_model_permissions', False):
13 return True
14
15 if not request.user:
16 return False
17
18 if self.authenticated_users_only and not request.user.is_authenticated:
19 return False
20
21 # if not request.user.is_staff:
22 # return False
23
24 perms = getattr(view, 'method_perms', {}).get(request.method, [])
25
26 return request.user.has_perms(perms)
1 from django.test import TestCase
2
3 # Create your tests here.
1 from django.urls import path
2 from . import views
3
4
5 urlpatterns = [
6 # path(r'login/', views.LoginView.as_view()),
7 ]
1 from django.shortcuts import render
2
3 # Create your views here.
...@@ -14,8 +14,9 @@ Including another URLconf ...@@ -14,8 +14,9 @@ Including another URLconf
14 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 14 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 """ 15 """
16 from django.contrib import admin 16 from django.contrib import admin
17 from django.urls import path 17 from django.urls import path, include
18 18
19 urlpatterns = [ 19 urlpatterns = [
20 path('admin/', admin.site.urls), 20 path('admin/', admin.site.urls),
21 path(r'apis/v1/users/', include('apps.account.urls')),
21 ] 22 ]
......
File mode changed
1 from django.apps import AppConfig
2
3
4 class CommonConfig(AppConfig):
5 name = 'common'
1 import logging
2 import six
3 from django.core.exceptions import PermissionDenied
4 from django.utils.translation import ugettext_lazy as _
5 from django.http import Http404
6 from rest_framework import exceptions, status
7 from rest_framework.response import Response
8
9 from .response import MetaStatus, res_content, APIResponse
10
11 error_logger = logging.getLogger('exception')
12
13
14 def exception_handler(exc, context):
15 """
16 Returns the response that should be used for any given exception.
17
18 By default we handle the REST framework `APIException`, and also
19 Django's built-in `Http404` and `PermissionDenied`
20
21 Any unhandled exceptions may return `None`, which will cause a 500 error
22 to be raised.
23 """
24 if isinstance(exc, exceptions.APIException):
25 headers = {}
26 if getattr(exc, 'auth_header', None):
27 headers['WWW-Authenticate'] = exc.auth_header
28 if getattr(exc, 'wait', None):
29 headers['Retry-After'] = '%d' % exc.wait
30
31 if exc.status_code == 403:
32 data = res_content(MetaStatus.NEED_LOGIN.value, MetaStatus.NEED_LOGIN.verbose_name)
33 else:
34 data = res_content(MetaStatus.INTERNAL_ERROR.value, exc.detail)
35
36 return Response(data, status=200, headers=headers)
37
38 elif isinstance(exc, Http404):
39 msg = _('Not found.')
40 data = {'detail': six.text_type(msg)}
41
42 return Response(data, status=status.HTTP_404_NOT_FOUND)
43
44 elif isinstance(exc, PermissionDenied):
45 msg = _('Permission denied.')
46 data = {'detail': six.text_type(msg)}
47
48 return Response(data, status=status.HTTP_403_FORBIDDEN)
49
50 elif isinstance(exc, Exception) and hasattr(exc, 'API_META_STATUS'):
51 msg = exc.API_META_STATUS.verbose_name
52 return APIResponse(exc.API_META_STATUS.value, msg=msg)
53
54 error_logger.exception('[system error]')
55 return APIResponse(MetaStatus.INTERNAL_ERROR.value,
56 msg=MetaStatus.INTERNAL_ERROR.verbose_name)
57
58
59 class NeedLoginException(Exception):
60 API_META_STATUS = MetaStatus.NEED_LOGIN
61
62
63 class InvalidParamsException(Exception):
64 API_META_STATUS = MetaStatus.INVALID_PARAMS
65
66
67 class InternalErrorException(Exception):
68 API_META_STATUS = MetaStatus.INTERNAL_ERROR
69
70
71 class ObjectNotExitException(Exception):
72 API_META_STATUS = MetaStatus.NOT_EXIST
73
74
75 class AsyncWaitException(Exception):
76 API_META_STATUS = MetaStatus.ASYNC_WAIT
77
78
79 class NoPermissionException(Exception):
80 API_META_STATUS = MetaStatus.NO_PERMISSION
81
82
83 class IllegalOperationException(Exception):
84 API_META_STATUS = MetaStatus.ILLEGAL_OPERATION
1 import logging
2 from django.contrib.auth.models import AnonymousUser
3 from rest_framework.generics import GenericAPIView
4 from common.exceptions import (
5 NeedLoginException,
6 InvalidParamsException,
7 InternalErrorException,
8 ObjectNotExitException,
9 AsyncWaitException,
10 NoPermissionException,
11 IllegalOperationException)
12
13
14 class GenericExceptionMixin:
15
16 def need_login(self, msg='need login'):
17 raise NeedLoginException(msg)
18
19 def invalid_params(self, msg='invalid params'):
20 raise InvalidParamsException(msg)
21
22 def internal_error(self, msg='internal error'):
23 raise InternalErrorException(msg)
24
25 def object_not_exit(self, msg='object not exit'):
26 raise ObjectNotExitException(msg)
27
28 def async_wait(self, msg='async wait'):
29 raise AsyncWaitException(msg)
30
31 def no_permission(self, msg='no permission'):
32 raise NoPermissionException(msg)
33
34 def illegal_operation(self, msg='illegal operation'):
35 raise IllegalOperationException(msg)
36
37
38 class LoggerMixin:
39 running_log = logging.getLogger('running')
40 exception_log = logging.getLogger('exception')
41 cronjob_log = logging.getLogger('cronjob')
42
43
44 class GenericView(LoggerMixin, GenericExceptionMixin, GenericAPIView):
45 need_print_logger = True
46
47 def print_logger(self, request):
48 parameters = getattr(request, request.method, {})
49 if not parameters:
50 parameters = getattr(request, 'data', {})
51 if not parameters:
52 parameters = {}
53 parameters_string = ''
54 for key, value in parameters.items():
55 parameters_string += '[%s=%s] ' % (key, value)
56 for key, value in self.kwargs.items():
57 parameters_string += '[%s=%s] ' % (key, value)
58 if request.user and not isinstance(request.user, AnonymousUser):
59 user_id = request.user.id
60 else:
61 user_id = 0
62 self.running_log.info('[%s_%s_request] with parameters [user_id=%s] %s'
63 % (self.__class__.__name__, request.method,
64 user_id, parameters_string))
65
66 def dispatch(self, request, *args, **kwargs):
67 """
68 `.dispatch()` is pretty much the same as Django's regular dispatch,
69 but with extra hooks for startup, finalize, and exception handling.
70 """
71 self.args = args
72 self.kwargs = kwargs
73 request = self.initialize_request(request, *args, **kwargs)
74 self.request = request
75 self.headers = self.default_response_headers # deprecate?
76 try:
77 self.initial(request, *args, **kwargs)
78
79 # Get the appropriate handler method
80 if request.method.lower() in self.http_method_names:
81 handler = getattr(self, request.method.lower(),
82 self.http_method_not_allowed)
83 else:
84 handler = self.http_method_not_allowed
85
86 if self.need_print_logger:
87 self.print_logger(request)
88
89 response = handler(request, *args, **kwargs)
90 except Exception as exc:
91 response = self.handle_exception(exc)
92
93 self.response = self.finalize_response(
94 request, response, *args, **kwargs)
95 return self.response
96
97 def get_object(self):
98 return None
1 import enum
2 from functools import lru_cache
3
4
5 class NamedEnum(enum.Enum):
6 """
7 >>> @enum.unique
8 ... class Status(NamedEnum):
9 ... CREATED = (3, '初始化')
10 ... CANCELED = (2, '损坏')
11 ... FINISHED = (1, '完成')
12 ... REJECTED = (4, '拒绝')
13 ... TEST = (6, '拒绝')
14 >>> assert Status.CREATED != 3
15 >>> assert Status.CREATED.value == 3
16 >>> assert Status.CREATED.name == 'CREATED'
17 >>> assert Status.CREATED.verbose_name == '初始化'
18 >>> assert Status(3) == Status.CREATED
19 >>> assert Status['CREATED'] == Status.CREATED
20 >>> assert Status.get_verbose_name_or_raise(3) == '初始化'
21 >>> assert Status.get_mappings() == {
22 ... 3: '初始化', 2: '损坏', 1: '完成', 4: '拒绝', 6: '拒绝'}
23 >>> assert Status.get_mapping_lst() == [
24 ... {'id': 3, 'name': '初始化'},
25 ... {'id': 2, 'name': '损坏'},
26 ... {'id': 1, 'name': '完成'},
27 ... {'id': 4, 'name': '拒绝'},
28 ... {'id': 6, 'name': '拒绝'}
29 ... ]
30 >>> assert Status.get_verbose_name(5, '默认') == '默认'
31 >>> assert Status.get_value_or_raise('完成') == 1
32 >>> assert Status.get_value('完成完成', 100) == 100
33 >>> assert Status.get_value_or_raise('拒绝') == 4
34 >>> # 测试扩展功能
35 >>> extend_values = {
36 ... 'EXTENDED': (6, '扩展')
37 ... }
38 >>> ExtendedStatus = extend(Status, 'ExtendedStatus', extend_values)
39 >>> assert ExtendedStatus.get_verbose_name(5, '默认') == '默认'
40 >>> assert ExtendedStatus.get_value_or_raise('完成') == 1
41 >>> assert ExtendedStatus.get_value('完成完成', 100) == 100
42 >>> assert ExtendedStatus.get_value_or_raise('拒绝') == 4
43 >>> assert ExtendedStatus.EXTENDED.value == 6
44 >>> ExtendedStatus = extend(
45 ... Status, 'ExtendedStatus', [('EXTENDED', (6, '扩展'))])
46 >>> assert ExtendedStatus.EXTENDED.value == 6
47 >>> try:
48 ... ExtendedStatus = extend(
49 ... Status, 'ExtendedStatus', extend_values, unique=True)
50 ... except ValueError as err:
51 ... assert 'duplicate' in str(err)
52 ... else:
53 ... raise Exception('except ValueError')
54 """
55
56 def __new__(cls, *args):
57 value, verbose_name = args
58 res = object.__new__(cls)
59 res._value_ = value
60 return res
61
62 def __init__(self, *args, **kwargs):
63 self._value_ = args[0]
64 self.verbose_name = args[1]
65
66 @classmethod
67 def get_verbose_name_or_raise(cls, value):
68 return cls.get_verbose_name(value, raise_on_missing=True)
69
70 @classmethod
71 def get_verbose_name(cls, value, default=None, raise_on_missing=False):
72 """尝试根据value获取verbose_name,如果失败,返回默认值
73 """
74 try:
75 return cls(value).verbose_name
76 except ValueError as err:
77 if raise_on_missing:
78 raise err
79 else:
80 return default
81
82 @classmethod
83 def get_value_or_raise(cls, verbose_name):
84 return cls.get_value(verbose_name, raise_on_missing=True)
85
86 @classmethod
87 def get_value(cls, verbose_name, default=None, raise_on_missing=False):
88 try:
89 return cls.from_verbose_name(verbose_name).value
90 except ValueError as err:
91 if raise_on_missing:
92 raise err
93 else:
94 return default
95
96 @classmethod
97 def from_verbose_name(cls, verbose_name):
98 """根据verbose_name获取NamedEnum,
99 如果verbose_name重复出现,会返回第一个定义的记录
100 """
101 for member in cls._value2member_map_.values():
102 if member.verbose_name == verbose_name:
103 return member
104 raise ValueError('%s is not a valid "%s"' % (
105 verbose_name, cls.__name__))
106
107 @classmethod
108 @lru_cache()
109 def get_mappings(cls):
110 return {
111 item.value: item.verbose_name
112 for _, item in cls._member_map_.items()
113 }
114
115 @classmethod
116 @lru_cache()
117 def get_mapping_lst(cls):
118 return list([
119 {'id': item.value, 'name': item.verbose_name}
120 for item in cls
121 ])
122
123 @classmethod
124 @lru_cache()
125 def get_value_lst(cls):
126 return list([
127 item.value
128 for item in cls
129 ])
130
131 @property
132 def raw_value(self):
133 return (self.value, self.verbose_name)
134
135
136 def extend(cls, sub_cls_name, names, unique=False):
137 assert issubclass(cls, NamedEnum)
138 target_names = {
139 item.name: item.raw_value for item in cls
140 }
141 if not isinstance(names, dict):
142 names = {item[0]: item[1] for item in names}
143 target_names.update(names)
144 sub_cls = NamedEnum(sub_cls_name, names=target_names)
145 if unique:
146 sub_cls = enum.unique(sub_cls)
147 return sub_cls
1 import enum
2 from django.http import JsonResponse
3 from .named_enum import NamedEnum
4
5
6 def res_content(meta_status, msg, data=None):
7 if data is None:
8 data = {}
9 return {'code': meta_status, 'message': msg, 'data': data}
10
11
12 @enum.unique
13 class MetaStatus(NamedEnum):
14 SUCCESS = (0, 'success')
15 NEED_LOGIN = (1, 'need login')
16 INVALID_PARAMS = (2, 'invalid params')
17 INTERNAL_ERROR = (3, 'internal error')
18 NOT_EXIST = (4, 'object not exist')
19 ASYNC_WAIT = (5, 'async wait')
20 NO_PERMISSION = (6, 'no permission')
21 ILLEGAL_OPERATION = (7, 'illegal operation')
22
23
24 class APIResponse(JsonResponse):
25 def __init__(self, meta_status, data=None, msg='', json_dumps_params=None, **kwargs):
26 data = res_content(meta_status, msg, data)
27 json_dumps_params = json_dumps_params or {'ensure_ascii': False}
28 kwargs['json_dumps_params'] = json_dumps_params
29 super().__init__(data, **kwargs)
30
31
32 def ok(**kwargs):
33 return APIResponse(MetaStatus.SUCCESS.value, msg=MetaStatus.SUCCESS.verbose_name, **kwargs)
...@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ ...@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
12 12
13 import os 13 import os
14 from . import conf 14 from . import conf
15 from logging import config
15 16
16 # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
17 BASE_DIR = conf.BASE_DIR 18 BASE_DIR = conf.BASE_DIR
...@@ -38,6 +39,8 @@ INSTALLED_APPS = [ ...@@ -38,6 +39,8 @@ INSTALLED_APPS = [
38 'django.contrib.sessions', 39 'django.contrib.sessions',
39 'django.contrib.messages', 40 'django.contrib.messages',
40 'django.contrib.staticfiles', 41 'django.contrib.staticfiles',
42 'rest_framework',
43 'common',
41 ] 44 ]
42 45
43 MIDDLEWARE = [ 46 MIDDLEWARE = [
...@@ -119,3 +122,20 @@ USE_TZ = False ...@@ -119,3 +122,20 @@ USE_TZ = False
119 # https://docs.djangoproject.com/en/2.1/howto/static-files/ 122 # https://docs.djangoproject.com/en/2.1/howto/static-files/
120 123
121 STATIC_URL = '/static/' 124 STATIC_URL = '/static/'
125
126 # RestFramework Settings
127 REST_FRAMEWORK = {
128 'DEFAULT_PERMISSION_CLASSES': (
129 'rest_framework.permissions.IsAuthenticated',
130 'apps.account.permissions.Permissions'
131 ),
132 'DEFAULT_AUTHENTICATION_CLASSES': (
133 'rest_framework.authentication.BasicAuthentication',
134 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
135 ),
136 'EXCEPTION_HANDLER': 'common.exceptions.exception_handler'
137 }
138
139 # 日志配置
140 LOGGING_CONFIG = None
141 config.fileConfig(conf.LOGGING_CONFIG_FILE, disable_existing_loggers=False)
......
1 [loggers]
2 keys=root, running, exception, cronjob, django.db.backends
3
4 [handlers]
5 keys=consoleHandler, django_rotateFileHandler, exceptionFileHandler, cronjobFileHandler, djangodbFileHandler
6
7 [formatters]
8 keys=SituFormatter, dataLogFormatter
9
10 [formatter_SituFormatter]
11 format=[%(asctime)s] [%(process)d] [%(thread)d] [%(threadName)s] [%(filename)s:%(lineno)d] %(levelname)s %(message)s
12 datefmt=
13
14 [formatter_dataLogFormatter]
15 class=situlogger.JsonFormatter
16 format=%(asctime)s %(levelname)s %(funcName)s
17
18 [handler_consoleHandler]
19 class=StreamHandler
20 level=ERROR
21 formatter=SituFormatter
22 args=(sys.stdout,)
23
24 [handler_django_rotateFileHandler]
25 class=situlogger.SituRotatingFileHandler
26 level=INFO
27 formatter=SituFormatter
28 args=('../logs/running.log',)
29
30 [handler_exceptionFileHandler]
31 class=situlogger.SituRotatingFileHandler
32 level=ERROR
33 formatter=SituFormatter
34 args=('../logs/exception.log',)
35
36 [handler_cronjobFileHandler]
37 class=situlogger.SituRotatingFileHandler
38 level=DEBUG
39 formatter=SituFormatter
40 args=('../logs/cronjob.log',)
41
42 [handler_djangodbFileHandler]
43 class=situlogger.SituRotatingFileHandler
44 level=DEBUG
45 formatter=SituFormatter
46 args=('../logs/sql.log',)
47
48 [logger_root]
49 level=DEBUG
50 handlers=consoleHandler
51
52 [logger_running]
53 level=INFO
54 handlers=django_rotateFileHandler
55 qualname=running
56 propagate=0
57
58 [logger_exception]
59 level=ERROR
60 handlers=exceptionFileHandler
61 qualname=exception
62 propagate=0
63
64 [logger_cronjob]
65 level=INFO
66 handlers=cronjobFileHandler
67 qualname=cronjob
68 propagate=0
69
70 [logger_django.db.backends]
71 level=DEBUG
72 handlers=djangodbFileHandler
73 qualname=django.db.backends
74 propagate=0
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!