Compare commits

...

2 Commits

Author SHA1 Message Date
3eb5974414 feat: api logger 적용 2025-06-12 18:05:29 +09:00
a573d7ffb1 feat: api logger 추가 2025-06-12 18:03:24 +09:00
5 changed files with 73 additions and 4 deletions

6
.gitignore vendored
View File

@ -45,4 +45,8 @@ next-env.d.ts
bun.lockb
pnpm-lock.yaml
pnpm-workspace.yaml
pnpm-workspace.yaml
# logs
logs/
*.log

View File

@ -6,7 +6,12 @@ const nextConfig: NextConfig = {
sassOptions: {
includePaths: [path.join(__dirname, './src/styles')],
},
serverExternalPackages: ['@react-pdf/renderer'],
serverExternalPackages: ['@react-pdf/renderer', 'pino'],
logging: {
fetches: {
fullUrl: true,
},
},
async rewrites() {
return [
{

View File

@ -29,6 +29,7 @@
"next": "15.2.4",
"nodemailer": "^7.0.3",
"pdf-lib": "^1.17.1",
"pino": "^9.7.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-to-pdf": "^2.0.0",

View File

@ -1,5 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { HttpStatusCode } from 'axios'
import { prisma } from '@/libs/prisma'
import { writeLog } from '@/libs/logger'
import { type Suitable } from '@/types/Suitable'
/**
@ -42,6 +44,7 @@ import { type Suitable } from '@/types/Suitable'
* ]
*/
export async function GET(request: NextRequest) {
let responseStatus: number = HttpStatusCode.InternalServerError
try {
const searchParams = request.nextUrl.searchParams
const pageNumber = parseInt(searchParams.get('pageNumber') || '0')
@ -51,7 +54,8 @@ export async function GET(request: NextRequest) {
/* 파라미터 체크 */
if (pageNumber === 0 || itemPerPage === 0) {
return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: 400 })
responseStatus = HttpStatusCode.BadRequest
return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: responseStatus })
}
let query = `
@ -104,6 +108,7 @@ export async function GET(request: NextRequest) {
const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage)
responseStatus = HttpStatusCode.Ok
return NextResponse.json(suitable, {
headers: {
'spinner-state': 'true',
@ -111,6 +116,9 @@ export async function GET(request: NextRequest) {
})
} catch (error) {
console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`)
return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: 500 })
responseStatus = HttpStatusCode.InternalServerError
return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: responseStatus })
} finally {
await writeLog(request, responseStatus)
}
}

51
src/libs/logger.ts Normal file
View File

@ -0,0 +1,51 @@
import { NextRequest } from 'next/server'
import { join } from 'path'
import pino from 'pino'
/* 로그 파일 경로 */
const logFilePath = join('.', 'logs', 'onsite-survey.log')
/* 로거 생성 함수 */
const createLogger = (): pino.Logger => {
try {
return pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
transport: {
targets: [
{
target: 'pino/file',
options: {
destination: logFilePath,
mkdir: true,
sync: false,
},
},
],
},
})
} catch (error) {
console.error('Pino transport 생성 실패, 기본 로거 사용:', error)
return pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
})
}
}
/* 로거 인스턴스 */
export const logger = createLogger()
/* 로그 기록 함수 */
export const writeLog = async (request: NextRequest, responseStatus: number): Promise<void> => {
const logData = {
timestamp: new Date().toISOString(),
status: responseStatus,
method: request.method,
url: request.url,
// headers: Object.fromEntries(request.headers),
query: Object.fromEntries(new URL(request.url).searchParams),
body: request.body ? await request.clone().text() : undefined,
}
logger.info(logData, 'API Request')
}