From bb11d8ea751ece5b3a91983949ea0775dbb56c93 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 18 Aug 2025 06:52:34 +0000
Subject: [PATCH 1/2] Initial plan
From fc112d3d5717f01e952550d13d69d2bd980e18f1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 18 Aug 2025 07:07:45 +0000
Subject: [PATCH 2/2] Migrate from Microsoft.Azure.ApplicationInsights.Query to
Azure.Monitor.Query
Co-authored-by: joergrosenkranz <310438+joergrosenkranz@users.noreply.github.com>
---
LogInsights/LogInsights.csproj | 2 +-
.../Implementations/AppInsightsLogReader.cs | 210 +++++++++++-------
2 files changed, 127 insertions(+), 85 deletions(-)
diff --git a/LogInsights/LogInsights.csproj b/LogInsights/LogInsights.csproj
index 49b2e41..a6cd73b 100644
--- a/LogInsights/LogInsights.csproj
+++ b/LogInsights/LogInsights.csproj
@@ -17,7 +17,7 @@
-
+
All
diff --git a/LogInsights/LogReader/Implementations/AppInsightsLogReader.cs b/LogInsights/LogReader/Implementations/AppInsightsLogReader.cs
index 21387bb..47c6d16 100644
--- a/LogInsights/LogReader/Implementations/AppInsightsLogReader.cs
+++ b/LogInsights/LogReader/Implementations/AppInsightsLogReader.cs
@@ -2,7 +2,11 @@
using CsvHelper.Configuration.Attributes;
using LogInsights.Helpers;
-using Microsoft.Azure.ApplicationInsights.Query;
+using Azure.Core;
+using Azure.Identity;
+using Azure.Monitor.Query;
+using Azure.Monitor.Query.Models;
+using System.Net.Http;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -15,11 +19,32 @@
// ReSharper disable UnusedMember.Local
-namespace LogInsights.LogReader
-{
+namespace LogInsights.LogReader
+{
+ // Custom TokenCredential for Application Insights API key authentication
+ internal class AppInsightsApiKeyCredential : TokenCredential
+ {
+ private readonly string _apiKey;
+
+ public AppInsightsApiKeyCredential(string apiKey)
+ {
+ _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
+ }
+
+ public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ // Return the API key as a bearer token - this may need adjustment based on actual Azure.Monitor.Query implementation
+ return new AccessToken(_apiKey, DateTimeOffset.UtcNow.AddHours(1));
+ }
+
+ public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ return new ValueTask(GetToken(requestContext, cancellationToken));
+ }
+ }
public class AppInsightsLogReader : LogReader
{
- private readonly ApplicationInsightsDataClient _client;
+ private readonly LogsQueryClient _client;
private readonly AppInsightsLogReaderConnectionStringBuilder _connString;
private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.General) {
@@ -92,8 +117,13 @@ public AppInsightsLogReader(AppInsightsLogReaderConnectionStringBuilder connecti
if ( string.IsNullOrEmpty(apiKey) )
throw new Exception($"Missing {nameof(apiKey)} value.");
- _client = new ApplicationInsightsDataClient(new ApiKeyClientCredentials(apiKey));
- Display = $"App Insights Reader - {_client.BaseUri.OriginalString}";
+ // Create LogsQueryClient for Application Insights
+ // Note: Application Insights now uses Log Analytics workspace under the hood
+ // API key authentication is handled via custom credential
+ var credential = new AppInsightsApiKeyCredential(apiKey);
+ var options = new LogsQueryClientOptions();
+ _client = new LogsQueryClient(credential, options);
+ Display = $"App Insights Reader - {appId}";
}
else
{
@@ -175,84 +205,96 @@ protected override async IAsyncEnumerable OnReadAsync(
}
}
- private async IAsyncEnumerable<_Row> _ReadAppInsightsDataAsync([EnumeratorCancellation] CancellationToken ct)
- {
- var events = new Events(_client);
-
- var result = await events.Client.Query.ExecuteAsync(
- _connString.AppId,
- _connString.Query,
- !string.IsNullOrEmpty(_connString.TimeSpan) ? _connString.TimeSpan : null,
- cancellationToken: ct)
- .ConfigureAwait(false);
-
- if (result.Tables.Count < 1)
- throw new Exception("Missing result table");
-
- var table = result.Tables[0];
-
- int timestampIdx = -1;
- int messageIdx = -1;
- int severityIdx = -1;
- int itemIdIdx = -1;
- int customDimensionsIdx = -1;
- int outerMessageIdx = -1;
- int detailsIdx = -1;
-
- for (var i = 0; i < table.Columns.Count; i++)
- {
- var column = table.Columns[i];
-
- switch (column.Name)
- {
- case "itemId":
- itemIdIdx = i;
- break;
-
- case "timestamp":
- timestampIdx = i;
- break;
-
- case "message":
- messageIdx = i;
- break;
-
- case "severityLevel":
- severityIdx = i;
- break;
-
- case "customDimensions":
- customDimensionsIdx = i;
- break;
-
- case "outerMessage":
- outerMessageIdx = i;
- break;
-
- case "details":
- detailsIdx = i;
- break;
- }
- }
-
- if (timestampIdx < 0 ||
- messageIdx < 0 ||
- itemIdIdx < 0)
- throw new Exception("Missing columns");
-
- foreach (var row in table.Rows)
- {
- yield return new _Row
- {
- ItemId = (string)row[itemIdIdx],
- TimeStamp = (DateTime)row[timestampIdx],
- Message = (string)row[messageIdx],
- Severity = severityIdx > -1 ? (long)row[severityIdx] : 1L,
- CustomDimensions = customDimensionsIdx > -1 ? (string)row[customDimensionsIdx] : "",
- Details = detailsIdx > -1 ? (string)row[detailsIdx] : "",
- OuterMessage = detailsIdx > -1 ? (string)row[outerMessageIdx] : ""
- };
- }
+ private async IAsyncEnumerable<_Row> _ReadAppInsightsDataAsync([EnumeratorCancellation] CancellationToken ct)
+ {
+ QueryTimeRange timeRange = QueryTimeRange.All;
+ if (!string.IsNullOrEmpty(_connString.TimeSpan))
+ {
+ // Try to parse the timespan - the old API used formats like "P1D" (ISO 8601 duration)
+ // The new API expects QueryTimeRange or TimeSpan objects
+ if (TimeSpan.TryParse(_connString.TimeSpan, out var parsedTimespan))
+ {
+ timeRange = new QueryTimeRange(parsedTimespan);
+ }
+ else
+ {
+ // Try parsing as ISO 8601 duration or use default
+ timeRange = QueryTimeRange.All;
+ }
+ }
+
+ var result = await _client.QueryWorkspaceAsync(
+ _connString.AppId,
+ _connString.Query,
+ timeRange,
+ cancellationToken: ct)
+ .ConfigureAwait(false);
+
+ var table = result.Value.Table;
+
+ int timestampIdx = -1;
+ int messageIdx = -1;
+ int severityIdx = -1;
+ int itemIdIdx = -1;
+ int customDimensionsIdx = -1;
+ int outerMessageIdx = -1;
+ int detailsIdx = -1;
+
+ for (var i = 0; i < table.Columns.Count; i++)
+ {
+ var column = table.Columns[i];
+
+ switch (column.Name)
+ {
+ case "itemId":
+ itemIdIdx = i;
+ break;
+
+ case "timestamp":
+ timestampIdx = i;
+ break;
+
+ case "message":
+ messageIdx = i;
+ break;
+
+ case "severityLevel":
+ severityIdx = i;
+ break;
+
+ case "customDimensions":
+ customDimensionsIdx = i;
+ break;
+
+ case "outerMessage":
+ outerMessageIdx = i;
+ break;
+
+ case "details":
+ detailsIdx = i;
+ break;
+ }
+ }
+
+ if (timestampIdx < 0 ||
+ messageIdx < 0 ||
+ itemIdIdx < 0)
+ throw new Exception("Missing columns");
+
+ foreach (var row in table.Rows)
+ {
+ yield return new _Row
+ {
+ ItemId = row[itemIdIdx]?.ToString(),
+ TimeStamp = row[timestampIdx] is DateTimeOffset dto ? dto.DateTime :
+ DateTime.TryParse(row[timestampIdx]?.ToString(), out var dt) ? dt : DateTime.MinValue,
+ Message = row[messageIdx]?.ToString(),
+ Severity = severityIdx > -1 && long.TryParse(row[severityIdx]?.ToString(), out var sev) ? sev : 1L,
+ CustomDimensions = customDimensionsIdx > -1 ? row[customDimensionsIdx]?.ToString() : "",
+ Details = detailsIdx > -1 ? row[detailsIdx]?.ToString() : "",
+ OuterMessage = outerMessageIdx > -1 ? row[outerMessageIdx]?.ToString() : ""
+ };
+ }
}
private async IAsyncEnumerable<_Row> _ReadCsvDataAsync()