Node 강의/숙련

2-3 [게시판 프로젝트] 미들웨어

kagan-draca 2024. 9. 9. 01:29

1. 로그(Log) 미들웨어

 

로그 미들웨어(Log Middleware)는 클라이언트의 모든 요청 사항을

기록하여 서버의 상태를 모니터링하기 위한 미들웨어입니다.

 

로그 미들웨어는 클라이언트의 요청을 기록하여 어플리케이션을 모니터링하고

문제가 발생할 때 빠르게 진단할 수 있습니다.

 

또한, 로그 데이터는 사용자의 행동을 분석하는 등 데이터 분석 작업에도 활용할 수 있습니다.

 

규모가 큰 프로젝트를 진행하게 되면, 화면에 표시되는 모든 로그를 일일이 확인하는 것은 불가능에 가깝습니다.

이런 경우를 대비해 로그 기능을 지원하는 morgan, winston과 같은 라이브러리를 사용하거나

AWSCloudWatch, Datadog와 같은 외부 모니터링 솔루션 서비스를 이용해 로그를 수집하거나 관리할 수 있습니다.

 

→ 최근에는 Datadog과 같은 서비스를 이용해 로그 수집,

    로그 분석과 같은 서비스를 전문 지식 없이 빠르게 구현할 수 있습니다.

 

클라이언트의 요청을 터미널에 기록하는 간단한 로그 미들웨어를 구현할 예정입니다.

 

middlewares/log.middleware.js 파일을 생성 후 winston 라이브러리를 이용하여

로그 미들웨어를 구현해보도록 하겠습니다.

 

2. 로그 미들웨어 만들기

 

1 ) yarn을 이용해 패키지 받기

 

yarn add winston

# yarn을 이용해 winston을 설치합니다.

 

2) 로그 미들웨어 만들기

import winston from "winston";

const logger = winston.createLogger({
  level: "info", // 로그 레벨을 'info'로 설정합니다.
  format: winston.format.json(), // 로그 포맷을 JSON 형식으로 설정합니다.
  transports: [
    new winston.transports.Console(), // 로그를 콘솔에 출력합니다.
  ],
});

export default (req, res, next) => {
  // 클라이언트의 요청이 시작된 시간을 기록합니다.
  const start = new Date().getTime();

  // 응답이 완료되면 로그를 기록합니다.
  res.on("finish", () => {
    const duration = new Date().getTime() - start;
    logger.info(
      `Method : ${req.method}, URL : ${req.url}, Status : ${res.statusCode}, Duration : ${duration}ms`
    );
  });
  next();
};

 

3) app.js에 로그 미들웨어를 추가해줍니다.

import express from "express";
import cookieParser from "cookie-parser";
import UsersRouter from "../routes/users.router.js";
import LogMiddleware from "../middlewares/log.middleware.js";

const app = express();
const PORT = 3018;

app.use(LogMiddleware);
app.use(express.json());
app.use(cookieParser());

app.use("/api", [UsersRouter]);

app.listen(PORT, () => {
  console.log(PORT, "프트로 서버가 열렸습니다");
});

처럼

import LogMiddleware from "../middlewares/log.middleware.js";

을 import 받고,

 

app.use(LogMiddleware);

를 추가해줬니다.

 

이제 Insomnia에서 아무거나 실행시켜보면,

과 같이 level이 출력되고, 메시지와 어떤 메서드를 호출했고,

URL로는 무슨 주소를 사용했고, 서버가 클라이언트에게 반환한 상태,

동작에 걸린 시간이 출력되는걸 확인할 수 있습니다.

 

3. 에러처리 미들웨어 만들기

 

에러 처리 미들웨어(Error Handling Middleware)

Express.js에서 발생한 에러를 통합적으로 처리하기 위한 미들웨어 입니다.

 

1) 에러처리 미들웨어 만들기

 

middlewares/errorhanding.middleware.js 파일을 만들어줍니다.

 

