Extending Python Logger for mailing Exceptions

Logging is most crucial part of any application or process you create, as it helps you debug or keep track of whats going on. Logging is a very vast topic in itself but super useful when you want to perform some side effects or redirect your output to some other services or perform some side computations etc.

Highly configurable by design, we can add extend functionalities of existing loggers, using custom Handlers.

We will try to extend functionality of existing logger to e-mail exceptions occurred during code execution. Let us create a python Logger using built-in library logging.

<span>import</span> <span>logging</span>
<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
<span>import</span> <span>logging</span>
<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
import logging logger: logging.Logger = logging.getLogger()

Enter fullscreen mode Exit fullscreen mode

Above code will return a root logger instance, since we are not mentioning any name explicitly. If you want a named logger instance, you can pass name to getLogger function.

Let us now create a custom Handler to handle records and perform some side effect. We can inherit from logging.Handler abstract base class to create our custom Handler.

logging.Handler base class provides multiple hooks that you can override. We will override emit hook for our requirement.

<span>import</span> <span>logging</span>
<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>
<span>def</span> <span>emit</span><span>(</span><span>self</span><span>,</span> <span>record</span><span>:</span> <span>logging</span><span>.</span><span>LogRecord</span><span>)</span> <span>-></span> <span>None</span><span>:</span>
<span>if</span> <span>record</span><span>.</span><span>exc_info</span><span>:</span>
<span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>record</span><span>.</span><span>exc_info</span><span>))</span>
<span>else</span><span>:</span>
<span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>sys</span><span>.</span><span>exc_info</span><span>()))</span>
<span>self</span><span>.</span><span>_send_mail</span><span>(</span><span>exception</span><span>)</span>
<span>import</span> <span>logging</span>
<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>

    <span>def</span> <span>emit</span><span>(</span><span>self</span><span>,</span> <span>record</span><span>:</span> <span>logging</span><span>.</span><span>LogRecord</span><span>)</span> <span>-></span> <span>None</span><span>:</span>
        <span>if</span> <span>record</span><span>.</span><span>exc_info</span><span>:</span>
            <span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>record</span><span>.</span><span>exc_info</span><span>))</span>
        <span>else</span><span>:</span>
            <span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>sys</span><span>.</span><span>exc_info</span><span>()))</span>
        <span>self</span><span>.</span><span>_send_mail</span><span>(</span><span>exception</span><span>)</span>
import logging class MailHandler(logging.Handler): def emit(self, record: logging.LogRecord) -> None: if record.exc_info: exception = "".join(traceback.format_exception(*record.exc_info)) else: exception = "".join(traceback.format_exception(*sys.exc_info())) self._send_mail(exception)

Enter fullscreen mode Exit fullscreen mode

We have added set of statements to intercept exception from the record and create formatted stack trace.

emit hook will receive logging.LogRecord which will contain all the details regarding the record like message, timestamp, line no, exception info etc. We have also added a instance method _send_mail to send formatted stack trace to the user.

Let us now complete _send_mail function. We will use AWS SES for sending e-mail. You may also use smtp as an alternative.

<span>import</span> <span>boto3</span>
<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>
<span>def</span> <span>_send_mail</span><span>(</span><span>self</span><span>,</span> <span>message</span><span>):</span>
<span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>'ses'</span><span>)</span>
<span>ses_arn</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_ARN'</span><span>)</span>
<span>source</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_SOURCE'</span><span>)</span>
<span>html</span> <span>=</span> <span>f</span><span>""" <p>Exception occurred while execution. Please check. </p> <pre></span><span>{</span><span>message</span><span>}</span><span></pre> """</span>
<span>self</span><span>.</span><span>client</span><span>.</span><span>send_email</span><span>(</span>
<span>Source</span><span>=</span><span>source</span><span>,</span>
<span>Destination</span><span>=</span><span>{</span>
<span>'ToAddresses'</span><span>:</span> <span>[</span>
<span>'foobar@gmail.com'</span><span>,</span>
<span>],</span>
<span>},</span>
<span>Message</span><span>=</span><span>{</span>
<span>'Subject'</span><span>:</span> <span>{</span>
<span>'Data'</span><span>:</span> <span>'Exception occurred while executing Lambda. Please check.'</span><span>,</span>
<span>},</span>
<span>'Body'</span><span>:</span> <span>{</span>
<span>'Html'</span><span>:</span> <span>{</span>
<span>'Data'</span><span>:</span> <span>html</span>
<span>}</span>
<span>}</span>
<span>},</span>
<span>SourceArn</span><span>=</span><span>ses_arn</span>
<span>)</span>
<span>import</span> <span>boto3</span>

