##// END OF EJS Templates
vendor: python3 fixes
super-admin -
r5041:dcbf3e8a default
parent child Browse files
Show More
@@ -1,236 +1,237 b''
1 1 '''
2 2 This library is provided to allow standard python logging
3 3 to output log data as JSON formatted strings
4 4 '''
5 5 import logging
6 import json
7 6 import re
8 7 from datetime import date, datetime, time, tzinfo, timedelta
9 8 import traceback
10 9 import importlib
11 10
12 11 from inspect import istraceback
13 12
14 13 from collections import OrderedDict
15 14 from rhodecode.lib.logging_formatter import _inject_req_id, ExceptionAwareFormatter
15 from rhodecode.lib.ext_json import sjson as json
16
16 17
17 18 ZERO = timedelta(0)
18 19 HOUR = timedelta(hours=1)
19 20
20 21
21 22 class UTC(tzinfo):
22 23 """UTC"""
23 24
24 25 def utcoffset(self, dt):
25 26 return ZERO
26 27
27 28 def tzname(self, dt):
28 29 return "UTC"
29 30
30 31 def dst(self, dt):
31 32 return ZERO
32 33
33 34 utc = UTC()
34 35
35 36
36 37 # skip natural LogRecord attributes
37 38 # http://docs.python.org/library/logging.html#logrecord-attributes
38 39 RESERVED_ATTRS = (
39 40 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
40 41 'funcName', 'levelname', 'levelno', 'lineno', 'module',
41 42 'msecs', 'message', 'msg', 'name', 'pathname', 'process',
42 43 'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')
43 44
44 45
45 46 def merge_record_extra(record, target, reserved):
46 47 """
47 48 Merges extra attributes from LogRecord object into target dictionary
48 49
49 50 :param record: logging.LogRecord
50 51 :param target: dict to update
51 52 :param reserved: dict or list with reserved keys to skip
52 53 """
53 54 for key, value in record.__dict__.items():
54 55 # this allows to have numeric keys
55 56 if (key not in reserved
56 57 and not (hasattr(key, "startswith")
57 58 and key.startswith('_'))):
58 59 target[key] = value
59 60 return target
60 61
61 62
62 63 class JsonEncoder(json.JSONEncoder):
63 64 """
64 65 A custom encoder extending the default JSONEncoder
65 66 """
66 67
67 68 def default(self, obj):
68 69 if isinstance(obj, (date, datetime, time)):
69 70 return self.format_datetime_obj(obj)
70 71
71 72 elif istraceback(obj):
72 73 return ''.join(traceback.format_tb(obj)).strip()
73 74
74 75 elif type(obj) == Exception \
75 76 or isinstance(obj, Exception) \
76 77 or type(obj) == type:
77 78 return str(obj)
78 79
79 80 try:
80 81 return super(JsonEncoder, self).default(obj)
81 82
82 83 except TypeError:
83 84 try:
84 85 return str(obj)
85 86
86 87 except Exception:
87 88 return None
88 89
89 90 def format_datetime_obj(self, obj):
90 91 return obj.isoformat()
91 92
92 93
93 94 class JsonFormatter(ExceptionAwareFormatter):
94 95 """
95 96 A custom formatter to format logging records as json strings.
96 97 Extra values will be formatted as str() if not supported by
97 98 json default encoder
98 99 """
99 100
100 101 def __init__(self, *args, **kwargs):
101 102 """
102 103 :param json_default: a function for encoding non-standard objects
103 104 as outlined in http://docs.python.org/2/library/json.html
104 105 :param json_encoder: optional custom encoder
105 106 :param json_serializer: a :meth:`json.dumps`-compatible callable
106 107 that will be used to serialize the log record.
107 108 :param json_indent: an optional :meth:`json.dumps`-compatible numeric value
108 109 that will be used to customize the indent of the output json.
109 110 :param prefix: an optional string prefix added at the beginning of
110 111 the formatted string
111 112 :param json_indent: indent parameter for json.dumps
112 113 :param json_ensure_ascii: ensure_ascii parameter for json.dumps
113 114 :param reserved_attrs: an optional list of fields that will be skipped when
114 115 outputting json log record. Defaults to all log record attributes:
115 116 http://docs.python.org/library/logging.html#logrecord-attributes
116 117 :param timestamp: an optional string/boolean field to add a timestamp when
117 118 outputting the json log record. If string is passed, timestamp will be added
118 119 to log record using string as key. If True boolean is passed, timestamp key
119 120 will be "timestamp". Defaults to False/off.
120 121 """
121 122 self.json_default = self._str_to_fn(kwargs.pop("json_default", None))
122 123 self.json_encoder = self._str_to_fn(kwargs.pop("json_encoder", None))
123 124 self.json_serializer = self._str_to_fn(kwargs.pop("json_serializer", json.dumps))
124 125 self.json_indent = kwargs.pop("json_indent", None)
125 126 self.json_ensure_ascii = kwargs.pop("json_ensure_ascii", True)
126 127 self.prefix = kwargs.pop("prefix", "")
127 128 reserved_attrs = kwargs.pop("reserved_attrs", RESERVED_ATTRS)
128 129 self.reserved_attrs = dict(list(zip(reserved_attrs, reserved_attrs)))
129 130 self.timestamp = kwargs.pop("timestamp", True)
130 131
131 132 # super(JsonFormatter, self).__init__(*args, **kwargs)
132 133 logging.Formatter.__init__(self, *args, **kwargs)
133 134 if not self.json_encoder and not self.json_default:
134 135 self.json_encoder = JsonEncoder
135 136
136 137 self._required_fields = self.parse()
137 138 self._skip_fields = dict(list(zip(self._required_fields,
138 139 self._required_fields)))
139 140 self._skip_fields.update(self.reserved_attrs)
140 141
141 142 def _str_to_fn(self, fn_as_str):
142 143 """
143 144 If the argument is not a string, return whatever was passed in.
144 145 Parses a string such as package.module.function, imports the module
145 146 and returns the function.
146 147
147 148 :param fn_as_str: The string to parse. If not a string, return it.
148 149 """
149 150 if not isinstance(fn_as_str, str):
150 151 return fn_as_str
151 152
152 153 path, _, function = fn_as_str.rpartition('.')
153 154 module = importlib.import_module(path)
154 155 return getattr(module, function)
155 156
156 157 def parse(self):
157 158 """
158 159 Parses format string looking for substitutions
159 160
160 161 This method is responsible for returning a list of fields (as strings)
161 162 to include in all log messages.
162 163 """
163 164 standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE)
164 165 return standard_formatters.findall(self._fmt)
165 166
166 167 def add_fields(self, log_record, record, message_dict):
167 168 """
168 169 Override this method to implement custom logic for adding fields.
169 170 """
170 171 for field in self._required_fields:
171 172 log_record[field] = record.__dict__.get(field)
172 173 log_record.update(message_dict)
173 174 merge_record_extra(record, log_record, reserved=self._skip_fields)
174 175
175 176 if self.timestamp:
176 177 key = self.timestamp if type(self.timestamp) == str else 'timestamp'
177 178 log_record[key] = datetime.fromtimestamp(record.created, tz=utc)
178 179
179 180 def process_log_record(self, log_record):
180 181 """
181 182 Override this method to implement custom logic
182 183 on the possibly ordered dictionary.
183 184 """
184 185 return log_record
185 186
186 187 def jsonify_log_record(self, log_record):
187 188 """Returns a json string of the log record."""
188 189 return self.json_serializer(log_record,
189 190 default=self.json_default,
190 191 cls=self.json_encoder,
191 192 indent=self.json_indent,
192 193 ensure_ascii=self.json_ensure_ascii)
193 194
194 195 def serialize_log_record(self, log_record):
195 196 """Returns the final representation of the log record."""
196 197 return "%s%s" % (self.prefix, self.jsonify_log_record(log_record))
197 198
198 199 def format(self, record):
199 200 """Formats a log record and serializes to json"""
200 201 message_dict = {}
201 202 # FIXME: logging.LogRecord.msg and logging.LogRecord.message in typeshed
202 203 # are always type of str. We shouldn't need to override that.
203 204 if isinstance(record.msg, dict):
204 205 message_dict = record.msg
205 206 record.message = None
206 207 else:
207 208 record.message = record.getMessage()
208 209 # only format time if needed
209 210 if "asctime" in self._required_fields:
210 211 record.asctime = self.formatTime(record, self.datefmt)
211 212
212 213 # Display formatted exception, but allow overriding it in the
213 214 # user-supplied dict.
214 215 if record.exc_info and not message_dict.get('exc_info'):
215 216 message_dict['exc_info'] = self.formatException(record.exc_info)
216 217 if not message_dict.get('exc_info') and record.exc_text:
217 218 message_dict['exc_info'] = record.exc_text
218 219 # Display formatted record of stack frames
219 220 # default format is a string returned from :func:`traceback.print_stack`
220 221 try:
221 222 if record.stack_info and not message_dict.get('stack_info'):
222 223 message_dict['stack_info'] = self.formatStack(record.stack_info)
223 224 except AttributeError:
224 225 # Python2.7 doesn't have stack_info.
225 226 pass
226 227
227 228 try:
228 229 log_record = OrderedDict()
229 230 except NameError:
230 231 log_record = {}
231 232
232 233 _inject_req_id(record, with_prefix=False)
233 234 self.add_fields(log_record, record, message_dict)
234 235 log_record = self.process_log_record(log_record)
235 236
236 237 return self.serialize_log_record(log_record)
@@ -1,52 +1,52 b''
1 1
2 2
3 3 import logging
4 4
5 5 from .stream import TCPStatsClient, UnixSocketStatsClient # noqa
6 6 from .udp import StatsClient # noqa
7 7
8 8 HOST = 'localhost'
9 9 PORT = 8125
10 10 IPV6 = False
11 11 PREFIX = None
12 12 MAXUDPSIZE = 512
13 13
14 14 log = logging.getLogger('rhodecode.statsd')
15 15
16 16
17 17 def statsd_config(config, prefix='statsd.'):
18 18 _config = {}
19 for key in config.keys():
19 for key in list(config.keys()):
20 20 if key.startswith(prefix):
21 21 _config[key[len(prefix):]] = config[key]
22 22 return _config
23 23
24 24
25 25 def client_from_config(configuration, prefix='statsd.', **kwargs):
26 26 from pyramid.settings import asbool
27 27
28 28 _config = statsd_config(configuration, prefix)
29 29 statsd_enabled = asbool(_config.pop('enabled', False))
30 30 if not statsd_enabled:
31 31 log.debug('statsd client not enabled by statsd.enabled = flag, skipping...')
32 32 return
33 33
34 34 host = _config.pop('statsd_host', HOST)
35 35 port = _config.pop('statsd_port', PORT)
36 36 prefix = _config.pop('statsd_prefix', PREFIX)
37 37 maxudpsize = _config.pop('statsd_maxudpsize', MAXUDPSIZE)
38 38 ipv6 = asbool(_config.pop('statsd_ipv6', IPV6))
39 39 log.debug('configured statsd client %s:%s', host, port)
40 40
41 41 try:
42 42 client = StatsClient(
43 43 host=host, port=port, prefix=prefix, maxudpsize=maxudpsize, ipv6=ipv6)
44 44 except Exception:
45 45 log.exception('StatsD is enabled, but failed to connect to statsd server, fallback: disable statsd')
46 46 client = None
47 47
48 48 return client
49 49
50 50
51 51 def get_statsd_client(request):
52 52 return client_from_config(request.registry.settings)
General Comments 0
You need to be logged in to leave comments. Login now