5
5
import logging
6
6
import json
7
7
import re
8
- import datetime
8
+ from datetime import date , datetime , time
9
9
import traceback
10
10
11
11
from inspect import istraceback
24
24
'msecs' , 'message' , 'msg' , 'name' , 'pathname' , 'process' ,
25
25
'processName' , 'relativeCreated' , 'stack_info' , 'thread' , 'threadName' )
26
26
27
- RESERVED_ATTR_HASH = dict (zip (RESERVED_ATTRS , RESERVED_ATTRS ))
28
27
29
-
30
- def merge_record_extra (record , target , reserved = RESERVED_ATTR_HASH ):
28
+ def merge_record_extra (record , target , reserved ):
31
29
"""
32
30
Merges extra attributes from LogRecord object into target dictionary
33
31
@@ -44,6 +42,36 @@ def merge_record_extra(record, target, reserved=RESERVED_ATTR_HASH):
44
42
return target
45
43
46
44
45
+ class JsonEncoder (json .JSONEncoder ):
46
+ """
47
+ A custom encoder extending the default JSONEncoder
48
+ """
49
+ def default (self , obj ):
50
+ if isinstance (obj , (date , datetime , time )):
51
+ return self .format_datetime_obj (obj )
52
+
53
+ elif istraceback (obj ):
54
+ return '' .join (traceback .format_tb (obj )).strip ()
55
+
56
+ elif type (obj ) == Exception \
57
+ or isinstance (obj , Exception ) \
58
+ or type (obj ) == type :
59
+ return str (obj )
60
+
61
+ try :
62
+ return super (JsonEncoder , self ).default (obj )
63
+
64
+ except TypeError :
65
+ try :
66
+ return str (obj )
67
+
68
+ except Exception :
69
+ return None
70
+
71
+ def format_datetime_obj (self , obj ):
72
+ return obj .isoformat ()
73
+
74
+
47
75
class JsonFormatter (logging .Formatter ):
48
76
"""
49
77
A custom formatter to format logging records as json strings.
@@ -58,32 +86,36 @@ def __init__(self, *args, **kwargs):
58
86
:param json_encoder: optional custom encoder
59
87
:param json_serializer: a :meth:`json.dumps`-compatible callable
60
88
that will be used to serialize the log record.
89
+ :param json_indent: an optional :meth:`json.dumps`-compatible numeric value
90
+ that will be used to customize the indent of the output json.
61
91
:param prefix: an optional string prefix added at the beginning of
62
92
the formatted string
93
+ :param reserved_attrs: an optional list of fields that will be skipped when
94
+ outputting json log record. Defaults to all log record attributes:
95
+ http://docs.python.org/library/logging.html#logrecord-attributes
96
+ :param timestamp: an optional string/boolean field to add a timestamp when
97
+ outputting the json log record. If string is passed, timestamp will be added
98
+ to log record using string as key. If True boolean is passed, timestamp key
99
+ will be "timestamp". Defaults to False/off.
63
100
"""
64
101
self .json_default = kwargs .pop ("json_default" , None )
65
102
self .json_encoder = kwargs .pop ("json_encoder" , None )
66
103
self .json_serializer = kwargs .pop ("json_serializer" , json .dumps )
67
104
self .json_indent = kwargs .pop ("json_indent" , None )
68
105
self .prefix = kwargs .pop ("prefix" , "" )
106
+ reserved_attrs = kwargs .pop ("reserved_attrs" , RESERVED_ATTRS )
107
+ self .reserved_attrs = dict (zip (reserved_attrs , reserved_attrs ))
108
+ self .timestamp = kwargs .pop ("timestamp" , False )
109
+
69
110
#super(JsonFormatter, self).__init__(*args, **kwargs)
70
111
logging .Formatter .__init__ (self , * args , ** kwargs )
71
112
if not self .json_encoder and not self .json_default :
72
- def _default_json_handler (obj ):
73
- '''Prints dates in ISO format'''
74
- if isinstance (obj , (datetime .date , datetime .time )):
75
- return obj .isoformat ()
76
- elif istraceback (obj ):
77
- tb = '' .join (traceback .format_tb (obj ))
78
- return tb .strip ()
79
- elif isinstance (obj , Exception ):
80
- return "Exception: %s" % str (obj )
81
- return str (obj )
82
- self .json_default = _default_json_handler
113
+ self .json_encoder = JsonEncoder
114
+
83
115
self ._required_fields = self .parse ()
84
116
self ._skip_fields = dict (zip (self ._required_fields ,
85
117
self ._required_fields ))
86
- self ._skip_fields .update (RESERVED_ATTR_HASH )
118
+ self ._skip_fields .update (self . reserved_attrs )
87
119
88
120
def parse (self ):
89
121
"""
@@ -104,6 +136,10 @@ def add_fields(self, log_record, record, message_dict):
104
136
log_record .update (message_dict )
105
137
merge_record_extra (record , log_record , reserved = self ._skip_fields )
106
138
139
+ if self .timestamp :
140
+ key = self .timestamp if type (self .timestamp ) == str else 'timestamp'
141
+ log_record [key ] = datetime .utcnow ()
142
+
107
143
def process_log_record (self , log_record ):
108
144
"""
109
145
Override this method to implement custom logic
0 commit comments