[ 살펴보기 ] Fastify - Decorator, Logging
![[ 살펴보기 ] Fastify - Decorator, Logging](https://cdn.hashnode.com/res/hashnode/image/upload/v1730446880945/ddc20aca-95e7-48d8-a2c8-d70888536d69.jpeg)
Decorator를 통해 fastify instance에 특정 function이다 property를 추가하여 사용할 수 있다. 다음 예제를 살펴보자.
import Fastify, { FastifyInstance } from "fastify";
const fastify = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
fastify.decorate("sum", (firstNum: number, secondNum: number) => {
return firstNum + secondNum;
});
위의 예제에서 fastify instance의 decorator method를 통해 sum이라는 function을 추가해주고 있다. 이제 decorator에 추가한 sum은 fastify instance를 통해 접근할 수 있다. 그 전에 typescirpt에 sum이라는 function을 fastify instance에 추가 했음을 알려주자.
project root에 types 폴더를 만들고 fastify.d.ts 파일을 생성하여 다음과 같이 declare 구문을 추가해준다.
fastify.d.ts
import { fastify } from "fastify";
declare module "fastify" {
export interface FastifyInstance<
HttpServer = http.Server,
HttpRequest = http.IncomingMessage,
HttpResponse = http.ServerResponse
> {
sum(firstNum: number, secondNum: number): number;
}
}
이제 다음과 같이 type error없이 fastify instance에서 바로 sum function에 접근할 수 있다.
...
fastify.get("/items", (request, reply) => {
const result = fastify.sum(2, 4);
reply.code(200).send({ success: true });
});
별도의 file로 route group을 구성했을 때 역시 fastify instance를 통해 추가한 decorator에 접근할 수 있다.
app.ts
import memberRoutes from "./routes/member.js";
const fastify = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
export type MyFastifyInstance = typeof fastify;
fastify.decorate("sum", (firstNum: number, secondNum: number) => {
return firstNum + secondNum;
});
fastify.register(memberRoutes, { prefix: "/member" });
member.ts
import { MyFastifyInstance } from "../app.js";
...
const memberRoutes = (
fastify: MyFastifyInstance,
options: FastifyPluginOptions
) => {
fastify.get<MemberRouteGet>("/", (request, reply) => {
const result = fastify.sum(7, 4);
...
});
...
};
member file은 ts파일이지만 app.ts에선 어째서 js extension으로 import되는지 의아하다면 tsconfig 설정에 따라 import file의 extension, extension 생략 등의 방식이 따라 달라질 수 있다. 위의 동작은 테스트를 진행하고 있는 tsconfig 환경에선 정상적인 결과이며 테스트를 진행할 때 사용된 tsconfig.json 파일은 다음과 같다. tsconfig의 대한 자세한 내용은 별도의 포스트에서 다룬다.
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["./src/**/*"]
}
decorateRequest
fastify instance에 decorator를 통해 custom function을 추가했듯이 request decorateRequest를 통해 request instance에도 custom function을 추가할 수 있다.
...
fastify.decorateRequest("subtract", (firstNum: number, secondNum: number) => {
return firstNum - secondNum;
});
마찬가지로 typescirpt에 custom function을 request instance에 추가 했음을 알려주자.
fastify.d.ts
import { fastify } from "fastify";
declare module "fastify" {
...
export interface FastifyRequest {
subtract(firstNum: number, secondNum: number): number;
}
}
이제 다음과 같이 request instance에서 decorate를 통해 추가한 custom function에 접근하여 사용할 수 있다.
...
fastify.get("/items", (request, reply) => {
const result = request.subtract(10, 2);
console.log({ result });
reply.code(200).send({ success: true });
});
decorateReply
만약 reply instance에 원하는 custom function을 decorate를 통해 추가하고 싶다면 decorateReply method를 사용할 수 있다.
fastify.decorateReply("multiply", (firstNum: number, secondNum: number) => {
return firstNum * secondNum;
});
fastify.d.ts
import { fastify } from "fastify";
declare module "fastify" {
...
export interface FastifyReply {
multiply(firstNum: number, secondNum: number): number;
}
}
decorateReply를 통해 추가한 custom function은 다음과 같이 접근할 수 있다.
...
fastify.get("/items", (request, reply) => {
const result = reply.multiply(10, 2);
console.log({ result });
reply.code(200).send({ success: true });
});
decorate scope
decorate를 통해 custom function을 추가할 때 주의할 점은 decorate가 추가되는 context에 따라 decorate를 통해 추가한 function을 사용할 수 있는 scope가 제한된다. 예를들어 다음과 같이 items url에 대한 route를 처리하는 router와 history url에 대한 route를 처리하는 router를 각각 register를 통해 추가한다고 가정해보자.
fastify.register((fastify, options) => {
fastify.decorate("sum", (firstNum: number, secondNum: number) => {
return firstNum + secondNum;
});
fastify.get("/items", (request, reply) => {
const result = fastify.sum(10, 2);
console.log({ result });
reply.code(200).send({ success: true });
});
});
fastify.register((fastify, options) => {
fastify.get("/history", (request, reply) => {
const result = fastify.sum(10, 2);
console.log({ result });
reply.code(200).send({ success: true });
});
});
위의 예제에서 decorate는 items url을 처리하는 router안에 선언되어 있다. 즉, sum function은 해당 register context에서만 사용이 가능하며 다른 context에선 사용이 불가능하다. 즉, history router에서 sum function을 사용하려고 하면 오류가 발생한다.
그러므로 decorate를 추가할 때 scope에 유의하여 추가하고 global level로 추가하는 것이 아니라면 type또한 적용하는 context에 한정하여 추가해주는 것이 좋다.
Logging
fastify는 내부적으로 pino를 통해 log 기능을 제공한다. 기본적인 log 기능을 활성화 하고 싶으면 다음과 같이 fastify instance를 생성할 때 logger option을 true로 설정해준다.
...
const fastify = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
log를 출력하는 level과 같은 기타 option을 설정하고 싶다면 아래와 같이 logger property에 pino에서 설정할 수 있는 option을 설정해준다. ( Reference - Options ) 설정할 수 있는 level list는 다음과 같다. fatal, error, warn, info, debug, trace, silent
const fastify = Fastify({
logger: {
level:"info"
},
}).withTypeProvider<TypeBoxTypeProvider>();
log를 log file을 통해 관리하고 싶다면 다음과 같이 file property에 log file 경로를 설정해준다.
const fastify = Fastify({
logger: {
level: "info",
file: "logs/log.txt",
}
}).withTypeProvider<TypeBoxTypeProvider>();
logger 기능을 통해 system level의 log뿐만 아니라 아래와 같이 request instance 또는 fastify instance를 통해 제공되는 log property를 통해 직접 log를 발생시킬 수도 있다.
fastify.register((fastify, options) => {
fastify.get("/items", (request, reply) => {
request.log.info("test log 1");
reply.code(200).send({ success: true });
});
});
fastify.log.info("test log 2");
위와 같이 request instance를 통해 log를 발생 시키면 info method를 통해 발생시킨 log message와 더불어 request 정보와 response 정보가 각각 req, res 객체에 담겨 함께 log된다. 이때 req, res객체에 담기는 정보를 serializer property를 통해 설정할 수 있다.
...
const fastify = Fastify({
logger: {
level: "info",
file: "logs/log.txt",
serializers: {
req(request) {
return { url: request.url, method: request.method };
},
res(reply) {
return { statusCode: reply.statusCode };
},
},
}
}).withTypeProvider<TypeBoxTypeProvider>();
만약 조금 더 보기 좋은 format으로 log를 관리하고 싶다면 pino-pretty package를 통해 log를 조금 더 보기 좋은 format으로 변경할 수 있다.
npm install -D pino-pretty
그리고 fastify instance의 logger property를 다음과 같이 설정해준다.
...
const fastify = Fastify({
logger: {
transport: {
target: "pino-pretty",
level: "info",
options: {
destination: "logs/log.txt",
colorize: false,
},
},
}
}).withTypeProvider<TypeBoxTypeProvider>();
fastify.register((fastify, options) => {
fastify.get("/items", (request, reply) => {
request.log.info("test log 1");
reply.code(200).send({ success: true });
});
});
그리고 log를 살펴보면 조금 더 보기 좋은 format으로 저장되는 것을 확인할 수 있다. pino-pretty에서 설정할 수 있는 option은 documentation을 통해 확인할 수 있다 ( Reference - Pino Pretty )
![[ 살펴보기 ] RDB - Relationships](https://cdn.hashnode.com/res/hashnode/image/upload/v1739711556668/48dc9e84-a621-42aa-9c9f-5fc5c436f0ec.jpeg)
![[ 살펴보기 ] MySQL - Data types](https://cdn.hashnode.com/res/hashnode/image/upload/v1739593589113/530f8704-4d27-42c9-a451-bb5c63150b99.jpeg)
![[ 살펴보기 ] TypeORM - Transactions, Migration](https://cdn.hashnode.com/res/hashnode/image/upload/v1739106042581/980b8133-61d4-406a-a026-65be9c28eace.jpeg)
![[ 살펴보기 ] TypeORM - Relations](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666874402/b688bd0b-b6bb-4f43-87d8-c1b46b59f1b7.jpeg)
![[ 살펴보기 ] TypeORM - Basics](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666803591/bef5df17-7dc7-4123-ae55-004d5042df39.jpeg)