서버를 구축하게 되면 로그를 관리하게 됩니다.
사용자가 만들어내는 로그 중에는 credential한 정보도 있기 때문에 필터링을 해야합니다. (DB에는 사용자의 Request를 넣어주면서 Log만
필터링)
기존 express 서버의 req.body의 filtering 하던 방식을 리팩토링 해보록 하겠습니다.
기존코드의 경우 문제점
password
와 passwordconfirm
이라는 두 단어만 필터링.(하드코딩)const loggingReqMiddleware(req: Requset, res: Response){
const body = req.body;
const filteredBody = { ...body };
Object.keys(body).forEach((k) => {
if (k.toLocaleLowerCase().indexOf("password") > -1) {
filteredBody.password = "FILTERED";
}
if (k.toLocaleLowerCase().indexOf("passwordconfirm") > -1) {
filteredBody.passwordConfirm = "FILTERED";
}
});
logger.info(`Parameters : ${JSON.stringify(filteredBody)}`);
}
기존 하드코딩 되어있떤 필터링할 단어를 배열로 선언하여 관리.
하지만 여전히 문제점이 있습니다.
CENSOR_WORD_LIST
가 모두 필터링 되었다고 해도 반복문 진행됨.
const CENSOR_WORD_LIST = ["password", "passwordConfirm"] as const;
const lowerCaseCensorWordList = CENSOR_WORD_LIST.map((list) =>
list.toLowerCase()
);
function xorArrFromBrr(arr: string[], brr: string[]) {
return arr
// include 시간복잡도 O(n)
// 검열할 단어 개수 : N
// 전체 키 수 : M
// O(n) * N
.map((key) => key.toLowerCase())
.filter((key) => !brr.includes(key));
}
const loggingReqMiddleware(req: Requset, res: Response){
const body = req.body;
logger.info(
`Parameters : ${JSON.stringify(
xorArrFromBrr(Object.keys({ ...body }), lowerCaseCensorWordList)
)}`
);
logger.info(`Parameters : ${JSON.stringify(filteredBody)}`);
}
Nested depth에도 필터링을 지원하기 위해
Body의 key가 string이 아닌 Object
인 경우 재귀를 한다.
기존 Body의 key 배열 전체 수 만큼 접근하여 필터링을 한 반면
필터링할 문자 배열 CENSOR_WORD_LIST
수 만큼만 반복을 돌며
find
를 사용하여 첫 요소를 만나는 경우 반환을 하여 불필요한 검색을 막음.
type BodyWithIndexSignature = Record<string, string | unknown>;
function filteringNestedBody(
body: BodyWithIndexSignature,
censoredWords: string[]
) {
const keys = Object.keys(body);
const keysOfObjectInBody = keys.filter((key) => body[key] instanceof Object);
if (keysOfObjectInBody.length > 0) {
for (const key of keysOfObjectInBody) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filteringNestedBody(body[key] as BodyWithIndexSignature, censoredWords);
}
}
// find 시간복잡도 O(1) 최악 O(N)
// 검열할 단어 개수 : N
// 전체 키 수 : M
// O(n) * N
for (const word of censoredWords) {
const key = keys.find((v) => v.toLocaleLowerCase() === word);
if (key) {
// 값을 변경해서 보여주거나, key자체를 삭제.
body[key] = "FILTERED";
// delete body[key];
}
}
return body;
}
const loggingReqMiddleware({body}: Requset, res: Response){
//const body = req.body;
// DB에 저장되는 data들이 필터링이 되면 안되게 깊은복사를 해줍니다.
logger.info(
`Parameters : ${JSON.stringify(
filteringNestedBody({ ...body }, lowerCaseCensorWordList)
)}`
);
logger.info(`Parameters : ${JSON.stringify(filteredBody)}`);
}
includes와 find 모두 최악의 경우 O(N)의 시간 복잡도를 갖음.
보통 Body의 키 수가 검열할 문자열 개수보다 많기 때문에
수정 2의 방법을 사용하는걸로 합니다.
실제로는 검열할 단어와 Body key가 엄청나게 많지 않을 것이고
수정1에 비해 수정2의 경우 재귀를 고려했기 때문에 수정2가 드라마틱한 성능 향상을 가진다고 할 수 없겠지만
효율을 생각해보며 연습겸 리팩토링하고 작성해봅니다.