๐Ÿ“Š ์šด์˜ ๋กœ๊ทธ(Operational Logging) ์‹œ์Šคํ…œ ์ฒด๊ณ„ ์„ค๊ณ„์™€ ๊ตฌํ˜„๊ธฐ

๋ฐ•์ค€ํ˜•ยท2025๋…„ 8์›” 17์ผ

์Šคํ”„๋ง ๊ฐœ๋ฐœ

๋ชฉ๋ก ๋ณด๊ธฐ
15/20
post-thumbnail

์ง€๊ธˆ ์ง„ํ–‰์ค‘์ธ Dataracy ํ”„๋กœ์ ํŠธ์—์„œ ๊ธฐ์กด์—๋Š” @Slf4j ์• ๋…ธํ…Œ์ด์…˜์„ ํด๋ž˜์Šค์— ๋ถ™์ด๊ณ  ์ง์ ‘ log.info, log.warn๊ณผ ๊ฐ™์ด ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์ง์ ‘ ๋‚จ๊ฒผ์—ˆ๋Š”๋ฐ ๋กœ๊ทธ ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•˜๊ณ  ๊ณตํ†ต ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ผ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

1. ์™œ ์šด์˜ ๋กœ๊ทธ๋ฅผ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋‹ค ๋ณด๋ฉด ๋‹จ์ˆœํžˆ โ€œ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹คโ€๋ฅผ ์•„๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค. ์–ด๋””์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€, ์‹œ๊ฐ„์ด ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ ธ๋Š”์ง€, ์–ด๋–ค ํ๋ฆ„์—์„œ ์‹คํŒจํ–ˆ๋Š”์ง€๋ฅผ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•ด์•ผ ํ•œ๋‹ค.

  • ๋ฌธ์ œ ์ƒํ™ฉ 1: API ์—๋Ÿฌ ๋กœ๊ทธ๋Š” ๋‚จ์•˜๋Š”๋ฐ DB์—์„œ ์‹คํŒจํ–ˆ๋Š”์ง€, Kafka ์ „์†ก์ด ์‹คํŒจํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.
  • ๋ฌธ์ œ ์ƒํ™ฉ 2: ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ ๋กœ๊ทธ๊ฐ€ ์ œ๊ฐ๊ฐ ์ฐํ˜€ ์žˆ์–ด์„œ ์–ด๋–ค api ๊ธฐ๋Šฅ์—์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์ฐพ๊ธฐ ํž˜๋“ค๋‹ค.
  • ๋ฌธ์ œ ์ƒํ™ฉ 3: ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ ์–ด๋А ๊ณ„์ธต์—์„œ ์ง€์—ฐ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.

๐Ÿ‘‰ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์šด์˜ ๋กœ๊ทธ๋ฅผ ๊ณ„์ธต๋ณ„๋กœ ๋‚˜๋ˆ„๊ณ , ํฌ๋งท์„ ํ†ต์ผํ•ด์„œ ๊ธฐ๋กํ•ด์•ผ ํ•œ๋‹ค.



2. ๊ณตํ†ต ๊ธฐ๋ฐ˜: BaseLogger

๋ชจ๋“  ์ „์šฉ Logger๋Š” BaseLogger๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•œ๋‹ค.

@Slf4j
public abstract class BaseLogger {
    protected void info(String format, Object... args) {
        log.info(format, args);
    }
    protected void debug(String format, Object... args) {
        log.debug(format, args);
    }
    protected void warn(String format, Object... args) {
        log.warn(format, args);
    }
    protected void error(String format, Object... args) {
        log.error(format, args);
    }
    protected void error(Throwable t, String format, Object... args) {
        log.error(format, args, t);
    }
}

๐Ÿ‘‰ ํšจ๊ณผ

  • ์ค‘๋ณต ์ œ๊ฑฐ: ๋ชจ๋“  Logger์—์„œ ๋™์ผํ•œ ์ถœ๋ ฅ ๋ฐฉ์‹์„ ์‚ฌ์šฉ
  • ์ผ๊ด€์„ฑ: ๋กœ๊ทธ ๋ ˆ๋ฒจยทํฌ๋งท์„ ๊ฐ•์ œ
  • ํ™•์žฅ์„ฑ: ์ƒˆ๋กœ์šด Logger๋ฅผ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ


