nvm 이란 Node Version Manager의 줄임말로, Nodejs의 버전을 관리해주는 프로그램이다. 쉽게 말해 여러 버전의 Node.js를 설치하고 원하는 버전을 선택하여 사용할 수 있도록 만들어주는 것이다.
Node.js의 가장 큰 장점은 npm을 이용하여 다른 사람이 개발해 놓은 모듈을 간편하게 활용할 수 있다는 것이다. 여기서 npm은 Node Package Manager로 Node.js로 개발된 모듈을 관리하는 도구를 말한다. Node.js를 설치하면 npm도 함께 설치되는데, npm의 버전 또한 nvm에서 관리한다. 이는 모듈의 버전과 Node.js의 버전이 달라 발생할 수 있는 오류를 방지하기 위함이다. 그러므로 개발의 편의성을 높이기 위해 Node.js 개발을 할 때에는 NVM을 설치하는 것이 권장된다.
Linux에서 아래의 명령을 차례대로 입력하여 nvm을 설치한다.
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
source ~/.bashrc
nvm -v # nvm 버전 (설치가 잘 되었는지 확인)
# node 버전을 21.0.0 으로 업데이트
nvm install 21.0.0 // node 설치
nvm list #21.0.0 버전이 default로 설정 되었는지 확인
Node.js 등의 JavaScript 런타임을 사용하여 서버를 실행(배포)할 때에는, JavaScript 파일을 리눅스의 서비스로 등록해주어야한다. 그러므로 이번에는 JavaScript 파일을 리눅스의 서비스로 등록하는 방법에 대해 알아보기로 하겠다.
① 아래의 명령을 통해 새로운 서비스 파일을 생성하고 등록할 수 있다.
sudo nano /etc/systemd/system/nubo.service
② 서비스 파일의 내용을 작성한다.
[Unit]
Description={서비스 설명}
After={네트워크 명} // 해당 네트워크가 온라인 상태로 활성화된 후에 서비스가 시작되도록 설정
Wants={네트워크 명} // 해당 네트워크에 대한 의존성 설정
StartLimitIntervalSec={서비스 재시작 제한 간격(초)}
StartLimitBurst={제한 간격 동안의 재시작 횟수} // IntervalSec 동안 Burst만큼의 재시작을 허용
[Service]
Restart=always // 서비스가 비정상적으로 종료되더라도 항상 재시작
RestartSec={서비스 재시작 시도 사이의 간격(초)}
KillSignal=SIGINT // 서비스를 중지할 때 사용할 시그널
WorkingDirectory={서비스가 실행될 작업 디렉터리}
// 서비스를 시작할 때 실행될 명령어를 지정 → Node.js로 /home/nubo/DBIntegration_server/src/index.js 파일을 실행
ExecStart=/home/nubo/.nvm/versions/node/v21.0.0/bin/node /home/nubo/DBIntegration_server/src/index.js
EnvironmentFile=/home/nubo/db_integration_server/nubo-server/.env // 환경 변수 설정 파일의 경로
[Install]
WantedBy=multi-user.target // 다중 사용자 시스템에서 서비스가 자동으로 시작되도록 설정
③ WinSCP를 이용해 본인이 서버에 배포하고자 하는 디렉토리를 /home/nubo 하위로 이동시키자.
④ 서비스 파일 저장이 완료되었다면, 아래의 명령어로 서비스를 등록하고 시작할 수 있게 된다.
sudo systemctl daemon-reload
sudo systemctl enable nubo.service
sudo systemctl start nubo.service
sudo systemctl status nubo.service // 서비스 동작 확인
⑤ 서비스에 오류가 발생하여 최근 로그를 확인하고 싶다면, 아래의 명령을 입력하면 된다.
journalctl -u {서비스명} // 서비스와 관련된 모든 로그 엔트리 조회
journalctl -u {서비스명} -n 10 // // 최근 10줄의 로그 엔트리 조회
Nubo 모바일 클라이언트의 서버는 Node.js로 구성되어 있다. Node.js 환경에서 사용되는 로깅 라이브러리를 이용해 로그를 효율적으로 관리하고 생성하는 방법에 대해 알아보자.
Node.js의 대표적인 로깅 라이브러리에는 Winston과 Winston-Daily-Rotate-File 이 있다. 좀 더 정확히 말하면, Winston은 로깅 기능을 제공하고, Winston-Daily-Rotate-File은 Winston의 특정 Transport 중에 하나로 로그 파일을 일자별로 관리하는 데에 사용된다. 참고로 Transport란, 로그 메시지가 어떻게 처리되고 저장될지를 결정하는 모듈을 말한다.
① 모듈 설치에 필요한 명령은 아래와 같다.
npm install winston winston-daily-rotate-file
② 모듈을 import 하는 명령은 아래와 같다.
const winston = require("winston");
const winstonDaily = require("winston-daily-rotate-file");
③ 일반적으로, config 디렉토리 하위로 winston.js 파일을 추가한 후, 아래의 내용을 입력하여 사용한다.
const winston = require("winston");
const winstonDaily = require("winston-daily-rotate-file");
const logDir = "logs"; // 로그가 저장될 디렉토리 경로
// winston.format에서 제공하는 로그 형식을 사용자가 원하는대로 조합 가능
const { combine, timestamp, printf } = winston.format; // combine: 여러 로그 형식을 조합 / timestamp: 로그메시지에 타임스탬프 추가 / printf: 로그메시지를 지정된 형식으로 출력
// 로그 형식 정의
const logFormat = printf((info) => {
return `${info.timestamp} ${info.level}: ${info.message}`; // 타임스탬프, 로그레벨, 메시지 ex) "2022-01-31 14:30:00 info: Hello, Winston!"
});
const logger = winston.createLogger({ // logger 객체 생성
format: combine(
timestamp({
format: "YYYY-MM-DD HH:mm:ss", // 타임스탬프의 형식을 지정
}),
logFormat
), // ex) "2022-01-31 14:30:00 info: Hello, Winston!"
transports: [ // 로그를 어떻게 처리할지
new winstonDaily({
level: "info",
datePattern: "YYYY-MM-DD",
dirname: logDir + "/info", // info 레벨의 로그를 저장할 경로
filename: `%DATE%.info.log`, // 날짜별로 파일을 회전시키기 위한 패턴
maxFiles: 30, // 유지할 로그 파일의 최대 개수
zippedArchive: true, // 로그파일을 압축하여 저장할지 여부
}),
new winstonDaily({
level: "error",
datePattern: "YYYY-MM-DD",
dirname: logDir + "/error", // error 레벨의 로그를 저장할 경로
filename: `%DATE%.error.log`,
maxFiles: 30,
zippedArchive: false,
}),
],
});
module.exports = { logger }; // logger 객체를 모듈 외부로 내보내 다른 파일에서 해당 logger 객체를 사용할 수 있도록 함.
④ 실제로 저장되는 로그는 아래와 같다.
⑤ error와 info 디렉토리 바로 아래에 보이는 json 파일은 로그 파일과 관련된 설정 정보를 담고 있다.
{
"keep": {
"days": false, // 로그 파일을 보관할 일수, false인 경우 amount만 적용
"amount": 30 // 보관할 수 있는 로그 파일의 최대 수
},
"auditLog": "logs\\error\\{감사 로그 파일의 경로}",
"files": [ // 파일의 정보, date / name / hash를 포함
{
"date": {로그 파일의 생성 날짜},
"name": {로그 파일의 경로},
"hash": {로그 파일에 사용될 Hash 값}
},
... // 일일이 입력하는 값이 아니라 자동으로 생성되는 값임
],
"hashType": "sha256" // 파일의 해시값을 생성할 때 사용된 해시 알고리즘
}
async/await는 비동기 작업을 효율적으로 처리하기 위해 사용된다. 비동기 작업은 빠른 처리 속도가 장점이지만, 순차적으로 실행되어야 하는 명령의 처리를 복잡하게 만든다는 단점이 있다. 비동기 작업을 순차적으로 처리하기 위해 사용해볼 수 있는 방법에는 아래와 같은 것들이 있다.
① Callback 함수 사용
② Promise 객체 사용
③ Async/Await 사용
Node.js에서 가장 권장되는 비동기 처리 방식은 단연 Async/Await이다. 그러므로, Async/Await의 사용 방법에 대해서 자세히 알아보기로 하자. 아래와 같은 코드가 있다고 하자.
const loginController = async (req, res, next) => {
console.log("로그인 서비스를 실행합니다.");
try {
const data = await adminService.login();
return res.status(data.status).send(success(data.status, data.message));
} catch (e) {
return next(e); // errorHandler(middleware)로 전달
}
};
위 예시는 로그인의 결과를 data 변수로 받아 data에 저장되어 있는 상태 코드와 메시지를 반환하는 코드이다. adminService.login 함수는 빠른 작업 처리를 위해 async 로 선언되어 있다.
adminService.login를 호출하고 있는 부분을 살펴보면, await 키워드가 사용된 것을 볼 수 있다. 이처럼, async 함수를 호출할 때에는 반드시 await 키워드를 사용해주어야 한다. 여기서 await은 Promise의 상태가 바뀔 때까지 기다린다는 의미를 갖는다. 즉, Promise가 성공 상태 또는 실패 상태를 갖기 전까지는 다음 명령을 수행하지 않는다는 것이다.
만약 async는 사용하고, await은 사용하지 않는다면 어떻게 될까? 위 예시를 이용해 설명하면, data에 Pending 상태의 Promise가 할당되면서, 로그인 API의 결과와 무관한 방식으로 동작하게 될 것이다.
그렇다면 Async/Await를 사용하는 방식에선 Callback, Promise를 사용할 때의 나타나는 문제점들을 어떻게 해결하였을까?
① 코드의 가독성/직관성
② Error Handling
① VSCode를 실행한 후 F1 버튼을 누른다. 입력창에 git clone을 입력하고, Clone from Github 버튼을 클릭한다.
② Issue를 생성한다.
③ VSCode 터미널에서 아래의 명령을 입력해 브랜치를 생성한다.
git checkout -b {브랜치명}
// 브랜치명을 잘못 생성한 경우 아래의 명령을 통해 수정 가능
git branch -m {기존 브랜치명} {새 브랜치명}
④ 코드를 수정하고 커밋을 진행한다. 커밋 방법에는 GUI 방식과 CLI 방식이 있다.
git status // 변경 사항 확인
git add -A // 모든 변경 내용을 스테이징 영역에 추가
git commit -m "{커밋 메시지}" // 커밋 메시지
git log // 커밋 내역 확인
⑤ 커밋을 완료하였다면, 이제 푸시를 진행해야 한다.
git remote add origin {원격 저장소 URL}
git push origin {브랜치 명}
⑥ Pull을 받고 싶다면, F1키를 누른 후 git:pull을 클릭해도 되고, 아래의 명령을 터미널에 입력해도 된다.
git pull origin main
① VSCode에서 업로드하고자 하는 프로젝트를 연다.
② F1 버튼을 누른 후, 입력창에 Publish to Github를 입력한다. 이후 생성할 Repository의 이름과 공개 여부(Private 또는 Public) 지정하고 엔터를 누른다.
③ 새로운 Repository가 생성된 것을 확인할 수 있다.
④ 생성된 Repository의 주소를 복사한다.
⑤ 기존 브랜치에 커밋을 완료한 후 원격 저장소로 push 한다.
git status // 변경 사항 확인
git add -A // 모든 변경 내용을 스테이징 영역에 추가
git commit -m "{커밋 메시지}" // 커밋 메시지
git remote add origin {원격 저장소 URL}
git push origin {브랜치 명}