airflow自定义邮件标题的实现

邮件发送函数

send_email_smtp函数

通过查看incubator-airflow/airflow/utils/email.py源码我们可以得出以下结论

  • email发送功能最终由send_email_smtp函数完成
  • send_email函数读取airflow的配置文件决定邮件的发送函数,默认是send_email_stmp函数
def send_email(to, subject, html_content, files=None, dryrun=False, cc=None, bcc=None, mime_subtype='mixed'):
    """
    Send email using backend specified in EMAIL_BACKEND.
    """
    path, attr = configuration.get('email', 'EMAIL_BACKEND').rsplit('.', 1)
    module = importlib.import_module(path)
    backend = getattr(module, attr)
    return backend(to, subject, html_content, files=files, dryrun=dryrun, cc=cc, bcc=bcc, mime_subtype=mime_subtype)

因此,airflow自定义邮件标题的实现就是在发送邮件时,能够使用“自定义的邮件标题”,即可以自定义subject的值。

实现方式

通过调研,有三种实现方式:

  1. 直接改变源码。可以修改其调用者models.py中的email_alert方法,这种方法最直观,也最简单实现,缺点是需要修改airflow的源码,适合于几乎不会更改并且修改源码较容易的场景。
  2. 添加controller。在send_email和send_email_smtp函数中间添加email_smtp_controller函数,此函数用于转发邮件的发送任务,此方法,可以添加多个自定义类似与send_email_smtp的函数,这种场景下,实现一个自定义的邮件发送者即可完成功能。这种方法,不需要修改airflow的源码,只需要简单修改配置文件,扩展性较强,相对第一种方法复杂度要高一些。适合于不修改源码,对扩展性有部分要求的场景。
  3. 对airflow进行adapter。通过封装airflow的来满足工作需求。此方法不需要修改airflow源码,需要额外维护airflow的adapter,复杂度高,但扩展性强。适合于有较多修改,并对扩展性要求较高的场景。

直接修改源码

这种方式比较简单,并且可在互联网上搜索到源码,此处就不再重复介绍。

添加controller

此方法的本质是,在send_email_smtp的某个调用层次改变subject的值。需要查看send_email_smtp的整个调用层次,通过源码分析,其调用层次如下:

调用层次

send_email_smtp函数的调用者涉及三个文件job.py,email_operator.py和models.py;其中send_email对send_email_smtp的调用通过airflow.cfg配置的。

email_backend = airflow.utils.email.send_email_smtp 

airflow报警邮件功能在models.py 中的email_alert函数中实现

    def email_alert(self, exception, is_retry=False):
        task = self.task
        title = "Airflow alert: {self}".format(**locals())
        exception = str(exception).replace('\n', '<br>')
        # For reporting purposes, we report based on 1-indexed,
        # not 0-indexed lists (i.e. Try 1 instead of
        # Try 0 for the first attempt).
        body = (
            "Try {try_number} out of {max_tries}<br>"
            "Exception:<br>{exception}<br>"
            "Log: <a href='{self.log_url}'>Link</a><br>"
            "Host: {self.hostname}<br>"
            "Log file: {self.log_filepath}<br>"
            "Mark success: <a href='{self.mark_success_url}'>Link</a><br>"
        ).format(try_number=self.try_number + 1, max_tries=self.max_tries + 1, **locals())
        send_email(task.email, title, body)

根据上面的调用层次,在不影响其它邮件调用的前提下,具体实现自定义报警邮件标题方法可采用:

  1. 复制send_email_smtp,使models.py与其它文件的调用分开
  2. 添加rpt_email_smtp_controller.py,做为控制器以选择合适的send_email_smtp
    第二种方法比第一种方法要高级一些,并且容易扩展,实现方式如下:
def send_email_smtp_controller(to, subject, html_content, files=None, dryrun=False, cc=None, bcc=None,
                               mime_subtype='mixed'):
    log = LoggingMixin().log

    custom_alerts = get_custom_alerters()
    email_context = EmailContext(subject, html_content)
    for custom_alert in custom_alerts:
        if custom_alert.sure_called_by_me(email_context):
            log.info("Sent an alert email by custom %s", custom_alert.__class__)
            custom_alert.send_email(to, subject, html_content, files, dryrun, cc, bcc, mime_subtype)
            return

    log.info("Sent an alert email by default %s", "send_email_smtp")
    send_email_smtp(to, subject, html_content, files, dryrun, cc, bcc, mime_subtype)

其中,get_custom_alerters是工厂函数,用于产生所有的alerters,每个alerter通过sure_called_by_me检查调用者来向和自己的处理范围,通过send_email自定义邮件的发送。

Airflow adapter

这种方法的实现原理,是基于python的导入操作的原理,python的倒入模块会进行3步处理,即:找到模块对应的文件、编译此文件、运行此文件;默认情况下,一个模块文件仅第一倒入时,执行上述3个步骤。再次导入此模块,不会执行上述步骤,转而重用已经被倒入到内存中的模块。
文件导入时,所有的名称赋值操作都会被执行,函数定义也算一种名称赋值操作,即函数名和相应的函数实现相绑定。
以下面的测试用例为例:
a.py 内容如下:

class TaskInstance:
    def test(self):
        print("a.TaskInstance.test()")
    def sum(self):
        print("a.TaskInstance.sum()")


t = TaskInstance()
t.test()

b.py 内容如下:

from a import *

def test(self):
    print("b.text()")

TaskInstance.test = test

TaskInstance().test()

b模块中定义了一个函数签名和a模块中成员函数完全相同的一个函数,此函数的目的就是为了覆盖a模块中的test函数。当执行python b.py之后其输出如下:

a.TaskInstance.test()
b.text()

由此,可证明a模块中的函数test被b模块中的函数test覆盖了。
同样,若想改变models.py中email_alert函数的作用,我们仍然可以添加一个覆盖函数,只需要保证我们新添加的覆盖函数在models.py中,email_alert之后即可。
这样,接下来就是通过什么方法,能够保证你所添加的功能模块能够在airflow原有功能模块之后!!
其中,可以实现覆盖的方式之一是,重写airflow的入口,在函数入口时,首先import你想覆盖的函数,之后,再导入覆盖函数覆盖之前导入的同名函数。

源码

https://github.com/ggchangan/custom_alert_email_subject

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容