<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>

    <span>def</span> <span>_send_mail</span><span>(</span><span>self</span><span>,</span> <span>message</span><span>):</span>
        <span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>'ses'</span><span>)</span>
        <span>ses_arn</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_ARN'</span><span>)</span>
        <span>source</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_SOURCE'</span><span>)</span>
        <span>html</span> <span>=</span> <span>f</span><span>""" <p>Exception occurred while execution. Please check. </p> <pre></span><span>{</span><span>message</span><span>}</span><span></pre> """</span>
        <span>self</span><span>.</span><span>client</span><span>.</span><span>send_email</span><span>(</span>
            <span>Source</span><span>=</span><span>source</span><span>,</span>
            <span>Destination</span><span>=</span><span>{</span>
                <span>'ToAddresses'</span><span>:</span> <span>[</span>
                    <span>'foobar@gmail.com'</span><span>,</span>
                <span>],</span>
            <span>},</span>
            <span>Message</span><span>=</span><span>{</span>
                <span>'Subject'</span><span>:</span> <span>{</span>
                    <span>'Data'</span><span>:</span> <span>'Exception occurred while executing Lambda. Please check.'</span><span>,</span>
                <span>},</span>
                <span>'Body'</span><span>:</span> <span>{</span>
                    <span>'Html'</span><span>:</span> <span>{</span>
                        <span>'Data'</span><span>:</span> <span>html</span>
                    <span>}</span>
                <span>}</span>
            <span>},</span>
            <span>SourceArn</span><span>=</span><span>ses_arn</span>
        <span>)</span>
import boto3 class MailHandler(logging.Handler): def _send_mail(self, message): self.client = boto3.client('ses') ses_arn = os.getenv('SES_ARN') source = os.getenv('SES_SOURCE') html = f""" <p>Exception occurred while execution. Please check. </p> <pre>{message}</pre> """ self.client.send_email( Source=source, Destination={ 'ToAddresses': [ 'foobar@gmail.com', ], }, Message={ 'Subject': { 'Data': 'Exception occurred while executing Lambda. Please check.', }, 'Body': { 'Html': { 'Data': html } } }, SourceArn=ses_arn )

Enter fullscreen mode Exit fullscreen mode

We are using boto3 library for connecting to SES and send e-mail.

I am reading ses_arn and source from environment variables. These will be required to send e-mail to the destination address using your configured SES record.

We are done with creating our custom handler. Let us register this with our logger instance.

<span>import</span> <span>logging</span>
<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
<span>handler</span> <span>=</span> <span>MailHandler</span><span>()</span>
<span>handler</span><span>.</span><span>setLevel</span><span>(</span><span>logging</span><span>.</span><span>ERROR</span><span>)</span>
<span>logger</span><span>.</span><span>logger_</span><span>.</span><span>addHandler</span><span>(</span><span>handler</span><span>)</span>
<span>import</span> <span>logging</span>
<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
<span>handler</span> <span>=</span> <span>MailHandler</span><span>()</span>
<span>handler</span><span>.</span><span>setLevel</span><span>(</span><span>logging</span><span>.</span><span>ERROR</span><span>)</span>
<span>logger</span><span>.</span><span>logger_</span><span>.</span><span>addHandler</span><span>(</span><span>handler</span><span>)</span>
import logging logger: logging.Logger = logging.getLogger() handler = MailHandler() handler.setLevel(logging.ERROR) logger.logger_.addHandler(handler)

Enter fullscreen mode Exit fullscreen mode

We have registered our custom handler with our logger instance. It will be only activated on error record type as we have set level to logging.ERROR. You may now test your custom handler as below.

