본문 바로가기
소프트웨어 마에스트로/BackEnd(Django)

[Django] Sentry 로깅 붙이기 + 모든 뷰에 붙이기

by alpakaka 2024. 9. 30.

어제 못했던 user tracking 을 추가적으로 붙여볼 예정이다.

일단 정보가 부족한 것 같으므로 붙여보면 좋을 듯 보인다.

https://docs.sentry.io/platforms/python/enriching-events/identify-user/

 

Users | Sentry for Python

Learn how to configure the SDK to capture the user and gain critical pieces of information that construct a unique identity in Sentry.

docs.sentry.io

ㅅ..성공했다.

오... 일단 저렇게만 넣어서 어떤 유저인지 아는 게 좋을 듯 하다.

코드는 다음과 같이 수정했다.

이런식으로 유저를 찾은 순간부터 set_user 를 통해 유저를 설정하도록 바꾸니까 위와 같이 해결할 수 있었다.

생각해보니까 아래의 코드에서 set_user 를 애초에 할 수가 없는 것이었다... 

user를 식별할 코드가 없어서 에러가 발생하는 건데 저기서 어떻게.. 

일단 디바이스명, url 정도는 나오니까 일단 이걸로 어떻게든 해결할 수 있지 않을까하고 생각해보고 있다.

 

이제 넣는 방법은 다 알았으니 모든 뷰에 넣어주면 된다!

이거 작성하다보니까 느낀건데 백엔드 코드에 try 가 들어가야하는데도 안들어간 부분이 너무 많다는 것을 알 수 있었다...ㅎㅎㅎ

전부 고쳐봅니다...

아 그리고 무작정 capture exception 이라고 하니까 너무 뜬금없는 느낌이 들어서 sentry_sdk.capture_exception으로 표시하는게 더 자연스러워보였다. 그래서 이런 방식으로 사용해보려고 한다!

최종적으로 다음과 같이 코드가 작성되었다.

class GoogleLogin(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        token = request.data.get("token")
        device_token = request.data.get("device_token")
        if not device_token or not token:
            sentry_sdk.capture_exception(LoginException())
            raise LoginException()
        try:
            idinfo = id_token.verify_oauth2_token(
                token, requests.Request(), audience=GOOGLE_CLIENT_ID
            )
            if "accounts.google.com" in idinfo["iss"]:
                email = idinfo["email"]
                user = User.get_or_create_user(email)
                FCMDevice.objects.get_or_create(
                    user=user, registration_id=device_token
                )
                refresh = CustomRefreshToken.for_user(user, device_token)
                return Response(
                    {
                        "refresh": str(refresh),
                        "access": str(refresh.access_token),
                    },
                    status=status.HTTP_200_OK,
                )
        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
            )


class UserRetrieveView(APIView):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            sentry_sdk.set_user(
                {
                    "id": request.user.id,
                    "username": request.user.username,
                }
            )
            user = User.objects.get(username=request.user.username)
            serializer = UserSerializer(user)
            return Response(serializer.data, status=status.HTTP_200_OK)
        except User.DoesNotExist as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": "User not found"}, status=status.HTTP_404_NOT_FOUND
            )

        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": "An unexpected error occurred"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def patch(self, request):
        """
        입력 : is_subscribe (Boolean)
        """
        try:
            user = request.user
            user.is_subscribed = request.data.get("is_subscribed")
            user.save()
            serializer = UserSerializer(user)
            return Response(serializer.data, status=status.HTTP_200_OK)
        except User.DoesNotExist as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": "User not found"}, status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": "An unexpected error occurred"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

 

이런 식으로 작성해보려고 한다.

git에서 코드를 찾아보니까 다들 exception as e 로 처리하고 있길래 나도 이런식으로 처리하였다.

 

이제... 슬프게도.. todos 에 있는 코드를 다음과 같이 수정해야한ㄷ.ㅏ....

클래스만 6개 있는데..... 그럼에도 해야겠지..

 

그래도 틀은 대략 만들어 놨으니까 그대로 따라가면 생각보다... 일찍 끝나지 않을까 싶다.

 

