internal sealed class AspireHostResourceLogWatcher()

in src/dotnet/AspireWorker/AspireHost/AspireHostResourceLogWatcher.cs [12:96]


internal sealed class AspireHostResourceLogWatcher(
    DashboardService.DashboardServiceClient client,
    Metadata headers,
    IRdConnectionWrapper connectionWrapper,
    AspireHostModel hostModel,
    ResiliencePipelineProvider<string> resiliencePipelineProvider,
    ILogger logger,
    Lifetime lifetime)
{
    private readonly ResiliencePipeline _pipeline =
        resiliencePipelineProvider.GetPipeline(nameof(AspireHostResourceLogWatcher));

    internal async Task WatchResourceLogs()
    {
        await connectionWrapper.ViewResources(hostModel, lifetime, (lt, resourceId, resource) =>
        {
            lt.StartAttachedAsync(
                TaskScheduler.Default,
                async () => await WatchResourceLogs(resourceId, resource, lt)
            );
        });
    }

    private async Task WatchResourceLogs(string resourceName, ResourceWrapper resource, Lifetime resourceLifetime)
    {
        try
        {
            logger.StartLogWatchingForResource(resourceName);

            if (!resource.IsInitialized.HasTrueValue())
            {
                await resource.IsInitialized.NextTrueValueAsync(resourceLifetime);
            }

            await Task.Delay(TimeSpan.FromSeconds(5), resourceLifetime);

            await _pipeline.ExecuteAsync(
                async token => await SendWatchResourceLogsRequest(resourceName, resource, token), resourceLifetime);

            logger.StopLogWatchingForResource(resourceName, resourceLifetime.IsAlive);
        }
        catch (OperationCanceledException)
        {
            logger.ResourceLogWatchingWasCancelled();
        }
    }

    private async Task SendWatchResourceLogsRequest(
        string resourceName,
        ResourceWrapper resource,
        CancellationToken ct)
    {
        logger.SendingLogWatchingRequestForResource(resourceName);

        try
        {
            var request = new WatchResourceConsoleLogsRequest { ResourceName = resourceName };
            var response = client.WatchResourceConsoleLogs(request, headers: headers, cancellationToken: ct);
            await foreach (var update in response.ResponseStream.ReadAllAsync(ct))
            {
                foreach (var logLine in update.LogLines)
                {
                    ct.ThrowIfCancellationRequested();

                    if (string.IsNullOrEmpty(logLine.Text)) continue;

                    logger.ResourceLogLineReceived(logLine);

                    var resourceLog = new ResourceLog(
                        logLine.Text,
                        // ReSharper disable once SimplifyConditionalTernaryExpression
                        logLine.HasIsStdErr ? logLine.IsStdErr : false,
                        logLine.LineNumber
                    );

                    await connectionWrapper.ResourceLogReceived(resource, resourceLog);
                }
            }
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            logger.LogWatchingRequestWasCancelled();
        }
    }
}