<span>logger</span><span>.</span><span>error</span><span>(</span><span>Exception</span><span>(</span><span>"Fake Exception"</span><span>))</span>
<span>logger</span><span>.</span><span>error</span><span>(</span><span>Exception</span><span>(</span><span>"Fake Exception"</span><span>))</span>
logger.error(Exception("Fake Exception"))

Enter fullscreen mode Exit fullscreen mode

You should receive an email, with exception and stack trace.

Below is the complete code for custom handler.

<span>import</span> <span>boto3</span>
<span>import</span> <span>logging</span>
<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>
<span>def</span> <span>emit</span><span>(</span><span>self</span><span>,</span> <span>record</span><span>:</span> <span>logging</span><span>.</span><span>LogRecord</span><span>)</span> <span>-></span> <span>None</span><span>:</span>
<span>if</span> <span>record</span><span>.</span><span>exc_info</span><span>:</span>
<span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>record</span><span>.</span><span>exc_info</span><span>))</span>
<span>else</span><span>:</span>
<span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>sys</span><span>.</span><span>exc_info</span><span>()))</span>
<span>self</span><span>.</span><span>_send_mail</span><span>(</span><span>exception</span><span>)</span>
<span>def</span> <span>_send_mail</span><span>(</span><span>self</span><span>,</span> <span>message</span><span>):</span>
<span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>'ses'</span><span>)</span>
<span>ses_arn</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_ARN'</span><span>)</span>
<span>source</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_SOURCE'</span><span>)</span>
<span>html</span> <span>=</span> <span>f</span><span>""" <p>Exception occurred while execution. Please check. </p> <pre></span><span>{</span><span>message</span><span>}</span><span></pre> """</span>
<span>self</span><span>.</span><span>client</span><span>.</span><span>send_email</span><span>(</span>
<span>Source</span><span>=</span><span>source</span><span>,</span>
<span>Destination</span><span>=</span><span>{</span>
<span>'ToAddresses'</span><span>:</span> <span>[</span>
<span>'foobar@gmail.com'</span><span>,</span>
<span>],</span>
<span>},</span>
<span>Message</span><span>=</span><span>{</span>
<span>'Subject'</span><span>:</span> <span>{</span>
<span>'Data'</span><span>:</span> <span>'Exception occurred while executing Lambda. Please check.'</span><span>,</span>
<span>},</span>
<span>'Body'</span><span>:</span> <span>{</span>
<span>'Html'</span><span>:</span> <span>{</span>
<span>'Data'</span><span>:</span> <span>html</span>
<span>}</span>
<span>}</span>
<span>},</span>
<span>SourceArn</span><span>=</span><span>ses_arn</span>
<span>)</span>
<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
<span>handler</span> <span>=</span> <span>MailHandler</span><span>()</span>
<span>handler</span><span>.</span><span>setLevel</span><span>(</span><span>logging</span><span>.</span><span>ERROR</span><span>)</span>
<span>logger</span><span>.</span><span>logger_</span><span>.</span><span>addHandler</span><span>(</span><span>handler</span><span>)</span>
<span>#raising exception </span><span>try</span><span>:</span>
<span>raise</span> <span>Exception</span><span>(</span><span>"Fake Exception"</span><span>)</span>
<span>except</span> <span>Exception</span> <span>as</span> <span>e</span><span>:</span>
<span>logger</span><span>.</span><span>error</span><span>(</span><span>e</span><span>,</span> <span>exc_info</span><span>=</span><span>True</span><span>)</span>
<span>import</span> <span>boto3</span>
<span>import</span> <span>logging</span>

