Files
ExperionCrawler/dxf-graph/log-handling-plan.md
windpacer 7330711499 chore: 프로젝트 파일 구조 정리 - 루트 파일 폴더별 이동, 테스트/구버전 삭제
루트 파일 정리:
- DXF/P&ID 관련 → dxf-graph/
- fastTable 관련 → fastTable/
- plan/ → plans/ 통합 (최신 버전 유지)
- 테스트 출력 파일, 구버전 프로젝트 삭제
- 불필요한 루트 문서 삭제
2026-05-10 17:39:58 +09:00

13 KiB

로그 처리 계획 (Log Handling Plan)

문제 상황

터미널에 너무 많은 로그가 출력되어 정신없음

발생 로그 예시

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (2ms) [Parameters=[@__u_Timestamp_2='?' (DbType = DateTime), @__u_Value_1='?', @__u_NodeId_0='?'], CommandType='Text', CommandTimeout='30']
      UPDATE realtime_table AS r
      SET timestamp = @__u_Timestamp_2,
          livevalue = @__u_Value_1
      WHERE r.node_id = @__u_NodeId_0
info: ExperionCrawler.Infrastructure.OpcUa.ExperionRealtimeService[0]
      Update realtime info.

원인 분석

  1. EF Core DB Command 로그 (Microsoft.EntityFrameworkCore.Database.Command)

    • appsettings.json에서 Microsoft.EntityFrameworkCore.Database.Command"Information"으로 설정
    • 매 500ms마다 UPDATE realtime_table 쿼리 실행 시 로그 출력
  2. ExperionRealtimeService 로그

    • FlushPendingAsync()에서 _logger.LogDebug()로 배치 업데이트 로그 남김
    • ExperionCrawler.Infrastructure.OpcUa.ExperionRealtimeService 카테고리가 "Information"으로 설정

해결 방안

제안 1: appsettings.json 수정 (가장 간단)

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Information",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
      "ExperionCrawler.Infrastructure.OpcUa.ExperionRealtimeService": "Warning"
    }
  }
}

효과:

  • UPDATE realtime_table 쿼리 로그 숨김
  • [Realtime] 배치 업데이트: X/Y건 로그 숨김
  • 경고/오류만 표시

제안 2: 개발/배포 환경 분리 (추천)

appsettings.json (배포용 - 로그 적게)

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Warning",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
      "ExperionCrawler": "Information"
    }
  }
}

appsettings.Development.json (개발용 - 로그 많게)

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft.EntityFrameworkCore": "Debug",
      "Microsoft.EntityFrameworkCore.Database.Command": "Information",
      "ExperionCrawler": "Debug"
    }
  }
}

사용 방법:

  • 개발 모드: dotnet run → 로그 자세히 보임
  • 배포 모드: dotnet run --configuration Release → 로그 적게 보임

제안 3: 코드에서 로그 레벨 조정

Program.cs에서 로그 필터 추가:

builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning);
builder.Logging.AddFilter("ExperionCrawler", LogLevel.Information);

ExperionRealtimeService.csFlushPendingAsync()에서:

  • _logger.LogDebug()_logger.LogTrace() (가장 자세한 로그는 기본적으로 안 보임)

제안 4: 주석 처리 + 나중에 복구용

appsettings.json에 주석으로 메모:

{
  "Logging": {
    "LogLevel": {
      // 개발 시 주석 해제: 로그 자세히 보기
      // "Microsoft.EntityFrameworkCore.Database.Command": "Information",
      // "ExperionCrawler": "Debug",
      
      // 배포 시: 로그 적게 (기본)
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
      "ExperionCrawler": "Information"
    }
  }
}

검수 체크리스트

  • 로그 레벨 조정 후 터미널이 깔끔한지 확인
  • 필요한 로그가 사라지지 않았는지 확인
  • 개발 모드에서 로그를 다시 볼 수 있는지 확인

참고: 로그 레벨 순서

