AgentSkillsCN

azure-appinsights-to-cloudwatch

将Azure Application Insights迁移至AWS CloudWatch与X-Ray。转换自定义指标、分布式追踪、结构化日志记录与异常追踪功能。保留遥测扩展方法的签名,以最大限度地减少对应用的影响。

SKILL.md
--- frontmatter
name: azure-appinsights-to-cloudwatch
description: |
  Migrate Azure Application Insights to AWS CloudWatch and X-Ray. Converts custom metrics,
  distributed tracing, structured logging, and exception tracking. Preserves telemetry
  extension method signatures for minimal impact on applications.
disposition: contextual
filePatterns:
  - "**/*.cs"
  - "**/*.csproj"
  - "**/appsettings*.json"
  - "**/Program.cs"
  - "**/Startup.cs"
version: 1.0.0

Azure Application Insights → AWS CloudWatch + X-Ray Migration

Overview

Migrate from Microsoft.ApplicationInsights to AWS CloudWatch (metrics/logs) and X-Ray (distributed tracing) while preserving telemetry extension method signatures.

Package Migration

Azure Packages (Remove)

xml
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.x.x" />
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.x.x" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.x.x" />

AWS Packages (Add)

xml
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="3.7.x" />
<PackageReference Include="AWSSDK.CloudWatch" Version="3.7.x" />
<PackageReference Include="AWSXRayRecorder.Core" Version="2.x.x" />
<PackageReference Include="AWSXRayRecorder.Handlers.AspNetCore" Version="2.x.x" />
<PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="2.x.x" />
<PackageReference Include="AWS.Logger.AspNetCore" Version="3.x.x" />

Alternative (OpenTelemetry - Vendor Neutral):

xml
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.x.x" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.x.x" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.x.x" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.x.x" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.x.x" />
<PackageReference Include="AWS.Distro.OpenTelemetry" Version="1.x.x" />

Configuration Migration

Azure Configuration (appsettings.json)

json
{
  "ApplicationInsights": {
    "InstrumentationKey": "...",
    "EnableAdaptiveSampling": true,
    "EnableDependencyTracking": true
  }
}

AWS Configuration (appsettings.json)

json
{
  "AWS": {
    "Region": "us-east-1"
  },
  "Logging": {
    "LogGroup": "/aws/application/myapp",
    "Region": "us-east-1"
  },
  "XRay": {
    "ServiceName": "myapp",
    "SamplingRate": 0.1
  },
  "CloudWatch": {
    "Namespace": "MyApp/Metrics"
  }
}

Service Registration

Azure (Before)

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddApplicationInsightsTelemetry(options =>
{
    options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
    options.EnableAdaptiveSampling = true;
    options.EnableDependencyTracking = true;
});

AWS (After)

csharp
var builder = WebApplication.CreateBuilder(args);

// CloudWatch Logs
builder.Logging.AddAWSProvider(builder.Configuration.GetAWSLoggingConfigSection());

// CloudWatch Metrics
builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
builder.Services.AddAWSService<IAmazonCloudWatch>();

// X-Ray Tracing
builder.Services.AddAWSService<IAmazonXRay>();
AWSXRayRecorder.InitializeInstance(builder.Configuration);

builder.Services.AddSingleton<ITelemetryService, AwsTelemetryService>();

var app = builder.Build();

// X-Ray middleware (must be early in pipeline)
app.UseXRay("myapp");

app.Run();

Extension Method Preservation

CRITICAL: Keep telemetry API surface identical for consuming code.

Example Extension Methods (Keep Signatures)

csharp
public interface ITelemetryService
{
    void TrackEvent(string eventName, Dictionary<string, string>? properties = null);
    void TrackMetric(string metricName, double value, Dictionary<string, string>? dimensions = null);
    void TrackException(Exception exception, Dictionary<string, string>? properties = null);
    void TrackDependency(string dependencyType, string target, string dependencyName, 
        DateTimeOffset startTime, TimeSpan duration, bool success);
    IDisposable StartOperation(string operationName);
}

