Dreamine.Logging.Wpf
Dreamine.Logging.Wpf는 Dreamine 애플리케이션을 위한 WPF 전용 로그 UI 패키지입니다.
Dreamine.Logging의 로그 파이프라인을 WPF 화면에 연결하기 위해 로그 패널 View, 상한이 있는 로그 패널 ViewModel, 고빈도 로그 갱신에 적합한 UI Dispatcher Helper를 제공합니다.
목적
이 패키지는 WPF 표시 계층과 UI 스레드 연동만 담당합니다.
핵심 로그 파이프라인, Sink, Formatter, 비동기 큐는 Dreamine.Logging 패키지가 담당합니다.
Dreamine.Logging.Wpf는 Dreamine.Logging에 의존합니다.
반대로 Dreamine.Logging은 WPF를 참조하면 안 됩니다.
주요 기능
- WPF 로그 표시용
DreamineLogPanelView - 로그 엔트리 바인딩용
DreamineLogPanelViewModel DefaultDisplayCapacity = 1000기준의 표시 로그 컬렉션 상한BatchedDispatcher<T>기반 UI 배치 갱신- WPF Dispatcher 접근을 위한
WpfLogUiDispatcher IDreamineLogStore.LogAdded이벤트 기반 실시간 로그 표시DreamineLogPanelViewModel.Clear()기반 Clear 지원AutoScroll,EntryAppended기반 자동 선택/자동 스크롤 지원- 선택된 로그의 상세 내용 표시
Dispose()에서 이벤트 구독 해제하여 ViewModel 보존 누수 방지
권장 구조
WPF 패키지는 메모리 로그 저장소를 관찰하는 역할만 담당해야 합니다. 파일 기록, Logger 소유, 비동기 큐 생성은 WPF 패키지의 책임이 아닙니다.
[Dreamine.Logging]
Logger
└─► AsyncQueueSink
└─► CompositeLogSink
├─► InMemoryLogStore ──(LogAdded event)──┐
└─► TextFileLogSink │
▼
[Dreamine.Logging.Wpf]
DreamineLogPanelViewModel
└─► DreamineLogPanelView
DreamineLogPanelViewModel은 Sink 체인의 자식이 아닙니다.
ViewModel은 InMemoryLogStore.LogAdded 이벤트를 구독하는 별도 Observer이며,
BatchedDispatcher<T>를 통해 UI 스레드로 배치 갱신을 전달합니다.
Batched UI Dispatch가 필요한 이유
로그 패널은 여러 Worker Thread에서 동시에 발생하는 로그 이벤트를 받을 수 있습니다.
로그 1개마다 Dispatcher.BeginInvoke를 하나씩 예약하면 WPF Dispatcher Queue가 UI 처리 속도보다 빠르게 증가할 수 있습니다.
이 경우 메모리 압박이 생기고 UI가 멈춘 것처럼 느려질 수 있습니다.
BatchedDispatcher<T>는 들어오는 로그를 제한된 UI 배치로 묶어서 처리합니다.
- 생산자는 어느 Thread에서든 Enqueue만 합니다.
- 동시에 예약되는 Dispatcher 작업은 최대 1개로 제한됩니다.
- 한 번의 UI 배치에서 최대
MaxBatchSize개를 처리합니다. - 남은 항목은 다음 Dispatcher 작업에서 이어서 처리됩니다.
- 표시 컬렉션은 설정된 display capacity까지만 유지됩니다.
XAML 사용 예시
<UserControl x:Class="SampleSmart.Pages.PageSub.PageLog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:logViews="clr-namespace:Dreamine.Logging.Wpf.Views;assembly=Dreamine.Logging.Wpf">
<Grid>
<logViews:DreamineLogPanelView DataContext="{Binding LogPanel}" />
</Grid>
</UserControl>
ViewModel 래퍼 예시
public sealed class PageLogViewModel : ViewModelBase
{
public DreamineLogPanelViewModel LogPanel { get; }
public PageLogViewModel(DreamineLogPanelViewModel logPanel)
{
LogPanel = logPanel;
}
}
등록 예시
로그 저장소는 Core Logging 등록에서 먼저 등록되어 있어야 합니다. WPF 패키지는 UI Dispatcher와 로그 패널 ViewModel만 등록하면 됩니다.
DMContainer.RegisterSingleton<WpfLogUiDispatcher>(
new WpfLogUiDispatcher());
DMContainer.RegisterSingleton<ILogUiDispatcher>(
DMContainer.Resolve<WpfLogUiDispatcher>());
DMContainer.Register<DreamineLogPanelViewModel>(() =>
new DreamineLogPanelViewModel(
DMContainer.Resolve<IDreamineLogStore>(),
DMContainer.Resolve<ILogUiDispatcher>()));
표시 로그 개수를 별도로 조정하려면 다음처럼 등록합니다.
DMContainer.Register<DreamineLogPanelViewModel>(() =>
new DreamineLogPanelViewModel(
DMContainer.Resolve<IDreamineLogStore>(),
DMContainer.Resolve<ILogUiDispatcher>(),
displayCapacity: 2000));
Logging + WPF 전체 등록 예시
private static IAsyncDisposable? _loggingShutdown;
private static void RegisterLogging()
{
_loggingShutdown = DreamineLoggingRegistration.Register(new DreamineLoggingOptions
{
Category = "SampleSmart",
LogDirectory = Path.Combine(AppContext.BaseDirectory, "Logs")
});
DMContainer.RegisterSingleton<WpfLogUiDispatcher>(new WpfLogUiDispatcher());
DMContainer.RegisterSingleton<ILogUiDispatcher>(
DMContainer.Resolve<WpfLogUiDispatcher>());
DMContainer.Register<DreamineLogPanelViewModel>(() =>
new DreamineLogPanelViewModel(
DMContainer.Resolve<IDreamineLogStore>(),
DMContainer.Resolve<ILogUiDispatcher>()));
}
종료 처리
비동기 등록 handle을 사용할 경우 애플리케이션 종료 시 dispose 하여 대기 로그를 비워야 합니다.
protected override void OnExit(ExitEventArgs e)
{
try
{
_loggingShutdown?.DisposeAsync().AsTask().GetAwaiter().GetResult();
}
catch
{
// 종료 중 로그 오류가 애플리케이션 종료를 막으면 안 됩니다.
}
finally
{
base.OnExit(e);
}
}
OnExit은 동기 메서드이므로 async void 대신 GetAwaiter().GetResult()로 대기해야
프로세스가 큐 Flush 완료 전에 종료되는 것을 막을 수 있습니다.
주의 사항
DreamineLogPanelView는 DreamineLogPanelViewModel을 DataContext로 받아야 합니다.
다른 페이지 내부에 로그 패널을 감싸서 사용할 경우 내부 View의 DataContext를 명시적으로 연결해야 합니다.
<logViews:DreamineLogPanelView DataContext="{Binding LogPanel}" />
상세 로그 텍스트는 읽기 전용 속성이므로 TextBox.Text 바인딩에는 Mode=OneWay를 사용해야 합니다.
<TextBox Text="{Binding SelectedDetailText, Mode=OneWay}" />
소유 Page 또는 Window가 완전히 제거될 때는 DreamineLogPanelViewModel.Dispose()가 호출되도록 처리해야 합니다.
이 처리는 IDreamineLogStore.LogAdded 이벤트 구독을 해제하여 ViewModel이 이벤트 소스에 의해 계속 살아있는 문제를 막습니다.
성능 참고 사항
- 표시용
Entries컬렉션은 상한이 있어 무한 증가하지 않습니다. - UI 갱신은 로그 1건당 Dispatcher 작업 1개가 아니라 배치 방식으로 처리됩니다.
- 로그 패널은 진단/모니터링 용도이며, 무제한 로그 보관소가 아닙니다.
- 장기 보관은
Dreamine.Logging의TextFileLogSink또는 다른 영속 Sink를 사용합니다. - 운영 Thread Loop에서는 진단 모드가 아닌 한 매 Cycle 로그 출력을 피하는 것이 좋습니다.
Dreamine.Logging과의 관계
Dreamine.Logging.Wpf
-> Dreamine.Logging
의존성 방향은 반드시 단방향으로 유지해야 합니다. WPF 패키지는 Core Logging 패키지 위에 올라가는 표시 계층 Adapter입니다.
향후 계획
DMContainer.UseDreamineLoggingWpf()(UI Dispatcher 및 패널 ViewModel 등록을 한 줄로 수행)DMContainer.UseDreamineLoggingForWpf(...)(Core + WPF 등록을 한 번에 수행하는 통합 진입점)- 내장 Clear Command 바인딩
- 로그 레벨 필터
- 검색/필터 UI
- 선택 로그 내보내기
- 선택적 Auto-scroll Behavior Helper
- Async 로그 Drop 상태 표시 기능 (
AsyncQueueSink.DroppedCount표시 UI)
라이선스
MIT License
구조 다이어그램
classDiagram
class WpfLogSink {
-ObservableCollection~LogEntry~ _entries
+Entries ObservableCollection~LogEntry~
+MaxEntries int
+Write(LogEntry) void
+Flush() Task
}
class LogViewerControl {
+ObservableCollection~LogEntry~ Entries
+LogLevel MinLevel
+string Filter
+bool AutoScroll
+Export() void
+Clear() void
}
class LogLevelConverter {
+Convert(object, Type, object, CultureInfo) object
}
class LogEntryTemplateSelector {
+SelectTemplate(object, DependencyObject) DataTemplate
}
class ILogSink {
<<interface>>
}
ILogSink <|.. WpfLogSink
LogViewerControl --> WpfLogSink
LogViewerControl --> LogLevelConverter
LogViewerControl --> LogEntryTemplateSelectorAPI 문서
타입
\if KO 빈번한 UI 갱신을 WPF UI 스레드에서 처리할 배치로 합칩니다. \endif \if EN Coalesces high-frequency UI updates into batches dispatched on the WPF UI thread. \endif
\if KO Dreamine WPF 로깅 서비스 등록 도우미를 제공합니다. \endif \if EN Provides registration helpers for Dreamine WPF logging services. \endif
\if KO Dreamine 로그 패널의 WPF 상호 작용 논리를 제공합니다. \endif \if EN Provides WPF interaction logic for the Dreamine log panel. \endif
\if KO Dreamine 로그 패널용 ViewModel을 제공합니다. \endif \if EN Provides the ViewModel for the Dreamine log panel. \endif
\if KO 로그 ViewModel용 UI 스레드 디스패치를 추상화합니다. \endif \if EN Abstracts UI-thread dispatching for log ViewModels. \endif
\if KO 앞쪽 항목을 한 번의 재설정 알림으로 제거할 수 있는 로그 컬렉션입니다. \endif \if EN Represents a log collection that trims leading items with one reset notification. \endif
\if KO WPF 로그 View용 UI 스레드 디스패치를 제공합니다. \endif \if EN Provides UI-thread dispatching for WPF log Views. \endif
BatchedDispatcher`1
\if KO 디스패처, 배치 콜백 및 우선순위로 를 초기화합니다. \endif \if EN Initializes with a dispatcher, batch callback, and priority. \endif
dispatcher— \if KO 대상 WPF 디스패처입니다. \endif \if EN The target WPF dispatcher. \endifonBatch— \if KO UI 스레드에서 배치와 함께 호출할 콜백입니다. \endif \if EN The callback invoked on the UI thread with a batch. \endifpriority— \if KO 디스패처 우선순위입니다. \endif \if EN The dispatcher priority. \endif\if KO UI 배치 전달을 위해 항목을 큐에 추가합니다. \endif \if EN Enqueues an item for batched UI delivery. \endif
item— \if KO 전달할 항목입니다. \endif \if EN The item to deliver. \endif\if KO 대기 항목을 제한 크기 배치로 꺼내 콜백에 전달하고 남은 작업을 다시 예약합니다. \endif \if EN Drains pending items into a bounded batch, invokes the callback, and reschedules remaining work. \endif
\if KO 예약된 작업이 없으면 UI 큐 비우기 작업을 예약합니다. \endif \if EN Schedules a UI flush operation when none is already pending. \endif
\if KO dispatcher 값을 보관합니다. \endif \if EN Stores the dispatcher value. \endif
\if KO on Batch 값을 보관합니다. \endif \if EN Stores the on batch value. \endif
\if KO pending 값을 보관합니다. \endif \if EN Stores the pending value. \endif
\if KO priority 값을 보관합니다. \endif \if EN Stores the priority value. \endif
\if KO scheduled 값을 보관합니다. \endif \if EN Stores the scheduled value. \endif
\if KO 한 UI 스레드 배치에서 처리할 최대 항목 수입니다. \endif \if EN The maximum number of items processed in one UI-thread batch. \endif
DreamineLoggingWpfRegistration
\if KO Dreamine 핵심 로깅 및 WPF UI 로깅 서비스를 등록합니다. \endif \if EN Registers Dreamine core logging and WPF UI logging services. \endif
configure— \if KO 선택적 로깅 구성 작업입니다. \endif \if EN The optional logging configuration action. \endif\if KO WPF 애플리케이션 종료 시 로그 파이프라인을 해제하는 처리기를 한 번 등록합니다. \endif \if EN Registers once an application-exit handler that disposes the logging pipeline. \endif
shutdownHandle— \if KO 종료할 로깅 핸들입니다. \endif \if EN The logging handle to shut down. \endif\if KO 테스트를 위해 정적 등록 상태를 초기화합니다. \endif \if EN Resets static registration state for tests. \endif
\if KO is Exit Handler Registered 값을 보관합니다. \endif \if EN Stores the is exit handler registered value. \endif
\if KO Sync Root 값을 보관합니다. \endif \if EN Stores the sync root value. \endif
DreamineLogPanelView
\if KO 의 새 인스턴스를 초기화합니다. \endif \if EN Initializes a new instance of . \endif
InitializeComponent
DreamineLogPanelViewModel
\if KO 기본 표시 용량으로 을 초기화합니다. \endif \if EN Initializes with the default display capacity. \endif
logStore— \if KO 로그 저장소입니다. \endif \if EN The log store. \endifdispatcher— \if KO WPF UI 디스패처 래퍼입니다. \endif \if EN The WPF UI dispatcher wrapper. \endif\if KO 지정한 표시 용량으로 을 초기화합니다. \endif \if EN Initializes with the specified display capacity. \endif
logStore— \if KO 로그 저장소입니다. \endif \if EN The log store. \endifdispatcher— \if KO WPF UI 디스패처 래퍼입니다. \endif \if EN The WPF UI dispatcher wrapper. \endifdisplayCapacity— \if KO 유지할 최대 표시 항목 수이며 0 이하면 기본값을 사용합니다. \endif \if EN The maximum displayed entry count; the default is used when non-positive. \endif\if KO 표시된 로그 항목과 기본 저장소를 모두 지웁니다. \endif \if EN Clears all displayed log entries and the underlying store. \endif
\if KO 로그 저장소 이벤트 구독을 해제합니다. \endif \if EN Releases the log-store event subscription. \endif
\if KO UI 스레드에서 로그 배치 또는 지우기 신호를 적용하고 표시 용량을 제한합니다. \endif \if EN Applies a log batch or clear sentinel on the UI thread and enforces display capacity. \endif
batch— \if KO 적용할 로그 항목 및 지우기 신호 배치입니다. \endif \if EN The batch of log entries and clear sentinels to apply. \endif\if KO 모든 스레드에서 수신한 새 로그 항목을 UI 배치 큐에 추가합니다. \endif \if EN Enqueues a new log entry received on any thread for UI batching. \endif
sender— \if KO 이벤트 발생 원본입니다. \endif \if EN The event source. \endifentry— \if KO 추가된 로그 항목입니다. \endif \if EN The added log entry. \endif\if KO 지정한 속성에 대한 변경 알림을 발생시킵니다. \endif \if EN Raises a change notification for the specified property. \endif
propertyName— \if KO 변경된 속성 이름입니다. \endif \if EN The changed property name. \endif\if KO 가장 최근 항목을 자동 선택할지 여부를 가져오거나 설정합니다. \endif \if EN Gets or sets whether the most recent entry is selected automatically. \endif
\if KO 로그 패널에 표시되는 로그 항목을 가져옵니다. \endif \if EN Gets the log entries displayed by the log panel. \endif
\if KO 선택된 로그 항목의 상세 텍스트를 가져옵니다. \endif \if EN Gets the detail text of the selected log entry. \endif
\if KO 선택된 로그 항목을 가져오거나 설정합니다. \endif \if EN Gets or sets the selected log entry. \endif
\if KO auto Scroll 값을 보관합니다. \endif \if EN Stores the auto scroll value. \endif
\if KO display Capacity 값을 보관합니다. \endif \if EN Stores the display capacity value. \endif
\if KO disposed 값을 보관합니다. \endif \if EN Stores the disposed value. \endif
\if KO log Store 값을 보관합니다. \endif \if EN Stores the log store value. \endif
\if KO selected Entry 값을 보관합니다. \endif \if EN Stores the selected entry value. \endif
\if KO ui Dispatcher 값을 보관합니다. \endif \if EN Stores the ui dispatcher value. \endif
\if KO 에 유지할 기본 최대 항목 수입니다. \endif \if EN The default maximum number of entries kept in . \endif
\if KO 배치가 추가된 후 가장 최근 항목과 함께 UI 스레드에서 발생합니다. \endif \if EN Occurs on the UI thread with the most recent entry after a batch is appended. \endif
\if KO 속성 값이 변경될 때 발생합니다. \endif \if EN Occurs when a property value changes. \endif
ILogUiDispatcher
\if KO 지정한 작업을 UI 스레드에서 비동기 실행합니다. \endif \if EN Executes the specified action asynchronously on the UI thread. \endif
action— \if KO 실행할 작업입니다. \endif \if EN The action to execute. \endif\if KO 지정한 작업을 UI 스레드에서 동기 실행합니다. \endif \if EN Executes the specified action synchronously on the UI thread. \endif
action— \if KO 실행할 작업입니다. \endif \if EN The action to execute. \endif\if KO 일괄 UI 갱신에 사용하는 기본 디스패처를 가져옵니다. \endif \if EN Gets the underlying dispatcher used by batched UI updates. \endif
LogEntryCollection
\if KO 컬렉션 시작에서 지정한 수만큼 항목을 제거합니다. \endif \if EN Removes up to the specified number of items from the start. \endif
count— \if KO 제거할 최대 항목 수입니다. \endif \if EN The maximum number of items to remove. \endifWpfLogUiDispatcher
\if KO 현재 애플리케이션 또는 스레드 디스패처로 를 초기화합니다. \endif \if EN Initializes with the current application or thread dispatcher. \endif
\if KO 지정한 WPF 디스패처로 를 초기화합니다. \endif \if EN Initializes with the specified WPF dispatcher. \endif
dispatcher— \if KO 사용할 WPF 디스패처입니다. \endif \if EN The WPF dispatcher to use. \endif\if KO 지정한 작업을 UI 스레드의 우선순위로 비동기 실행합니다. \endif \if EN Executes the action asynchronously on the UI thread at priority. \endif
action— \if KO 실행할 작업입니다. \endif \if EN The action to execute. \endif\if KO 지정한 작업을 UI 스레드에서 동기 실행합니다. \endif \if EN Executes the specified action synchronously on the UI thread. \endif
action— \if KO 실행할 작업입니다. \endif \if EN The action to execute. \endif\if KO 기본 WPF 를 가져옵니다. \endif \if EN Gets the underlying WPF . \endif
\if KO dispatcher 값을 보관합니다. \endif \if EN Stores the dispatcher value. \endif