NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
@Mod 클래스와 생성자 패턴이벤트 버스 — Mod / NeoForge 두 가지 차이Registry와 DeferredRegister 개념ModConfigSpec과 설정 GUISLF4J Logger와 로그 레벨클라이언트/서버 분리와 Dist모드 내 다국어 — lang/<locale>.json
모딩 기초

SLF4J Logger와 로그 레벨

NeoForge 26에서 사용하는 SLF4J Logger와 LogUtils.getLogger() 패턴, 로그 레벨(trace/debug/info/warn/error)을 학습합니다.

SLF4J Logger와 로그 레벨

모드를 개발하다 보면 "지금 이 코드가 실행되고 있나?", "이 값이 맞나?" 같은 질문이 끊임없이 생깁니다. System.out.println으로 확인하고 싶은 충동이 들지만, NeoForge 환경에서는 그 방법이 통하지 않습니다. 로그 파일에 남지 않고, 필터링도 안 되고, 멀티스레드 환경에서 출력 순서도 보장되지 않습니다.

NeoForge는 SLF4J를 로깅 facade로 사용합니다. 코드에서는 SLF4J API만 호출하고, 실제 출력은 Log4j2가 처리합니다. 이 챕터에서는 LogUtils.getLogger() 패턴과 다섯 가지 로그 레벨을 익힙니다.


1. SLF4J란

SLF4J(Simple Logging Facade for Java)는 로깅 구현체를 교체할 수 있도록 만든 추상 레이어입니다. 코드는 org.slf4j.Logger 인터페이스만 사용하고, 실제 로그를 어디에 어떻게 쓸지는 런타임에 연결된 구현체가 결정합니다.

NeoForge는 SLF4J 뒤에 Log4j2를 연결합니다. 덕분에 run/logs/ 폴더에 파일로 남고, 레벨별 필터링이 가능하며, 비동기 처리로 성능 영향도 최소화됩니다.

코드 (SLF4J API) → NeoForge/Log4j2 → 콘솔 + 파일

2. LogUtils.getLogger() 패턴

ExampleMod.java를 보면 클래스 상단에 이 한 줄이 있습니다.

import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
 
@Mod(ExampleMod.MODID)
public class ExampleMod {
    public static final String MODID = "examplemod";
 
    // SLF4J Logger — LogUtils.getLogger()가 호출 클래스 이름을 자동으로 사용
    public static final Logger LOGGER = LogUtils.getLogger();
 
    // ...
}

LogUtils.getLogger()는 Mojang이 제공하는 유틸리티로, 호출한 클래스의 이름을 자동으로 logger 이름으로 씁니다. 결과적으로 로그에 [ExampleMod] 같은 prefix가 붙어서 어느 클래스에서 찍힌 로그인지 바로 알 수 있습니다.

static final로 선언하는 이유는 두 가지입니다. 클래스당 하나만 만들어 재사용하고, 어디서든 ExampleMod.LOGGER로 접근할 수 있게 합니다.


3. 로그 레벨

SLF4J는 다섯 가지 레벨을 제공합니다. 레벨이 높을수록 심각도가 높고, 운영 환경에서는 낮은 레벨 로그를 필터링해서 성능을 아낍니다.

레벨메서드사용 시점예시
TRACELOGGER.trace(...)매우 상세한 호출 흐름메서드 진입/종료, 루프 반복
DEBUGLOGGER.debug(...)개발 디버깅 정보변수 값, 분기 결과, 조건 확인
INFOLOGGER.info(...)정상 운영 알림모드 로드 완료, 서버 시작
WARNLOGGER.warn(...)잠재적 문제deprecated API 사용, 설정 누락
ERRORLOGGER.error(...)에러/예외 발생모드 로드 실패, 예외 스택 트레이스

기본 설정에서 개발 환경은 DEBUG 이상, 배포 환경은 INFO 이상만 출력합니다. TRACE는 별도 설정이 필요합니다.


4. 실제 사용 패턴

ExampleMod.java의 commonSetup 메서드를 보면 세 가지 패턴이 모두 나옵니다.

private void commonSetup(FMLCommonSetupEvent event) {
    // 1. 단순 문자열 메시지
    LOGGER.info("HELLO FROM COMMON SETUP");
 
    // 2. 플레이스홀더 {} 로 값 삽입 — 문자열 연결(+) 보다 효율적
    if (Config.LOG_DIRT_BLOCK.getAsBoolean()) {
        LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
    }
 
    // 3. 여러 값 삽입
    LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt());
 
    // 4. 컬렉션 순회
    Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item));
}

{} 플레이스홀더는 SLF4J의 핵심 기능입니다. "값: " + value 처럼 문자열을 미리 연결하면 로그 레벨이 꺼져 있어도 연결 연산이 실행됩니다. {} 방식은 실제로 로그를 출력할 때만 문자열을 만들어서 성능이 더 좋습니다.