Azure Implementation (Before)

csharp
public class AzureTelemetryService : ITelemetryService
{
    private readonly TelemetryClient _telemetryClient;

    public AzureTelemetryService(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public void TrackEvent(string eventName, Dictionary<string, string>? properties = null)
    {
        _telemetryClient.TrackEvent(eventName, properties);
    }

    public void TrackMetric(string metricName, double value, Dictionary<string, string>? dimensions = null)
    {
        var metric = new MetricTelemetry(metricName, value);
        if (dimensions != null)
        {
            foreach (var dimension in dimensions)
            {
                metric.Properties[dimension.Key] = dimension.Value;
            }
        }
        _telemetryClient.TrackMetric(metric);
    }

    public void TrackException(Exception exception, Dictionary<string, string>? properties = null)
    {
        _telemetryClient.TrackException(exception, properties);
    }

    public void TrackDependency(string dependencyType, string target, string dependencyName,
        DateTimeOffset startTime, TimeSpan duration, bool success)
    {
        _telemetryClient.TrackDependency(dependencyType, target, dependencyName, 
            startTime, duration, success);
    }

    public IDisposable StartOperation(string operationName)
    {
        return _telemetryClient.StartOperation<RequestTelemetry>(operationName);
    }
}

AWS Implementation (After)

csharp
public class AwsTelemetryService : ITelemetryService
{
    private readonly IAmazonCloudWatch _cloudWatch;
    private readonly ILogger<AwsTelemetryService> _logger;
    private readonly string _namespace;

    public AwsTelemetryService(
        IAmazonCloudWatch cloudWatch,
        IConfiguration configuration,
        ILogger<AwsTelemetryService> logger)
    {
        _cloudWatch = cloudWatch;
        _logger = logger;
        _namespace = configuration["CloudWatch:Namespace"] ?? "MyApp/Metrics";
    }

    public void TrackEvent(string eventName, Dictionary<string, string>? properties = null)
    {
        // Log as structured event
        using var scope = _logger.BeginScope(properties ?? new Dictionary<string, string>());
        _logger.LogInformation("Event: {EventName}", eventName);

        // Also send as CloudWatch metric (event count)
        TrackMetric($"Events/{eventName}", 1, properties);
    }

    public void TrackMetric(string metricName, double value, Dictionary<string, string>? dimensions = null)
    {
        var metricData = new MetricDatum
        {
            MetricName = metricName,
            Value = value,
            Timestamp = DateTime.UtcNow,
            Unit = StandardUnit.None
        };

        if (dimensions != null)
        {
            metricData.Dimensions = dimensions.Select(d => new Dimension
            {
                Name = d.Key,
                Value = d.Value
            }).ToList();
        }

        var request = new PutMetricDataRequest
        {
            Namespace = _namespace,
            MetricData = new List<MetricDatum> { metricData }
        };

        // Fire and forget (async void pattern for telemetry)
        _ = Task.Run(async () =>
        {
            try
            {
                await _cloudWatch.PutMetricDataAsync(request);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send metric {MetricName}", metricName);
            }
        });
    }

    public void TrackException(Exception exception, Dictionary<string, string>? properties = null)
    {
        using var scope = _logger.BeginScope(properties ?? new Dictionary<string, string>());
        _logger.LogError(exception, "Exception tracked");

        // Also record in X-Ray
        AWSXRayRecorder.Instance.AddException(exception);
    }

    public void TrackDependency(string dependencyType, string target, string dependencyName,
        DateTimeOffset startTime, TimeSpan duration, bool success)
    {
        // X-Ray subsegment for dependency
        var subsegment = AWSXRayRecorder.Instance.BeginSubsegment(dependencyName);
        subsegment.SetStartTime(startTime.UtcDateTime);
        subsegment.SetEndTime(startTime.Add(duration).UtcDateTime);
        
        if (!success)
        {
            subsegment.AddError(new Exception($"Dependency {dependencyName} failed"));
        }

        subsegment.AddMetadata("DependencyType", dependencyType);
        subsegment.AddMetadata("Target", target);
        
        AWSXRayRecorder.Instance.EndSubsegment();

        _logger.LogInformation(
            "Dependency: {DependencyType} to {Target}/{DependencyName} - {Duration}ms ({Success})",
            dependencyType, target, dependencyName, duration.TotalMilliseconds, success ? "Success" : "Failed");
    }