export default (err, req, res, next) => {
  console.error(err);

  res.status(500).json({ errorMessage: "서버 내부 에러가 발생했습니다." });
};

 

간단한 에러처리 미들웨어를 구현해봤습니다.

 

이제 에러가 날 수 있는 부분에 추가해줍니다.

 

2) 회원가입 API 리팩토링 하기

UsersRouter.post("/sign-up", async (req, res, next) => {
  try {
    const { email, password, name, age, gender, profileImage } = req.body;
    const isExistUser = await prisma.Users.findFirst({
      where: {
        email,
      },
    });
    if (isExistUser)
      return res.status(409).json({ message: "이미 존재하는 이메일 입니다." });

    const hashedPassword = await bcrypt.hash(password, 10);

    // User 테이블에 값 넣기
    const user = await prisma.Users.create({
      data: {
        email,
        password: hashedPassword,
      },
    });

    // UserInfos 테이블에 값 넣기
    const userInfo = await prisma.UserInfos.create({
      data: {
        userId: user.userId,
        //생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
        name,
        age,
        gender: gender.toUpperCase(), //성별 대문자로 변환
        profileImage,
      },
    });
    return res.status(201).json({ massage: "회원가입에 성공했습니다" });
  } catch (err) {
    next(err);
  }
});

 

이런 식으로, 기존 내용 전체를 복사해

 

try{} 안에 넣어주고,

catch(err){} 안에 next(err)로 다음 미들웨어로 err를 보내줍니다.

 

(내용이 많아보이지만, 기존 코드를 복사해서 try 안에 넣어준 것 밖에 없습니다.)

 

3) app.js에 에러 처리 미들웨어를 추가해줍니다.

import express from "express";
import cookieParser from "cookie-parser";
import UsersRouter from "../routes/users.router.js";
import LogMiddleware from "../middlewares/log.middleware.js";
import ErrorHandingMiddleware from "../middlewares/errorhanding.middleware.js";

const app = express();
const PORT = 3018;

app.use(LogMiddleware);
app.use(express.json());
app.use(cookieParser());

app.use("/api", [UsersRouter]);
 
app.use(ErrorHandingMiddleware);
app.listen(PORT, () => {
  console.log(PORT, "프트로 서버가 열렸습니다");
});

 

위와 같이

import ErrorHandingMiddleware from "../middlewares/errorhanding.middleware.js";

import로 에러 처리 미들웨어를 가져오고,

app.use(ErrorHandingMiddleware);
app.listen(PORT, () => {
  console.log(PORT, "프트로 서버가 열렸습니다");
});

UserRouter 뒤에 에러처리 미들웨어를 use로 사용해줍니다.

(미들웨어는 위에서부터 아래로 실행되기 때문에 꼭 사용하고자 하는 위치에 둬야한다.)

(

  UserRouter의 회원가입에서 에러가 발생했을 경우 next(err)로 그 다음 미들웨어로  err를 보내게 되는데

  (다른 Router가 추가될 경우를 고려해 listen 위에 배치해서 Error가 ErrorHandingMiddleware에서 실행되게 한다.)

  이  err를 ErrorHandingMiddleware가 받게 만들어 에러를 출력하게 만들어줍니다.

)

 

Insomnia에서 에러를 고의로 발생시켜보면,

위와 같이 Client에게 응답이 발생한 것을 확인할 수 있습니다. 그리고 콘솔에서는

위와 같이 에러를 확인할 수 있습니다.

 

에러처리 미들웨어는 모든 에러를 관리하는 미들웨어, 서버 내부에서 발생한 에러를 상세하게

클라이언트에게 제공한다면 악의적인 사용자에게 공격의 표적이 될 수 있습니다.

 

그렇기 때문에, 에러 처리 미들웨어에서는 "서버에서 에러가 발생하였습니다" 와 같은 추상적인

내용을 클라이언트에게 전달하도록 구현해야 합니다.