incubator-airflow/airflow/sentry.py

170 строки
5.1 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
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"))
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 = conf.get("sentry", "sentry_dsn")
if dsn:
init(dsn=dsn, integrations=integrations)
else:
# Setting up Sentry using environment variables.
log.debug("Defaulting to SENTRY_DSN in environment.")
init(integrations=integrations)
def add_tagging(self, task_instance):
"""
Function to add tagging for a task_instance.
"""
task = task_instance.task
with 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)
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 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)
capture_exception(e)
raise
return wrapper
Sentry = DummySentry() # type: DummySentry
try:
# Verify blinker installation
from blinker import signal # noqa: F401 pylint: disable=unused-import
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk import (
push_scope,
configure_scope,
add_breadcrumb,
init,
capture_exception,
)
Sentry = ConfiguredSentry()
except ImportError as e:
log.debug("Could not configure Sentry: %s, using DummySentry instead.", e)