logging config & common app
Showing
19 changed files
with
521 additions
and
2 deletions
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 | ... | ... |
src/apps/account/__init__.py
0 → 100644
File mode changed
src/apps/account/admin.py
0 → 100644
src/apps/account/apps.py
0 → 100644
src/apps/account/models.py
0 → 100644
src/apps/account/permissions.py
0 → 100644
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) |
src/apps/account/tests.py
0 → 100644
src/apps/account/urls.py
0 → 100644
src/apps/account/views.py
0 → 100644
... | @@ -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 | ] | ... | ... |
src/common/__init__.py
0 → 100644
File mode changed
src/common/apps.py
0 → 100644
src/common/exceptions.py
0 → 100644
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 |
src/common/mixins.py
0 → 100644
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 |
src/common/named_enum.py
0 → 100644
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 |
src/common/response.py
0 → 100644
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 | ... | ... |
-
Please register or sign in to post a comment