AzdResourceLogService.cs•6.07 kB
using System.Diagnostics.CodeAnalysis;
using Azure.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
namespace Areas.Deploy.Services.Util;
public static class AzdResourceLogService
{
    private const string AzureYamlFileName = "azure.yaml";
    public static async Task<string> GetAzdResourceLogsAsync(
        TokenCredential credential,
        string workspaceFolder,
        string azdEnvName,
        string subscriptionId,
        int? limit = null)
    {
        var toolErrorLogs = new List<string>();
        var appLogs = new List<string>();
        try
        {
            var azdAppLogRetriever = new AzdAppLogRetriever(credential, subscriptionId, azdEnvName);
            await azdAppLogRetriever.InitializeAsync();
            await azdAppLogRetriever.GetLogAnalyticsWorkspacesInfoAsync();
            var services = GetServicesFromAzureYaml(workspaceFolder);
            foreach (var (serviceName, service) in services)
            {
                try
                {
                    if (service.Host != null)
                    {
                        var resourceType = ResourceTypeExtensions.GetResourceTypeFromHost(service.Host);
                        var logs = await azdAppLogRetriever.QueryAppLogsAsync(resourceType, serviceName, limit);
                        appLogs.Add(logs);
                    }
                }
                catch (Exception ex)
                {
                    toolErrorLogs.Add($"Error finding app logs for service {serviceName}: {ex.Message}");
                }
            }
        }
        catch (Exception ex)
        {
            toolErrorLogs.Add(ex.Message);
        }
        if (appLogs.Count > 0)
        {
            return $"App logs retrieved:\n{string.Join("\n\n", appLogs)}";
        }
        if (toolErrorLogs.Count > 0)
        {
            return $"Error during retrieval of app logs of azd project:\n{string.Join("\n", toolErrorLogs)}";
        }
        return "No logs found.";
    }
    private static Dictionary<string, Service> GetServicesFromAzureYaml(string workspaceFolder)
    {
        var azureYamlPath = Path.Combine(workspaceFolder, AzureYamlFileName);
        if (!File.Exists(azureYamlPath))
        {
            throw new FileNotFoundException($"Azure YAML file not found at {azureYamlPath}");
        }
        var yamlContent = File.ReadAllText(azureYamlPath);
        using var stringReader = new StringReader(yamlContent);
        var parser = new YamlDotNet.Core.Parser(stringReader);
        return ParseAzureYamlServices(parser);
    }
    private static Dictionary<string, Service> ParseAzureYamlServices(YamlDotNet.Core.Parser parser)
    {
        var result = new Dictionary<string, Service>();
        parser.Consume<StreamStart>();
        parser.Consume<DocumentStart>();
        parser.Consume<MappingStart>();
        while (parser.Accept<MappingEnd>(out _) == false)
        {
            var key = parser.Consume<Scalar>().Value;
            if (key == "services")
            {
                parser.Consume<MappingStart>();
                while (parser.Accept<MappingEnd>(out _) == false)
                {
                    var serviceName = parser.Consume<Scalar>().Value;
                    parser.Consume<MappingStart>();
                    string? host = null;
                    string? project = null;
                    string? language = null;
                    while (parser.Accept<MappingEnd>(out _) == false)
                    {
                        var propertyKey = parser.Consume<Scalar>().Value;
                        // Only accept properties host, project, and language which are scalars
                        if (parser.Accept<Scalar>(out _))
                        {
                            var propertyValue = parser.Consume<Scalar>().Value;
                            switch (propertyKey)
                            {
                                case "host":
                                    host = propertyValue;
                                    break;
                                case "project":
                                    project = propertyValue;
                                    break;
                                case "language":
                                    language = propertyValue;
                                    break;
                            }
                        }
                        else
                        {
                            SkipValue(parser);
                        }
                    }
                    parser.Consume<MappingEnd>();
                    result[serviceName] = new Service(
                        Host: host,
                        Project: project,
                        Language: language
                    );
                }
                parser.Consume<MappingEnd>();
            }
            else
            {
                SkipValue(parser);
            }
        }
        if (result.Count == 0)
        {
            throw new InvalidOperationException("No services section found in azure.yaml");
        }
        return result;
    }
    private static void SkipValue(YamlDotNet.Core.Parser parser)
    {
        if (parser.Accept<Scalar>(out _))
        {
            parser.Consume<Scalar>();
        }
        else if (parser.Accept<MappingStart>(out _))
        {
            parser.Consume<MappingStart>();
            while (!parser.Accept<MappingEnd>(out _))
            {
                SkipValue(parser);
                SkipValue(parser);
            }
            parser.Consume<MappingEnd>();
        }
        else if (parser.Accept<SequenceStart>(out _))
        {
            parser.Consume<SequenceStart>();
            while (!parser.Accept<SequenceEnd>(out _))
            {
                SkipValue(parser);
            }
            parser.Consume<SequenceEnd>();
        }
    }
}
public record Service(
    string? Host = null,
    string? Project = null,
    string? Language = null
);