From 13afedfafef4acf2427857611391c91f22dd67ef Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 00:14:38 +0900 Subject: [PATCH 1/7] =?UTF-8?q?test:=20fastify.logger=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FastifyLogger 테스트 - info()는 ✓ 요청 로그를 올바르게 처리해야 한다. (1 ms) ✓ 응답 로그를 올바르게 처리해야 한다. (1 ms) ✓ should log simple message - error()는 ✓ 에러 로그 정보를 올바르게 처리해야 한다. --- .../test/common/logger/fastify.logger.test.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 backend/proxy-server/test/common/logger/fastify.logger.test.ts diff --git a/backend/proxy-server/test/common/logger/fastify.logger.test.ts b/backend/proxy-server/test/common/logger/fastify.logger.test.ts new file mode 100644 index 00000000..36170d14 --- /dev/null +++ b/backend/proxy-server/test/common/logger/fastify.logger.test.ts @@ -0,0 +1,78 @@ +import type { FastifyInstance } from 'fastify'; +import { FastifyLogger } from '../../../src/common/logger/fastify.logger'; +import type { RequestLogEntity } from '../../../src/domain/log/request-log.entity'; +import type { ResponseLogEntity } from '../../../src/domain/log/response-log.entity'; + +const mockFastifyInstance = { + log: { + info: jest.fn(), + error: jest.fn(), + }, +} as unknown as FastifyInstance; + +describe('FastifyLogger 테스트', () => { + let logger: FastifyLogger; + + beforeEach(() => { + jest.clearAllMocks(); + logger = new FastifyLogger(mockFastifyInstance); + }); + + describe('info()는 ', () => { + it('요청 로그를 올바르게 처리해야 한다.', () => { + const requestLog = { + method: 'GET', + host: 'www.example.com', + path: '/test', + } as RequestLogEntity; + + logger.info(requestLog); + expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(requestLog); + }); + + it('응답 로그를 올바르게 처리해야 한다.', () => { + const responseLog = { + method: 'POST', + host: 'www.example.com', + path: '/test', + statusCode: 200, + responseTime: 100, + } as ResponseLogEntity; + + logger.info(responseLog); + expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(responseLog); + }); + + it('should log simple message', () => { + const messageLog = { message: 'Test message' }; + + logger.info(messageLog); + expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(messageLog); + }); + }); + + describe('error()는 ', () => { + it('에러 로그 정보를 올바르게 처리해야 한다.', () => { + const errorLog = { + method: 'GET', + host: 'www.example.com', + request: { + method: 'POST', + host: 'localhost', + headers: { + 'user-agent': 'test-agent', + 'content-type': 'application/json', + }, + }, + error: { + message: 'Test error', + name: 'Error', + stack: 'Error stack', + }, + }; + + logger.error(errorLog); + expect(mockFastifyInstance.log.error).toHaveBeenCalledWith(errorLog); + }); + }); +}); From 2299f6d7362a03b691f668c7ab9743ab718dd88f Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 00:14:49 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20fastify.logger=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FastifyLogger 테스트 - info()는 ✓ 요청 로그를 올바르게 처리해야 한다. (1 ms) ✓ 응답 로그를 올바르게 처리해야 한다. (1 ms) ✓ should log simple message - error()는 ✓ 에러 로그 정보를 올바르게 처리해야 한다. --- .../test/common/fastify.logger.test.ts | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 backend/proxy-server/test/common/fastify.logger.test.ts diff --git a/backend/proxy-server/test/common/fastify.logger.test.ts b/backend/proxy-server/test/common/fastify.logger.test.ts deleted file mode 100644 index 3d513daf..00000000 --- a/backend/proxy-server/test/common/fastify.logger.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { FastifyLogger } from '../../src/common/logger/fastify.logger'; -import type { FastifyInstance } from 'fastify'; -import type { RequestLog, ResponseLog, ErrorLog } from '../../src/domain/log/log.interface'; - -describe('fastify.logger 테스트', () => { - let mockServer: FastifyInstance; - let logger: FastifyLogger; - - beforeEach(() => { - mockServer = { - log: { - info: jest.fn(), - error: jest.fn(), - }, - } as unknown as FastifyInstance; - - logger = new FastifyLogger(mockServer); - }); - - describe('logger.info()는 ', () => { - it('요청 로그를 올바르게 수집해야 한다.', () => { - const requestLog: RequestLog = { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - }; - - logger.info(requestLog); - - expect(mockServer.log.info).toHaveBeenCalledWith(requestLog); - }); - - it('응답 로그를 올바르게 수집해야 한다.', () => { - const responseLog: ResponseLog = { - method: 'POST', - host: 'api.example.com', - path: '/api/v1/users', - statusCode: 201, - responseTime: 123, - }; - - logger.info(responseLog); - - expect(mockServer.log.info).toHaveBeenCalledWith(responseLog); - }); - }); - - describe('logger.error()는 ', () => { - it('에러 로그를 올바르게 수집해야 한다.', () => { - const errorLog: ErrorLog = { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - request: { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - headers: { - 'user-agent': 'test-client/1.0', - 'content-type': 'application/json', - 'x-forwarded-for': '10.0.0.1', - }, - }, - error: { - message: 'Connection refused', - name: 'ConnectionError', - stack: 'Error: Connection refused\n at Object.', - originalError: new Error('ECONNREFUSED'), - }, - }; - - logger.error(errorLog); - - expect(mockServer.log.error).toHaveBeenCalledWith(errorLog); - }); - }); -}); From 64a042a2ff0906d54a0d7a7e8c3e57d8707739cc Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 00:15:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20eslint=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=BD=EA=B3=A0=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/common/logger/error-log.repository.ts | 2 +- .../src/common/logger/fastify.logger.ts | 4 +- .../src/common/logger/logger.interface.ts | 6 +- .../src/domain/log/log.repository.ts | 4 +- .../src/domain/log/log.service.ts | 7 +- .../src/domain/log/request-log.entity.ts | 4 +- .../src/domain/log/response-log.entity.ts | 2 +- .../src/domain/project/project.entity.ts | 4 +- .../src/domain/project/project.repository.ts | 2 +- .../src/domain/project/project.service.ts | 2 +- .../proxy-server/src/server/error.handler.ts | 4 +- .../proxy-server/src/server/proxy-server.ts | 11 +- .../test/proxy/proxy-server-fastify.test.ts | 77 ----- .../test/proxy/proxy-service.test.ts | 98 ------ .../test/service/log.service.test.ts | 294 ------------------ 15 files changed, 25 insertions(+), 496 deletions(-) delete mode 100644 backend/proxy-server/test/proxy/proxy-server-fastify.test.ts delete mode 100644 backend/proxy-server/test/proxy/proxy-service.test.ts delete mode 100644 backend/proxy-server/test/service/log.service.test.ts diff --git a/backend/proxy-server/src/common/logger/error-log.repository.ts b/backend/proxy-server/src/common/logger/error-log.repository.ts index 160e19b5..b18ba924 100644 --- a/backend/proxy-server/src/common/logger/error-log.repository.ts +++ b/backend/proxy-server/src/common/logger/error-log.repository.ts @@ -1,6 +1,6 @@ import path from 'path'; import fs from 'fs'; -import { ErrorLog } from './logger.interface'; +import type { ErrorLog } from './logger.interface'; export class ErrorLogRepository { private readonly logDir = 'logs'; diff --git a/backend/proxy-server/src/common/logger/fastify.logger.ts b/backend/proxy-server/src/common/logger/fastify.logger.ts index e5a1c98a..fa1d96f6 100644 --- a/backend/proxy-server/src/common/logger/fastify.logger.ts +++ b/backend/proxy-server/src/common/logger/fastify.logger.ts @@ -1,7 +1,7 @@ import type { FastifyInstance } from 'fastify'; import type { ErrorLog, Logger } from './logger.interface'; -import { RequestLogEntity } from '../../domain/log/request-log.entity'; -import { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import type { RequestLogEntity } from '../../domain/log/request-log.entity'; +import type { ResponseLogEntity } from '../../domain/log/response-log.entity'; export class FastifyLogger implements Logger { constructor(private readonly server: FastifyInstance) {} diff --git a/backend/proxy-server/src/common/logger/logger.interface.ts b/backend/proxy-server/src/common/logger/logger.interface.ts index 9d5016e0..8af216db 100644 --- a/backend/proxy-server/src/common/logger/logger.interface.ts +++ b/backend/proxy-server/src/common/logger/logger.interface.ts @@ -1,6 +1,6 @@ -import { RequestLogEntity } from '../../domain/log/request-log.entity'; -import { ResponseLogEntity } from '../../domain/log/response-log.entity'; -import { BaseLog } from '../../domain/core/base-log'; +import type { RequestLogEntity } from '../../domain/log/request-log.entity'; +import type { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import type { BaseLog } from '../../domain/core/base-log'; export interface ErrorLog extends BaseLog { request: { diff --git a/backend/proxy-server/src/domain/log/log.repository.ts b/backend/proxy-server/src/domain/log/log.repository.ts index befa4350..b11ce72d 100644 --- a/backend/proxy-server/src/domain/log/log.repository.ts +++ b/backend/proxy-server/src/domain/log/log.repository.ts @@ -1,5 +1,5 @@ -import { RequestLogEntity } from './request-log.entity'; -import { ResponseLogEntity } from './response-log.entity'; +import type { RequestLogEntity } from './request-log.entity'; +import type { ResponseLogEntity } from './response-log.entity'; export interface LogRepository { insertRequestLog(log: RequestLogEntity): Promise; diff --git a/backend/proxy-server/src/domain/log/log.service.ts b/backend/proxy-server/src/domain/log/log.service.ts index 6175cd70..93674f45 100644 --- a/backend/proxy-server/src/domain/log/log.service.ts +++ b/backend/proxy-server/src/domain/log/log.service.ts @@ -1,8 +1,7 @@ import { DatabaseQueryError } from '../../common/error/database-query.error'; -import { RequestLogEntity } from './request-log.entity'; -import { ResponseLogEntity } from './response-log.entity'; -import { LogRepository } from './log.repository'; - +import type { RequestLogEntity } from './request-log.entity'; +import type { ResponseLogEntity } from './response-log.entity'; +import type { LogRepository } from './log.repository'; export class LogService { constructor(private readonly logRepository: LogRepository) {} diff --git a/backend/proxy-server/src/domain/log/request-log.entity.ts b/backend/proxy-server/src/domain/log/request-log.entity.ts index 76f16ddc..bd60f37a 100644 --- a/backend/proxy-server/src/domain/log/request-log.entity.ts +++ b/backend/proxy-server/src/domain/log/request-log.entity.ts @@ -1,6 +1,6 @@ -import { BaseLog } from '../core/base-log'; +import type { BaseLog } from '../core/base-log'; -interface RequestLog extends BaseLog {} +type RequestLog = BaseLog; export class RequestLogEntity { readonly method: string; diff --git a/backend/proxy-server/src/domain/log/response-log.entity.ts b/backend/proxy-server/src/domain/log/response-log.entity.ts index e9b045d0..8fcb9191 100644 --- a/backend/proxy-server/src/domain/log/response-log.entity.ts +++ b/backend/proxy-server/src/domain/log/response-log.entity.ts @@ -1,4 +1,4 @@ -import { BaseLog } from '../core/base-log'; +import type { BaseLog } from '../core/base-log'; interface ResponseLog extends BaseLog { statusCode: number; diff --git a/backend/proxy-server/src/domain/project/project.entity.ts b/backend/proxy-server/src/domain/project/project.entity.ts index 6e7e6da3..25abf8c1 100644 --- a/backend/proxy-server/src/domain/project/project.entity.ts +++ b/backend/proxy-server/src/domain/project/project.entity.ts @@ -1,6 +1,6 @@ -import { ProjectRow } from '../../database/query/project.repository.mysql'; +import type { ProjectRow } from '../../database/query/project.repository.mysql'; -interface Project extends ProjectRow {} +type Project = ProjectRow; export class ProjectEntity { readonly ip: string; diff --git a/backend/proxy-server/src/domain/project/project.repository.ts b/backend/proxy-server/src/domain/project/project.repository.ts index b9b5d8c2..0333e118 100644 --- a/backend/proxy-server/src/domain/project/project.repository.ts +++ b/backend/proxy-server/src/domain/project/project.repository.ts @@ -1,4 +1,4 @@ -import { ProjectEntity } from './project.entity'; +import type { ProjectEntity } from './project.entity'; export interface ProjectRepository { findIpByDomain(domain: string): Promise; diff --git a/backend/proxy-server/src/domain/project/project.service.ts b/backend/proxy-server/src/domain/project/project.service.ts index 9d9c71a1..46be96f1 100644 --- a/backend/proxy-server/src/domain/project/project.service.ts +++ b/backend/proxy-server/src/domain/project/project.service.ts @@ -1,4 +1,4 @@ -import { ProjectRepository } from './project.repository'; +import type { ProjectRepository } from './project.repository'; export class ProjectService { constructor(private readonly projectRepository: ProjectRepository) {} diff --git a/backend/proxy-server/src/server/error.handler.ts b/backend/proxy-server/src/server/error.handler.ts index 56911520..71cefd00 100644 --- a/backend/proxy-server/src/server/error.handler.ts +++ b/backend/proxy-server/src/server/error.handler.ts @@ -3,8 +3,8 @@ import type { FastifyLogger } from '../common/logger/fastify.logger'; import { ProxyError } from '../common/core/proxy.error'; import { isProxyError } from '../common/core/proxy-error.type.guard'; import type { IncomingHttpHeaders } from 'node:http'; -import { ErrorLogRepository } from '../common/logger/error-log.repository'; -import { ErrorLog } from '../common/logger/logger.interface'; +import type { ErrorLogRepository } from '../common/logger/error-log.repository'; +import type { ErrorLog } from '../common/logger/logger.interface'; interface ProxyErrorHandlerOptions { logger: FastifyLogger; diff --git a/backend/proxy-server/src/server/proxy-server.ts b/backend/proxy-server/src/server/proxy-server.ts index cbea75b8..e74160df 100644 --- a/backend/proxy-server/src/server/proxy-server.ts +++ b/backend/proxy-server/src/server/proxy-server.ts @@ -7,13 +7,12 @@ import { fastifyConfig } from './config/fastify.config'; import { HOST_HEADER } from '../common/constant/http.constant'; import { ErrorHandler } from './error.handler'; import { FastifyLogger } from '../common/logger/fastify.logger'; -import { LogService } from '../domain/log/log.service'; -import { RequestLogEntity } from '../domain/log/request-log.entity'; -import { ResponseLogEntity } from '../domain/log/response-log.entity'; -import { ProjectService } from '../domain/project/project.service'; +import type { LogService } from '../domain/log/log.service'; +import type { RequestLogEntity } from '../domain/log/request-log.entity'; +import type { ResponseLogEntity } from '../domain/log/response-log.entity'; +import type { ProjectService } from '../domain/project/project.service'; import { DatabaseQueryError } from '../common/error/database-query.error'; -import { ErrorLog } from '../common/logger/logger.interface'; -import { ErrorLogRepository } from '../common/logger/error-log.repository'; +import type { ErrorLogRepository } from '../common/logger/error-log.repository'; export class ProxyServer { private readonly server: FastifyInstance; diff --git a/backend/proxy-server/test/proxy/proxy-server-fastify.test.ts b/backend/proxy-server/test/proxy/proxy-server-fastify.test.ts deleted file mode 100644 index 80b5a0c9..00000000 --- a/backend/proxy-server/test/proxy/proxy-server-fastify.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { FastifyLogger } from '../../src/common/logger/fastify.logger'; -import type { FastifyInstance } from 'fastify'; -import type { RequestLog, ResponseLog, ErrorLog } from '../../src/domain/log/log.interface'; - -describe('server-server-fastify 테스트', () => { - let mockServer: FastifyInstance; - let logger: FastifyLogger; - - beforeEach(() => { - mockServer = { - log: { - info: jest.fn(), - error: jest.fn(), - }, - } as unknown as FastifyInstance; - - logger = new FastifyLogger(mockServer); - }); - - describe('info()는 ', () => { - it('요청 정보를 올바르게 로깅해야한다.', () => { - const requestLog: RequestLog = { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - }; - - logger.info(requestLog); - - expect(mockServer.log.info).toHaveBeenCalledWith(requestLog); - }); - - it('응답 정보를 올바르게 로깅해야한다.', () => { - const responseLog: ResponseLog = { - method: 'POST', - host: 'api.example.com', - path: '/api/v1/users', - statusCode: 201, - responseTime: 123, - }; - - logger.info(responseLog); - - expect(mockServer.log.info).toHaveBeenCalledWith(responseLog); - }); - }); - - describe('error()는 ', () => { - it('오류 정보를 올바르게 로깅해야 한다.', () => { - const originalError = new Error('Connection timeout'); - const errorLog: ErrorLog = { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - request: { - method: 'GET', - host: 'api.example.com', - path: '/api/v1/users', - headers: { - 'content-type': 'application/json', - 'user-agent': 'test-client/1.0', - }, - }, - error: { - message: 'Connection timeout', - name: 'Error', - stack: originalError.stack, - originalError, - }, - }; - - logger.error(errorLog); - - expect(mockServer.log.error).toHaveBeenCalledWith(errorLog); - }); - }); -}); diff --git a/backend/proxy-server/test/proxy/proxy-service.test.ts b/backend/proxy-server/test/proxy/proxy-service.test.ts deleted file mode 100644 index a094721d..00000000 --- a/backend/proxy-server/test/proxy/proxy-service.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Utils } from '../../src/server/utils'; -import { projectQuery } from '../../src/database/query/project.repository.mysql'; -import { DomainNotFoundError } from '../../src/common/error/domain-not-found.error'; -import { MissingHostHeaderError } from '../../src/common/error/missing-host-header.error'; -import { DatabaseQueryError } from '../../src/common/error/database-query.error'; -import { ProxyError } from '../../src/common/core/proxy.error'; - -jest.mock('../../src/database/query/project.repository.mysql'); - -describe('server-service 테스트', () => { - let proxyService: Utils; - - beforeEach(() => { - proxyService = new Utils(); - jest.clearAllMocks(); - }); - - describe('resolveDomain()은 ', () => { - it('도메인을 ip로 성공적으로 확인해야 한다.', async () => { - const mockHost = 'api.example.com'; - const mockIp = '10.0.0.1'; - - (projectQuery.findIpByDomain as jest.Mock).mockResolvedValue(mockIp); - - const result = await proxyService.resolveDomain(mockHost); - - expect(result).toBe(mockIp); - expect(projectQuery.findIpByDomain).toHaveBeenCalledWith(mockHost); - }); - - it('ip를 찾을 수 없는 경우에는 DomainNotFoundError를 던져야 한다.', async () => { - const mockHost = 'nonexistent-api.example.com'; - - (projectQuery.findIpByDomain as jest.Mock).mockResolvedValue(''); - - await expect(proxyService.resolveDomain(mockHost)).rejects.toThrow(DomainNotFoundError); - }); - - it('데이터베이스 쿼리가 실패하면 DatabaseQueryError를 던져야 한다.', async () => { - const mockHost = 'api.example.com'; - const mockError = new Error('Database connection failed'); - - (projectQuery.findIpByDomain as jest.Mock).mockRejectedValue(mockError); - - await expect(proxyService.resolveDomain(mockHost)).rejects.toThrow(DatabaseQueryError); - }); - }); - - describe('buildTargetUrl()은 ', () => { - it('path를 포함한 URL을 올바르게 생성해야 한다.', () => { - const ip = '10.0.0.1'; - const path = '/api/v1/users'; - - const result = proxyService.buildTargetUrl(ip, path); - - expect(result).toBe('http://10.0.0.1/api/v1/users'); - }); - - it('빈 경로가 주어졌을 때 URL을 올바르게 생성해야 한다.', () => { - const ip = '10.0.0.1'; - const path = ''; - - const result = proxyService.buildTargetUrl(ip, path); - - expect(result).toBe('http://10.0.0.1/'); - }); - - it('URL 생성 중 오류가 발생하면 ProxyError를 throw해야 한다.', () => { - const badIp = { - toString: () => { - throw new Error('IP 주소 변환 중 오류 발생'); - }, - }; - - expect(() => { - proxyService.buildTargetUrl(badIp as unknown as string, '/test'); - }).toThrow(ProxyError); - }); - }); - - describe('validateHost()은 ', () => { - it('유효한 호스트가 주어졌을 때 해당 호스트를 반환해야 한다.', () => { - const mockHost = 'api.example.com'; - - const result = proxyService.validateHost(mockHost); - - expect(result).toBe(mockHost); - }); - - it('호스트가 undefined일 때 MissingHostHeaderError 예외를 던져야 한다', () => { - expect(() => proxyService.validateHost(undefined)).toThrow(MissingHostHeaderError); - }); - - it('호스트가 빈 문자열일 때 MissingHostHeaderError 예외를 던져야 한다.', () => { - expect(() => proxyService.validateHost('')).toThrow(MissingHostHeaderError); - }); - }); -}); diff --git a/backend/proxy-server/test/service/log.service.test.ts b/backend/proxy-server/test/service/log.service.test.ts deleted file mode 100644 index 66bc84ef..00000000 --- a/backend/proxy-server/test/service/log.service.test.ts +++ /dev/null @@ -1,294 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { LogService } from '../../src/domain/log/log.service'; -import { DatabaseQueryError } from '../../src/common/error/database-query.error'; -import type { RequestLog, ResponseLog, ErrorLog } from '../../src/domain/log/log.interface'; - -jest.mock('fs', () => ({ - existsSync: jest.fn(), - mkdirSync: jest.fn(), - promises: { - appendFile: jest.fn(), - }, -})); - -jest.mock('path', () => ({ - join: jest.fn(), -})); - -const mockInsertRequestLog = jest.fn(); -const mockInsertResponseLog = jest.fn(); - -jest.mock('../../src/database/query/log.repository.clickhouse', () => { - return { - LogQuery: jest.fn().mockImplementation(() => ({ - insertRequestLog: mockInsertRequestLog, - insertResponseLog: mockInsertResponseLog, - })), - }; -}); - -describe('LogService 테스트', () => { - let logService: LogService; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('initialization()는', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('로그 디렉토리가 없다면 생성해야한다.', () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - - new LogService(); - - expect(fs.existsSync).toHaveBeenCalledWith('logs'); - expect(fs.mkdirSync).toHaveBeenCalledWith('logs'); - }); - - it('로그 디렉토리가 이미 존재하는 경우 생성하지 않아야 한다.', () => { - (fs.existsSync as jest.Mock).mockReturnValue(true); - - new LogService(); - - expect(fs.existsSync).toHaveBeenCalledWith('logs'); - expect(fs.mkdirSync).not.toHaveBeenCalled(); - }); - - it('디렉토리 생성 중 발생하는 에러를 처리할 수 있어야 한다.', () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - (fs.mkdirSync as jest.Mock).mockImplementation(() => { - throw new Error('Failed to create directory'); - }); - - const consoleSpy = jest.spyOn(console, 'error'); - - new LogService(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'Failed to create log directory:', - expect.any(Error), - ); - }); - - it('로그 디렉토리 생성 시 권한 에러가 발생하면 에러를 처리할 수 있어야 한다.', () => { - const permissionError = new Error('EACCES: permission denied'); - (fs.mkdirSync as jest.Mock).mockImplementation(() => { - throw permissionError; - }); - (fs.existsSync as jest.Mock).mockReturnValue(false); - - const consoleSpy = jest.spyOn(console, 'error'); - - new LogService(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'Failed to create log directory:', - permissionError, - ); - }); - }); - - describe('saveRequestLog()는', () => { - beforeEach(() => { - logService = new LogService(); - }); - - it('성공적으로 requestLog를 저장해야 한다.', async () => { - const requestLog: RequestLog = { - method: 'GET', - host: 'example.com', - path: '/test', - }; - await logService.saveRequestLog(requestLog); - - expect(mockInsertRequestLog).toHaveBeenCalledWith(requestLog); - }); - - it('databaseQueryError 발생 시, 그대로 DatabaseQueryError를 던져야 한다.', async () => { - const requestLog: RequestLog = { - method: 'GET', - host: 'example.com', - path: '/test', - }; - - const databaseQueryError = new DatabaseQueryError(new Error('Original DB Error')); - mockInsertRequestLog.mockRejectedValue(databaseQueryError); - - await expect(logService.saveRequestLog(requestLog)).rejects.toBe(databaseQueryError); - }); - - it('일반 에러가 발생하면 DatabaseQueryError로 감싸서 throw해야 한다.', async () => { - const requestLog: RequestLog = { - method: 'GET', - host: 'example.com', - path: '/test', - }; - - const originalError = new Error('DB Error'); - mockInsertRequestLog.mockRejectedValue(originalError); - - try { - await logService.saveRequestLog(requestLog); - fail('에러가 발생해야 합니다.'); - } catch (error) { - expect(error).toBeInstanceOf(DatabaseQueryError); - const dbError = error as DatabaseQueryError; - expect(dbError.message).toBe('데이터베이스 쿼리 중 문제가 발생했습니다.'); - expect(dbError.originalError).toBe(originalError); - } - }); - }); - - describe('saveResponseLog()는', () => { - beforeEach(() => { - logService = new LogService(); - }); - - it('responseLog를 올바르게 저장해야 한다.', async () => { - const responseLog: ResponseLog = { - method: 'GET', - host: 'example.com', - path: '/test', - statusCode: 200, - responseTime: 100, - }; - - await logService.saveResponseLog(responseLog); - - expect(mockInsertResponseLog).toHaveBeenCalledWith(responseLog); - }); - - it('DatabaseQueryError가 발생하면 그대로 throw해야 한다.', async () => { - const responseLog: ResponseLog = { - method: 'GET', - host: 'example.com', - path: '/test', - statusCode: 200, - responseTime: 100, - }; - - const databaseQueryError = new DatabaseQueryError(new Error('Original DB Error')); - mockInsertResponseLog.mockRejectedValue(databaseQueryError); - - await expect(logService.saveResponseLog(responseLog)).rejects.toBe(databaseQueryError); - }); - - it('일반 에러가 발생하면 DatabaseQueryError로 감싸서 throw해야 한다.', async () => { - const responseLog: ResponseLog = { - method: 'GET', - host: 'example.com', - path: '/test', - statusCode: 200, - responseTime: 100, - }; - - const originalError = new Error('DB Error'); - mockInsertResponseLog.mockRejectedValue(originalError); - - try { - await logService.saveResponseLog(responseLog); - fail('에러가 발생해야 합니다.'); - } catch (error) { - expect(error).toBeInstanceOf(DatabaseQueryError); - const dbError = error as DatabaseQueryError; - expect(dbError.message).toBe('데이터베이스 쿼리 중 문제가 발생했습니다.'); - expect(dbError.originalError).toBe(originalError); - } - }); - }); - - describe('saveErrorLog()는', () => { - beforeEach(() => { - logService = new LogService(); - }); - - it('errorLog를 올바르게 파일에 저장할 수 있어야 한다.', async () => { - const errorLog: ErrorLog = { - method: 'GET', - host: 'example.com', - path: '/test', - request: { - method: 'GET', - host: 'example.com', - path: '/test', - headers: { - 'user-agent': 'test-agent', - }, - }, - error: { - message: 'Test error', - name: 'TestError', - }, - }; - - (path.join as jest.Mock).mockReturnValue('logs/error.log'); - - await logService.saveErrorLog(errorLog); - - expect(path.join).toHaveBeenCalledWith('logs', 'error.log'); - expect(fs.promises.appendFile).toHaveBeenCalledWith( - 'logs/error.log', - expect.stringContaining('"Test error"'), - ); - }); - - it('쓰는 동안 발생하는 에러를 성공적으로 처리할 수 있어야 한다.', async () => { - const errorLog: ErrorLog = { - method: 'GET', - host: 'example.com', - path: '/test', - request: { - method: 'GET', - host: 'example.com', - path: '/test', - headers: {}, - }, - error: { - message: 'Test error', - name: 'TestError', - }, - }; - - (fs.promises.appendFile as jest.Mock).mockRejectedValue(new Error('Write failed')); - const consoleSpy = jest.spyOn(console, 'error'); - - await logService.saveErrorLog(errorLog); - - expect(consoleSpy).toHaveBeenCalledWith( - 'Failed to write error log:', - expect.any(Error), - ); - }); - - it('파일 쓰기 권한이 없을 때 에러를 처리할 수 있어야 한다.', async () => { - const errorLog: ErrorLog = { - method: 'GET', - host: 'example.com', - path: '/test', - request: { - method: 'GET', - host: 'example.com', - path: '/test', - headers: {}, - }, - error: { - message: 'Test error', - name: 'TestError', - }, - }; - - const permissionError = new Error('EACCES: permission denied'); - (fs.promises.appendFile as jest.Mock).mockRejectedValue(permissionError); - - const consoleSpy = jest.spyOn(console, 'error'); - - await logService.saveErrorLog(errorLog); - - expect(consoleSpy).toHaveBeenCalledWith('Failed to write error log:', permissionError); - }); - }); -}); From c03bf6b72f741777bcb545970cfb8eb9eb2e761c Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 00:23:56 +0900 Subject: [PATCH 4/7] =?UTF-8?q?test:=20error-log.repository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ErrorLogRepository 테스트 - constructor()는 ✓ 로그 디렉토리가 없다면, 생성해야 한다. - saveErrorLog()는 ✓ 에러 로그를 파일에 저장할 수 있어야 한다. (1 ms) ✓ 에러를 파일에 기록할 수 있어야 한다. (1 ms) --- .../logger/error-log.repository.test.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 backend/proxy-server/test/common/logger/error-log.repository.test.ts diff --git a/backend/proxy-server/test/common/logger/error-log.repository.test.ts b/backend/proxy-server/test/common/logger/error-log.repository.test.ts new file mode 100644 index 00000000..a6036f0d --- /dev/null +++ b/backend/proxy-server/test/common/logger/error-log.repository.test.ts @@ -0,0 +1,90 @@ +import { ErrorLogRepository } from '../../../src/common/logger/error-log.repository'; +import fs from 'fs'; +import path from 'path'; + +jest.mock('fs', () => ({ + promises: { + appendFile: jest.fn(), + }, + existsSync: jest.fn(), + mkdirSync: jest.fn(), +})); + +describe('ErrorLogRepository 테스트', () => { + let repository: ErrorLogRepository; + + beforeEach(() => { + jest.clearAllMocks(); + repository = new ErrorLogRepository(); + }); + + describe('constructor()는 ', () => { + it('로그 디렉토리가 없다면, 생성해야 한다.', () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + + new ErrorLogRepository(); + + expect(fs.existsSync).toHaveBeenCalledWith('logs'); + expect(fs.mkdirSync).toHaveBeenCalledWith('logs'); + }); + }); + + describe('saveErrorLog()는 ', () => { + it('에러 로그를 파일에 저장할 수 있어야 한다.', async () => { + const errorLog = { + method: 'GET', + host: 'www.example.com', + request: { + method: 'POST', + host: 'localhost', + headers: { + 'user-agent': 'test-agent', + 'content-type': 'application/json', + }, + }, + error: { + message: 'Test error', + name: 'Error', + stack: 'Error stack', + }, + }; + + await repository.saveErrorLog(errorLog); + + expect(fs.promises.appendFile).toHaveBeenCalledWith( + path.join('logs', 'error.log'), + expect.stringContaining('"method":"GET"'), + ); + }); + + it('에러를 파일에 기록할 수 있어야 한다.', async () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + (fs.promises.appendFile as jest.Mock).mockRejectedValue(new Error('Write failed')); + + const errorLog = { + method: 'GET', + host: 'www.example.com', + request: { + method: 'POST', + host: 'localhost', + headers: { + 'user-agent': 'test-agent', + 'content-type': 'application/json', + }, + }, + error: { + message: 'Test error', + name: 'Error', + stack: 'Error stack', + }, + }; + await repository.saveErrorLog(errorLog); + + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to write error log:', + expect.any(Error), + ); + consoleSpy.mockRestore(); + }); + }); +}); From e351ef6b530eb3a5556aead10ae4adcb6bf7d00f Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 00:50:59 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20clickhouseClient=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 : clickhouse 사용 - 변경 : clickhouseClient 사용 --- backend/proxy-server/package-lock.json | 19 +++++++++++++++ backend/proxy-server/package.json | 1 + .../clickhouse/clickhouse-database.ts | 8 +++---- .../clickhouse/config/clickhouse.config.ts | 22 ++++++----------- .../query/log.repository.clickhouse.ts | 24 +++++++++---------- 5 files changed, 43 insertions(+), 31 deletions(-) diff --git a/backend/proxy-server/package-lock.json b/backend/proxy-server/package-lock.json index 838aacee..da45f4ae 100644 --- a/backend/proxy-server/package-lock.json +++ b/backend/proxy-server/package-lock.json @@ -8,6 +8,7 @@ "name": "proxy-server", "version": "1.0.0", "dependencies": { + "@clickhouse/client": "^1.8.0", "@fastify/reply-from": "^11.0.1", "clickhouse": "^2.6.0", "dotenv": "^16.4.5", @@ -1895,6 +1896,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@clickhouse/client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.8.0.tgz", + "integrity": "sha512-IJ+/r3Wbg2t67sEtTA9dUgP2HjyGuze7ksNqeDfb7Ahnws1dzSVP2kg1Y9xOrb2e37K29JWWr26cX+rOiESm1A==", + "license": "Apache-2.0", + "dependencies": { + "@clickhouse/client-common": "1.8.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@clickhouse/client-common": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@clickhouse/client-common/-/client-common-1.8.0.tgz", + "integrity": "sha512-aQgH0UODGuFHfL8rgeLSrGCoh3NCoNUs0tFGl0o79iyfASfvWtT/K/X3RM0QJpXXOgXpB//T2nD5XvCFtdk32w==", + "license": "Apache-2.0" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", diff --git a/backend/proxy-server/package.json b/backend/proxy-server/package.json index 8ff593d6..8b975101 100644 --- a/backend/proxy-server/package.json +++ b/backend/proxy-server/package.json @@ -32,6 +32,7 @@ "typescript": "^5.6.3" }, "dependencies": { + "@clickhouse/client": "^1.8.0", "@fastify/reply-from": "^11.0.1", "clickhouse": "^2.6.0", "dotenv": "^16.4.5", diff --git a/backend/proxy-server/src/database/clickhouse/clickhouse-database.ts b/backend/proxy-server/src/database/clickhouse/clickhouse-database.ts index 7a2bfea4..b03dbf86 100644 --- a/backend/proxy-server/src/database/clickhouse/clickhouse-database.ts +++ b/backend/proxy-server/src/database/clickhouse/clickhouse-database.ts @@ -1,12 +1,12 @@ -import { ClickHouse } from 'clickhouse'; +import { createClient, ClickHouseClient } from '@clickhouse/client'; import { clickhouseConfig } from './config/clickhouse.config'; export class ClickhouseDatabase { - private static instance: ClickHouse; + private static instance: ClickHouseClient; - public static getInstance(): ClickHouse { + public static getInstance(): ClickHouseClient { if (!ClickhouseDatabase.instance) { - ClickhouseDatabase.instance = new ClickHouse(clickhouseConfig); + ClickhouseDatabase.instance = createClient(clickhouseConfig); } return ClickhouseDatabase.instance; } diff --git a/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts b/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts index 79e7f408..96993f21 100644 --- a/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts +++ b/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts @@ -1,16 +1,8 @@ -export const clickhouseConfig = { - url: process.env.CLICKHOUSE_URL || 'http://localhost', - port: Number(process.env.CLICKHOUSE_PORT) || 8123, - debug: false, - basicAuth: process.env.CLICKHOUSE_AUTH - ? { - username: process.env.CLICKHOUSE_USERNAME || 'default', - password: process.env.CLICKHOUSE_PASSWORD || '', - } - : null, - format: 'json', - config: { - session_timeout: 60, - database: process.env.CLICKHOUSE_DATABASE, - }, +import { ClickHouseClientConfigOptions } from '@clickhouse/client'; + +export const clickhouseConfig: ClickHouseClientConfigOptions = { + host: process.env.CLICKHOUSE_URL || 'http://localhost:8123', + username: process.env.CLICKHOUSE_USERNAME || 'default', + password: process.env.CLICKHOUSE_PASSWORD || '', + database: process.env.CLICKHOUSE_DATABASE, }; diff --git a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts index a66ab7e7..acb40699 100644 --- a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts +++ b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts @@ -1,12 +1,12 @@ -import type { ClickHouse } from 'clickhouse'; import { ClickhouseDatabase } from '../clickhouse/clickhouse-database'; import { DatabaseQueryError } from '../../common/error/database-query.error'; import { LogRepository } from '../../domain/log/log.repository'; import { RequestLogEntity } from '../../domain/log/request-log.entity'; import { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import { ClickHouseClient } from '@clickhouse/client'; export class LogRepositoryClickhouse implements LogRepository { - private readonly clickhouse: ClickHouse; + private readonly clickhouse: ClickHouseClient; constructor() { this.clickhouse = ClickhouseDatabase.getInstance(); @@ -23,9 +23,11 @@ export class LogRepositoryClickhouse implements LogRepository { ]; try { - const query = `\nINSERT INTO request_log FORMAT JSONEachRow ${JSON.stringify(values)}`; - - await this.clickhouse.insert(query).toPromise(); + await this.clickhouse.insert({ + table: 'request_log', + values: values, + format: 'JSONEachRow', + }); } catch (error) { console.error('ClickHouse Error:', error); throw new DatabaseQueryError(error as Error); @@ -45,19 +47,17 @@ export class LogRepositoryClickhouse implements LogRepository { ]; try { - const query = `\nINSERT INTO response_log FORMAT JSONEachRow ${this.formatValues(values)}`; - - await this.clickhouse.query(query).toPromise(); + await this.clickhouse.insert({ + table: 'response_log', + values: values, + format: 'JSONEachRow', + }); } catch (error) { console.error('ClickHouse Error:', error); throw new DatabaseQueryError(error as Error); } } - private formatValues(values: any): string { - return JSON.stringify(values[0]); - } - private formatDate(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); From 58b6924a9f939259f025c71c1f0a9845ddac0f74 Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 11:24:37 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 requestLog -> 삭제 - 기존의 responseLog -> httpLog - 기존의 requestLogEntity -> 삭제 - 기존의 responseLogEntity -> httpLogEntity - 기존 requestLog를 save,insert 하는 로직 삭제 (responseLog와 중복 정보를 저장하는 로직이기 때문에 삭제 진행) --- .../src/common/logger/fastify.logger.ts | 5 ++-- .../src/common/logger/logger.interface.ts | 5 ++-- .../clickhouse/config/clickhouse.config.ts | 2 +- .../query/log.repository.clickhouse.ts | 27 ++----------------- ...ponse-log.entity.ts => http-log.entity.ts} | 6 ++--- .../src/domain/log/log.repository.ts | 6 ++--- .../src/domain/log/log.service.ts | 18 +++---------- .../src/domain/log/request-log.entity.ts | 15 ----------- .../proxy-server/src/server/proxy-server.ts | 26 +++--------------- .../test/common/logger/fastify.logger.test.ts | 22 ++++----------- 10 files changed, 24 insertions(+), 108 deletions(-) rename backend/proxy-server/src/domain/log/{response-log.entity.ts => http-log.entity.ts} (80%) delete mode 100644 backend/proxy-server/src/domain/log/request-log.entity.ts diff --git a/backend/proxy-server/src/common/logger/fastify.logger.ts b/backend/proxy-server/src/common/logger/fastify.logger.ts index fa1d96f6..422f01dd 100644 --- a/backend/proxy-server/src/common/logger/fastify.logger.ts +++ b/backend/proxy-server/src/common/logger/fastify.logger.ts @@ -1,12 +1,11 @@ import type { FastifyInstance } from 'fastify'; import type { ErrorLog, Logger } from './logger.interface'; -import type { RequestLogEntity } from '../../domain/log/request-log.entity'; -import type { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import type { HttpLogEntity } from '../../domain/log/http-log.entity'; export class FastifyLogger implements Logger { constructor(private readonly server: FastifyInstance) {} - public info(log: RequestLogEntity | ResponseLogEntity | { message: string }): void { + public info(log: HttpLogEntity | { message: string }): void { this.server.log.info(log); } diff --git a/backend/proxy-server/src/common/logger/logger.interface.ts b/backend/proxy-server/src/common/logger/logger.interface.ts index 8af216db..513500ef 100644 --- a/backend/proxy-server/src/common/logger/logger.interface.ts +++ b/backend/proxy-server/src/common/logger/logger.interface.ts @@ -1,5 +1,4 @@ -import type { RequestLogEntity } from '../../domain/log/request-log.entity'; -import type { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import type { HttpLogEntity } from '../../domain/log/http-log.entity'; import type { BaseLog } from '../../domain/core/base-log'; export interface ErrorLog extends BaseLog { @@ -22,6 +21,6 @@ export interface ErrorLog extends BaseLog { } export interface Logger { - info(log: RequestLogEntity | ResponseLogEntity | { message: string }): void; + info(log: HttpLogEntity | { message: string }): void; error(log: ErrorLog): void; } diff --git a/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts b/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts index 96993f21..4406760a 100644 --- a/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts +++ b/backend/proxy-server/src/database/clickhouse/config/clickhouse.config.ts @@ -1,7 +1,7 @@ import { ClickHouseClientConfigOptions } from '@clickhouse/client'; export const clickhouseConfig: ClickHouseClientConfigOptions = { - host: process.env.CLICKHOUSE_URL || 'http://localhost:8123', + url: process.env.CLICKHOUSE_URL || 'http://localhost:8123', username: process.env.CLICKHOUSE_USERNAME || 'default', password: process.env.CLICKHOUSE_PASSWORD || '', database: process.env.CLICKHOUSE_DATABASE, diff --git a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts index acb40699..40bdcaac 100644 --- a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts +++ b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts @@ -1,8 +1,7 @@ import { ClickhouseDatabase } from '../clickhouse/clickhouse-database'; import { DatabaseQueryError } from '../../common/error/database-query.error'; import { LogRepository } from '../../domain/log/log.repository'; -import { RequestLogEntity } from '../../domain/log/request-log.entity'; -import { ResponseLogEntity } from '../../domain/log/response-log.entity'; +import { HttpLogEntity } from '../../domain/log/http-log.entity'; import { ClickHouseClient } from '@clickhouse/client'; export class LogRepositoryClickhouse implements LogRepository { @@ -12,29 +11,7 @@ export class LogRepositoryClickhouse implements LogRepository { this.clickhouse = ClickhouseDatabase.getInstance(); } - public async insertRequestLog(log: RequestLogEntity): Promise { - const values = [ - { - method: log.method, - path: log.path || '', - host: log.host, - timestamp: this.formatDate(new Date()), - }, - ]; - - try { - await this.clickhouse.insert({ - table: 'request_log', - values: values, - format: 'JSONEachRow', - }); - } catch (error) { - console.error('ClickHouse Error:', error); - throw new DatabaseQueryError(error as Error); - } - } - - public async insertResponseLog(log: ResponseLogEntity): Promise { + public async insertHttpLog(log: HttpLogEntity): Promise { const values = [ { method: log.method, diff --git a/backend/proxy-server/src/domain/log/response-log.entity.ts b/backend/proxy-server/src/domain/log/http-log.entity.ts similarity index 80% rename from backend/proxy-server/src/domain/log/response-log.entity.ts rename to backend/proxy-server/src/domain/log/http-log.entity.ts index 8fcb9191..0b293cfa 100644 --- a/backend/proxy-server/src/domain/log/response-log.entity.ts +++ b/backend/proxy-server/src/domain/log/http-log.entity.ts @@ -1,18 +1,18 @@ import type { BaseLog } from '../core/base-log'; -interface ResponseLog extends BaseLog { +interface HttpLog extends BaseLog { statusCode: number; responseTime: number; } -export class ResponseLogEntity { +export class HttpLogEntity { readonly method: string; readonly host: string; readonly path: string | undefined; readonly statusCode: number; readonly responseTime: number; - constructor(log: ResponseLog) { + constructor(log: HttpLog) { this.method = log.method; this.host = log.host; this.path = log.path; diff --git a/backend/proxy-server/src/domain/log/log.repository.ts b/backend/proxy-server/src/domain/log/log.repository.ts index b11ce72d..6b7e14d9 100644 --- a/backend/proxy-server/src/domain/log/log.repository.ts +++ b/backend/proxy-server/src/domain/log/log.repository.ts @@ -1,7 +1,5 @@ -import type { RequestLogEntity } from './request-log.entity'; -import type { ResponseLogEntity } from './response-log.entity'; +import type { HttpLogEntity } from './http-log.entity'; export interface LogRepository { - insertRequestLog(log: RequestLogEntity): Promise; - insertResponseLog(log: ResponseLogEntity): Promise; + insertHttpLog(log: HttpLogEntity): Promise; } diff --git a/backend/proxy-server/src/domain/log/log.service.ts b/backend/proxy-server/src/domain/log/log.service.ts index 93674f45..a1d6856c 100644 --- a/backend/proxy-server/src/domain/log/log.service.ts +++ b/backend/proxy-server/src/domain/log/log.service.ts @@ -1,24 +1,12 @@ import { DatabaseQueryError } from '../../common/error/database-query.error'; -import type { RequestLogEntity } from './request-log.entity'; -import type { ResponseLogEntity } from './response-log.entity'; +import type { HttpLogEntity } from './http-log.entity'; import type { LogRepository } from './log.repository'; export class LogService { constructor(private readonly logRepository: LogRepository) {} - public async saveRequestLog(log: RequestLogEntity): Promise { + public async saveHttpLog(log: HttpLogEntity): Promise { try { - await this.logRepository.insertRequestLog(log); - } catch (error) { - if (error instanceof DatabaseQueryError) { - throw error; - } - throw new DatabaseQueryError(error as Error); - } - } - - public async saveResponseLog(log: ResponseLogEntity): Promise { - try { - await this.logRepository.insertResponseLog(log); + await this.logRepository.insertHttpLog(log); } catch (error) { if (error instanceof DatabaseQueryError) { throw error; diff --git a/backend/proxy-server/src/domain/log/request-log.entity.ts b/backend/proxy-server/src/domain/log/request-log.entity.ts deleted file mode 100644 index bd60f37a..00000000 --- a/backend/proxy-server/src/domain/log/request-log.entity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { BaseLog } from '../core/base-log'; - -type RequestLog = BaseLog; - -export class RequestLogEntity { - readonly method: string; - readonly host: string; - readonly path: string | undefined; - - constructor(log: RequestLog) { - this.method = log.method; - this.host = log.host; - this.path = log.path; - } -} diff --git a/backend/proxy-server/src/server/proxy-server.ts b/backend/proxy-server/src/server/proxy-server.ts index e74160df..d80c4aa2 100644 --- a/backend/proxy-server/src/server/proxy-server.ts +++ b/backend/proxy-server/src/server/proxy-server.ts @@ -8,8 +8,7 @@ import { HOST_HEADER } from '../common/constant/http.constant'; import { ErrorHandler } from './error.handler'; import { FastifyLogger } from '../common/logger/fastify.logger'; import type { LogService } from '../domain/log/log.service'; -import type { RequestLogEntity } from '../domain/log/request-log.entity'; -import type { ResponseLogEntity } from '../domain/log/response-log.entity'; +import type { HttpLogEntity } from '../domain/log/http-log.entity'; import type { ProjectService } from '../domain/project/project.service'; import { DatabaseQueryError } from '../common/error/database-query.error'; import type { ErrorLogRepository } from '../common/logger/error-log.repository'; @@ -49,31 +48,14 @@ export class ProxyServer { } private initializeHooks(): void { - this.server.addHook('onRequest', (request, reply, done) => { - this.logRequest(request); - done(); - }); - this.server.addHook('onResponse', (request, reply, done) => { this.logResponse(request, reply); done(); }); } - private async logRequest(request: FastifyRequest): Promise { - const requestLog: RequestLogEntity = { - method: request.method, - host: request.host, - path: request.raw.url, - }; - - this.logger.info(requestLog); - - await this.logService.saveRequestLog(requestLog); - } - private async logResponse(request: FastifyRequest, reply: FastifyReply): Promise { - const responseLog: ResponseLogEntity = { + const httpLog: HttpLogEntity = { method: request.method, host: request.host, path: request.raw.url, @@ -81,8 +63,8 @@ export class ProxyServer { responseTime: reply.elapsedTime, }; - this.logger.info(responseLog); - await this.logService.saveResponseLog(responseLog); + this.logger.info(httpLog); + await this.logService.saveHttpLog(httpLog); } private initializeErrorHandler(): void { diff --git a/backend/proxy-server/test/common/logger/fastify.logger.test.ts b/backend/proxy-server/test/common/logger/fastify.logger.test.ts index 36170d14..c258b837 100644 --- a/backend/proxy-server/test/common/logger/fastify.logger.test.ts +++ b/backend/proxy-server/test/common/logger/fastify.logger.test.ts @@ -1,7 +1,6 @@ import type { FastifyInstance } from 'fastify'; import { FastifyLogger } from '../../../src/common/logger/fastify.logger'; -import type { RequestLogEntity } from '../../../src/domain/log/request-log.entity'; -import type { ResponseLogEntity } from '../../../src/domain/log/response-log.entity'; +import type { HttpLogEntity } from '../../../src/domain/log/http-log.entity'; const mockFastifyInstance = { log: { @@ -19,28 +18,17 @@ describe('FastifyLogger 테스트', () => { }); describe('info()는 ', () => { - it('요청 로그를 올바르게 처리해야 한다.', () => { - const requestLog = { - method: 'GET', - host: 'www.example.com', - path: '/test', - } as RequestLogEntity; - - logger.info(requestLog); - expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(requestLog); - }); - it('응답 로그를 올바르게 처리해야 한다.', () => { - const responseLog = { + const httpLog = { method: 'POST', host: 'www.example.com', path: '/test', statusCode: 200, responseTime: 100, - } as ResponseLogEntity; + } as HttpLogEntity; - logger.info(responseLog); - expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(responseLog); + logger.info(httpLog); + expect(mockFastifyInstance.log.info).toHaveBeenCalledWith(httpLog); }); it('should log simple message', () => { From ca2ccf77ec380b9fe42f34d3a45497a201616c8d Mon Sep 17 00:00:00 2001 From: hyo-limilimee <1001lily@naver.com> Date: Wed, 13 Nov 2024 11:38:47 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - request_log -> http_log --- .../src/database/query/log.repository.clickhouse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts index 40bdcaac..61de226d 100644 --- a/backend/proxy-server/src/database/query/log.repository.clickhouse.ts +++ b/backend/proxy-server/src/database/query/log.repository.clickhouse.ts @@ -25,7 +25,7 @@ export class LogRepositoryClickhouse implements LogRepository { try { await this.clickhouse.insert({ - table: 'response_log', + table: 'http_log', values: values, format: 'JSONEachRow', });