ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Sentry on premise 적용하기
    프로그래밍/opensource 2022. 4. 13. 11:22

    0. 시작하기

    현재 개발하고, 사용중인 앱의 성능을 측정할 수 있는 기능이 필요하였습니다. 

    여러 솔루션 서비스와 기능들을 검색 중, opensource인 sentry on premis를 적용 해보았습니다.

     

    총 3단계로 나누어서 profile을 합니다.

    • 서버 총응답시간
    • 컨트롤러 처리 시간
    • DB 쿼리시간


    1. ec2 환경 설치하기

    ec2환경에서 docker로 sentry를 실행시킵니다.

    ㄱ. Sentry 설치 시 요구사항 (2022-04-10일 기준)

    • Docker 19.03.6+
    • Compose 1.28.0+
    • 4 CPU Cores
    • 8 GB RAM
    • 20 GB Free Disk Space

     

    ㄴ. ec2 linux sentry 실행

    //yum 업데이트
    sudo yum update
    
    //도커 설치
    sudo yum install docker
    
    //도커 실행
    sudo service docker start
    
    //docker-compose 설치
    sudo curl -L "https://github.com/docker/compose/releases/download/1.28.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    
    //docker-compose 링크 설정
    sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
    
    //실행권한주기
    sudo chmod +x /usr/local/bin/docker-compose
    
    //git 설치
    sudo yum install git
    
    //폴더 만들기 => /data/sentry
    
    //git clone
    git clone https://github.com/getsentry/self-hosted.git
    
    //sentry 설치
    sudo ./install.sh
    
    //sentry 설치 후 docker-compose를 실행 시킨다
    sudo docker-compose up -d
    
    //현재 컨테이너가 떠져 있는 리스트를 확인한다.
    sudo docker ps -a

     

    ㄷ. sudo docker ps -a를 입력하면 9000포트로 호출을 할 수 있습니다. 

     

    ㄹ. sudo docker ps -a를 입력하면 9000포트로 호출을 할 수 있습니다. 

    • 정상 연결이 되는 지 확인 하기 위해, 다른 ip에서 해당 ip로 호출 해 봅니다.                                              wget xxx.xx.xx.xxx:9000

     정상적으로 호출이 되는 것을 볼 수 있습니다. 실제 접속 화면은 아래와 같습니다.


    2. 앱과 sentry 연결하기

    ㄱ. Sentry With Express

    npm install --save @sentry/node @sentry/tracing
    
    import express from "express";
    import * as Sentry from "@sentry/node";
    import * as Tracing from "@sentry/tracing";
    
    // or using CommonJS
    // const express = require('express');
    // const Sentry = require('@sentry/node');
    
    const app = express();
    
    Sentry.init({
      dsn: "<https://examplePublicKey@o0.ingest.sentry.io/0>", //하단 DSN 참조 
      integrations: [
         	new Sentry.Integrations.Http({ tracing: true }),
         	new Tracing.Integrations.Express({app,}),
    	new Tracing.Integrations.Mongo(), 
    	new Tracing.Integrations.Postgres(),
      ],
    
      // We recommend adjusting this value in production, or using tracesSampler
      // for finer control
      tracesSampleRate: 1.0,
    });
    
    // The request handler must be the first middleware on the app
    app.use(Sentry.Handlers.requestHandler() as express.RequestHandler);
    
    // All controllers should live here
    app.get("/", function rootHandler(req, res) {
      res.end("Hello world!");
    });
    
    // The error handler must be before any other error middleware and after all controllers
    app.use(Sentry.Handlers.errorHandler() as express.ErrorRequestHandler);
    
    // Optional fallthrough error handler
    app.use(function onError(err, req, res, next) {
      // The error id is attached to `res.sentry` to be returned
      // and optionally displayed to the user for support.
      res.statusCode = 500;
      res.end(res.sentry + "\\n");
    });
    • 오류가 발생하는 router를 생성하여 Sentry를 테스트 할 수 있습니다.
    app.get("/debug-sentry", function mainHandler(req, res) {
      throw new Error("My first Sentry error!");
    });
    

    ㄴ. DSN 설정

    • Settings → Projects → Client Keys(DSN)
    • {PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID}
    • Sentry.init({ dsn: "<https://public@apm.xxxxx.com/1>" });

    ㄷ. ENV 설정

    개발 서버와 운영서버가 나누어져있기 때문에, env설정을 통하여 구분 해 줍니다.

    APM_SENTRY_ENV=production

     

    ㄹ. Custom Instrumentation

       typescript의 데코레이터를 활용해서 측정할 영역을 지정하도록 하겠습니다.

    //profiler.ts
    
    import express from 'express'
    import * as Sentry from "@sentry/node";
    import { Span } from "@sentry/types/types/span";
    
    interface Params {
        op: string,
        description: string,
    }
    
    export function captureTransaction(params:Params){
        return function (target: any, key: string, desc: PropertyDescriptor): void {
            const method = desc.value;
            desc.value = async function (req: express.Request, res: express.Response, next: express.NextFunction) {
                try {
                    const transaction = Sentry.getCurrentHub().getScope()?.getTransaction();
                    let span: Span | undefined;
                    if (transaction) {
                        span = transaction.startChild({
                            op: params.op,
                            description: params.description,
                        });
                    }
    
                    // Do something
                    const result = await method(req, res, next) as express.Response;
    
                    if (transaction && span) {
                        span.finish();
                    }
                    return result;
                } catch (error) {
                    console.error(error);
                    next(error);
                }
            }
        }
    }

    만든 함수를 annotation형식으로 사용합니다.

    import express from 'express'
    import { captureTransaction } from '../../util/profiler'
    
    class AchievementController {
      @captureTransaction({op: "controller", description: "getMyAchievement"})
      static async getMyAchievement (req: express.Request, res: express.Response, next: express.NextFunction) {
        let errors = validationResult(req);
        if (!errors.isEmpty()) {
          console.error(errors.array());
          return res.status(400).json({ resCode: resCode.InvalidParameter });
        }
    
        try {
          const result = await ChildAchievementV5.findAll({
            where: {
              childId: res.locals.childId,
            },
            include: [{
              model: AchievementV5,
              as: 'Achievement'
            }],
            order: [['mainAchieveOrder', 'ASC']]
          });
    
          return res.json({ resCode: resCode.OK, myAchievements: result });
        } catch (e) {
          console.log(e);
          return res.status(500).json({ resCode: resCode.RetryLater });
        }
      }
    }

    4. 마무리

    아래 사진처럼, 데코레이터로 걸어 놓은 지점의 처리 속도를 파악할 수 있습니다.


    5. 참조

    https://develop.sentry.dev/self-hosted/

     

    Self-Hosted Sentry

    In addition to making its source code available publicly, Sentry offers and maintains a minimal setup that works out-of-the-box for simple use cases. This repos

    develop.sentry.dev

    https://github.com/getsentry/self-hosted

Designed by Tistory.