Skip to content

Commit 298361d

Browse files
Update log streaming to support both connection string and Instrumentation Key (#4586)
* Fixing log stream. --------- Co-authored-by: Lilian Kasem <[email protected]>
1 parent b3ae769 commit 298361d

File tree

4 files changed

+104
-9
lines changed

4 files changed

+104
-9
lines changed

release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
#### Changes
99

1010
- Add support for .NET 10 isolated model (#4589)
11+
- Update log streaming to support both connection string and instrumentation Key (#4586)

src/Cli/func/Actions/AzureActions/LogStreamAction.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
using System.Net;
55
using System.Net.Http.Headers;
66
using System.Text;
77
using Azure.Functions.Cli.Arm.Models;
88
using Azure.Functions.Cli.Common;
9+
using Azure.Functions.Cli.Extensions;
910
using Azure.Functions.Cli.Helpers;
1011
using Colors.Net;
1112
using Fclp;
@@ -16,7 +17,8 @@ namespace Azure.Functions.Cli.Actions.AzureActions
1617
[Action(Name = "logstream", Context = Context.Azure, SubContext = Context.FunctionApp, HelpText = "Show interactive streaming logs for an Azure-hosted Function App")]
1718
internal class LogStreamAction : BaseFunctionAppAction
1819
{
19-
private const string ApplicationInsightsIKeySetting = "APPINSIGHTS_INSTRUMENTATIONKEY";
20+
private const string ApplicationInsightsInstrumentationKeySetting = "APPINSIGHTS_INSTRUMENTATIONKEY";
21+
private const string ApplicationInsightsConnectionStringSetting = "APPLICATIONINSIGHTS_CONNECTION_STRING";
2022
private const string LiveMetricsUriTemplate = "https://portal.azure.com/#blade/AppInsightsExtension/QuickPulseBladeV2/ComponentId/{0}/ResourceId/{1}";
2123

2224
public bool UseBrowser { get; set; }
@@ -44,9 +46,9 @@ public override async Task RunAsync()
4446
return;
4547
}
4648

47-
if (functionApp.IsLinux && functionApp.IsDynamic)
49+
if (functionApp.IsFlex || (functionApp.IsLinux && functionApp.IsDynamic))
4850
{
49-
throw new CliException("Log stream is not currently supported in Linux Consumption Apps. " +
51+
throw new CliException("Log stream is not currently supported in Linux Consumption and Flex Apps. " +
5052
"Please use --browser to open Azure Application Insights Live Stream in the Azure portal.");
5153
}
5254

@@ -92,20 +94,31 @@ public override async Task RunAsync()
9294

9395
public async Task OpenLiveStreamInBrowser(Site functionApp, IEnumerable<ArmSubscription> allSubscriptions)
9496
{
95-
if (!functionApp.AzureAppSettings.ContainsKey(ApplicationInsightsIKeySetting))
97+
string instrumentationKey;
98+
99+
// First, check for a connection string. If it's not available, default to using the Instrumentation Key.
100+
if (functionApp.AzureAppSettings.TryGetValue(ApplicationInsightsConnectionStringSetting, out var connectionString))
101+
{
102+
instrumentationKey = connectionString.GetValueFromDelimitedString("InstrumentationKey");
103+
}
104+
else if (functionApp.AzureAppSettings.TryGetValue(ApplicationInsightsInstrumentationKeySetting, out var key))
105+
{
106+
ColoredConsole.WriteLine(WarningColor("Support for instrumentation key ingestion has ended. Switch to connection strings to access new features."));
107+
instrumentationKey = key;
108+
}
109+
else
96110
{
97-
throw new CliException($"Missing {ApplicationInsightsIKeySetting} App Setting. " +
111+
throw new CliException($"Missing {ApplicationInsightsConnectionStringSetting} App Setting. " +
98112
$"Please make sure you have Application Insights configured with your function app.");
99113
}
100114

101-
var iKey = functionApp.AzureAppSettings[ApplicationInsightsIKeySetting];
102-
if (string.IsNullOrEmpty(iKey))
115+
if (string.IsNullOrWhiteSpace(instrumentationKey))
103116
{
104117
throw new CliException("Invalid Instrumentation Key found. Please make sure that the Application Insights is configured correctly.");
105118
}
106119

107120
ColoredConsole.WriteLine("Retrieving Application Insights information...");
108-
var appId = await AzureHelper.GetApplicationInsightIDFromIKey(iKey, AccessToken, ManagementURL, allSubs: allSubscriptions);
121+
var appId = await AzureHelper.GetApplicationInsightIDFromIKey(instrumentationKey, AccessToken, ManagementURL, allSubs: allSubscriptions);
109122
var armResourceId = AzureHelper.ParseResourceId(appId);
110123
var componentId = $@"{{""Name"":""{armResourceId.Name}"",""SubscriptionId"":""{armResourceId.Subscription}"",""ResourceGroup"":""{armResourceId.ResourceGroup}""}}";
111124

src/Cli/func/Extensions/StringExtensions.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,53 @@ public static bool EqualsIgnoreCase(this string str1, string str2)
5858
{
5959
return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase);
6060
}
61+
62+
/// <summary>
63+
/// Retrieves the value associated with a specified key from a delimited key-value string.
64+
/// </summary>
65+
/// <param name="input">Retrieves the value associated with a specified key from a delimited key-value pair.</param>
66+
/// <param name="key">The key whose value will be retrieved (case-insensitive match).</param>
67+
/// <param name="delimiter">The character that separates each key-value pair in the input string (default is ';').</param>
68+
/// <returns>The value associated with the specified key, or <c>null</c> if the key is not found or the input is invalid.</returns>
69+
public static string GetValueFromDelimitedString(this string input, string key, char delimiter = ';')
70+
{
71+
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(key))
72+
{
73+
return null;
74+
}
75+
76+
var span = input.AsSpan();
77+
var start = 0;
78+
79+
while (start < span.Length)
80+
{
81+
var delimiterIndex = span.Slice(start).IndexOf(delimiter);
82+
var length = delimiterIndex == -1 ? span.Length - start : delimiterIndex;
83+
var segment = span.Slice(start, length).Trim();
84+
85+
start += length + (delimiterIndex == -1 ? 0 : 1);
86+
87+
if (segment.IsEmpty)
88+
{
89+
continue;
90+
}
91+
92+
int equalsIndex = segment.IndexOf('=');
93+
if (equalsIndex <= 0 || equalsIndex >= segment.Length - 1)
94+
{
95+
continue;
96+
}
97+
98+
var keyPart = segment.Slice(0, equalsIndex).Trim();
99+
var valuePart = segment.Slice(equalsIndex + 1).Trim();
100+
101+
if (keyPart.Equals(key, StringComparison.OrdinalIgnoreCase))
102+
{
103+
return valuePart.ToString();
104+
}
105+
}
106+
107+
return null;
108+
}
61109
}
62110
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using Azure.Functions.Cli.Actions.AzureActions;
5+
using Azure.Functions.Cli.Arm.Models;
6+
using Azure.Functions.Cli.Extensions;
7+
using Xunit;
8+
9+
namespace Azure.Functions.Cli.UnitTests.HelperTests
10+
{
11+
public class StringExtensionsTests
12+
{
13+
[Theory]
14+
[InlineData(null, "InstrumentationKey", ';', null)]
15+
[InlineData("", "InstrumentationKey", ';', null)]
16+
[InlineData(";", "InstrumentationKey", ';', null)]
17+
[InlineData("InstrumentationKey=abc123;", "InstrumentationKey", ';', "abc123")]
18+
[InlineData("InstrumentationKey=abc123;IngestionEndpoint=https://...", "InstrumentationKey", ';', "abc123")]
19+
[InlineData(" InstrumentationKey = abc123 ;", "InstrumentationKey", ';', "abc123")]
20+
[InlineData("InstrumentationKey=abc123;OtherKey=xyz", "InstrumentationKey", ';', "abc123")]
21+
[InlineData("otherKey=xyz;InstrumentationKey=abc123;", "InstrumentationKey", ';', "abc123")]
22+
[InlineData("otherKey=xyz;InstrumentationKey=abc123", "InstrumentationKey", ';', "abc123")]
23+
[InlineData("InstrumentationKey=ABC123", "InstrumentationKey", ';', "ABC123")]
24+
[InlineData("instrumentationkey=abc123", "InstrumentationKey", ';', "abc123")]
25+
[InlineData("InstrumentationKey= ;", "InstrumentationKey", ';', null)]
26+
[InlineData("SomeKey=SomeValue;AnotherKey=AnotherValue", "InstrumentationKey", ';', null)]
27+
public void ExtractIKeyFromConnectionString_ReturnsExpectedInstrumentationKey(string connectionString, string key, char delimiter, string expected)
28+
{
29+
var actual = connectionString.GetValueFromDelimitedString(key, delimiter);
30+
Assert.Equal(expected, actual);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)