Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
- Update `func init` to default to the .NET 8 template for in-proc apps (#4557)
- Implement (2 second) graceful timeout period for the CLI shutdown (#4540)
- Overwrite `AZURE_FUNCTIONS_ENVIRONMENT` to `Development` if it is already set (#4563)
- Update log streaming to support both connection string and instrumentation Key (#4586)
23 changes: 17 additions & 6 deletions src/Cli/func/Actions/AzureActions/LogStreamAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Azure.Functions.Cli.Actions.AzureActions
internal class LogStreamAction : BaseFunctionAppAction
{
private const string ApplicationInsightsIKeySetting = "APPINSIGHTS_INSTRUMENTATIONKEY";
private const string ApplicationInsightsConnectionString = "APPLICATIONINSIGHTS_CONNECTION_STRING";
private const string LiveMetricsUriTemplate = "https://portal.azure.com/#blade/AppInsightsExtension/QuickPulseBladeV2/ComponentId/{0}/ResourceId/{1}";

public bool UseBrowser { get; set; }
Expand Down Expand Up @@ -44,9 +45,9 @@ public override async Task RunAsync()
return;
}

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

Expand Down Expand Up @@ -92,14 +93,24 @@ public override async Task RunAsync()

public async Task OpenLiveStreamInBrowser(Site functionApp, IEnumerable<ArmSubscription> allSubscriptions)
{
if (!functionApp.AzureAppSettings.ContainsKey(ApplicationInsightsIKeySetting))
string iKey;

// First, check for a connection string. If it's not available, default to using the Instrumentation Key.
if (functionApp.AzureAppSettings.TryGetValue(ApplicationInsightsConnectionString, out var connectionString))
{
iKey = Utilities.ExtractIKeyFromConnectionString(connectionString);
}
else if (functionApp.AzureAppSettings.TryGetValue(ApplicationInsightsIKeySetting, out var instrumentationKey))
{
iKey = instrumentationKey;
}
else
{
throw new CliException($"Missing {ApplicationInsightsIKeySetting} App Setting. " +
throw new CliException($"Missing {ApplicationInsightsConnectionString} App Setting. " +
$"Please make sure you have Application Insights configured with your function app.");
}

var iKey = functionApp.AzureAppSettings[ApplicationInsightsIKeySetting];
if (string.IsNullOrEmpty(iKey))
if (string.IsNullOrWhiteSpace(iKey))
{
throw new CliException("Invalid Instrumentation Key found. Please make sure that the Application Insights is configured correctly.");
}
Expand Down
41 changes: 41 additions & 0 deletions src/Cli/func/Common/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,46 @@ internal static bool IsMinifiedVersion()

return false;
}

internal static string ExtractIKeyFromConnectionString(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
return null;
}

var span = connectionString.AsSpan();
var start = 0;

while (start < span.Length)
{
var semicolonIndex = span.Slice(start).IndexOf(';');
var length = semicolonIndex == -1 ? span.Length - start : semicolonIndex;
var segment = span.Slice(start, length).Trim();

start += length + (semicolonIndex == -1 ? 0 : 1);

if (segment.IsEmpty)
{
continue;
}

int equalsIndex = segment.IndexOf('=');
if (equalsIndex <= 0 || equalsIndex >= segment.Length - 1)
{
continue;
}

var key = segment.Slice(0, equalsIndex).Trim();
var value = segment.Slice(equalsIndex + 1).Trim();

if (key.Equals("InstrumentationKey", StringComparison.OrdinalIgnoreCase))
{
return value.ToString();
}
}

return null;
}
}
}
22 changes: 21 additions & 1 deletion test/Cli/Func.UnitTests/HelperTests/UtilitiesTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Microsoft.Azure.WebJobs.Script.Configuration;
Expand Down Expand Up @@ -77,5 +77,25 @@ public void Test_IsMinifiedVersion(bool expected)
File.Delete(filePath);
Assert.Equal(expected, output);
}

[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(";", null)]
[InlineData("InstrumentationKey=abc123;", "abc123")]
[InlineData("InstrumentationKey=abc123;IngestionEndpoint=https://...", "abc123")]
[InlineData(" InstrumentationKey = abc123 ;", "abc123")]
[InlineData("InstrumentationKey=abc123;OtherKey=xyz", "abc123")]
[InlineData("otherKey=xyz;InstrumentationKey=abc123;", "abc123")]
[InlineData("InstrumentationKey=ABC123", "ABC123")]
[InlineData("instrumentationkey=abc123", "abc123")]
[InlineData("InstrumentationKey= ;", null)]
[InlineData("SomeKey=SomeValue;AnotherKey=AnotherValue", null)]
public void ExtractIKeyFromConnectionString_ReturnsExpectedInstrumentationKey(string connectionString, string expected)
{
var actual = Utilities.ExtractIKeyFromConnectionString(connectionString);

Assert.Equal(expected, actual);
}
}
}