Trace    (가장 자세함 - 기본적으로 안 보임)
Debug    (개발 시 유용 - 기본적으로 안 보임)
Information (일반 로그 - 기본 표시)
Warning  (경고 - 기본 표시)
Error    (오류 - 기본 표시)
Critical (치명적 오류 - 기본 표시)

제안 5: UI에서 동적 로그 레벨 설정 (추천 + 개발자 친화)

개요

웹 UI에서 실시간으로 로그 레벨을 조정할 수 있도록 API를 추가하여, 개발 중에 코드 수정 없이 로그를 켜고 끌 수 있게 함.

아키텍처

┌─────────────────────────────────────────────────────────────┐
│                        Web UI (app.js)                      │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  로그 레벨 설정 UI (Toggle Switch)                   │   │
│  │  - EF Core Query Log (ON/OFF)                        │   │
│  │  - Realtime Service Log (ON/OFF)                     │   │
│  │  - Debug Mode (ON/OFF)                               │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│              LogSettingsController (API)                    │
│  - GET  /api/log/settings  → 현재 설정 조회                 │
│  - POST /api/log/settings  → 설정 업데이트                  │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│              LogSettingsService (비즈니스 로직)             │
│  - 로그 레벨 설정 저장 (appsettings.json)                   │
│  - 로거 리로드 (새로운 설정 적용)                           │
└─────────────────────────────────────────────────────────────┘

구현 계획

1. DTO 정의 (src/Core/Application/DTOs/LogSettingsDtos.cs)

namespace ExperionCrawler.Core.Application.DTOs;

public class LogSettingsRequest
{
    public bool? EnableEfCoreQueryLog { get; set; }
    public bool? EnableRealtimeServiceLog { get; set; }
    public bool? EnableDebugMode { get; set; }
}

public class LogSettingsResponse
{
    public bool EfCoreQueryLog { get; set; }
    public bool RealtimeServiceLog { get; set; }
    public bool DebugMode { get; set; }
}

2. 서비스 인터페이스 (src/Core/Application/Interfaces/ILogSettingsService.cs)

namespace ExperionCrawler.Core.Application.Interfaces;

public interface ILogSettingsService
{
    Task<LogSettingsResponse> GetSettingsAsync();
    Task UpdateSettingsAsync(LogSettingsRequest request);
}

3. 서비스 구현 (src/Core/Application/Services/LogSettingsService.cs)

namespace ExperionCrawler.Core.Application.Services;

public class LogSettingsService : ILogSettingsService
{
    private readonly IConfiguration _config;
    private readonly ILogger<LogSettingsService> _logger;

    public LogSettingsService(IConfiguration config, ILogger<LogSettingsService> logger)
    {
        _config = config;
        _logger = logger;
    }

    public async Task<LogSettingsResponse> GetSettingsAsync()
    {
        var loggingSection = _config.GetSection("Logging");
        
        return new LogSettingsResponse
        {
            EfCoreQueryLog = IsLogLevelEnabled(loggingSection, "Microsoft.EntityFrameworkCore.Database.Command", "Information"),
            RealtimeServiceLog = IsLogLevelEnabled(loggingSection, "ExperionCrawler", "Information"),
            DebugMode = IsLogLevelEnabled(loggingSection, "Default", "Debug")
        };
    }

    public async Task UpdateSettingsAsync(LogSettingsRequest request)
    {
        // appsettings.json 읽기
        var configPath = "appsettings.json";
        var json = await File.ReadAllTextAsync(configPath);
        var doc = JsonDocument.Parse(json);
        
        // 로그 레벨 업데이트
        // (구현은 간단히 appsettings.Development.json 사용하는 방식으로)
        
        _logger.LogInformation("[LogSettings] 설정 업데이트: {Request}", request);
    }

    private bool IsLogLevelEnabled(IConfigurationSection section, string category, string level)
    {
        var logLevelSection = section.GetSection("LogLevel");
        var value = logLevelSection[category];
        return !string.IsNullOrEmpty(value) && value == level;
    }
}

