33# License, v. 2.0. If a copy of the MPL was not distributed with this
44# file, You can obtain one at http://mozilla.org/MPL/2.0/.
55
6+ import logging
7+ import logging .handlers
68import os
9+ import sys
710
8- import logbook
9- import logbook .more
10- import raven
11- import raven .handlers .logbook
1211import structlog
1312
13+ import pkg_resources
14+ import sentry_sdk
15+ from sentry_sdk .integrations .logging import LoggingIntegration
1416
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
2946
3047
3148def setup_papertrail (project_name , channel , PAPERTRAIL_HOST , PAPERTRAIL_PORT ):
@@ -34,14 +51,18 @@ def setup_papertrail(project_name, channel, PAPERTRAIL_HOST, PAPERTRAIL_PORT):
3451 """
3552
3653 # Setup papertrail
37- papertrail = logbook .SyslogHandler (
38- application_name = f"code-coverage/{ channel } /{ project_name } " ,
54+ papertrail = logging .handlers .SysLogHandler (
3955 address = (PAPERTRAIL_HOST , int (PAPERTRAIL_PORT )),
40- level = logbook .INFO ,
41- format_string = "{record.time} {record.channel}: {record.message}" ,
42- bubble = True ,
4356 )
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 )
4566
4667
4768def setup_sentry (name , channel , dsn ):
@@ -58,67 +79,91 @@ def setup_sentry(name, channel, dsn):
5879 else :
5980 site = "unknown"
6081
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 (
6291 dsn = dsn ,
63- site = site ,
64- name = name ,
92+ integrations = [ sentry_logging ] ,
93+ server_name = name ,
6594 environment = channel ,
66- release = raven . fetch_package_version (f"code-coverage-{ name } " ),
95+ release = pkg_resources . get_distribution (f"code-coverage-{ name } " ). version ,
6796 )
97+ sentry_sdk .set_tag ("site" , site )
6898
6999 if task_id is not None :
70100 # 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 })
73103
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
78123
79124
80125def init_logger (
81126 project_name ,
82127 channel = None ,
83- level = logbook .INFO ,
128+ level = logging .INFO ,
84129 PAPERTRAIL_HOST = None ,
85130 PAPERTRAIL_PORT = None ,
86131 sentry_dsn = None ,
87132):
88-
89133 if not channel :
90134 channel = os .environ .get ("APP_CHANNEL" )
91135
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+ )
96142
97143 # Log to papertrail
98144 if channel and PAPERTRAIL_HOST and PAPERTRAIL_PORT :
99145 setup_papertrail (project_name , channel , PAPERTRAIL_HOST , PAPERTRAIL_PORT )
100146
101- # Log to senty
147+ # Log to sentry
102148 if channel and sentry_dsn :
103149 setup_sentry (project_name , channel , sentry_dsn )
104150
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
111152 processors = [
112153 structlog .stdlib .PositionalArgumentsFormatter (),
113154 structlog .processors .StackInfoRenderer (),
114155 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 ,
116161 ]
117162
118163 structlog .configure (
119- context_class = structlog .threadlocal .wrap_dict (dict ),
120164 processors = processors ,
121- logger_factory = logbook_factory ,
165+ context_class = structlog .threadlocal .wrap_dict (dict ),
166+ logger_factory = structlog .stdlib .LoggerFactory (),
122167 wrapper_class = structlog .stdlib .BoundLogger ,
123168 cache_logger_on_first_use = True ,
124169 )
0 commit comments