395 lines
13 KiB
Markdown
395 lines
13 KiB
Markdown
# 로그 처리 계획 (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 수정 (가장 간단)
|
|
|
|
```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** (배포용 - 로그 적게)
|
|
```json
|
|
{
|
|
"Logging": {
|
|
"LogLevel": {
|
|
"Default": "Information",
|
|
"Microsoft.AspNetCore": "Warning",
|
|
"Microsoft.EntityFrameworkCore": "Warning",
|
|
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
|
"ExperionCrawler": "Information"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**appsettings.Development.json** (개발용 - 로그 많게)
|
|
```json
|
|
{
|
|
"Logging": {
|
|
"LogLevel": {
|
|
"Default": "Debug",
|
|
"Microsoft.EntityFrameworkCore": "Debug",
|
|
"Microsoft.EntityFrameworkCore.Database.Command": "Information",
|
|
"ExperionCrawler": "Debug"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**사용 방법:**
|
|
- 개발 모드: `dotnet run` → 로그 자세히 보임
|
|
- 배포 모드: `dotnet run --configuration Release` → 로그 적게 보임
|
|
|
|
---
|
|
|
|
### 제안 3: 코드에서 로그 레벨 조정
|
|
|
|
**Program.cs**에서 로그 필터 추가:
|
|
```csharp
|
|
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning);
|
|
builder.Logging.AddFilter("ExperionCrawler", LogLevel.Information);
|
|
```
|
|
|
|
**ExperionRealtimeService.cs**의 `FlushPendingAsync()`에서:
|
|
- `_logger.LogDebug()` → `_logger.LogTrace()` (가장 자세한 로그는 기본적으로 안 보임)
|
|
|
|
---
|
|
|
|
### 제안 4: 주석 처리 + 나중에 복구용
|
|
|
|
**appsettings.json**에 주석으로 메모:
|
|
```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`)
|
|
|
|
```csharp
|
|
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`)
|
|
|
|
```csharp
|
|
namespace ExperionCrawler.Core.Application.Interfaces;
|
|
|
|
public interface ILogSettingsService
|
|
{
|
|
Task<LogSettingsResponse> GetSettingsAsync();
|
|
Task UpdateSettingsAsync(LogSettingsRequest request);
|
|
}
|
|
```
|
|
|
|
#### 3. 서비스 구현 (`src/Core/Application/Services/LogSettingsService.cs`)
|
|
|
|
```csharp
|
|
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`)
|
|
|
|
```csharp
|
|
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`)
|
|
|
|
```javascript
|
|
// 로그 설정 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 등록
|
|
|
|
```csharp
|
|
// LogSettingsService 등록
|
|
builder.Services.AddScoped<ILogSettingsService, LogSettingsService>();
|
|
```
|
|
|
|
### 사용 방법
|
|
|
|
1. 웹 UI에 로그 설정 패널이 오른쪽 상단에 표시됨
|
|
2. 체크박스를 켜고 끄면 실시간으로 로그 레벨이 변경됨
|
|
3. 설정은 세션 간 유지되지 않음 (개발 편의용)
|
|
|
|
### 장점
|
|
- 코드 수정 없이 로그 테스트 가능
|
|
- 실시간으로 로그 켜고 끌 수 있음
|
|
- UI에서 직관적으로 조작 가능
|
|
|
|
### 단점
|
|
- 설정이 메모리에만 저장됨 (재시작 시 초기화)
|
|
- 영구 설정을 원하면 appsettings.json 수정 필요
|
|
|
|
---
|