的版本 4 (V4) 适用于 .NET 的 AWS SDK 已经发布!
有关重大更改和迁移应用程序的信息,请参阅迁移主题。
本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
CloudWatch 使用 适用于 .NET 的 SDK (v4) 记录示例
以下代码示例向您展示了如何使用 适用于 .NET 的 AWS SDK (v4) with Lo CloudWatch gs 来执行操作和实现常见场景。
操作是大型程序的代码摘录,必须在上下文中运行。您可以通过操作了解如何调用单个服务函数,还可以通过函数相关场景的上下文查看操作。
场景是向您演示如何通过在一个服务中调用多个函数或与其他 AWS 服务结合来完成特定任务的代码示例。
每个示例都包含一个指向完整源代码的链接,您可以从中找到有关如何在上下文中设置和运行代码的说明。
操作
以下代码示例演示了如何使用 GetQueryResults。
- 适用于 .NET 的 SDK (v4)
-
注意
还有更多相关信息 GitHub。在 AWS 代码示例存储库
中查找完整示例,了解如何进行设置和运行。 /// <summary> /// Gets the results of a CloudWatch Logs Insights query. /// </summary> /// <param name="queryId">The ID of the query.</param> /// <returns>The query results response.</returns> public async Task<GetQueryResultsResponse?> GetQueryResultsAsync(string queryId) { try { var request = new GetQueryResultsRequest { QueryId = queryId }; var response = await _amazonCloudWatchLogs.GetQueryResultsAsync(request); return response; } catch (ResourceNotFoundException ex) { _logger.LogError($"Query not found: {ex.Message}"); return null; } catch (Exception ex) { _logger.LogError($"An error occurred while getting query results: {ex.Message}"); return null; } }-
有关 API 的详细信息,请参阅 适用于 .NET 的 AWS SDK API 参考GetQueryResults中的。
-
以下代码示例演示了如何使用 StartQuery。
- 适用于 .NET 的 SDK (v4)
-
注意
还有更多相关信息 GitHub。在 AWS 代码示例存储库
中查找完整示例,了解如何进行设置和运行。 /// <summary> /// Starts a CloudWatch Logs Insights query. /// </summary> /// <param name="logGroupName">The name of the log group to query.</param> /// <param name="queryString">The CloudWatch Logs Insights query string.</param> /// <param name="startTime">The start time for the query (seconds since epoch).</param> /// <param name="endTime">The end time for the query (seconds since epoch).</param> /// <param name="limit">The maximum number of results to return.</param> /// <returns>The query ID if successful, null otherwise.</returns> public async Task<string?> StartQueryAsync( string logGroupName, string queryString, long startTime, long endTime, int limit = 10000) { try { var request = new StartQueryRequest { LogGroupName = logGroupName, QueryString = queryString, StartTime = startTime, EndTime = endTime, Limit = limit }; var response = await _amazonCloudWatchLogs.StartQueryAsync(request); return response.QueryId; } catch (InvalidParameterException ex) { _logger.LogError($"Invalid parameter for query: {ex.Message}"); return null; } catch (ResourceNotFoundException ex) { _logger.LogError($"Log group not found: {ex.Message}"); return null; } catch (Exception ex) { _logger.LogError($"An error occurred while starting query: {ex.Message}"); return null; } }-
有关 API 的详细信息,请参阅 适用于 .NET 的 AWS SDK API 参考StartQuery中的。
-
场景
以下代码示例展示了如何使用 CloudWatch 日志查询超过 10,000 条记录。
- 适用于 .NET 的 SDK (v4)
-
注意
还有更多相关信息 GitHub。在 AWS 代码示例存储库
中查找完整示例,了解如何进行设置和运行。 这是演示大型查询场景的主要工作流程。
using System.Diagnostics; using System.Text.RegularExpressions; using Amazon.CloudFormation; using Amazon.CloudFormation.Model; using Amazon.CloudWatchLogs; using Amazon.CloudWatchLogs.Model; using CloudWatchLogsActions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace CloudWatchLogsScenario; public class LargeQueryWorkflow { /* Before running this .NET code example, set up your development environment, including your credentials. This .NET code example performs the following tasks for the CloudWatch Logs Large Query workflow: 1. Prepare the Application: - Prompt the user to deploy CloudFormation stack and generate sample logs. - Deploy the CloudFormation template for resource creation. - Generate 50,000 sample log entries using CloudWatch Logs API. - Wait 5 minutes for logs to be fully ingested. 2. Execute Large Query: - Perform recursive queries to retrieve all logs using binary search. - Display progress for each query executed. - Show total execution time and logs found. 3. Clean up: - Prompt the user to delete the CloudFormation stack and all resources. - Destroy the CloudFormation stack and wait until removed. */ public static ILogger<LargeQueryWorkflow> _logger = null!; public static CloudWatchLogsWrapper _wrapper = null!; public static IAmazonCloudFormation _amazonCloudFormation = null!; private static string _logGroupName = "/workflows/cloudwatch-logs/large-query"; private static string _logStreamName = "stream1"; private static long _queryStartDate; private static long _queryEndDate; public static bool _interactive = true; public static string _stackName = "CloudWatchLargeQueryStack"; private static string _stackResourcePath = "../../../../../../../scenarios/features/cloudwatch_logs_large_query/resources/stack.yaml"; public static async Task Main(string[] args) { using var host = Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => logging.AddFilter("System", LogLevel.Debug) .AddFilter("Microsoft", LogLevel.Information)) .ConfigureServices((_, services) => services.AddAWSService<IAmazonCloudWatchLogs>() .AddAWSService<IAmazonCloudFormation>() .AddTransient<CloudWatchLogsWrapper>() ) .Build(); if (_interactive) { _logger = LoggerFactory.Create(builder => { builder.AddConsole(); }) .CreateLogger<LargeQueryWorkflow>(); _wrapper = host.Services.GetRequiredService<CloudWatchLogsWrapper>(); _amazonCloudFormation = host.Services.GetRequiredService<IAmazonCloudFormation>(); } Console.WriteLine(new string('-', 80)); Console.WriteLine("Welcome to the CloudWatch Logs Large Query Scenario."); Console.WriteLine(new string('-', 80)); Console.WriteLine("This scenario demonstrates how to perform large-scale queries on"); Console.WriteLine("CloudWatch Logs using recursive binary search to retrieve more than"); Console.WriteLine("the 10,000 result limit."); Console.WriteLine(); try { Console.WriteLine(new string('-', 80)); var prepareSuccess = await PrepareApplication(); Console.WriteLine(new string('-', 80)); if (prepareSuccess) { Console.WriteLine(new string('-', 80)); await ExecuteLargeQuery(); Console.WriteLine(new string('-', 80)); } Console.WriteLine(new string('-', 80)); await Cleanup(); Console.WriteLine(new string('-', 80)); } catch (Exception ex) { _logger.LogError(ex, "There was a problem with the scenario, initiating cleanup..."); _interactive = false; await Cleanup(); } Console.WriteLine("CloudWatch Logs Large Query scenario completed."); } /// <summary> /// Runs the scenario workflow. Used for testing. /// </summary> public static async Task RunScenario() { Console.WriteLine(new string('-', 80)); Console.WriteLine("Welcome to the CloudWatch Logs Large Query Scenario."); Console.WriteLine(new string('-', 80)); Console.WriteLine("This scenario demonstrates how to perform large-scale queries on"); Console.WriteLine("CloudWatch Logs using recursive binary search to retrieve more than"); Console.WriteLine("the 10,000 result limit."); Console.WriteLine(); try { Console.WriteLine(new string('-', 80)); var prepareSuccess = await PrepareApplication(); Console.WriteLine(new string('-', 80)); if (prepareSuccess) { Console.WriteLine(new string('-', 80)); await ExecuteLargeQuery(); Console.WriteLine(new string('-', 80)); } Console.WriteLine(new string('-', 80)); await Cleanup(); Console.WriteLine(new string('-', 80)); } catch (Exception ex) { _logger.LogError(ex, "There was a problem with the scenario, initiating cleanup..."); _interactive = false; await Cleanup(); } Console.WriteLine("CloudWatch Logs Large Query scenario completed."); } /// <summary> /// Prepares the application by creating the necessary resources. /// </summary> /// <returns>True if the application was prepared successfully.</returns> public static async Task<bool> PrepareApplication() { Console.WriteLine("Preparing the application..."); Console.WriteLine(); try { var deployStack = !_interactive || GetYesNoResponse( "Would you like to deploy the CloudFormation stack and generate sample logs? (y/n) "); if (deployStack) { if (_interactive) { Console.Write( $"Enter a path for the CloudFormation stack resource .yaml file (or press Enter for default '{_stackResourcePath}'): "); string? inputPath = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(inputPath)) { _stackResourcePath = inputPath; } } _stackName = PromptUserForStackName(); var deploySuccess = await DeployCloudFormationStack(_stackName); if (deploySuccess) { Console.WriteLine(); Console.WriteLine("Generating 50,000 sample log entries..."); var generateSuccess = await GenerateSampleLogs(); if (generateSuccess) { Console.WriteLine(); Console.WriteLine("Sample logs created. Waiting 5 minutes for logs to be fully ingested..."); await WaitWithCountdown(300); Console.WriteLine("Application preparation complete."); return true; } } } else { _logGroupName = PromptUserForInput("Enter the log group name ", _logGroupName); _logStreamName = PromptUserForInput("Enter the log stream name ", _logStreamName); var startDateMs = PromptUserForLong("Enter the query start date (milliseconds since epoch): "); var endDateMs = PromptUserForLong("Enter the query end date (milliseconds since epoch): "); _queryStartDate = startDateMs / 1000; _queryEndDate = endDateMs / 1000; Console.WriteLine("Application preparation complete."); return true; } } catch (Exception ex) { _logger.LogError(ex, "An error occurred while preparing the application."); } Console.WriteLine("Application preparation failed."); return false; } /// <summary> /// Deploys the CloudFormation stack with the necessary resources. /// </summary> /// <param name="stackName">The name of the CloudFormation stack.</param> /// <returns>True if the stack was deployed successfully.</returns> private static async Task<bool> DeployCloudFormationStack(string stackName) { Console.WriteLine($"\nDeploying CloudFormation stack: {stackName}"); try { var request = new CreateStackRequest { StackName = stackName, TemplateBody = await File.ReadAllTextAsync(_stackResourcePath) }; var response = await _amazonCloudFormation.CreateStackAsync(request); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { Console.WriteLine($"CloudFormation stack creation started: {stackName}"); bool stackCreated = await WaitForStackCompletion(response.StackId); if (stackCreated) { Console.WriteLine("CloudFormation stack created successfully."); return true; } else { _logger.LogError($"CloudFormation stack creation failed: {stackName}"); return false; } } else { _logger.LogError($"Failed to create CloudFormation stack: {stackName}"); return false; } } catch (AlreadyExistsException) { _logger.LogWarning($"CloudFormation stack '{stackName}' already exists. Please provide a unique name."); var newStackName = PromptUserForStackName(); return await DeployCloudFormationStack(newStackName); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while deploying the CloudFormation stack: {stackName}"); return false; } } /// <summary> /// Waits for the CloudFormation stack to be in the CREATE_COMPLETE state. /// </summary> /// <param name="stackId">The ID of the CloudFormation stack.</param> /// <returns>True if the stack was created successfully.</returns> private static async Task<bool> WaitForStackCompletion(string stackId) { int retryCount = 0; const int maxRetries = 30; const int retryDelay = 10000; while (retryCount < maxRetries) { var describeStacksRequest = new DescribeStacksRequest { StackName = stackId }; var describeStacksResponse = await _amazonCloudFormation.DescribeStacksAsync(describeStacksRequest); if (describeStacksResponse.Stacks.Count > 0) { if (describeStacksResponse.Stacks[0].StackStatus == StackStatus.CREATE_COMPLETE) { return true; } if (describeStacksResponse.Stacks[0].StackStatus == StackStatus.CREATE_FAILED || describeStacksResponse.Stacks[0].StackStatus == StackStatus.ROLLBACK_COMPLETE) { return false; } } Console.WriteLine("Waiting for CloudFormation stack creation to complete..."); await Task.Delay(retryDelay); retryCount++; } _logger.LogError("Timed out waiting for CloudFormation stack creation to complete."); return false; } /// <summary> /// Generates sample logs directly using CloudWatch Logs API. /// Creates 50,000 log entries spanning 5 minutes. /// </summary> /// <returns>True if logs were generated successfully.</returns> private static async Task<bool> GenerateSampleLogs() { const int totalEntries = 50000; const int entriesPerBatch = 10000; const int fiveMinutesMs = 5 * 60 * 1000; try { // Calculate timestamps var startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var timestampIncrement = fiveMinutesMs / totalEntries; Console.WriteLine($"Generating {totalEntries} log entries..."); var entryCount = 0; var currentTimestamp = startTimeMs; var numBatches = totalEntries / entriesPerBatch; // Generate and upload logs in batches for (int batchNum = 0; batchNum < numBatches; batchNum++) { var logEvents = new List<InputLogEvent>(); for (int i = 0; i < entriesPerBatch; i++) { logEvents.Add(new InputLogEvent { Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(currentTimestamp).UtcDateTime, Message = $"Entry {entryCount}" }); entryCount++; currentTimestamp += timestampIncrement; } // Upload batch var success = await _wrapper.PutLogEventsAsync(_logGroupName, _logStreamName, logEvents); if (!success) { _logger.LogError($"Failed to upload batch {batchNum + 1}/{numBatches}"); return false; } Console.WriteLine($"Uploaded batch {batchNum + 1}/{numBatches}"); } // Set query date range (convert milliseconds to seconds for query API) _queryStartDate = startTimeMs / 1000; _queryEndDate = (currentTimestamp - timestampIncrement) / 1000; Console.WriteLine($"Query start date: {DateTimeOffset.FromUnixTimeSeconds(_queryStartDate):yyyy-MM-ddTHH:mm:ss.fffZ}"); Console.WriteLine($"Query end date: {DateTimeOffset.FromUnixTimeSeconds(_queryEndDate):yyyy-MM-ddTHH:mm:ss.fffZ}"); Console.WriteLine($"Successfully uploaded {totalEntries} log entries"); return true; } catch (Exception ex) { _logger.LogError(ex, "An error occurred while generating sample logs."); return false; } } /// <summary> /// Executes the large query workflow. /// </summary> public static async Task ExecuteLargeQuery() { Console.WriteLine("Starting recursive query to retrieve all logs..."); Console.WriteLine(); var queryLimit = PromptUserForInteger("Enter the query limit (max 10000) ", 10000); if (queryLimit > 10000) queryLimit = 10000; var queryString = "fields @timestamp, @message | sort @timestamp asc"; var stopwatch = Stopwatch.StartNew(); var allResults = await PerformLargeQuery(_logGroupName, queryString, _queryStartDate, _queryEndDate, queryLimit); stopwatch.Stop(); Console.WriteLine(); Console.WriteLine($"Queries finished in {stopwatch.Elapsed.TotalSeconds:F3} seconds."); Console.WriteLine($"Total logs found: {allResults.Count}"); // Check for duplicates Console.WriteLine(); Console.WriteLine("Checking for duplicate logs..."); var duplicates = FindDuplicateLogs(allResults); if (duplicates.Count > 0) { Console.WriteLine($"WARNING: Found {duplicates.Count} duplicate log entries!"); Console.WriteLine("Duplicate entries (showing first 10):"); foreach (var dup in duplicates.Take(10)) { Console.WriteLine($" [{dup.Timestamp}] {dup.Message} (appears {dup.Count} times)"); } var uniqueCount = allResults.Count - duplicates.Sum(d => d.Count - 1); Console.WriteLine($"Unique logs: {uniqueCount}"); } else { Console.WriteLine("No duplicates found. All logs are unique."); } Console.WriteLine(); var viewSample = !_interactive || GetYesNoResponse("Would you like to see a sample of the logs? (y/n) "); if (viewSample) { Console.WriteLine(); Console.WriteLine($"Sample logs (first 10 of {allResults.Count}):"); for (int i = 0; i < Math.Min(10, allResults.Count); i++) { var timestamp = allResults[i].Find(f => f.Field == "@timestamp")?.Value ?? "N/A"; var message = allResults[i].Find(f => f.Field == "@message")?.Value ?? "N/A"; Console.WriteLine($"[{timestamp}] {message}"); } } } /// <summary> /// Performs a large query using recursive binary search. /// </summary> private static async Task<List<List<ResultField>>> PerformLargeQuery( string logGroupName, string queryString, long startTime, long endTime, int limit) { var queryId = await _wrapper.StartQueryAsync(logGroupName, queryString, startTime, endTime, limit); if (queryId == null) { return new List<List<ResultField>>(); } var results = await PollQueryResults(queryId); if (results == null || results.Count == 0) { return new List<List<ResultField>>(); } var startDate = DateTimeOffset.FromUnixTimeSeconds(startTime).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var endDate = DateTimeOffset.FromUnixTimeSeconds(endTime).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); Console.WriteLine($"Query date range: {startDate} ({startTime}s) to {endDate} ({endTime}s). Found {results.Count} logs."); if (results.Count < limit) { Console.WriteLine($" -> Returning {results.Count} logs (less than limit of {limit})"); return results; } Console.WriteLine($" -> Hit limit of {limit}. Need to split and recurse."); // Get the timestamp of the last log (sorted to find the actual last one) var lastLogTimestamp = GetLastLogTimestamp(results); if (lastLogTimestamp == null) { Console.WriteLine($" -> No timestamp found in results. Returning {results.Count} logs."); return results; } Console.WriteLine($" -> Last log timestamp: {lastLogTimestamp}"); // Parse the timestamp and add 1 millisecond to avoid querying the same log again var lastLogDate = DateTimeOffset.Parse(lastLogTimestamp + " +0000"); Console.WriteLine($" -> Last log as DateTimeOffset: {lastLogDate:yyyy-MM-ddTHH:mm:ss.fffZ} ({lastLogDate.ToUnixTimeSeconds()}s)"); var offsetLastLogDate = lastLogDate.AddMilliseconds(1); Console.WriteLine($" -> Offset timestamp (last + 1ms): {offsetLastLogDate:yyyy-MM-ddTHH:mm:ss.fffZ} ({offsetLastLogDate.ToUnixTimeSeconds()}s)"); // Convert to seconds, but round UP to the next second to avoid overlapping with logs in the same second // This ensures we don't re-query logs that share the same second as the last log var offsetLastLogTime = offsetLastLogDate.ToUnixTimeSeconds(); if (offsetLastLogDate.Millisecond > 0) { offsetLastLogTime++; // Move to the next full second Console.WriteLine($" -> Adjusted to next full second: {offsetLastLogTime}s ({DateTimeOffset.FromUnixTimeSeconds(offsetLastLogTime):yyyy-MM-ddTHH:mm:ss.fffZ})"); } Console.WriteLine($" -> Comparing: offsetLastLogTime={offsetLastLogTime}s vs endTime={endTime}s"); Console.WriteLine($" -> End time as date: {DateTimeOffset.FromUnixTimeSeconds(endTime):yyyy-MM-ddTHH:mm:ss.fffZ}"); // Check if there's any time range left to query if (offsetLastLogTime >= endTime) { Console.WriteLine($" -> No time range left to query. Offset time ({offsetLastLogTime}s) >= end time ({endTime}s)"); return results; } // Split the remaining date range in half var (range1Start, range1End, range2Start, range2End) = SplitDateRange(offsetLastLogTime, endTime); var range1StartDate = DateTimeOffset.FromUnixTimeSeconds(range1Start).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var range1EndDate = DateTimeOffset.FromUnixTimeSeconds(range1End).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var range2StartDate = DateTimeOffset.FromUnixTimeSeconds(range2Start).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var range2EndDate = DateTimeOffset.FromUnixTimeSeconds(range2End).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); Console.WriteLine($" -> Splitting remaining range:"); Console.WriteLine($" Range 1: {range1StartDate} ({range1Start}s) to {range1EndDate} ({range1End}s)"); Console.WriteLine($" Range 2: {range2StartDate} ({range2Start}s) to {range2EndDate} ({range2End}s)"); // Query both halves recursively Console.WriteLine($" -> Querying range 1..."); var results1 = await PerformLargeQuery(logGroupName, queryString, range1Start, range1End, limit); Console.WriteLine($" -> Range 1 returned {results1.Count} logs"); Console.WriteLine($" -> Querying range 2..."); var results2 = await PerformLargeQuery(logGroupName, queryString, range2Start, range2End, limit); Console.WriteLine($" -> Range 2 returned {results2.Count} logs"); // Combine all results var allResults = new List<List<ResultField>>(results); allResults.AddRange(results1); allResults.AddRange(results2); Console.WriteLine($" -> Combined total: {allResults.Count} logs ({results.Count} + {results1.Count} + {results2.Count})"); return allResults; } /// <summary> /// Gets the timestamp string of the most recent log from a list of logs. /// Sorts timestamps to find the actual last one. /// </summary> private static string? GetLastLogTimestamp(List<List<ResultField>> logs) { var timestamps = logs .Select(log => log.Find(f => f.Field == "@timestamp")?.Value) .Where(t => !string.IsNullOrEmpty(t)) .OrderBy(t => t) .ToList(); if (timestamps.Count == 0) { return null; } return timestamps[timestamps.Count - 1]; } /// <summary> /// Splits a date range in half. /// Range 2 starts at midpoint + 1 second to avoid overlap. /// </summary> private static (long range1Start, long range1End, long range2Start, long range2End) SplitDateRange(long startTime, long endTime) { var midpoint = startTime + (endTime - startTime) / 2; // Range 2 starts at midpoint + 1 to avoid querying the same second twice return (startTime, midpoint, midpoint + 1, endTime); } /// <summary> /// Polls for query results until complete. /// </summary> private static async Task<List<List<ResultField>>?> PollQueryResults(string queryId) { int retryCount = 0; const int maxRetries = 60; const int retryDelay = 1000; while (retryCount < maxRetries) { var response = await _wrapper.GetQueryResultsAsync(queryId); if (response == null) { return null; } if (response.Status == QueryStatus.Complete) { return response.Results; } if (response.Status == QueryStatus.Failed || response.Status == QueryStatus.Cancelled || response.Status == QueryStatus.Timeout || response.Status == QueryStatus.Unknown) { _logger.LogError($"Query failed with status: {response.Status}"); return null; } await Task.Delay(retryDelay); retryCount++; } _logger.LogError("Timed out waiting for query results."); return null; } /// <summary> /// Cleans up the resources created during the scenario. /// </summary> public static async Task<bool> Cleanup() { var cleanup = !_interactive || GetYesNoResponse( "Do you want to delete the CloudFormation stack and all resources? (y/n) "); if (cleanup) { try { var stackDeleteSuccess = await DeleteCloudFormationStack(_stackName, false); return stackDeleteSuccess; } catch (Exception ex) { _logger.LogError(ex, "An error occurred while cleaning up the resources."); return false; } } Console.WriteLine($"Resources will remain. Stack name: {_stackName}, Log group: {_logGroupName}"); _logger.LogInformation("CloudWatch Logs Large Query scenario is complete."); return true; } /// <summary> /// Deletes the CloudFormation stack and waits for confirmation. /// </summary> private static async Task<bool> DeleteCloudFormationStack(string stackName, bool forceDelete) { var request = new DeleteStackRequest { StackName = stackName, }; if (forceDelete) { request.DeletionMode = DeletionMode.FORCE_DELETE_STACK; } await _amazonCloudFormation.DeleteStackAsync(request); Console.WriteLine($"CloudFormation stack '{stackName}' is being deleted. This may take a few minutes."); bool stackDeleted = await WaitForStackDeletion(stackName, forceDelete); if (stackDeleted) { Console.WriteLine($"CloudFormation stack '{stackName}' has been deleted."); return true; } else { _logger.LogError($"Failed to delete CloudFormation stack '{stackName}'."); return false; } } /// <summary> /// Waits for the stack to be deleted. /// </summary> private static async Task<bool> WaitForStackDeletion(string stackName, bool forceDelete) { int retryCount = 0; const int maxRetries = 30; const int retryDelay = 10000; while (retryCount < maxRetries) { var describeStacksRequest = new DescribeStacksRequest { StackName = stackName }; try { var describeStacksResponse = await _amazonCloudFormation.DescribeStacksAsync(describeStacksRequest); if (describeStacksResponse.Stacks.Count == 0 || describeStacksResponse.Stacks[0].StackStatus == StackStatus.DELETE_COMPLETE) { return true; } if (!forceDelete && describeStacksResponse.Stacks[0].StackStatus == StackStatus.DELETE_FAILED) { return await DeleteCloudFormationStack(stackName, true); } } catch (AmazonCloudFormationException ex) when (ex.ErrorCode == "ValidationError") { return true; } Console.WriteLine($"Waiting for CloudFormation stack '{stackName}' to be deleted..."); await Task.Delay(retryDelay); retryCount++; } _logger.LogError($"Timed out waiting for CloudFormation stack '{stackName}' to be deleted."); return false; } /// <summary> /// Waits with a countdown display. /// </summary> private static async Task WaitWithCountdown(int seconds) { for (int i = seconds; i > 0; i--) { Console.Write($"\rWaiting: {i} seconds remaining... "); await Task.Delay(1000); } Console.WriteLine("\rWait complete. "); } /// <summary> /// Helper method to get a yes or no response from the user. /// </summary> private static bool GetYesNoResponse(string question) { Console.WriteLine(question); var ynResponse = Console.ReadLine(); var response = ynResponse != null && ynResponse.Equals("y", StringComparison.InvariantCultureIgnoreCase); return response; } /// <summary> /// Prompts the user for a stack name. /// </summary> private static string PromptUserForStackName() { if (_interactive) { Console.Write($"Enter a name for the CloudFormation stack (press Enter for default '{_stackName}'): "); string? input = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(input)) { var regex = "[a-zA-Z][-a-zA-Z0-9]*"; if (!Regex.IsMatch(input, regex)) { Console.WriteLine($"Invalid stack name. Using default: {_stackName}"); return _stackName; } return input; } } return _stackName; } /// <summary> /// Prompts the user for input with a default value. /// </summary> private static string PromptUserForInput(string prompt, string defaultValue) { if (_interactive) { Console.Write($"{prompt}(press Enter for default '{defaultValue}'): "); string? input = Console.ReadLine(); return string.IsNullOrWhiteSpace(input) ? defaultValue : input; } return defaultValue; } /// <summary> /// Prompts the user for an integer value. /// </summary> private static int PromptUserForInteger(string prompt, int defaultValue) { if (_interactive) { Console.Write($"{prompt}(press Enter for default '{defaultValue}'): "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input) || !int.TryParse(input, out var result)) { return defaultValue; } return result; } return defaultValue; } /// <summary> /// Prompts the user for a long value. /// </summary> private static long PromptUserForLong(string prompt) { if (_interactive) { Console.Write(prompt); string? input = Console.ReadLine(); if (long.TryParse(input, out var result)) { return result; } } return 0; } /// <summary> /// Finds duplicate log entries based on timestamp and message. /// </summary> private static List<(string Timestamp, string Message, int Count)> FindDuplicateLogs(List<List<ResultField>> logs) { var logSignatures = new Dictionary<string, int>(); foreach (var log in logs) { var timestamp = log.Find(f => f.Field == "@timestamp")?.Value ?? ""; var message = log.Find(f => f.Field == "@message")?.Value ?? ""; var signature = $"{timestamp}|{message}"; if (logSignatures.ContainsKey(signature)) { logSignatures[signature]++; } else { logSignatures[signature] = 1; } } return logSignatures .Where(kvp => kvp.Value > 1) .Select(kvp => { var parts = kvp.Key.Split('|'); return (Timestamp: parts[0], Message: parts[1], Count: kvp.Value); }) .OrderByDescending(x => x.Count) .ToList(); } }-
有关 API 详细信息,请参阅《适用于 .NET 的 AWS SDK API Reference》中的以下主题。
-