Skip to content

Commit 88462df

Browse files
Create a function-level request telemetry when none exists. (#3146)
* Adding a default activity.
1 parent efd60f1 commit 88462df

File tree

6 files changed

+126
-2
lines changed

6 files changed

+126
-2
lines changed

sample/SampleHost/Functions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public async Task ProcessWorkItem_ServiceBus(
6262
}
6363

6464
/// <summary>
65-
/// If there is an error during function invocation, function execution will be retried up to 3 times waiting for "00:00:20" between each retry
65+
/// If there is an error during function invocation, function execution will be retried up to 3 times waiting for "00:00:20" between each retry.
6666
/// </summary>
6767
/// <param name="events"></param>
6868
/// <param name="log"></param>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
#nullable enable
5+
6+
using Microsoft.Azure.WebJobs.Host.Executors;
7+
using System;
8+
9+
namespace Microsoft.Azure.WebJobs.Host.Abstractions
10+
{
11+
/// <summary>
12+
/// Abstraction for ActivitySource to avoid dependency on System.Diagnostics.DiagnosticSource
13+
/// </summary>
14+
public interface IActivitySourceAbstraction
15+
{
16+
IDisposable? StartActivity(IFunctionInstanceEx functionInstance);
17+
}
18+
}

src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.ExceptionServices;
1111
using System.Threading;
1212
using System.Threading.Tasks;
13+
using Microsoft.Azure.WebJobs.Host.Abstractions;
1314
using Microsoft.Azure.WebJobs.Host.Bindings;
1415
using Microsoft.Azure.WebJobs.Host.Indexers;
1516
using Microsoft.Azure.WebJobs.Host.Loggers;
@@ -31,6 +32,7 @@ internal class FunctionExecutor : IFunctionExecutor, IFunctionActivityStatusProv
3132
private readonly IEnumerable<IFunctionFilter> _globalFunctionFilters;
3233
private readonly IDrainModeManager _drainModeManager;
3334
private readonly ConcurrencyManager _concurrencyManager;
35+
private readonly IActivitySourceAbstraction _activitySource;
3436
private int _outstandingInvocations;
3537
private int _outstandingRetries;
3638

@@ -57,7 +59,8 @@ public FunctionExecutor(
5759
ConcurrencyManager concurrencyManager,
5860
ILoggerFactory loggerFactory = null,
5961
IEnumerable<IFunctionFilter> globalFunctionFilters = null,
60-
IDrainModeManager drainModeManager = null)
62+
IDrainModeManager drainModeManager = null,
63+
IActivitySourceAbstraction activitySource = null)
6164
{
6265
_functionInstanceLogger = functionInstanceLogger ?? throw new ArgumentNullException(nameof(functionInstanceLogger));
6366
_functionOutputLogger = functionOutputLogger;
@@ -68,6 +71,7 @@ public FunctionExecutor(
6871
_globalFunctionFilters = globalFunctionFilters ?? Enumerable.Empty<IFunctionFilter>();
6972
_drainModeManager = drainModeManager;
7073
_concurrencyManager = concurrencyManager ?? throw new ArgumentNullException(nameof(concurrencyManager));
74+
_activitySource = activitySource;
7175
}
7276

7377
public HostOutputMessage HostOutputMessage
@@ -97,6 +101,7 @@ public async Task<IDelayedException> TryExecuteAsync(IFunctionInstance functionI
97101
}
98102

99103
using (_resultsLogger?.BeginFunctionScope(functionInstanceEx, HostOutputMessage.HostInstanceId))
104+
using (_activitySource?.StartActivity(functionInstanceEx))
100105
using (parameterHelper)
101106
{
102107
try
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.Azure.WebJobs.Host.Abstractions;
2+
using Microsoft.Azure.WebJobs.Host.Executors;
3+
using System;
4+
using System.Diagnostics;
5+
6+
#nullable enable
7+
8+
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
9+
{
10+
public class ActivitySourceWrapper : IActivitySourceAbstraction
11+
{
12+
private readonly ActivitySource _activitySource;
13+
14+
public ActivitySourceWrapper(string sourceName)
15+
{
16+
_activitySource = new ActivitySource(sourceName);
17+
}
18+
19+
public IDisposable? StartActivity(IFunctionInstanceEx functionInstance)
20+
{
21+
if (Activity.Current != null)
22+
{
23+
return null;
24+
}
25+
26+
return _activitySource.StartActivity(functionInstance.FunctionDescriptor.LogName, ActivityKind.Server);
27+
}
28+
}
29+
}

test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionExecutorTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
7+
using System.Diagnostics;
78
using System.Linq;
89
using System.Reflection;
910
using System.Threading;
@@ -328,6 +329,28 @@ private FunctionExecutor GetTestFunctionExecutor(DrainModeManager drainModeManag
328329
return functionExecutor;
329330
}
330331

332+
private FunctionExecutor GetActivitySourceWrapperExecutor(DrainModeManager drainModeManager = null)
333+
{
334+
var mockFunctionInstanceLogger = new Mock<IFunctionInstanceLogger>();
335+
var mockFunctionOutputLogger = new NullFunctionOutputLogger();
336+
var mockExceptionHandler = new Mock<IWebJobsExceptionHandler>();
337+
var mockFunctionEventCollector = new Mock<IAsyncCollector<FunctionInstanceLogEntry>>();
338+
var mockConcurrencyManager = new Mock<ConcurrencyManager>();
339+
var activitySourceAbstraction = new ActivitySourceWrapper("Microsoft.Azure.WebJobs");
340+
341+
var functionExecutor = new FunctionExecutor(
342+
mockFunctionInstanceLogger.Object,
343+
mockFunctionOutputLogger,
344+
mockExceptionHandler.Object,
345+
mockFunctionEventCollector.Object,
346+
mockConcurrencyManager.Object,
347+
NullLoggerFactory.Instance,
348+
null,
349+
drainModeManager, activitySourceAbstraction);
350+
351+
return functionExecutor;
352+
}
353+
331354
private static void TestFunction()
332355
{
333356
// used for a FunctionDescriptor
@@ -441,6 +464,30 @@ await TestHelpers.Await(() =>
441464
Assert.Equal(status.OutstandingRetries, 0);
442465
}
443466

467+
[Fact]
468+
public async Task TryExecuteAsync_DefaultActivity_Expected()
469+
{
470+
using var testListener = new ActivityTestListener("Microsoft.Azure.WebJobs");
471+
var triggerData = new TriggeredFunctionData
472+
{
473+
TriggerValue = 123,
474+
TriggerDetails = new Dictionary<string, string>()
475+
};
476+
var functionDescriptor = FunctionExecutorTestHelper.GetFunctionDescriptor();
477+
var functionInstance = FunctionExecutorTestHelper.CreateFunctionInstance(Guid.NewGuid(), triggerData.TriggerDetails, false, functionDescriptor, 1000);
478+
FunctionExecutor executor = GetActivitySourceWrapperExecutor();
479+
480+
// Arrange
481+
HostStartedMessage testMessage = new HostStartedMessage();
482+
executor.HostOutputMessage = testMessage;
483+
484+
await executor.TryExecuteAsync(functionInstance, CancellationToken.None);
485+
486+
// Assert
487+
Assert.True(testListener.Activities.Count > 0, "No activities were captured by the listener.");
488+
Assert.Contains(testListener.Activities, a => a.OperationName == "TestFunction");
489+
}
490+
444491
private void RunOnFunctionTimeoutTest(bool isDebugging, string expectedMessage)
445492
{
446493
System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromMinutes(1).TotalMilliseconds);
@@ -498,4 +545,28 @@ public static void NoCancellationTokenParameter()
498545
}
499546
}
500547
}
548+
549+
internal sealed class ActivityTestListener : IDisposable
550+
{
551+
public List<Activity> Activities { get; } = new List<Activity>();
552+
private readonly ActivityListener _listener;
553+
554+
public ActivityTestListener(string sourceName)
555+
{
556+
_listener = new ActivityListener
557+
{
558+
ShouldListenTo = s => s.Name == sourceName,
559+
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
560+
ActivityStarted = activity => Activities.Add(activity),
561+
ActivityStopped = _ => { }
562+
};
563+
564+
ActivitySource.AddActivityListener(_listener);
565+
}
566+
567+
public void Dispose()
568+
{
569+
_listener.Dispose();
570+
}
571+
}
501572
}

test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public void WebJobs_Host_VerifyPublicSurfaceArea()
158158
"FunctionResultAggregatorOptions",
159159
"FunctionTimeoutAbortException",
160160
"FunctionTimeoutException",
161+
"IActivitySourceAbstraction",
161162
"IArgumentBinding`1",
162163
"IArgumentBindingProvider`1",
163164
"IAsyncConverter`2",

0 commit comments

Comments
 (0)