3. LoggerFactory (์‹ฑ๊ธ€ํ†ค ๊ตฌ์กฐ)

public class LoggerFactory {
    private static final ApiLogger API_LOGGER = new ApiLogger();
    private static final ServiceLogger SERVICE_LOGGER = new ServiceLogger();
    private static final DomainLogger DOMAIN_LOGGER = new DomainLogger();
    private static final PersistenceLogger PERSISTENCE_LOGGER = new PersistenceLogger();
    private static final QueryDslLogger QUERY_DSL_LOGGER = new QueryDslLogger();
    private static final KafkaLogger KAFKA_LOGGER = new KafkaLogger();
    private static final ElasticLogger ELASTIC_LOGGER = new ElasticLogger();
    private static final RedisLogger REDIS_LOGGER = new RedisLogger();
    private static final SchedulerLogger SCHEDULER_LOGGER = new SchedulerLogger();
    private static final DistributedLockLogger DISTRIBUTED_LOCK_LOGGER = new DistributedLockLogger();
    private static final CommonLogger COMMON_LOGGER = new CommonLogger();

    public static ApiLogger api() { return API_LOGGER; }
    public static ServiceLogger service() { return SERVICE_LOGGER; }
    public static DomainLogger domain() { return DOMAIN_LOGGER; }
    public static PersistenceLogger db() { return PERSISTENCE_LOGGER; }
    public static QueryDslLogger query() { return QUERY_DSL_LOGGER; }
    public static KafkaLogger kafka() { return KAFKA_LOGGER; }
    public static ElasticLogger elastic() { return ELASTIC_LOGGER; }
    public static RedisLogger redis() { return REDIS_LOGGER; }
    public static SchedulerLogger scheduler() { return SCHEDULER_LOGGER; }
    public static DistributedLockLogger lock() { return DISTRIBUTED_LOCK_LOGGER; }
    public static CommonLogger common() { return COMMON_LOGGER; }
}

์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์„ค๊ณ„ํ•œ ์ด์œ 

  • ๊ฒฝ๋Ÿ‰ ๊ฐ์ฒด: Logger๋Š” ์ƒํƒœ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š๊ณ  ๋‹จ์ˆœํžˆ ๋กœ๊ทธ๋งŒ ์ฐ๋Š”๋‹ค. ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ์ผ๊ด€์„ฑ: ๋ชจ๋“  ๊ณ„์ธต์—์„œ ๋™์ผํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๊ทธ ํฌ๋งท/์ •์ฑ…์ด ๋ณด์žฅ๋œ๋‹ค.
  • ํŽธ์˜์„ฑ: LoggerFactory.db().logSave(...) ์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๊ฐ ๊ณ„์ธต๋งˆ๋‹ค ์„ค์ •ํ•œ ๋กœ๊ทธ๋ฅผ ์–ด๋””์„œ๋“  ๋ฐ”๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.


4. ๊ณ„์ธต๋ณ„ Logger ์ฃผ์š” ๊ธฐ๋Šฅ

๊ฐ Logger๋Š” ๊ณ„์ธต ํŠน์„ฑ์— ๋งž๊ฒŒ ํ•ญ๋ชฉ๊ณผ Duration ์ธก์ •์„ ์ง€์›ํ•œ๋‹ค.

  • ApiLogger: ์š”์ฒญ/์‘๋‹ต, ์†Œ์š” ์‹œ๊ฐ„ ๊ธฐ๋ก
  • ServiceLogger: UseCase ๋‹จ์œ„ ์‹œ์ž‘/์„ฑ๊ณต, Duration
  • PersistenceLogger / QueryDslLogger: DB ์ž‘์—… (์กฐํšŒ ์‹œ์ž‘/๋, CRUD), Duration
  • KafkaLogger: ๋ฉ”์‹œ์ง€ ์ „์†ก/์ˆ˜์‹ /์‹คํŒจ ๊ธฐ๋ก
  • RedisLogger: ์บ์‹œ ์กฐํšŒ/์ €์žฅ/์‚ญ์ œ, Duration
  • ElasticLogger: ์ƒ‰์ธ/๊ฒ€์ƒ‰/์—…๋ฐ์ดํŠธ, Duration
  • DistributedLockLogger: ๋ฝ ํš๋“/์‹คํŒจ/ํ•ด์ œ, Duration
  • SchedulerLogger: ๋ฐฐ์น˜/์Šค์ผ€์ค„ ์ž‘์—… ์‹œ์ž‘/์ข…๋ฃŒ/์—๋Ÿฌ ๊ธฐ๋ก
  • DomainLogger: ๋„๋ฉ”์ธ ๊ทœ์น™ ์œ„๋ฐ˜ ๊ธฐ๋ก
  • CommonLogger: ๋ฒ”์šฉ Start/End/Warning/Error

