[변경] AptabaseClient.cs

- 종료 직전에 보내는 Event 누락 방지하기 위한 코드 수정
  : 큐에 이벤트를 추가하면 +1, 큐에서 읽어와서 Event를 Send하면 -1하는 필드 추가
  : 필드가 0이되기를 기다리는 함수 추가(대기 한계 시간 기본값 1500ms)

[변경] AptabaseClientBase.cs
- EventData에 ipAddredd 할당하는 코드 제거
 : 매번 API 호출하면서 속도 지연의 원인이 됨
- aptabase-debug.log 파일 생성하는 코드 제거
 : 파일 관리가 안되고 있으므로 제거
This commit is contained in:
정나래
2025-09-12 17:47:24 +09:00
parent be3d38d066
commit 3475a7b988
2 changed files with 53 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Threading.Channels; using System.Threading.Channels;
using System; using System;
using System.Diagnostics;
namespace Aptabase.WPF; namespace Aptabase.WPF;
@@ -10,6 +11,10 @@ public class AptabaseClient : IAptabaseClient
private Task? _processingTask; private Task? _processingTask;
private AptabaseClientBase? _client; private AptabaseClientBase? _client;
private CancellationTokenSource? _cts; private CancellationTokenSource? _cts;
private ILogger? _logger;
private int _queueItemCount = 0;
public bool IsRunning => _processingTask != null && !_processingTask.IsCompleted;
public AptabaseClient(string appKey, AptabaseOptions? options, ILogger? logger) public AptabaseClient(string appKey, AptabaseOptions? options, ILogger? logger)
{ {
@@ -20,30 +25,44 @@ public class AptabaseClient : IAptabaseClient
{ {
_client = new AptabaseClientBase(appKey, options, logger); _client = new AptabaseClientBase(appKey, options, logger);
_channel = Channel.CreateUnbounded<EventData>(); _channel = Channel.CreateUnbounded<EventData>();
_processingTask = Task.Run(ProcessEventsAsync);
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
_processingTask = Task.Run(() => ProcessEventsAsync(_cts.Token));
_logger = logger;
} }
public Task TrackEvent(string eventName, Dictionary<string, object>? props = null) public async Task TrackEvent(string eventName, Dictionary<string, object>? props = null)
{ {
_channel?.Writer.TryWrite(new EventData(eventName, props)); try
{
return Task.CompletedTask; if (_channel != null)
{
await _channel.Writer.WriteAsync(new EventData(eventName, props));
Interlocked.Increment(ref _queueItemCount);
Debug.WriteLine($"Queued event {eventName} to Aptabase {_queueItemCount}");
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error queueing event {EventName} to Aptabase", eventName);
Console.WriteLine(ex.ToString());
}
} }
private async Task ProcessEventsAsync() private async Task ProcessEventsAsync(CancellationToken cancellationToken)
{ {
try try
{ {
if (_channel is null || _cts is null || _client is null) if (_channel is null || _cts is null || _client is null)
{ {
Interlocked.CompareExchange(ref _queueItemCount, 0, 0);
return; return;
} }
while (await _channel.Reader.WaitToReadAsync()) while (await _channel.Reader.WaitToReadAsync(cancellationToken))
{ {
if (_cts.IsCancellationRequested) if (_cts.IsCancellationRequested)
{ {
Interlocked.CompareExchange(ref _queueItemCount, 0, 0);
break; break;
} }
@@ -51,23 +70,30 @@ public class AptabaseClient : IAptabaseClient
{ {
if (_cts.IsCancellationRequested) if (_cts.IsCancellationRequested)
{ {
Interlocked.CompareExchange(ref _queueItemCount, 0, 0);
break; break;
} }
try try
{ {
await _client.TrackEvent(eventData); await _client.TrackEvent(eventData);
Interlocked.Decrement(ref _queueItemCount);
Debug.WriteLine($"Sent event {eventData.EventName} to Aptabase {eventData.Timestamp} / {_queueItemCount}");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger?.LogError(ex, "Error sending event {EventName} to Aptabase", eventData.EventName);
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
Debug.WriteLine($"Error sending event {eventData.EventName} to Aptabase: {ex.Message}");
} }
} }
} }
} }
catch (ChannelClosedException) catch (ChannelClosedException ex)
{ {
// ignore Interlocked.CompareExchange(ref _queueItemCount, 0, 0);
_logger?.LogError(ex, "Channel closed unexpectedly");
Debug.WriteLine($"Channel closed unexpectedly: {ex.Message}");
} }
} }
@@ -94,4 +120,18 @@ public class AptabaseClient : IAptabaseClient
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public async Task StopAsync(int timeoutMs = 1500)
{
_channel?.Writer.Complete();
var sw = Stopwatch.StartNew();
while (Volatile.Read(ref _queueItemCount) > 0 && sw.ElapsedMilliseconds < timeoutMs)
{
await Task.Delay(100); // 큐가 비워질 때까지 대기
}
if (_processingTask is { IsCompleted: false })
await Task.WhenAny(_processingTask, Task.Delay(timeoutMs)); // 드레인 대기 (타임아웃 허용)
}
} }

View File

@@ -73,13 +73,13 @@ internal class AptabaseClientBase : IAsyncDisposable
if (eventData.Props is null) if (eventData.Props is null)
eventData.Props = new Dictionary<string, object>(); eventData.Props = new Dictionary<string, object>();
eventData.Props["ipAddress"] = await GetPublicIpAddress(); //eventData.Props["ipAddress"] = await GetPublicIpAddress(); 느려서 제외. 필요하면 다른 방법 사용 요망
var body = JsonContent.Create(eventData); var body = JsonContent.Create(eventData);
var response = await _http.PostAsync("/api/v0/event", body); var response = await _http.PostAsync("/api/v0/event", body);
var logPath = "aptabase-debug.log"; //var logPath = "aptabase-debug.log"; //파일 관리 안되므로 제거. 필요하면 다른 방법 사용 요망
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
@@ -94,11 +94,11 @@ internal class AptabaseClientBase : IAsyncDisposable
var responseBody = await response.Content.ReadAsStringAsync(); var responseBody = await response.Content.ReadAsStringAsync();
_logger?.LogError("Failed to perform TrackEvent due to {StatusCode} and response body {Body}", response.StatusCode, responseBody); _logger?.LogError("Failed to perform TrackEvent due to {StatusCode} and response body {Body}", response.StatusCode, responseBody);
File.AppendAllText(logPath, $"Failed to perform TrackEvent due to {response.StatusCode} and response body {responseBody}\n"); //File.AppendAllText(logPath, $"{eventData.EventName} Failed to perform TrackEvent due to {response.StatusCode} and response body {responseBody}\n");
} }
else else
{ var responseBody = await response.Content.ReadAsStringAsync(); { var responseBody = await response.Content.ReadAsStringAsync();
File.AppendAllText(logPath, $"TrackEvent successful with response body {responseBody}\n"); //File.AppendAllText(logPath, $"{eventData.EventName} TrackEvent successful with response body {responseBody}\n");
} }
} }