diff --git a/src/app/layout.js b/src/app/layout.js index 40386d49..59b1ddcc 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -10,6 +10,8 @@ import Header from '@/components/header/Header' import QModal from '@/components/common/modal/QModal' import PopupManager from '@/components/common/popupManager/PopupManager' import ErrorBoundary from '@/components/common/ErrorBoundary' +import PageTracker from '@/components/common/PageTracker' // [PAGE-TRACKER 2026-05-14] 라우트 변경 시 콘솔/탭타이틀에 경로 표시 +import ModalTracker from '@/components/common/ModalTracker' // [MODAL-TRACKER 2026-05-14] 모달/팝업 open·close 추적 import './globals.css' import '../styles/style.scss' @@ -73,6 +75,8 @@ export default async function RootLayout({ children }) { + + {headerPathname === '/login' || headerPathname === '/join' ? ( {children} ) : ( diff --git a/src/util/logger.js b/src/util/logger.js index 5830c8db..e88b23ff 100644 --- a/src/util/logger.js +++ b/src/util/logger.js @@ -1,30 +1,88 @@ // utils/logger.js const isLoggingEnabled = process.env.NEXT_PUBLIC_ENABLE_LOGGING; +// [LOGGER-CALLER 2026-05-14] stack 에서 logger.js 이외의 첫 frame 을 추출해 prefix 로 붙임. +// 페이지/hook/컴포넌트 어디서 찍힌 로그인지 콘솔에서 즉시 식별 가능. webpack chunk 매칭 위해 파일명만 추출. +const FILE_LINE_RE = /([\w\-\.]+\.(?:jsx?|tsx?)):(\d+)/ + +// [LOGGER-FORMAT 2026-05-14] 통일 포맷: [HH:mm:ss.SSS] [LEVEL] [caller] +// 레벨별 색상 배지로 콘솔에서 한눈에 구분. +const STYLE_TS = 'color:#888' +const STYLE_CALLER = 'color:#888;font-style:italic' +const STYLE_LEVEL = { + log: 'background:#616161;color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold', + info: 'background:#1e88e5;color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold', + warn: 'background:#fb8c00;color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold', + error: 'background:#e53935;color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold', + debug: 'background:#8e24aa;color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold', +} + +function formatTime() { + const d = new Date() + const hh = String(d.getHours()).padStart(2, '0') + const mm = String(d.getMinutes()).padStart(2, '0') + const ss = String(d.getSeconds()).padStart(2, '0') + const ms = String(d.getMilliseconds()).padStart(3, '0') + return `${hh}:${mm}:${ss}.${ms}` +} + +function getCallerTag() { + try { + const stack = new Error().stack + if (!stack) return '' + const lines = stack.split('\n') + for (let i = 1; i < lines.length; i++) { + const m = lines[i].match(FILE_LINE_RE) + if (m && m[1] !== 'logger.js') return `${m[1]}:${m[2]}` + } + } catch (_) {} + return '' +} + +function emit(level, args) { + const ts = formatTime() + const caller = getCallerTag() + const levelStyle = STYLE_LEVEL[level] || STYLE_LEVEL.log + const levelLabel = level.toUpperCase() + + // prefix: '[ts] %cLEVEL%c [caller] ' → styled level + dimmed caller + // user 첫 인자가 string 이면 prefix 와 합쳐 %c 호환 유지. + const prefixText = caller + ? `%c[${ts}] %c${levelLabel}%c [${caller}]` + : `%c[${ts}] %c${levelLabel}%c` + const prefixStyles = [STYLE_TS, levelStyle, STYLE_CALLER] + + if (typeof args[0] === 'string') { + console[level](`${prefixText} ${args[0]}`, ...prefixStyles, ...args.slice(1)) + } else { + console[level](prefixText, ...prefixStyles, ...args) + } +} + export const logger = { log: (...args) => { if (isLoggingEnabled) { - console.log(...args); + emit('log', args); } }, error: (...args) => { // 에러는 항상 로깅하거나, 또는 환경에 따라 다르게 처리 - console.error(...args); + emit('error', args); // 운영 환경에서는 서버로 에러를 보내는 코드를 추가할 수도 있음 }, warn: (...args) => { if (isLoggingEnabled) { - console.warn(...args); + emit('warn', args); } }, info: (...args) => { if (isLoggingEnabled) { - console.info(...args); + emit('info', args); } }, debug: (...args) => { if (isLoggingEnabled) { - console.debug(...args); + emit('debug', args); } } -}; \ No newline at end of file +};