    public IDisposable StartOperation(string operationName)
    {
        return new XRayOperationScope(operationName);
    }

    private class XRayOperationScope : IDisposable
    {
        private readonly string _segmentName;

        public XRayOperationScope(string segmentName)
        {
            _segmentName = segmentName;
            AWSXRayRecorder.Instance.BeginSubsegment(_segmentName);
        }

        public void Dispose()
        {
            AWSXRayRecorder.Instance.EndSubsegment();
        }
    }
}

IAM Requirements

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/aws/application/myapp:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudwatch:PutMetricData"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "cloudwatch:namespace": "MyApp/Metrics"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": [
        "xray:PutTraceSegments",
        "xray:PutTelemetryRecords"
      ],
      "Resource": "*"
    }
  ]
}

CloudWatch Logs Insights Queries

Azure Log Analytics (KQL):

kql
requests
| where timestamp > ago(1h)
| summarize count() by resultCode

AWS CloudWatch Logs Insights:

code
fields @timestamp, statusCode
| filter @timestamp > 1h
| stats count() by statusCode

X-Ray vs Application Insights Comparison

FeatureApplication InsightsX-Ray
Auto-instrumentation✅ Automatic⚠️ SDK required
Dependency tracking✅ Automatic✅ With SDK handlers
Distributed tracing✅ Built-in✅ Built-in
Custom metrics✅ TrackMetric()❌ Use CloudWatch
Live metrics✅ Built-in❌ Use Lambda Insights
Sampling✅ Adaptive✅ Configurable

OpenTelemetry Alternative (Recommended)

For better vendor neutrality, consider OpenTelemetry:

csharp
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddAWSInstrumentation()
        .AddXRayExporter())
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddPrometheusExporter());

Benefits:

  • Vendor-neutral API
  • Easy to switch exporters (X-Ray, Jaeger, Zipkin)
  • Future-proof
  • Industry standard

Testing with Localstack

docker-compose.yml:

yaml
version: '3.8'
services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
    environment:
      - SERVICES=cloudwatch,logs,xray
      - DEBUG=1

Test configuration:

json
{
  "AWS": {
    "Region": "us-east-1",
    "ServiceURL": "http://localhost:4566"
  }
}

Migration Checklist

  • Remove Application Insights NuGet packages
  • Add CloudWatch/X-Ray NuGet packages (or OpenTelemetry)
  • Update service registration
  • Convert TelemetryClient to ITelemetryService
  • Update log configuration
  • Add X-Ray middleware
  • Instrument AWS SDK calls for X-Ray
  • Add IAM policies
  • Update CloudWatch Logs queries (KQL → Insights query syntax)
  • Set up CloudWatch dashboards
  • Update alerts and alarms
  • Verify telemetry signatures unchanged
  • Test with Localstack

Common Pitfalls

⚠️ No live metrics: AWS doesn't have direct equivalent (use Lambda Insights for Lambda functions)
⚠️ Metric aggregation: CloudWatch requires pre-aggregation (vs App Insights server-side)
⚠️ Query language: CloudWatch Insights very different from KQL
⚠️ Sampling: X-Ray sampling rules more manual than App Insights adaptive sampling
⚠️ Cost model: CloudWatch charges per GB ingested + per query (different from App Insights)

Success Criteria

✅ All Application Insights code replaced with CloudWatch/X-Ray
✅ Extension method signatures unchanged
✅ Distributed tracing working across services
✅ Custom metrics appearing in CloudWatch
✅ Logs searchable in CloudWatch Logs Insights
✅ IAM policies applied
✅ Dashboards and alarms configured
✅ No breaking changes to consuming applications