File size: 5,048 Bytes
ef1ad9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# --- Library Imports ---
from typing import Union
from fastapi import Request
from fastapi.exceptions import RequestValidationError, HTTPException
from fastapi.exception_handlers import (
    request_validation_exception_handler as _request_validation_exception_handler,
)
from fastapi.responses import JSONResponse
from fastapi.responses import PlainTextResponse
from fastapi.responses import Response
from opencensus.ext.azure.trace_exporter import AzureExporter
from opencensus.trace.tracer import Tracer
from opencensus.trace.samplers import ProbabilitySampler
from opencensus.trace.span import SpanKind
from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES
import traceback
# ---

# --- User Imports ---
from app.config.env import env
from app.utils.app_logger.logger import logger
# ---

# --- Constants ---
OPENAI_TRACER = Tracer(exporter=AzureExporter(
    connection_string=env.APPLICATIONINSIGHTS_CONNECTION_STRING), sampler=ProbabilitySampler(1.0))
HTTP_URL = COMMON_ATTRIBUTES['HTTP_URL']
HTTP_STATUS_CODE = COMMON_ATTRIBUTES['HTTP_STATUS_CODE']
ERROR_NAME = COMMON_ATTRIBUTES['ERROR_NAME']
ERROR_MESSAGE = COMMON_ATTRIBUTES['ERROR_MESSAGE']
HTTP_METHOD = COMMON_ATTRIBUTES['HTTP_METHOD']
HTTP_PATH = COMMON_ATTRIBUTES['HTTP_PATH']
STACKTRACE = COMMON_ATTRIBUTES['STACKTRACE']
# ---


def add_trace_to_azure_appinsight(request: Request, exc: HTTPException):
    """

    This function adds required traces to azure app insight logs for proper identification and dashboarding

    """
    exception_traceback = traceback.format_exc(limit=2)

    with OPENAI_TRACER.span("main") as span:
        span.span_kind = SpanKind.SERVER
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=HTTP_STATUS_CODE,
            attribute_value=exc.status_code)
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=HTTP_URL,
            attribute_value=str(request.url))
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=ERROR_NAME,
            attribute_value=str(type(exc).__name__))
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=ERROR_MESSAGE,
            attribute_value=str(exc.detail))
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=HTTP_METHOD,
            attribute_value=str(request.method))
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=HTTP_PATH,
            attribute_value=str(request.url.path))
        OPENAI_TRACER.add_attribute_to_current_span(
            attribute_key=STACKTRACE,
            attribute_value=str(exception_traceback))


async def request_validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
    """

    This is a wrapper to the default RequestValidationException handler of FastAPI.

    This function will be called when client input is not valid.

    """
    logger.debug("Our custom request_validation_exception_handler was called")
    body = await request.body()
    query_params = request.query_params._dict  # pylint: disable=protected-access
    detail = {"errors": exc.errors(), "body": body.decode(),
              "query_params": query_params}
    logger.info(detail)
    return await _request_validation_exception_handler(request, exc)


async def http_exception_handler(request: Request, exc: HTTPException) -> Union[JSONResponse, Response]:
    """

    This is a wrapper to the default HTTPException handler of FastAPI.

    This function will be called when a HTTPException is explicitly raised.

    """
    exception_traceback = traceback.format_exc(limit=2)
    url = f"{request.url.path}?{request.query_params}" if request.query_params else request.url.path
    logger.exception({
        "status": exc.status_code,
        "message": exc.detail,
        "url": url,
        "method": request.method,
        "trace": exception_traceback,
    })
    add_trace_to_azure_appinsight(request, exc)
    return JSONResponse(
        status_code=exc.status_code,
        content={"status": exc.status_code,
                 "message": exc.detail, "success": False},
    )


async def unhandled_exception_handler(request: Request, exc: Exception) -> PlainTextResponse:
    """

    This middleware will log all unhandled exceptions.

    Unhandled exceptions are all exceptions that are not HTTPExceptions or RequestValidationErrors.

    """
    exception_traceback = traceback.format_exc(limit=2)
    url = f"{request.url.path}?{request.query_params}" if request.query_params else request.url.path
    logger.exception({
        "status": 500,
        "message": exc,
        "url": url,
        "method": request.method,
        "trace": exception_traceback,
    })
    add_trace_to_azure_appinsight(request, exc)
    return JSONResponse(
        status_code=500,
        content={"status": 500,
                 "message": str(exc), "success": False},
    )