File: ResourceLoggerServiceTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.Tests.Utils;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Logging;
using Xunit;
 
namespace Aspire.Hosting.Tests;
 
public class ResourceLoggerServiceTests
{
    [Fact]
    public async Task AddingResourceLoggerAnnotationAllowsLogging()
    {
        var testResource = new TestResource("myResource");
        var service = ConsoleLoggingTestHelpers.GetResourceLoggerService();
        var logger = service.GetLogger(testResource);
 
        var subsLoop = WatchForSubscribers(service);
 
        var logsEnumerator1 = service.WatchAsync(testResource).GetAsyncEnumerator();
        var logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator1, 2);
 
        // Wait for subscriber to be added
        await subsLoop.DefaultTimeout();
 
        // Log
        logger.LogInformation("Hello, world!");
        logger.LogError("Hello, error!");
 
        // Wait for logs to be read
        var allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", allLogs[0].Content);
        Assert.False(allLogs[0].IsErrorMessage);
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", allLogs[1].Content);
        Assert.True(allLogs[1].IsErrorMessage);
 
        // New sub should get the previous logs
        subsLoop = WatchForSubscribers(service);
        var logsEnumerator2 = service.WatchAsync(testResource).GetAsyncEnumerator();
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator2, 2);
        await subsLoop.DefaultTimeout();
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal(2, allLogs.Count);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", allLogs[0].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", allLogs[1].Content);
 
        await logsEnumerator1.DisposeAsync().DefaultTimeout();
        await logsEnumerator2.DisposeAsync().DefaultTimeout();
    }
 
    [Fact]
    public async Task StreamingLogsCancelledAfterComplete()
    {
        var testResource = new TestResource("myResource");
        var service = ConsoleLoggingTestHelpers.GetResourceLoggerService();
        var logger = service.GetLogger(testResource);
 
        var subsLoop = WatchForSubscribers(service);
        var logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(service, 2, testResource);
 
        // Wait for subscriber to be added
        await subsLoop.DefaultTimeout();
 
        logger.LogInformation("Hello, world!");
        logger.LogError("Hello, error!");
 
        // Complete the log stream & log afterwards
        service.Complete(testResource);
        logger.LogInformation("The third log");
 
        // Wait for logs to be read
        var allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Collection(allLogs,
            l => Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", l.Content),
            l => Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", l.Content));
 
        // The backlog should be cleared once there are no subscribers.
        Assert.Empty(service.GetResourceLoggerState(testResource.Name).GetBacklogSnapshot());
 
        // New sub should replay logs again.
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(service, 100, testResource);
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Collection(allLogs,
            l => Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", l.Content),
            l => Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", l.Content));
    }
 
    [Fact]
    public async Task SecondSubscriberGetsBacklog()
    {
        var testResource = new TestResource("myResource");
        var service = ConsoleLoggingTestHelpers.GetResourceLoggerService();
        var logger = service.GetLogger(testResource);
 
        var subsLoop = WatchForSubscribers(service);
        var logsEnumerator1 = service.WatchAsync(testResource).GetAsyncEnumerator();
        var logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator1, 2);
 
        // Wait for subscriber to be added
        await subsLoop.DefaultTimeout();
 
        // Log
        logger.LogInformation("Hello, world!");
        logger.LogError("Hello, error!");
 
        // Wait for logs to be read
        var allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", allLogs[0].Content);
        Assert.False(allLogs[0].IsErrorMessage);
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", allLogs[1].Content);
        Assert.True(allLogs[1].IsErrorMessage);
 
        // New sub should get the previous logs (backlog)
        subsLoop = WatchForSubscribers(service);
        var logsEnumerator2 = service.WatchAsync(testResource).GetAsyncEnumerator();
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator2, 2);
        await subsLoop.DefaultTimeout();
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal(2, allLogs.Count);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", allLogs[0].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", allLogs[1].Content);
 
        // Clear the backlog and ensure new subs only get new logs
        service.ClearBacklog(testResource.Name);
 
        subsLoop = WatchForSubscribers(service);
        var logsEnumerator3 = service.WatchAsync(testResource).GetAsyncEnumerator();
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator3, 1);
        await subsLoop.DefaultTimeout();
        logger.LogInformation("The third log");
        allLogs = await logsLoop.DefaultTimeout();
 
        // The backlog should be cleared so only new logs are received
        Assert.Single(allLogs);
        Assert.Equal("2000-12-29T20:59:59.0000000Z The third log", allLogs[0].Content);
    }
 
    [Fact]
    public async Task InMemoryLogsPreservedBetweenWatches()
    {
        var testResource = new TestResource("myResource");
        var service = ConsoleLoggingTestHelpers.GetResourceLoggerService();
        var logger = service.GetLogger(testResource);
 
        // Log before watching
        logger.LogInformation("Before watching!");
 
        var subsLoop = WatchForSubscribers(service);
        var logsEnumerator1 = service.WatchAsync(testResource).GetAsyncEnumerator();
        var logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator1, 1);
 
        // Wait for subscriber to be added
        await subsLoop.DefaultTimeout();
 
        // Read before watching log
        var allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Before watching!", allLogs[0].Content);
        Assert.False(allLogs[0].IsErrorMessage);
 
        // Log while watching
        logger.LogInformation("While watching!");
 
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator1, 1);
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z While watching!", allLogs[0].Content);
        Assert.False(allLogs[0].IsErrorMessage);
 
        // New sub should get the previous logs (backlog)
        subsLoop = WatchForSubscribers(service);
        var logsEnumerator2 = service.WatchAsync(testResource).GetAsyncEnumerator();
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator2, 2);
        await subsLoop.DefaultTimeout();
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal(2, allLogs.Count);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Before watching!", allLogs[0].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z While watching!", allLogs[1].Content);
 
        await logsEnumerator1.DisposeAsync().DefaultTimeout();
        await logsEnumerator2.DisposeAsync().DefaultTimeout();
 
        logger.LogInformation("After watching!");
 
        // The backlog should be cleared once there are no subscribers.
        Assert.Empty(service.GetResourceLoggerState(testResource.Name).GetBacklogSnapshot());
 
        subsLoop = WatchForSubscribers(service);
        var logsEnumerator3 = service.WatchAsync(testResource).GetAsyncEnumerator();
        logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator3, 4);
        await subsLoop.DefaultTimeout();
        logger.LogInformation("While watching again!");
        allLogs = await logsLoop.DefaultTimeout();
 
        Assert.Equal(4, allLogs.Count);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Before watching!", allLogs[0].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z While watching!", allLogs[1].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z After watching!", allLogs[2].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z While watching again!", allLogs[3].Content);
    }
 
    [Fact]
    public async Task MultipleInstancesLogsToAll()
    {
        var testResource = new TestResource("myResource");
        testResource.Annotations.Add(new DcpInstancesAnnotation([new DcpInstance("instance0", "0", 0), new DcpInstance("instance1", "1", 1)]));
 
        var service = ConsoleLoggingTestHelpers.GetResourceLoggerService();
        var logger = service.GetLogger(testResource);
 
        var subsLoop = WatchForSubscribers(service);
 
        var logsEnumerator = service.WatchAsync(testResource).GetAsyncEnumerator();
        var logsLoop = ConsoleLoggingTestHelpers.WatchForLogsAsync(logsEnumerator, 4);
 
        // Wait for subscriber to be added
        await subsLoop.DefaultTimeout();
 
        // Log
        logger.LogInformation("Hello, world!");
        logger.LogError("Hello, error!");
 
        Assert.True(service.Loggers.ContainsKey("instance0"));
        Assert.True(service.Loggers.ContainsKey("instance1"));
 
        // Wait for logs to be read
        var allLogs = await logsLoop.DefaultTimeout();
 
        var sortedLogs = allLogs.OrderBy(l => l.LineNumber).ToList();
 
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", sortedLogs[0].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, world!", sortedLogs[1].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", sortedLogs[2].Content);
        Assert.Equal("2000-12-29T20:59:59.0000000Z Hello, error!", sortedLogs[3].Content);
 
        service.Complete(testResource);
 
        Assert.False(await logsEnumerator.MoveNextAsync().DefaultTimeout());
 
        await logsEnumerator.DisposeAsync().DefaultTimeout();
    }
 
    private sealed class TestResource(string name) : Resource(name)
    {
 
    }
 
    private static Task WatchForSubscribers(ResourceLoggerService service)
    {
        return Task.Run(async () =>
        {
            await foreach (var sub in service.WatchAnySubscribersAsync())
            {
                if (sub.AnySubscribers)
                {
                    break;
                }
            }
        });
    }
}