선언적 에러처리 : Vue ErrorBoundary 구현 (feat : Sentry를 곁들인...)

KJH·2023년 7월 26일
1
post-thumbnail

ErrorBoundary가 뭘까

에러 바운더리는 선언적으로 에러를 처리할 수 있게 컴포넌트 형태로 사용되는 에러 헨들링 방법 중 하나입니다.

상세 내용 참고 👉 리액트18 공식 문서 에러 바운더리

vue 기반의 서비스에서 저 좋은 에러 헨들링 기법을 적용하고 싶다는 강렬한 생각이 들었습니다...🔥

Vue에서 ErrorBoundary 구현하기

먼저 Vue에는 다행이도 에러를 감지해주는 라이프사이클 함수가 존재합니다.
이를 적극적으로 이용해 리액트에서와 같이 컴포넌트 형태로 구현이 가능해집니다.

Vue2 : errorCaptured 이용하기

먼저 ErrorBoundary와 같은 프로바이더 개념의 컴포넌트를 구현해봅니다.

ErrorBoundary.vue

<script>
export default {
  name: "ErrorBoundary",
  props: {
    fallback: {
      type: Object
    }
  },
  data() {
    return {
      hasError: false
    };
  },
  errorCaptured(error,compInst,errorInfo) {
    this.hasError = true;
    console.log("error: ", error);
    console.log("compInst: ", compInst);
    console.log("errorInfo: ", errorInfo);
  },
  render(createElement) {
    return this.hasError
      ? createElement(this.fallback)
      : this.$slots.default[0];
  }
};
</script>

이제는 해당 에러바운더리에 감지되었을때 리턴해줄 fallback ui를 구현해봅니다.

ErrorFallBack.vue

<template>
  <div id="error-page">
    <p class="text">서비스를 불러오는데 실패했습니다.</p>
      <p class="text">새로고침 버튼을 눌러주세요.</p>
      <button class="button" @click="refreshPage()">새로고침 하기</button>
  </div>
</template>

<script>
export default {
  methods: {
    refreshPage(){
      window.location.reload();
    },
  },
};
</script>

<style scoped>
... 원하는 스타일
</style>

해당 구현된 내용을 vue 앱의 최상단인 App.vue에 적용해봅니다.

App.vue에 ErrorBoundary 적용하기

<template>
<ErrorBoundary>
	<div>
    	...
    </div>
</ErrorBoundary>
</template>

<script>
import ErrorBoundary from './~~/ErrorBoundary.vue'
import ErrorFallBack from './~~/ErrorFallBack.vue'

