ec7b3159 by 周伟奇

situ logger v0.1

0 parents
1 *.idea
...\ No newline at end of file ...\ No newline at end of file
1 from setuptools import setup
2
3 setup(name='situlogger',
4 version='0.1',
5 description='situ logger',
6 author='clay',
7 author_email='zhouweiqi@situdata.com',
8 packages=['situlogger'],
9 classifiers=[
10 "Programming Language :: Python :: 3",
11 "Operating System :: OS Independent",
12 ],)
...\ No newline at end of file ...\ No newline at end of file
1 import os
2 import re
3 import stat
4 import time
5 import json
6 import errno
7 import logging
8 import datetime
9 import traceback
10
11 from functools import wraps
12 from inspect import istraceback
13
14
15 class SituRotatingFileHandler(logging.FileHandler, object):
16 """
17 这个类提供了一种多进程环境下的rotating filehandler。保证多进程间切日志不会丢失。
18
19 所有日志的后缀都是'%Y-%m-%d',比如:situ_log_name.log.2020-03-14。
20
21 其原理是,在每天半夜进行rotating的时候,不像TimeRotatingFileHandler一样进行rename操作,
22 而是直接用当前时间生成新的日期后缀,创建新的日志文件。
23 """
24
25 def __init__(self, filename):
26 self._filename = filename
27 self._rotate_at = self._next_rotate_at()
28 super(SituRotatingFileHandler, self).__init__(filename, mode='a')
29
30 @classmethod
31 def _next_rotate_at(cls):
32 now = datetime.datetime.now()
33 next_day = (now.replace(hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1)).timetuple()
34 return time.mktime(next_day)
35
36 def _open(self):
37 now = datetime.datetime.now()
38 log_today = "{0}.{1}".format(self._filename,
39 now.strftime('%Y-%m-%d'))
40 try:
41 fd = os.open(log_today, os.O_CREAT | os.O_EXCL,
42 stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP |
43 stat.S_IROTH)
44 os.close(fd)
45 except OSError as e:
46 if e.errno != errno.EEXIST:
47 raise
48 self.baseFilename = log_today
49 return super(SituRotatingFileHandler, self)._open()
50
51 def emit(self, record):
52 if time.time() > self._rotate_at:
53 self._rotate_at = self._next_rotate_at()
54 self.close()
55 super(SituRotatingFileHandler, self).emit(record)
56
57
58 '''
59 This library is provided to allow standard python logging
60 to output log data as JSON formatted strings
61 '''
62 # Support order in python 2.7 and 3
63 try:
64 from collections import OrderedDict
65 except ImportError:
66 pass
67
68 # skip natural LogRecord attributes
69 # http://docs.python.org/library/logging.html#logrecord-attributes
70 RESERVED_ATTRS = (
71 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
72 'funcName', 'levelname', 'levelno', 'lineno', 'module',
73 'msecs', 'message', 'msg', 'name', 'pathname', 'process',
74 'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')
75
76 RESERVED_ATTR_HASH = dict(zip(RESERVED_ATTRS, RESERVED_ATTRS))
77
78
79 def merge_record_extra(record, target, reserved=RESERVED_ATTR_HASH):
80 """
81 Merges extra attributes from LogRecord object into target dictionary
82
83 :param record: logging.LogRecord
84 :param target: dict to update
85 :param reserved: dict or list with reserved keys to skip
86 """
87 for key, value in record.__dict__.items():
88 # this allows to have numeric keys
89 if (key not in reserved
90 and not (hasattr(key, "startswith")
91 and key.startswith('_'))):
92 target[key] = value
93 return target
94
95
96 def situ_log_request(a_logger):
97 """"django view装饰器,用来记录日志"""
98
99 def a_log(view_func):
100 @wraps(view_func)
101 def returned_wrapper(request, *args, **kwargs):
102 try:
103 request.ALOG_DATA = {}
104 return view_func(request, *args, **kwargs)
105 finally:
106 a_logger.info(request.ALOG_DATA)
107
108 return returned_wrapper
109
110 return a_log
111
112
113 class JsonFormatter(logging.Formatter, object):
114 """
115 A custom formatter to format logging records as json strings.
116 extra values will be formatted as str() if nor supported by
117 json default encoder
118 """
119
120 def __init__(self, *args, **kwargs):
121 """
122 :param json_default: a function for encoding non-standard objects
123 as outlined in http://docs.python.org/2/library/json.html
124 :param json_encoder: optional custom encoder
125 :param json_serializer: a :meth:`json.dumps`-compatible callable
126 that will be used to serialize the log record.
127 :param prefix: an optional string prefix added at the beginning of
128 the formatted string
129 """
130 self.json_default = kwargs.pop("json_default", None)
131 self.json_encoder = kwargs.pop("json_encoder", None)
132 self.json_serializer = json.dumps
133 self.prefix = kwargs.pop("prefix", "")
134 # super(JsonFormatter, self).__init__(*args, **kwargs)
135 logging.Formatter.__init__(self, *args, **kwargs)
136 if not self.json_encoder and not self.json_default:
137 def _default_json_handler(obj):
138 '''Prints dates in ISO format'''
139 if isinstance(obj, (datetime.date, datetime.time)):
140 return obj.isoformat()
141 elif istraceback(obj):
142 tb = ''.join(traceback.format_tb(obj))
143 return tb.strip()
144 elif isinstance(obj, Exception):
145 return "Exception: %s" % str(obj)
146 return str(obj)
147
148 self.json_default = _default_json_handler
149 self._required_fields = self.parse()
150 self._skip_fields = dict(zip(self._required_fields,
151 self._required_fields))
152 self._skip_fields.update(RESERVED_ATTR_HASH)
153
154 def parse(self):
155 """Parses format string looking for substitutions"""
156 standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE)
157 return standard_formatters.findall(self._fmt)
158
159 def add_fields(self, log_record, record, message_dict):
160 """
161 Override this method to implement custom logic for adding fields.
162 """
163 for field in self._required_fields:
164 log_record[field] = record.__dict__.get(field)
165 log_record.update(message_dict)
166 merge_record_extra(record, log_record, reserved=self._skip_fields)
167
168 def process_log_record(self, log_record):
169 """
170 Override this method to implement custom logic
171 on the possibly ordered dictionary.
172 """
173 return log_record
174
175 def jsonify_log_record(self, log_record):
176 """Returns a json string of the log record."""
177 return self.json_serializer(log_record)
178
179 def format(self, record):
180 """Formats a log record and serializes to json"""
181 message_dict = {}
182 if isinstance(record.msg, dict):
183 message_dict = record.msg
184 record.message = None
185 else:
186 record.message = record.getMessage()
187 # only format time if needed
188 if "asctime" in self._required_fields:
189 record.asctime = self.formatTime(record, self.datefmt)
190
191 # Display formatted exception, but allow overriding it in the
192 # user-supplied dict.
193 if record.exc_info and not message_dict.get('exc_info'):
194 message_dict['exc_info'] = self.formatException(record.exc_info)
195 if not message_dict.get('exc_info') and record.exc_text:
196 message_dict['exc_info'] = record.exc_text
197
198 try:
199 log_record = OrderedDict()
200 except NameError:
201 log_record = {}
202
203 self.add_fields(log_record, record, message_dict)
204 log_record = self.process_log_record(log_record)
205
206 return "%s%s" % (self.prefix, self.jsonify_log_record(log_record))
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!