지난 Pino 포스트에서 고성능 JSON 로깅을 다뤘다면, 이번에는 정반대의 철학을 가진 Consola를 살펴보겠습니다.
“아름다운 로그가 개발 경험을 바꾼다”는 믿음으로 시작된 Consola는 Python의 Loguru와 가장 유사한 “설정 없이 바로 사용” 경험을 제공합니다. 복잡한 설정 대신 직관적인 API와 시각적으로 매력적인 출력으로 개발자들의 사랑을 받고 있습니다.
Consola란?
Consola는 Nuxt.js 팀에서 개발한 “Elegant Console Logger for Node.js and Browser”입니다. 2018년에 처음 공개되었으며, 개발자 경험(Developer Experience)을 최우선으로 설계되었습니다.
Consola의 핵심 철학
Zero Configuration: 별도 설정 없이 import
하자마자 바로 사용 가능
Visual Excellence: 이모지, 색상, 아이콘을 통한 직관적인 로그 구분
Developer First: 개발 과정에서 필요한 기능들을 우선적으로 고려
Universal Support: Node.js와 브라우저 모두에서 동일한 API 제공
설치 및 즉시 사용
npm install consola
# 또는
yarn add consola
바로 사용해보기
// lib/logger.js
import { consola } from 'consola';
// 설정 없이 바로 사용!
consola.info('서버가 시작되었습니다');
consola.success('데이터베이스 연결 성공');
consola.warn('캐시 만료 임박');
consola.error('API 호출 실패');
consola.debug('사용자 데이터:', { id: 123, name: 'John' });
export default consola;
이게 전부입니다! 다른 라이브러리처럼 복잡한 Transport 설정이나 Format 지정이 필요 없습니다.
시각적으로 아름다운 출력
Consola의 가장 큰 매력은 바로 출력 결과입니다:
ℹ 서버가 시작되었습니다
✔ 데이터베이스 연결 성공
⚠ 캐시 만료 임박
✖ API 호출 실패
🐛 사용자 데이터: { id: 123, name: 'John' }
각 로그 레벨마다 고유한 아이콘과 색상이 자동으로 적용되어 한눈에 구분할 수 있습니다.
다양한 로그 레벨과 아이콘
import { consola } from 'consola';
consola.silent('보이지 않는 로그'); // (출력 안됨)
consola.fatal('치명적 오류'); // ✖ (빨간색)
consola.error('오류 발생'); // ✖ (빨간색)
consola.warn('경고 메시지'); // ⚠ (노란색)
consola.info('정보성 메시지'); // ℹ (파란색)
consola.success('작업 성공'); // ✔ (초록색)
consola.debug('디버그 정보'); // 🐛 (회색)
consola.trace('추적 정보'); // 🔍 (회색)
consola.verbose('상세 정보'); // 📝 (회색)
// 특별한 로그 타입들
consola.ready('서버 준비 완료'); // 🚀 (초록색)
consola.start('작업 시작'); // 🏁 (파란색)
consola.box('중요한 공지사항'); // 박스로 감싸서 출력
Next.js 프로젝트 설정
환경별 로그 레벨 관리
// lib/logger.js
import { consola } from 'consola';
// 환경별 로그 레벨 설정
const LOG_LEVELS = {
development: 'debug',
test: 'warn',
staging: 'info',
production: 'error'
};
// 환경에 따른 로그 레벨 적용
consola.level = LOG_LEVELS[process.env.NODE_ENV] || 'info';
// 개발 환경에서만 디버그 정보 표시
if (process.env.NODE_ENV === 'development') {
consola.wrapConsole(); // console.log도 consola 스타일로 변경
}
export default consola;
커스텀 리포터 설정
// lib/logger.js 확장
import { consola, createConsola } from 'consola';
const logger = createConsola({
level: process.env.LOG_LEVEL || 'info',
// 커스텀 포맷터
formatOptions: {
date: true, // 타임스탬프 표시
colors: true, // 색상 활성화
compact: false // 상세 출력
},
// 브라우저 호환성
fancy: typeof window === 'undefined', // 서버에서만 fancy 출력
// 커스텀 리포터
reporters: process.env.NODE_ENV === 'production' ? [
// 프로덕션에서는 JSON 출력
{
log: (logObj) => {
console.log(JSON.stringify({
level: logObj.level,
message: logObj.args[0],
timestamp: new Date().toISOString(),
...logObj.args[1]
}));
}
}
] : undefined
});
export default logger;
실제 프로젝트 적용 예시
1. API 라우트 로깅
// pages/api/users/[id].js
import consola from '../../../lib/logger';
export default async function handler(req, res) {
const { id } = req.query;
const { method } = req;
consola.start(`${method} /api/users/${id} 요청 처리 시작`);
try {
switch (method) {
case 'GET':
consola.info('사용자 정보 조회 중...', { userId: id });
const user = await getUserById(id);
if (!user) {
consola.warn('존재하지 않는 사용자 요청', { userId: id });
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
consola.success('사용자 조회 성공', {
userId: id,
userName: user.name
});
return res.status(200).json(user);
case 'PUT':
consola.info('사용자 정보 업데이트 중...', {
userId: id,
updatedFields: Object.keys(req.body)
});
const updatedUser = await updateUser(id, req.body);
consola.success('사용자 정보 업데이트 완료', {
userId: id,
changes: Object.keys(req.body).length
});
return res.status(200).json(updatedUser);
case 'DELETE':
consola.warn('사용자 삭제 요청', { userId: id });
await deleteUser(id);
consola.success('사용자 삭제 완료', { userId: id });
return res.status(204).end();
default:
consola.error('지원하지 않는 HTTP 메서드', {
method,
supportedMethods: ['GET', 'PUT', 'DELETE']
});
return res.status(405).json({
error: `${method} 메서드는 지원하지 않습니다`
});
}
} catch (error) {
consola.error('API 요청 처리 중 오류 발생', {
method,
userId: id,
error: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
return res.status(500).json({ error: '서버 내부 오류가 발생했습니다' });
}
}
2. 빌드 과정 로깅
// scripts/build-logger.js
import { consola } from 'consola';
export class BuildLogger {
constructor() {
this.startTime = Date.now();
this.steps = [];
}
step(name) {
const stepStart = Date.now();
consola.start(`📦 ${name} 시작`);
return {
success: (message) => {
const duration = Date.now() - stepStart;
this.steps.push({ name, duration, status: 'success' });
consola.success(`✨ ${name} 완료 (${duration}ms)`, { message });
},
error: (error) => {
const duration = Date.now() - stepStart;
this.steps.push({ name, duration, status: 'error' });
consola.error(`💥 ${name} 실패 (${duration}ms)`, { error: error.message });
},
warn: (message) => {
consola.warn(`⚠️ ${name} 경고`, { message });
}
};
}
summary() {
const totalDuration = Date.now() - this.startTime;
const successCount = this.steps.filter(s => s.status === 'success').length;
const errorCount = this.steps.filter(s => s.status === 'error').length;
consola.box([
'🏗️ 빌드 완료 요약',
'',
`⏱️ 총 소요시간: ${totalDuration}ms`,
`✅ 성공: ${successCount}개`,
`❌ 실패: ${errorCount}개`,
'',
'📊 단계별 소요시간:',
...this.steps.map(step =>
` ${step.status === 'success' ? '✅' : '❌'} ${step.name}: ${step.duration}ms`
)
].join('\n'));
if (errorCount === 0) {
consola.ready('🎉 빌드가 성공적으로 완료되었습니다!');
} else {
consola.fatal(`💥 ${errorCount}개의 오류로 인해 빌드가 실패했습니다`);
}
}
}
// 사용 예시 - next.config.js에서
export default {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
if (!dev) {
const buildLogger = new BuildLogger();
const bundleAnalyzer = buildLogger.step('Bundle Analyzer');
// 번들 분석 로직
bundleAnalyzer.success('번들 크기 분석 완료');
const optimization = buildLogger.step('Optimization');
// 최적화 로직
optimization.success('코드 최적화 완료');
buildLogger.summary();
}
return config;
}
};
3. 개발 서버 로깅
// lib/dev-logger.js
import { consola } from 'consola';
export class DevLogger {
constructor() {
this.requestCount = 0;
this.errorCount = 0;
this.startTime = Date.now();
}
logServerStart(port) {
consola.box([
'🚀 Next.js Development Server',
'',
`🌐 Local: http://localhost:${port}`,
`📱 Network: http://192.168.1.100:${port}`, // 실제 IP로 교체
'',
'💡 개발 서버가 준비되었습니다!'
].join('\n'));
consola.ready('Hot reload가 활성화되었습니다');
}
logRequest(req) {
this.requestCount++;
const method = req.method;
const url = req.url;
// API 요청과 페이지 요청 구분
if (url.startsWith('/api/')) {
consola.info(`📡 API 요청: ${method} ${url}`);
} else if (url.startsWith('/_next/')) {
// Next.js 내부 요청은 debug 레벨로
consola.debug(`🔧 내부 요청: ${method} ${url}`);
} else {
consola.info(`📄 페이지 요청: ${method} ${url}`);
}
}
logHotReload(files) {
consola.info('🔥 Hot Reload 감지', {
changedFiles: files.map(f => f.replace(process.cwd(), ''))
});
}
logError(error, context = {}) {
this.errorCount++;
consola.error('💥 개발 서버 오류', {
message: error.message,
...context,
totalErrors: this.errorCount
});
if (process.env.DEBUG) {
consola.trace('스택 추적:', error.stack);
}
}
logStats() {
const uptime = Date.now() - this.startTime;
const uptimeMinutes = Math.floor(uptime / 60000);
consola.info('📊 개발 서버 통계', {
uptime: `${uptimeMinutes}분`,
totalRequests: this.requestCount,
totalErrors: this.errorCount,
averageRequestsPerMinute: Math.round(this.requestCount / (uptimeMinutes || 1))
});
}
}
// middleware.js에서 사용
import { NextResponse } from 'next/server';
import { devLogger } from './lib/dev-logger';
export function middleware(request) {
if (process.env.NODE_ENV === 'development') {
devLogger.logRequest(request);
}
return NextResponse.next();
}
4. 테스트 로깅
// lib/test-logger.js
import { consola } from 'consola';
export class TestLogger {
constructor(suiteName) {
this.suiteName = suiteName;
this.tests = [];
this.startTime = Date.now();
consola.start(`🧪 테스트 시작: ${suiteName}`);
}
testStart(testName) {
const testStart = Date.now();
consola.info(` 🔍 ${testName}`);
return {
pass: (message) => {
const duration = Date.now() - testStart;
this.tests.push({ name: testName, status: 'pass', duration });
consola.success(` ✅ 통과 (${duration}ms)`, { message });
},
fail: (error) => {
const duration = Date.now() - testStart;
this.tests.push({ name: testName, status: 'fail', duration });
consola.error(` ❌ 실패 (${duration}ms)`, { error: error.message });
},
skip: (reason) => {
this.tests.push({ name: testName, status: 'skip', duration: 0 });
consola.warn(` ⏭️ 건너뜀`, { reason });
}
};
}
summary() {
const totalDuration = Date.now() - this.startTime;
const passed = this.tests.filter(t => t.status === 'pass').length;
const failed = this.tests.filter(t => t.status === 'fail').length;
const skipped = this.tests.filter(t => t.status === 'skip').length;
const summaryLines = [
`🧪 테스트 완료: ${this.suiteName}`,
'',
`⏱️ 총 소요시간: ${totalDuration}ms`,
`✅ 통과: ${passed}개`,
`❌ 실패: ${failed}개`,
`⏭️ 건너뜀: ${skipped}개`,
`📊 성공률: ${Math.round((passed / (passed + failed)) * 100)}%`
];
if (failed === 0) {
consola.success('🎉 모든 테스트가 통과했습니다!');
} else {
consola.error(`💥 ${failed}개의 테스트가 실패했습니다`);
}
consola.box(summaryLines.join('\n'));
}
}
// Jest 테스트에서 사용
describe('User API', () => {
const testLogger = new TestLogger('User API Tests');
afterAll(() => {
testLogger.summary();
});
test('사용자 생성', async () => {
const test = testLogger.testStart('사용자 생성 테스트');
try {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' });
expect(response.status).toBe(201);
test.pass('사용자가 성공적으로 생성됨');
} catch (error) {
test.fail(error);
}
});
});
브라우저 환경 지원
Consola의 독특한 특징 중 하나는 브라우저에서도 동일한 API를 사용할 수 있다는 것입니다:
// components/ClientLogger.js
import { consola } from 'consola';
export default function ClientComponent() {
const handleClick = () => {
consola.info('버튼 클릭됨');
consola.debug('클릭 이벤트 데이터', {
timestamp: Date.now(),
userAgent: navigator.userAgent
});
};
const handleError = () => {
try {
throw new Error('의도적인 오류');
} catch (error) {
consola.error('클라이언트 오류 발생', { error: error.message });
}
};
return (
<div>
<button onClick={handleClick}>로그 생성</button>
<button onClick={handleError}>오류 발생</button>
</div>
);
}
브라우저 개발자 도구에서도 서버와 동일한 형태의 로그를 확인할 수 있습니다.
고급 기능 활용
1. 커스텀 리포터 생성
// lib/custom-reporter.js
export class SlackReporter {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
log(logObj) {
// 에러 레벨 이상만 Slack으로 전송
if (logObj.level >= 50) { // error 레벨
this.sendToSlack({
text: `🚨 ${logObj.args[0]}`,
attachments: [{
color: 'danger',
fields: [{
title: 'Details',
value: JSON.stringify(logObj.args[1] || {}, null, 2),
short: false
}]
}]
});
}
}
async sendToSlack(payload) {
try {
await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
} catch (error) {
console.error('Slack 전송 실패:', error);
}
}
}
// 사용법
import { createConsola } from 'consola';
import { SlackReporter } from './custom-reporter';
const logger = createConsola({
reporters: [
// 기본 콘솔 출력
...(process.env.NODE_ENV === 'development' ? ['fancy'] : ['basic']),
// Slack 알림 (프로덕션에서만)
...(process.env.NODE_ENV === 'production' ? [new SlackReporter(process.env.SLACK_WEBHOOK)] : [])
]
});
2. 진행 표시기 (Progress Bar)
// lib/progress-logger.js
import { consola } from 'consola';
export class ProgressLogger {
constructor(total, title = '진행중') {
this.total = total;
this.current = 0;
this.title = title;
this.startTime = Date.now();
this.update();
}
increment(amount = 1) {
this.current = Math.min(this.current + amount, this.total);
this.update();
if (this.current === this.total) {
this.complete();
}
}
update() {
const percentage = Math.round((this.current / this.total) * 100);
const progressBar = this.createProgressBar(percentage);
const elapsed = Date.now() - this.startTime;
const eta = this.current > 0 ? (elapsed / this.current * (this.total - this.current)) : 0;
process.stdout.write(`\r🔄 ${this.title}: ${progressBar} ${percentage}% (${this.current}/${this.total}) ETA: ${Math.round(eta/1000)}s`);
}
createProgressBar(percentage) {
const width = 20;
const filled = Math.round(width * percentage / 100);
const empty = width - filled;
return '█'.repeat(filled) + '░'.repeat(empty);
}
complete() {
const duration = Date.now() - this.startTime;
console.log(); // 새 줄
consola.success(`✨ ${this.title} 완료! (${duration}ms)`);
}
}
// 사용 예시
const progress = new ProgressLogger(100, '파일 처리');
for (let i = 0; i < 100; i++) {
await processFile(files[i]);
progress.increment();
}
3. 로그 그룹핑
// lib/grouped-logger.js
import { consola } from 'consola';
export class GroupedLogger {
constructor(groupName) {
this.groupName = groupName;
this.logs = [];
this.startTime = Date.now();
consola.info(`📂 ${groupName} 시작`);
}
info(message, data) {
this.logs.push({ level: 'info', message, data, timestamp: Date.now() });
consola.info(` ℹ️ ${message}`, data);
}
success(message, data) {
this.logs.push({ level: 'success', message, data, timestamp: Date.now() });
consola.success(` ✅ ${message}`, data);
}
warn(message, data) {
this.logs.push({ level: 'warn', message, data, timestamp: Date.now() });
consola.warn(` ⚠️ ${message}`, data);
}
error(message, data) {
this.logs.push({ level: 'error', message, data, timestamp: Date.now() });
consola.error(` ❌ ${message}`, data);
}
end() {
const duration = Date.now() - this.startTime;
const summary = this.logs.reduce((acc, log) => {
acc[log.level] = (acc[log.level] || 0) + 1;
return acc;
}, {});
consola.info(`📊 ${this.groupName} 완료 (${duration}ms)`, summary);
}
}
// 사용 예시
const deployment = new GroupedLogger('배포 과정');
deployment.info('환경 변수 확인');
deployment.success('환경 설정 완료');
deployment.info('빌드 시작');
deployment.success('빌드 완료');
deployment.info('배포 시작');
deployment.success('배포 완료');
deployment.end();
성능과 최적화
1. 조건부 로깅
// lib/conditional-logger.js
import { consola } from 'consola';
// 환경별 로그 레벨 설정
const isDev = process.env.NODE_ENV === 'development';
const isDebug = process.env.DEBUG === 'true';
export const logger = {
// 항상 출력
info: consola.info.bind(consola),
warn: consola.warn.bind(consola),
error: consola.error.bind(consola),
success: consola.success.bind(consola),
// 개발 환경에서만 출력
debug: isDev ? consola.debug.bind(consola) : () => {},
trace: isDev ? consola.trace.bind(consola) : () => {},
// DEBUG=true일 때만 출력
verbose: isDebug ? consola.verbose.bind(consola) : () => {},
// 비용이 큰 로깅을 위한 래퍼
expensive: (level, messageFactory, dataFactory) => {
if (consola.level <= consola.levels[level]) {
const message = typeof messageFactory === 'function' ? messageFactory() : messageFactory;
const data = typeof dataFactory === 'function' ? dataFactory() : dataFactory;
consola[level](message, data);
}
}
};
// 사용 예시
logger.expensive('debug',
() => '복잡한 디버그 정보',
() => computeExpensiveData() // 로그 레벨이 debug 이상일 때만 실행
);
2. 로그 버퍼링
// lib/buffered-logger.js
import { consola } from 'consola';
class BufferedLogger {
constructor(flushInterval = 1000) {
this.buffer = [];
this.flushInterval = flushInterval;
this.timer = null;
this.startFlushing();
}
log(level, message, data) {
this.buffer.push({
level,
message,
data,
timestamp: Date.now()
});
// 버퍼가 가득 차면 즉시 플러시
if (this.buffer.length >= 100) {
this.flush();
}
}
flush() {
if (this.buffer.length === 0) return;
const logs = [...this.buffer];
this.buffer = [];
logs.forEach(log => {
consola[log.level](log.message, log.data);
});
}
startFlushing() {
this.timer = setInterval(() => {
this.flush();
}, this.flushInterval);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.flush(); // 마지막 플러시
}
}
}
export const bufferedLogger = new BufferedLogger();
// 프로세스 종료 시 버퍼 플러시
process.on('exit', () => bufferedLogger.stop());
process.on('SIGINT', () => {
bufferedLogger.stop();
process.exit();
});
Consola vs 다른 라이브러리
Winston과의 비교
특성 | Consola | Winston |
---|---|---|
설정 복잡도 | 매우 간단 | 복잡 |
시각적 출력 | 뛰어남 | 기본적 |
기능 풍부함 | 기본적 | 매우 풍부 |
학습 곡선 | 완만 | 가파름 |
프로덕션 적합성 | 제한적 | 매우 좋음 |
Pino와의 비교
특성 | Consola | Pino |
---|---|---|
성능 | 보통 | 최고 |
개발 경험 | 최고 | 보통 |
구조화 로깅 | 기본적 | 뛰어남 |
브라우저 지원 | 완전 | 제한적 |
JSON 출력 | 선택적 | 기본 |
언제 Consola를 선택해야 할까?
✅ Consola 추천 상황
- 개발 환경 로깅 개선
- 소규모 프로젝트나 프로토타이핑
- CLI 도구나 스크립트 개발
- 팀 온보딩이 중요한 프로젝트
- 시각적 피드백이 중요한 작업
❌ Consola 비추천 상황
- 대규모 프로덕션 환경
- 복잡한 로깅 요구사항
- 고성능이 중요한 서비스
- 로그 분석 시스템과 연동
실전 팁과 베스트 프랙티스
1. 환경별 설정 전략
// lib/smart-logger.js
import { consola, createConsola } from 'consola';
const createEnvironmentLogger = () => {
switch (process.env.NODE_ENV) {
case 'development':
return createConsola({
level: 'debug',
fancy: true,
formatOptions: {
date: true,
colors: true
}
});
case 'test':
return createConsola({
level: 'warn',
fancy: false,
reporters: ['basic']
});
case 'production':
return createConsola({
level: 'error',
fancy: false,
reporters: [{
log: (logObj) => {
// 구조화된 JSON 로그 출력
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: logObj.type,
message: logObj.args[0],
data: logObj.args[1],
service: 'nextjs-app'
}));
}
}]
});
default:
return consola;
}
};
export default createEnvironmentLogger();
2. 개발 워크플로우 통합
// lib/workflow-logger.js
import { consola } from 'consola';
export class WorkflowLogger {
constructor() {
this.workflows = new Map();
}
startWorkflow(name, description) {
const workflow = {
name,
description,
startTime: Date.now(),
steps: [],
status: 'running'
};
this.workflows.set(name, workflow);
consola.box([
`🚀 워크플로우 시작: ${name}`,
`📝 ${description}`,
`⏰ ${new Date().toLocaleTimeString()}`
].join('\n'));
return {
step: (stepName) => this.addStep(name, stepName),
complete: () => this.completeWorkflow(name),
fail: (error) => this.failWorkflow(name, error)
};
}
addStep(workflowName, stepName) {
const workflow = this.workflows.get(workflowName);
if (!workflow) return;
const step = {
name: stepName,
startTime: Date.now(),
status: 'running'
};
workflow.steps.push(step);
consola.start(` 📋 ${stepName}`);
return {
complete: (message) => {
step.status = 'completed';
step.endTime = Date.now();
step.duration = step.endTime - step.startTime;
consola.success(` ✅ ${stepName} 완료 (${step.duration}ms)`, { message });
},
fail: (error) => {
step.status = 'failed';
step.endTime = Date.now();
step.duration = step.endTime - step.startTime;
step.error = error;
consola.error(` ❌ ${stepName} 실패 (${step.duration}ms)`, { error: error.message });
}
};
}
completeWorkflow(name) {
const workflow = this.workflows.get(name);
if (!workflow) return;
workflow.status = 'completed';
workflow.endTime = Date.now();
workflow.totalDuration = workflow.endTime - workflow.startTime;
const completedSteps = workflow.steps.filter(s => s.status === 'completed').length;
const failedSteps = workflow.steps.filter(s => s.status === 'failed').length;
consola.box([
`🎉 워크플로우 완료: ${workflow.name}`,
'',
`⏱️ 총 소요시간: ${workflow.totalDuration}ms`,
`✅ 완료된 단계: ${completedSteps}개`,
`❌ 실패한 단계: ${failedSteps}개`,
'',
'📊 단계별 소요시간:',
...workflow.steps.map(step =>
` ${step.status === 'completed' ? '✅' : '❌'} ${step.name}: ${step.duration || 0}ms`
)
].join('\n'));
}
}
// 사용 예시
const workflowLogger = new WorkflowLogger();
const deployment = workflowLogger.startWorkflow(
'Production Deployment',
'Next.js 앱을 프로덕션 환경에 배포합니다'
);
const buildStep = deployment.step('Build Application');
// 빌드 로직
buildStep.complete('애플리케이션 빌드 성공');
const testStep = deployment.step('Run Tests');
// 테스트 로직
testStep.complete('모든 테스트 통과');
const deployStep = deployment.step('Deploy to Server');
// 배포 로직
deployStep.complete('서버 배포 성공');
deployment.complete();
마무리
Consola는 “개발자 경험이 곧 생산성”이라는 철학을 구현한 라이브러리입니다. 복잡한 설정 없이도 아름답고 직관적인 로그를 통해 개발 과정을 더욱 즐겁게 만들어줍니다.
Winston이나 Pino처럼 모든 기능을 제공하지는 않지만, 개발 환경에서의 편의성과 시각적 만족도는 최고 수준입니다. 특히 팀 협업에서 모든 개발자가 쉽게 적응할 수 있다는 장점이 있습니다.
다음 포스트에서는 Java 개발자들에게 친숙한 Log4js를 다뤄보겠습니다. Log4j의 철학을 JavaScript로 구현한 엔터프라이즈급 로깅 라이브러리의 특징을 살펴보세요.
1 thought on “Consola로 Next.js 개발자 친화적 로깅 경험 만들기”