๐Ÿ‘‰ Duration(Instant.now())์„ ํ™œ์šฉํ•ด ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ์ง€์ ๊นŒ์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋˜ํ•œ ๊ฐ์ž์˜ ๊ณ„์ธต์— ๋งž๋Š” ๋กœ๊น… ๋ฉ”์„œ๋“œ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๊ตฌํ˜„์ฒด์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.



5. ์‹ค์ œ ์ฝ”๋“œ ์‚ฌ์šฉ ์˜ˆ์‹œ

5.1 DB ์ž‘์—… (UserCommandDbAdapter)

@Override
public User saveUser(User user) {
    UserEntity savedUser = userJpaRepository.save(UserEntityMapper.toEntity(user));
    LoggerFactory.db().logSave("UserEntity", String.valueOf(savedUser.getId()), "DB์— ์œ ์ €๋ฅผ ์ €์žฅํ•˜์˜€์Šต๋‹ˆ๋‹ค.");
    return UserEntityMapper.toDomain(savedUser);
}

@Override
public void changePassword(Long userId, String encodePassword) {
    UserEntity userEntity = userJpaRepository.findById(userId)
            .orElseThrow(() -> {
                LoggerFactory.db().logWarning("UserEntity", "[๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ] ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. userId=" + userId);
                return new UserException(UserErrorStatus.NOT_FOUND_USER);
            });
    userEntity.changePassword(encodePassword);
    LoggerFactory.db().logUpdate("UserEntity", String.valueOf(userEntity.getId()), "์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค.");
}

๐Ÿ‘‰ ํšจ๊ณผ:

  • ์–ด๋–ค ์—”ํ‹ฐํ‹ฐ(UserEntity)์— ์–ด๋–ค ์ž‘์—…(์ €์žฅ/์ˆ˜์ •)์ด ์ผ์–ด๋‚ฌ๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ธฐ๋ก
  • ์˜ˆ์™ธ ์ƒํ™ฉ๊นŒ์ง€ ๋กœ๊น…(logWarning)

5.2 Kafka ์ด๋ฒคํŠธ ๋ฐœ์†ก (LikeKafkaProducerAdapter)

kafkaTemplate.send(TOPIC_PROJECT_LIKE_INCREASE, String.valueOf(targetId), targetId)
    .whenComplete((result, ex) -> {
        if (ex == null) {
            LoggerFactory.kafka().logProduce(TOPIC_PROJECT_LIKE_INCREASE,
                "ํ”„๋กœ์ ํŠธ ์ข‹์•„์š” ์ด๋ฒคํŠธ ๋ฐœ์†ก๋จ: projectId=" + targetId);
        } else {
            LoggerFactory.kafka().logError(TOPIC_PROJECT_LIKE_INCREASE,
                "ํ”„๋กœ์ ํŠธ ์ข‹์•„์š” ์ด๋ฒคํŠธ ๋ฐœ์†ก ์ฒ˜๋ฆฌ ์‹คํŒจ: projectId=" + targetId, ex);
        }
    });

๐Ÿ‘‰ ํšจ๊ณผ:

  • ์ด๋ฒคํŠธ ๋ฐœ์†ก ์„ฑ๊ณต/์‹คํŒจ๋ฅผ ์ฆ‰์‹œ ๋กœ๊ทธ๋กœ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ
  • ์žฅ์•  ์‹œ ํ† ํ”ฝ๋ช… + ๋Œ€์ƒ ID + ์˜ˆ์™ธ ์ •๋ณด๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ์›์ธ ๋ถ„์„์ด ๋น ๋ฅด๋‹ค

    ์ด๋ ‡๊ฒŒ ๊ฐ์ž์˜ ๊ณ„์ธต๋งˆ๋‹ค์˜ ์ปค์Šคํ…€ ์šด์˜ ๋กœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.


6. ๊ทธ๋ž˜์„œ, ์šด์˜ ๋กœ๊ทธ์˜ ํ•ต์‹ฌ์€?

