187 строки
6.4 KiB
Python
187 строки
6.4 KiB
Python
#
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Sentry Integration"""
|
|
import logging
|
|
from functools import wraps
|
|
|
|
from airflow.configuration import conf
|
|
from airflow.utils.session import provide_session
|
|
from airflow.utils.state import State
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class DummySentry:
|
|
"""
|
|
Blank class for Sentry.
|
|
"""
|
|
|
|
@classmethod
|
|
def add_tagging(cls, task_instance):
|
|
"""
|
|
Blank function for tagging.
|
|
"""
|
|
|
|
@classmethod
|
|
def add_breadcrumbs(cls, task_instance, session=None):
|
|
"""
|
|
Blank function for breadcrumbs.
|
|
"""
|
|
|
|
@classmethod
|
|
def enrich_errors(cls, run):
|
|
"""
|
|
Blank function for formatting a TaskInstance._run_raw_task.
|
|
"""
|
|
return run
|
|
|
|
def flush(self):
|
|
"""
|
|
Blank function for flushing errors.
|
|
"""
|
|
|
|
|
|
Sentry: DummySentry = DummySentry()
|
|
if conf.getboolean("sentry", 'sentry_on', fallback=False):
|
|
import sentry_sdk
|
|
# Verify blinker installation
|
|
from blinker import signal # noqa: F401 pylint: disable=unused-import
|
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
|
from sentry_sdk.integrations.logging import ignore_logger
|
|
|
|
class ConfiguredSentry(DummySentry):
|
|
"""
|
|
Configure Sentry SDK.
|
|
"""
|
|
|
|
SCOPE_TAGS = frozenset(
|
|
("task_id", "dag_id", "execution_date", "operator", "try_number")
|
|
)
|
|
SCOPE_CRUMBS = frozenset(("task_id", "state", "operator", "duration"))
|
|
|
|
UNSUPPORTED_SENTRY_OPTIONS = frozenset(
|
|
("integrations", "in_app_include", "in_app_exclude", "ignore_errors",
|
|
"before_breadcrumb", "before_send", "transport")
|
|
)
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initialize the Sentry SDK.
|
|
"""
|
|
ignore_logger("airflow.task")
|
|
ignore_logger("airflow.jobs.backfill_job.BackfillJob")
|
|
executor_name = conf.get("core", "EXECUTOR")
|
|
|
|
sentry_flask = FlaskIntegration()
|
|
|
|
# LoggingIntegration is set by default.
|
|
integrations = [sentry_flask]
|
|
|
|
if executor_name == "CeleryExecutor":
|
|
from sentry_sdk.integrations.celery import CeleryIntegration
|
|
|
|
sentry_celery = CeleryIntegration()
|
|
integrations.append(sentry_celery)
|
|
|
|
dsn = None
|
|
sentry_config_opts = conf.getsection("sentry") or {}
|
|
if sentry_config_opts:
|
|
sentry_config_opts.pop("sentry_on")
|
|
old_way_dsn = sentry_config_opts.pop("sentry_dsn", None)
|
|
new_way_dsn = sentry_config_opts.pop("dsn", None)
|
|
# supported backward compability with old way dsn option
|
|
dsn = old_way_dsn or new_way_dsn
|
|
|
|
unsupported_options = self.UNSUPPORTED_SENTRY_OPTIONS.intersection(
|
|
sentry_config_opts.keys())
|
|
if unsupported_options:
|
|
log.warning(
|
|
"There are unsupported options in [sentry] section: %s",
|
|
", ".join(unsupported_options)
|
|
)
|
|
|
|
if dsn:
|
|
sentry_sdk.init(dsn=dsn, integrations=integrations, **sentry_config_opts)
|
|
else:
|
|
# Setting up Sentry using environment variables.
|
|
log.debug("Defaulting to SENTRY_DSN in environment.")
|
|
sentry_sdk.init(integrations=integrations, **sentry_config_opts)
|
|
|
|
def add_tagging(self, task_instance):
|
|
"""
|
|
Function to add tagging for a task_instance.
|
|
"""
|
|
task = task_instance.task
|
|
|
|
with sentry_sdk.configure_scope() as scope:
|
|
for tag_name in self.SCOPE_TAGS:
|
|
attribute = getattr(task_instance, tag_name)
|
|
if tag_name == "operator":
|
|
attribute = task.__class__.__name__
|
|
scope.set_tag(tag_name, attribute)
|
|
|
|
@provide_session
|
|
def add_breadcrumbs(self, task_instance, session=None):
|
|
"""
|
|
Function to add breadcrumbs inside of a task_instance.
|
|
"""
|
|
if session is None:
|
|
return
|
|
execution_date = task_instance.execution_date
|
|
task = task_instance.task
|
|
dag = task.dag
|
|
task_instances = dag.get_task_instances(
|
|
state={State.SUCCESS, State.FAILED},
|
|
end_date=execution_date,
|
|
start_date=execution_date,
|
|
session=session,
|
|
)
|
|
|
|
for ti in task_instances:
|
|
data = {}
|
|
for crumb_tag in self.SCOPE_CRUMBS:
|
|
data[crumb_tag] = getattr(ti, crumb_tag)
|
|
|
|
sentry_sdk.add_breadcrumb(category="completed_tasks", data=data, level="info")
|
|
|
|
def enrich_errors(self, func):
|
|
"""
|
|
Wrap TaskInstance._run_raw_task to support task specific tags and breadcrumbs.
|
|
"""
|
|
|
|
@wraps(func)
|
|
def wrapper(task_instance, *args, session=None, **kwargs):
|
|
# Wrapping the _run_raw_task function with push_scope to contain
|
|
# tags and breadcrumbs to a specific Task Instance
|
|
with sentry_sdk.push_scope():
|
|
try:
|
|
return func(task_instance, *args, session=session, **kwargs)
|
|
except Exception as e:
|
|
self.add_tagging(task_instance)
|
|
self.add_breadcrumbs(task_instance, session=session)
|
|
sentry_sdk.capture_exception(e)
|
|
raise
|
|
|
|
return wrapper
|
|
|
|
def flush(self):
|
|
sentry_sdk.flush()
|
|
|
|
Sentry = ConfiguredSentry()
|