Skip to content

Commit 3247736

Browse files
committed
Use sentry_sdk instead of raven and improve Sentry grouping
Fixes #1521 by bringing in the changes from: - mozilla/code-review#1253 - mozilla/code-review#1276 - mozilla/code-review#1333 - mozilla/code-review#1365
1 parent 6a33349 commit 3247736

File tree

3 files changed

+97
-52
lines changed

3 files changed

+97
-52
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ repos:
9090
pass_filenames: false
9191
additional_dependencies:
9292
- types-requests==0.1.11
93+
- types-setuptools==67.6.0.0
9394
- types-pytz==2022.6.0.1
9495
- repo: meta
9596
hooks:

tools/code_coverage_tools/log.py

Lines changed: 95 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,46 @@
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
68
import os
9+
import sys
710

8-
import logbook
9-
import logbook.more
10-
import raven
11-
import raven.handlers.logbook
1211
import 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

3148
def 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

4768
def 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

80125
def 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
)

tools/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
aiohttp==3.8.4
22
async-timeout==4.0.2
3-
logbook==1.5.3
43
pytz==2022.7.1
5-
raven==6.10.0
4+
sentry-sdk==1.16.0
65
structlog==22.3.0
76
taskcluster==47.1.2

0 commit comments

Comments
 (0)