전부 바꿨다...

코드의 줄은 .. 800줄을 향해가고 있다.. 더 짧게 정리할 수 있는 방법이 있을까... 너무 긴데 내가 썼는데 보기 힘들다...

그런데 다음과 같은 오류가 발생했다.

pytest 결과인데 두번째꺼는 아마 고쳤는데 머지를 안했나 싶고, 첫번째와 세번째는 당연한 에러가 떴다..

왜냐하면 지금까지 post 에서 user_id 를 데이터로 받다가 헤더에서 받는 방식으로 바꿨기 때문이다.

그러므로 테스트 코드가 잘못된 것이니...

테스트 코드를 고쳐야한다.

 

@pytest.mark.django_db
def test_create_todo_invalid_user_id(
    authenticated_client, create_category, date, content, order
):
    
    url = reverse("todos")
    data = {
        "user_id": 999,
        "start_date": date + timedelta(days=1),
        "end_date": date + timedelta(days=2),
        "content": content,
        "category_id": create_category.id,
        "order": order(0),
    }
    response = authenticated_client.post(url, data, format="json")
    assert response.status_code == 400

이렇게 작성된 내용인데 이젠 user_id 를 받지 않으므로 이걸 삭제하고,

view 내에서 request.user.id 로 받고 있는 상황인데 이걸 Mock 처리를 해줘야한다.

그래서 바뀐 사항은 다음과 같다.

근데 생각해보니까... 그냥 저 테스트를 지우면 될 것 같다.

이제 헤더에서 받게 되는데, 인증을 못 받으면 애초에 접근이 안되는 상황이고, 그러므로 이미 이 api 에 들어왔다면 user 가 식별이 가능한 상태인 것 같은데..

그러므로 이미 식별 가능한 유저니까 이런 상황은 없지 않을까..... 라고 생각하고 있는데 일단 팀장님께 물어봐야겠다.

 

일단 지우고 마저 수정을 해야할 듯 싶다.

일단 저 테스트를 수정한다면 밑의 코드를 참고하면 된다. 잠깐 만들어 놓은 테스트다.

@pytest.mark.django_db
@patch("your_app.views.User.objects.get")
def test_invalid_user(mock_get, authenticated_client):
    # Mock the User.objects.get method to raise User.DoesNotExist
    mock_get.side_effect = User.DoesNotExist

    url = reverse("user")  # URL name for the categoryView patch method
    data = {
        "is_subscribed": True,
    }
    response = authenticated_client.get(url, data, format="json")
    
    # Assert that the response status code is 404 (Not Found)
    assert response.status_code == 404
    assert response.data["error"] == "User not found"

이런식으로 바로 유저 없음 에러를 발생시키는 코드이다. 여기서 약간씩 바꾸려고했는데 흠.. 더이상 신경 안써도 괜찮을 듯 싶다.

 

일단 저기에서 두 개의 테스트는 삭제했다. 

나머지 테스트들은 끝났다.

 

그리고 쓰다가 언제부턴가 set_user 를 빼먹어서 이걸 넣어준다.

 

그리고 중간중간에 try, catch 가 부족한 부분들이 있어 채워주었다.

view..가 너무 큰 것 같다... app 으로 나눌걸그랬나...

어쨌든 일단 모든 테스트는 통과했고 sentry 를 봐보니 

 

어 음 .. 절망적이게도..

어째서 나타난 건지 모를 에러들이 많이 로그 되어 있었다.

음 코드에 문제가 있었구나...

의도치 않은 에러들이...

내일은 이 에러들을 해결해야 할 듯 싶다....

그래도 희망적인 건 deleted__at 만 이상한 것 같다....... 그러길 바라고 있다....

오호.... 그래도 열심히 했구나......... 비극은 아래와 같다..

충돌...났구나... 어흑흑 다 고쳤으니까 어쩔 수 없지...

 

 

내일 질문 사항 : isAuthenticated 설정이 되어있는데, 이거 user가 invalid 인지 테스트가 필요할까요?