3
3
# License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
5
6
+ import logging
7
+ import logging .handlers
6
8
import os
9
+ import sys
7
10
8
- import logbook
9
- import logbook .more
10
- import raven
11
- import raven .handlers .logbook
12
11
import structlog
13
12
13
+ import pkg_resources
14
+ import sentry_sdk
15
+ from sentry_sdk .integrations .logging import LoggingIntegration
14
16
15
- class UnstructuredRenderer (structlog .processors .KeyValueRenderer ):
16
- def __call__ (self , logger , method_name , event_dict ):
17
- event = None
18
- if "event" in event_dict :
19
- event = event_dict .pop ("event" )
20
- if event_dict or event is None :
21
- # if there are other keys, use the parent class to render them
22
- # and append to the event
23
- rendered = super (UnstructuredRenderer , self ).__call__ (
24
- logger , method_name , event_dict
25
- )
26
- return f"{ event } ({ rendered } )"
27
- else :
28
- return event
17
+ root = logging .getLogger ()
18
+
19
+
20
+ class AppNameFilter (logging .Filter ):
21
+ def __init__ (self , project_name , channel , * args , ** kwargs ):
22
+ self .project_name = project_name
23
+ self .channel = channel
24
+ super ().__init__ (* args , ** kwargs )
25
+
26
+ def filter (self , record ):
27
+ record .app_name = f"code-coverage/{ self .channel } /{ self .project_name } "
28
+ return True
29
+
30
+
31
+ class ExtraFormatter (logging .Formatter ):
32
+ def format (self , record ):
33
+ log = super ().format (record )
34
+
35
+ extra = {
36
+ key : value
37
+ for key , value in record .__dict__ .items ()
38
+ if key
39
+ not in list (sentry_sdk .integrations .logging .COMMON_RECORD_ATTRS )
40
+ + ["asctime" , "app_name" ]
41
+ }
42
+ if len (extra ) > 0 :
43
+ log += " | extra=" + str (extra )
44
+
45
+ return log
29
46
30
47
31
48
def setup_papertrail (project_name , channel , PAPERTRAIL_HOST , PAPERTRAIL_PORT ):
@@ -34,14 +51,18 @@ def setup_papertrail(project_name, channel, PAPERTRAIL_HOST, PAPERTRAIL_PORT):
34
51
"""
35
52
36
53
# Setup papertrail
37
- papertrail = logbook .SyslogHandler (
38
- application_name = f"code-coverage/{ channel } /{ project_name } " ,
54
+ papertrail = logging .handlers .SysLogHandler (
39
55
address = (PAPERTRAIL_HOST , int (PAPERTRAIL_PORT )),
40
- level = logbook .INFO ,
41
- format_string = "{record.time} {record.channel}: {record.message}" ,
42
- bubble = True ,
43
56
)
44
- papertrail .push_application ()
57
+ formatter = ExtraFormatter (
58
+ "%(app_name)s: %(asctime)s %(filename)s: %(message)s" ,
59
+ datefmt = "%Y-%m-%d %H:%M:%S" ,
60
+ )
61
+ papertrail .setLevel (logging .INFO )
62
+ papertrail .setFormatter (formatter )
63
+ # This filter is used to add the 'app_name' value to all logs to be formatted
64
+ papertrail .addFilter (AppNameFilter (project_name , channel ))
65
+ root .addHandler (papertrail )
45
66
46
67
47
68
def setup_sentry (name , channel , dsn ):
@@ -58,67 +79,91 @@ def setup_sentry(name, channel, dsn):
58
79
else :
59
80
site = "unknown"
60
81
61
- sentry_client = raven .Client (
82
+ # This integration allows sentry to catch logs from logging and process them
83
+ # By default, the 'event_level' is set to ERROR, we are defining it to WARNING
84
+ sentry_logging = LoggingIntegration (
85
+ level = logging .INFO , # Capture INFO and above as breadcrumbs
86
+ event_level = logging .WARNING , # Send WARNINGs as events
87
+ )
88
+ # sentry_sdk will automatically retrieve the 'extra' attribute from logs and
89
+ # add contained values as Additional Data on the dashboard of the Sentry issue
90
+ sentry_sdk .init (
62
91
dsn = dsn ,
63
- site = site ,
64
- name = name ,
92
+ integrations = [ sentry_logging ] ,
93
+ server_name = name ,
65
94
environment = channel ,
66
- release = raven . fetch_package_version (f"code-coverage-{ name } " ),
95
+ release = pkg_resources . get_distribution (f"code-coverage-{ name } " ). version ,
67
96
)
97
+ sentry_sdk .set_tag ("site" , site )
68
98
69
99
if task_id is not None :
70
100
# Add a Taskcluster task id when available
71
- # It will be shown in the Additional Data section on the dashboard
72
- sentry_client . context . merge ({ "extra" : {"task_id" : task_id } })
101
+ # It will be shown in a new section called Task on the dashboard
102
+ sentry_sdk . set_context ( "task" , {"task_id" : task_id })
73
103
74
- sentry_handler = raven .handlers .logbook .SentryHandler (
75
- sentry_client , level = logbook .WARNING , bubble = True
76
- )
77
- sentry_handler .push_application ()
104
+
105
+ class RenameAttrsProcessor (structlog .processors .KeyValueRenderer ):
106
+ """
107
+ Rename event_dict keys that will attempt to overwrite LogRecord common
108
+ attributes during structlog.stdlib.render_to_log_kwargs processing
109
+ """
110
+
111
+ def __call__ (self , logger , method_name , event_dict ):
112
+ to_rename = [
113
+ key
114
+ for key in event_dict
115
+ if key in sentry_sdk .integrations .logging .COMMON_RECORD_ATTRS
116
+ ]
117
+
118
+ for key in to_rename :
119
+ event_dict [f"{ key } _" ] = event_dict [key ]
120
+ event_dict .pop (key )
121
+
122
+ return event_dict
78
123
79
124
80
125
def init_logger (
81
126
project_name ,
82
127
channel = None ,
83
- level = logbook .INFO ,
128
+ level = logging .INFO ,
84
129
PAPERTRAIL_HOST = None ,
85
130
PAPERTRAIL_PORT = None ,
86
131
sentry_dsn = None ,
87
132
):
88
-
89
133
if not channel :
90
134
channel = os .environ .get ("APP_CHANNEL" )
91
135
92
- # Output logs on stderr, with color support on consoles
93
- fmt = "{record.time} [{record.level_name:<8}] {record.channel}: {record.message}"
94
- handler = logbook .more .ColorizedStderrHandler (level = level , format_string = fmt )
95
- handler .push_application ()
136
+ logging .basicConfig (
137
+ format = "%(asctime)s.%(msecs)06d [%(levelname)-8s] %(filename)s: %(message)s" ,
138
+ datefmt = "%Y-%m-%d %H:%M:%S" ,
139
+ stream = sys .stdout ,
140
+ level = level ,
141
+ )
96
142
97
143
# Log to papertrail
98
144
if channel and PAPERTRAIL_HOST and PAPERTRAIL_PORT :
99
145
setup_papertrail (project_name , channel , PAPERTRAIL_HOST , PAPERTRAIL_PORT )
100
146
101
- # Log to senty
147
+ # Log to sentry
102
148
if channel and sentry_dsn :
103
149
setup_sentry (project_name , channel , sentry_dsn )
104
150
105
- def logbook_factory (* args , ** kwargs ):
106
- # Logger given to structlog
107
- logbook .compat .redirect_logging ()
108
- return logbook .Logger (level = level , * args , ** kwargs )
109
-
110
- # Setup structlog over logbook, with args list at the end
151
+ # Setup structlog
111
152
processors = [
112
153
structlog .stdlib .PositionalArgumentsFormatter (),
113
154
structlog .processors .StackInfoRenderer (),
114
155
structlog .processors .format_exc_info ,
115
- UnstructuredRenderer (),
156
+ RenameAttrsProcessor (),
157
+ # Transpose the 'event_dict' from structlog into keyword arguments for logging.log
158
+ # E.g.: 'event' become 'msg' and, at the end, all remaining values from 'event_dict'
159
+ # are added as 'extra'
160
+ structlog .stdlib .render_to_log_kwargs ,
116
161
]
117
162
118
163
structlog .configure (
119
- context_class = structlog .threadlocal .wrap_dict (dict ),
120
164
processors = processors ,
121
- logger_factory = logbook_factory ,
165
+ context_class = structlog .threadlocal .wrap_dict (dict ),
166
+ logger_factory = structlog .stdlib .LoggerFactory (),
122
167
wrapper_class = structlog .stdlib .BoundLogger ,
123
168
cache_logger_on_first_use = True ,
124
169
)
0 commit comments