<span>class</span> <span>MailHandler</span><span>(</span><span>logging</span><span>.</span><span>Handler</span><span>):</span>
    <span>def</span> <span>emit</span><span>(</span><span>self</span><span>,</span> <span>record</span><span>:</span> <span>logging</span><span>.</span><span>LogRecord</span><span>)</span> <span>-></span> <span>None</span><span>:</span>
        <span>if</span> <span>record</span><span>.</span><span>exc_info</span><span>:</span>
            <span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>record</span><span>.</span><span>exc_info</span><span>))</span>
        <span>else</span><span>:</span>
            <span>exception</span> <span>=</span> <span>""</span><span>.</span><span>join</span><span>(</span><span>traceback</span><span>.</span><span>format_exception</span><span>(</span><span>*</span><span>sys</span><span>.</span><span>exc_info</span><span>()))</span>
        <span>self</span><span>.</span><span>_send_mail</span><span>(</span><span>exception</span><span>)</span>

    <span>def</span> <span>_send_mail</span><span>(</span><span>self</span><span>,</span> <span>message</span><span>):</span>
        <span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>'ses'</span><span>)</span>
        <span>ses_arn</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_ARN'</span><span>)</span>
        <span>source</span> <span>=</span> <span>os</span><span>.</span><span>getenv</span><span>(</span><span>'SES_SOURCE'</span><span>)</span>
        <span>html</span> <span>=</span> <span>f</span><span>""" <p>Exception occurred while execution. Please check. </p> <pre></span><span>{</span><span>message</span><span>}</span><span></pre> """</span>
        <span>self</span><span>.</span><span>client</span><span>.</span><span>send_email</span><span>(</span>
            <span>Source</span><span>=</span><span>source</span><span>,</span>
            <span>Destination</span><span>=</span><span>{</span>
                <span>'ToAddresses'</span><span>:</span> <span>[</span>
                    <span>'foobar@gmail.com'</span><span>,</span>
                <span>],</span>
            <span>},</span>
            <span>Message</span><span>=</span><span>{</span>
                <span>'Subject'</span><span>:</span> <span>{</span>
                    <span>'Data'</span><span>:</span> <span>'Exception occurred while executing Lambda. Please check.'</span><span>,</span>
                <span>},</span>
                <span>'Body'</span><span>:</span> <span>{</span>
                    <span>'Html'</span><span>:</span> <span>{</span>
                        <span>'Data'</span><span>:</span> <span>html</span>
                    <span>}</span>
                <span>}</span>
            <span>},</span>
            <span>SourceArn</span><span>=</span><span>ses_arn</span>
        <span>)</span>

<span>logger</span><span>:</span> <span>logging</span><span>.</span><span>Logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>()</span>
<span>handler</span> <span>=</span> <span>MailHandler</span><span>()</span>
<span>handler</span><span>.</span><span>setLevel</span><span>(</span><span>logging</span><span>.</span><span>ERROR</span><span>)</span>
<span>logger</span><span>.</span><span>logger_</span><span>.</span><span>addHandler</span><span>(</span><span>handler</span><span>)</span>

<span>#raising exception </span><span>try</span><span>:</span>
    <span>raise</span> <span>Exception</span><span>(</span><span>"Fake Exception"</span><span>)</span>
<span>except</span> <span>Exception</span> <span>as</span> <span>e</span><span>:</span>
    <span>logger</span><span>.</span><span>error</span><span>(</span><span>e</span><span>,</span> <span>exc_info</span><span>=</span><span>True</span><span>)</span>
import boto3 import logging class MailHandler(logging.Handler): def emit(self, record: logging.LogRecord) -> None: if record.exc_info: exception = "".join(traceback.format_exception(*record.exc_info)) else: exception = "".join(traceback.format_exception(*sys.exc_info())) self._send_mail(exception) def _send_mail(self, message): self.client = boto3.client('ses') ses_arn = os.getenv('SES_ARN') source = os.getenv('SES_SOURCE') html = f""" <p>Exception occurred while execution. Please check. </p> <pre>{message}</pre> """ self.client.send_email( Source=source, Destination={ 'ToAddresses': [ 'foobar@gmail.com', ], }, Message={ 'Subject': { 'Data': 'Exception occurred while executing Lambda. Please check.', }, 'Body': { 'Html': { 'Data': html } } }, SourceArn=ses_arn ) logger: logging.Logger = logging.getLogger() handler = MailHandler() handler.setLevel(logging.ERROR) logger.logger_.addHandler(handler) #raising exception try: raise Exception("Fake Exception") except Exception as e: logger.error(e, exc_info=True)

Enter fullscreen mode Exit fullscreen mode

You can now customize logging handlers has per your requirements.

Happy Logging .

原文链接:Extending Python Logger for mailing Exceptions

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
Real dream is the other shore of reality.
真正的梦就是现实的彼岸
评论 抢沙发

请登录后发表评论

    暂无评论内容