예외를 로깅할 때는 마지막 인자로 Throwable을 넘기면 스택 트레이스가 자동으로 붙습니다.

try {
    // 위험한 작업
} catch (Exception e) {
    LOGGER.error("작업 실패: {}", e.getMessage(), e);
    //                                              ^ Throwable을 마지막에
}
ℹ️

📷 스크린샷 자리 (직접 캡처해 추가하세요)

IntelliJ 콘솔에서 LOGGER.info 출력 결과 화면


5. 로그 파일 위치

게임을 실행하면 로그는 두 곳에 남습니다.

run/
└── logs/
    ├── latest.log          # 가장 최근 실행 로그 (평문)
    ├── debug.log           # DEBUG 레벨 포함 상세 로그
    └── 2026-05-26-1.log.gz # 이전 실행 로그 (gzip 압축)

latest.log는 INFO 이상만 담습니다. DEBUG 로그를 보려면 debug.log를 확인하세요. 압축된 이전 로그는 gzip -d 또는 7-Zip으로 풀 수 있습니다.

IntelliJ의 Run 탭 콘솔에서도 실시간으로 볼 수 있지만, 파일로 남기는 습관을 들이면 나중에 버그를 추적할 때 훨씬 편합니다.


6. LOGGER.info vs Component (채팅 메시지)

헷갈리기 쉬운 부분입니다. LOGGER.info는 개발자용 로그고, Component는 플레이어에게 보이는 채팅 메시지입니다.

// 개발자 로그 — 콘솔/파일에만 남음, 플레이어는 못 봄
LOGGER.info("서버 시작됨");
 
// 플레이어 채팅 메시지 — 게임 화면에 표시
player.sendSystemMessage(Component.literal("서버가 시작되었습니다."));

모드 내부 상태를 추적할 때는 LOGGER, 플레이어에게 알림을 줄 때는 Component를 씁니다. 둘을 혼용하면 플레이어 화면이 디버그 메시지로 도배됩니다.


안티패턴

⚠️ System.out.println 사용 금지

// ❌ 이렇게 하면 안 됩니다
System.out.println("debug: " + value);
// - 로그 파일에 남지 않음
// - 로그 레벨 필터링 불가
// - 멀티스레드에서 출력 순서 보장 안 됨
// - 배포 전 일일이 찾아서 지워야 함
 
// ✅ 이렇게 하세요
LOGGER.debug("debug: {}", value);

⚠️ Apache Log4j 직접 import 금지

// ❌ Log4j2 직접 사용
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private static final Logger LOGGER = LogManager.getLogger();
 
// ✅ SLF4J + LogUtils 사용
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
public static final Logger LOGGER = LogUtils.getLogger();

NeoForge는 SLF4J를 표준으로 씁니다. Log4j2를 직접 import하면 구현체 교체 시 코드를 전부 바꿔야 합니다.


정리

  • LogUtils.getLogger()로 클래스마다 Logger를 static final로 선언합니다.
  • 레벨은 TRACE < DEBUG < INFO < WARN < ERROR 순서입니다. 개발 중에는 DEBUG, 운영에서는 INFO 이상을 씁니다.
  • {} 플레이스홀더로 값을 삽입하면 문자열 연결보다 효율적입니다.
  • 로그는 run/logs/latest.log와 run/logs/debug.log에 남습니다.
  • System.out.println은 절대 쓰지 않습니다.

다음 챕터에서는 NeoForge의 이벤트 시스템을 더 깊이 파고들어 @SubscribeEvent와 이벤트 버스 분리를 다룹니다.

ModConfigSpec과 설정 GUI

NeoForge 26의 ModConfigSpec.Builder로 모드 설정을 정의하고 Mods 메뉴의 Config GUI에서 변경하는 방법을 학습합니다.

클라이언트/서버 분리와 Dist

Minecraft의 물리적/논리적 사이드 개념과 NeoForge의 Dist enum, @OnlyIn 어노테이션, ExampleModClient 분리 패턴을 학습합니다.

On this page

SLF4J Logger와 로그 레벨1. SLF4J란2. LogUtils.getLogger() 패턴3. 로그 레벨4. 실제 사용 패턴5. 로그 파일 위치6. LOGGER.info vs Component (채팅 메시지)안티패턴정리
NeoForge 26.1 Docs

NeoForge 26.1 모딩 개발 문서 사이트

GitHubDiscord

문서

  • 문서
  • 노트

GitHub

  • GitHub
  • Discord

© 2026 NeoForge 26.1 Docs. 콘텐츠는 MIT 라이선스로 제공됩니다.

Built with Next.js · Tailwind CSS · shadcn/ui