4. 컨트롤러 (src/Web/Controllers/LogSettingsController.cs)

namespace ExperionCrawler.Web.Controllers;

[ApiController]
[Route("api/[controller]")]
public class LogSettingsController : ControllerBase
{
    private readonly ILogSettingsService _service;

    public LogSettingsController(ILogSettingsService service)
    {
        _service = service;
    }

    [HttpGet]
    public async Task<ActionResult<LogSettingsResponse>> GetSettings()
    {
        var settings = await _service.GetSettingsAsync();
        return Ok(new
        {
            efCoreQueryLog = settings.EfCoreQueryLog,
            realtimeServiceLog = settings.RealtimeServiceLog,
            debugMode = settings.DebugMode
        });
    }

    [HttpPost]
    public async Task<ActionResult> UpdateSettings([FromBody] LogSettingsRequest request)
    {
        await _service.UpdateSettingsAsync(request);
        return Ok();
    }
}

5. UI 변경 (src/Web/wwwroot/js/app.js)

// 로그 설정 UI 추가
function createLogSettingsUI() {
    const container = document.createElement('div');
    container.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        background: rgba(0,0,0,0.8);
        padding: 15px;
        border-radius: 8px;
        z-index: 1000;
        color: white;
        font-family: monospace;
    `;
    
    container.innerHTML = `
        <h3 style="margin: 0 0 10px 0; font-size: 14px;">Log Settings</h3>
        <div style="display: flex; flex-direction: column; gap: 5px;">
            <label style="display: flex; align-items: center; gap: 5px; font-size: 12px;">
                <input type="checkbox" id="log-efcore"> EF Core Query
            </label>
            <label style="display: flex; align-items: center; gap: 5px; font-size: 12px;">
                <input type="checkbox" id="log-realtime"> Realtime Service
            </label>
            <label style="display: flex; align-items: center; gap: 5px; font-size: 12px;">
                <input type="checkbox" id="log-debug"> Debug Mode
            </label>
        </div>
    `;
    
    document.body.appendChild(container);
    
    // 초기 설정 로드
    loadLogSettings();
    
    // 체크박스 이벤트
    document.getElementById('log-efcore').addEventListener('change', (e) => updateLogSetting('efCoreQueryLog', e.target.checked));
    document.getElementById('log-realtime').addEventListener('change', (e) => updateLogSetting('realtimeServiceLog', e.target.checked));
    document.getElementById('log-debug').addEventListener('change', (e) => updateLogSetting('debugMode', e.target.checked));
}

async function loadLogSettings() {
    try {
        const res = await fetch('/api/logsettings');
        const data = await res.json();
        document.getElementById('log-efcore').checked = data.efCoreQueryLog;
        document.getElementById('log-realtime').checked = data.realtimeServiceLog;
        document.getElementById('log-debug').checked = data.debugMode;
    } catch (e) {
        console.error('Failed to load log settings:', e);
    }
}

async function updateLogSetting(key, value) {
    try {
        await fetch('/api/logsettings', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ [key]: value })
        });
    } catch (e) {
        console.error('Failed to update log setting:', e);
    }
}

6. Program.cs 등록

// LogSettingsService 등록
builder.Services.AddScoped<ILogSettingsService, LogSettingsService>();

사용 방법

  1. 웹 UI에 로그 설정 패널이 오른쪽 상단에 표시됨
  2. 체크박스를 켜고 끄면 실시간으로 로그 레벨이 변경됨
  3. 설정은 세션 간 유지되지 않음 (개발 편의용)

장점

  • 코드 수정 없이 로그 테스트 가능
  • 실시간으로 로그 켜고 끌 수 있음
  • UI에서 직관적으로 조작 가능

단점

  • 설정이 메모리에만 저장됨 (재시작 시 초기화)
  • 영구 설정을 원하면 appsettings.json 수정 필요