6.1 ์™œ ํ•„์š”ํ•œ๊ฐ€

  • ์žฅ์•  ์‹œ โ€œ์–ด๋””์„œโ€ ๋ฌธ์ œ๊ฐ€ ๋‚ฌ๋Š”์ง€ ๊ณ„์ธต๋ณ„๋กœ ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • ์„ฑ๋Šฅ ๋ถ„์„ ์‹œ Duration์œผ๋กœ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„์„ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋กœ๊ทธ๋ฅผ ๋ณด๋Š” ์‚ฌ๋žŒ(๊ฐœ๋ฐœ/์šด์˜/๋ถ„์„์ž)๋งˆ๋‹ค ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

6.2 ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋Š”๊ฐ€

  • BaseLogger โ†’ ๊ณตํ†ต ๊ธฐ๋ฐ˜
  • LoggerFactory โ†’ ์‹ฑ๊ธ€ํ†ค ์ „์—ญ ์ ‘๊ทผ
  • ๊ฐ ๊ณ„์ธต๋ณ„ Logger โ†’ ํฌ๋งท ํ†ต์ผ + Duration ์ธก์ • ๋“ฑ

6.3 ์–ด๋–ค ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”๊ฐ€

  • ๊ฐ€๋…์„ฑ: [DB ์ €์žฅ] ..., [Kafka ์ „์†ก] ... โ†’ ํ•œ ์ค„๋งŒ ๋ด๋„ ์ถœ์ฒ˜ ์‹๋ณ„
  • ๋ถ„์„ ํšจ์œจ์„ฑ: ์žฅ์• /์„ฑ๋Šฅ ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์›ํ•˜๋Š” ๋กœ๊ทธ๋งŒ ๋น ๋ฅด๊ฒŒ ํ•„ํ„ฐ๋ง
  • ํ‘œ์ค€ํ™”: ํŒ€ ์ „์ฒด๊ฐ€ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ๋กœ๊ทธ ์ž‘์„ฑ โ†’ ํ˜‘์—… ํšจ์œจ ์ฆ๊ฐ€


7. ๋งˆ๋ฌด๋ฆฌ

ํ–‰๋™ ๋กœ๊ทธ(MDC ๊ธฐ๋ฐ˜)๋Š” ์‚ฌ์šฉ์ž์˜ โ€œ๋ฌด์—‡์„ ํ–ˆ๋Š”๊ฐ€(์–ด๋–ค ๊ธฐ๋Šฅ)โ€๋ฅผ ์ถ”์ ํ•œ๋‹ค๋ฉด,
์šด์˜ ๋กœ๊ทธ๋Š” ์‹œ์Šคํ…œ์˜ โ€œ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ–ˆ๋Š”๊ฐ€โ€๋ฅผ ์ถ”์ ํ•œ๋‹ค.

์šด์˜ ๋กœ๊ทธ๋ฅผ ๊ณ„์ธต๋ณ„ Logger + ์‹ฑ๊ธ€ํ†ค Factory ๊ตฌ์กฐ๋กœ ๊ตฌํ˜„ํ•จ์œผ๋กœ์จ,

  • ์žฅ์•  ๋Œ€์‘ ์†๋„๊ฐ€ ๋นจ๋ผ์ง€๊ณ 
  • ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ์ง€์ ์ด ๋“œ๋Ÿฌ๋‚˜๊ณ 
  • ํŽธ์˜์„ฑ์žˆ๊ฒŒ ํ•„ํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ
  • ๋กœ๊ทธ ๋ฌธํ™”๊ฐ€ ํ‘œ์ค€ํ™”๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ฆ‰, ์šด์˜ ๋กœ๊ทธ๋Š” ๋‹จ์ˆœํ•œ ๋กœ๊ทธ ์ถœ๋ ฅ์ด ์•„๋‹ˆ๋ผ, ์„œ๋น„์Šค ์•ˆ์ •์„ฑ๊ณผ ์šด์˜ ํšจ์œจ์„ฑ์„ ๋’ท๋ฐ›์นจํ•˜๋Š” ํ•ต์‹ฌ ์ธํ”„๋ผ๋‹ค.

profile
๋งค์ผ ๋งค์ผ ์„ฑ์žฅํ•˜๊ธฐ

0๊ฐœ์˜ ๋Œ“๊ธ€