export default {
  components: {
    ErrorBoundary,
    ErrorFallBack
  },
  
  computed: {
    fallbackUI() {
      return ErrorFallBack;
    },
  }
</script>

위와 같이 적용하면 ErrorBoundary 하위에서 발생하는 에러를 감지하여 fallbackUI를 보여주고 앱이 멈추어 작동하지 않는 모습을 보여주지 않을 수 있습니다.

Vue3 : onErrorCaptured 이용하기

위와 마찬가지의 순서로 구현해봅니다.

ErrorBoundary.vue

<template>
  <Error v-if="isError"/>
  <slot v-else/>
</template>

<script setup lang="ts">
import {
  onErrorCaptured,
  ref,
} from 'vue';
import Error from './Error.vue';

const isError = ref(false);

onErrorCaptured(() => {
  isError.value = true;
});
</script>

ErrorFallBack.vue

<template>
  <div id="error-page">
    <p class="text">
      서비스를 불러오는데 실패했습니다.
    </p>
    <p class="text">
      새로고침 버튼을 눌러주세요.
    </p>
    <button
      class="button"
      @click="refreshPage()"
    >
      새로고침 하기
    </button>
  </div>
</template>

<script setup lang="ts">

const refreshPage = () => {
  window.location.reload();
};

</script>

<style lang="scss" scoped>
... 원하는 스타일
</style>

App.vue에 ErrorBoundary 적용하기

<template>
  <ErrorProvider>
    <Suspense>
      <router-view/>
    </Suspense>
  </ErrorProvider>
</template>

위처럼 적용하면 위에서 언급했듯 오류 발생시 원하는 오류화면을 보여줄 수 있게 됩니다.

Sentry를 곁들이자...

오류가 났을때 왜 오류가 났는지 추적하려고 로컬에서 켜서 디버깅하는 경험 많았을 것입니다.
sentry를 통해서 프론트 페이지에서 발생하는 오류를 수집하고 미리 어떠 오류인지 보고
로컬에서 접근하여 수정할 수 있어 디버깅시간을 단축시켜줄 수 있습니다.

Sentry가 뭔데...?

Sentry는 실시간 로그 취합 및 분석 도구이자 모니터링 플랫폼입니다.
참조 👉 Sentry 공홈

Sentry를 이용해 오류를 수집하고 분석할 수 있어 ErrorBoundary와 같이 에러 헨들링에 녹이면 아주 좋겠다는 생각이 들어 적용해보았습니다.

바로 적용해봅시다.

Vue 프로젝트 내 main.js에 적용하기

Sentry.init({
    Vue,
    dsn: "본인 센트리 프로젝트에서 제공하는 url",
    initialScope: (scope) => {
      // 객체 형태로 센트리 내 태그를 선택적으로 커스텀하여 넣어줄 수 있습니다.
      const customInfo = {
		key : value
      }
      scope.setTags(customInfo);
      return scope;
    },
  })

Axios Error에 Sentry 추가하기

위 설정만 가지고는 네트워크에서 발생하는 오류를 상세히 모니터링하기 어렵기 때문에
axios의 interceptor를 사용하여 추가해줍니다.

Axios interceptor 추가 코드


import * as Sentry from "@sentry/vue";
import axios from "axios";

axios.interceptors.response.use(
  async (response) => {
    /*
          http status가 200인 경우
          응답 성공 직전 호출됩니다.
          .then() 으로 이어집니다.
      */

    return response;
  },
  async (error) => {
    /*
        http status가 200이 아닌 경우
        응답 에러 직전 호출됩니다.
        .catch() 으로 이어집니다.
    */

    if(error.response){

      const {data, status} = error.response
      const {method, url} = error.config
  
      // 이슈 하위 문서 내용에 정보 추가
      Sentry.setContext('Api Response Detail', {
        status,
        data
      });
		
      // 커스텀 태그 형성
      Sentry.withScope((scope)=>{
        scope.setTag('type', 'api')
        scope.setTag('api-status', status || 'no value')
        scope.setTag('api-data', data ? JSON.stringify(data) : 'no value')

        // 이슈의 레벨을 설정
        scope.setLevel('error');

        // 이슈를 그룹화 시켜줌
        scope.setFingerprint([method, status, url])

        // 센트리로 오류 전송
        Sentry.captureException(new Error('API Internal Server Error'))
      })
    } else {
      const {method, url, params, data, headers} = error.config
	
      // 이슈 하위 문서 내용에 정보 추가
      Sentry.setContext('Api Request Detail', {
        message:'네크워크 요청은 갔으나 응답이 오지 않음',
        method,
        url,
        params,
        data,
        headers
      });
	
      // 커스텀 태그 형성
      Sentry.withScope((scope)=>{
        scope.setTag('type', 'api')
        scope.setTag('api-method', method || 'no value')
        scope.setTag('api-url', url || 'no value')
        scope.setTag('api-params', params ? JSON.stringify(params) : 'no value')
        scope.setTag('api-data', data ? JSON.stringify(data) : 'no value')
        scope.setTag('api-headers', headers ? JSON.stringify(headers) : 'no value')
		
        // 이슈의 레벨을 설정
        scope.setLevel('error');

        // 이슈를 그룹화 시켜줌
        scope.setFingerprint([method, url])

        // 센트리로 오류 전송
        Sentry.captureException(new Error('API Not Found Error'))
      })
    }

    return await Promise.reject(error);
  }
);

export default axios

이로써 Sentry에서 서비스 내 js 오류 및 네트워크 오류를 전부 추적할 수 있게 되었습니다.

행복한 디버깅을 할 수 있으시겠군요..! 😇

참조

카카오 세션 : https://if.kakao.com/2022/session/84
리액트 18 에러 바운더리 : https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
뷰2 에러캡쳐 : https://vuejs.org/api/options-lifecycle.html#errorcaptured
뷰3 에러캡쳐 : https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured
뷰 에러 바운더리 코드 참고 : https://engineer-mole.tistory.com/369
센트리 sdk 참고 : https://docs.sentry.io/platforms/javascript/guides/vue/?original_referrer=https%3A%2F%2Fwww.google.com%2F

profile
Front-End Developer

0개의 댓글