2

I'd like to be able to make a call (e.g., to an API) from within an Azure function, and make a log entry if it is taking longer than n milliseconds, but not interrupt or end the call. Note this is not for the UI, it would only be for logging.

How can I do this in C#?

12
  • 7
    What have you tried so far? Commented Dec 15 at 15:21
  • 2
    You should probably post this as one of the open-ended question categories, like Advice. There can be a lot of ways to handle this. I'd start with Activity/ActivitySource just for the abstractions, and either check for long calls in whatever monitoring I use, or create a custom listener that triggers a notification or log if an activity isn't finished/disposes in X seconds. Commented Dec 15 at 15:32
  • 2
    "Is there a library that would facilitate this?" That part is specifically OT on SO, remember? (that's where the close requests come from) I highly recommend to just get rid of that - the question is really interesting. Would be a shame if "anything happened to it". :D Commented Dec 15 at 15:42
  • 1
    Voted to reopen. Asker has explained what they think they need and just needs some guidance or ideas. Commented Dec 15 at 17:10
  • 1
    Why would a log entry interrupt or end the call? Usually you just check the duration after the call has completed (or timed out). Do you need the log entry to be created before that for some reason? (This affects the answer) Commented Dec 16 at 1:09

4 Answers 4

6

Here is an application-agnostic solution (not tailored specifically for Azure).

static IDisposable CreateSlowCallLogger(string title, int dueTimeMilliseconds = 1000)
{
    System.Threading.Timer timer = new(_ =>
    {
        // Log the slow call
        Console.WriteLine($"{title} was slower than {dueTimeMilliseconds:#,0} msec!");
    });
    timer.Change(dueTimeMilliseconds, Timeout.Infinite);
    return timer;
}

Usage example:

using (CreateSlowCallLogger("TheAPI"))
{
    TheAPI();
}

The above implementation logs the slow call on a ThreadPool thread, while the call is still running. So there is no information available about how long the call lasted from start to finish. If you want this information you could implement something similar using a Stopwatch instead of a Timer, and logging synchronously after the completion of the call.

Sign up to request clarification or add additional context in comments.

5 Comments

This code just does a delayed logging invocation. It doesn't inspect function's primary execution to see if it has finished or is in progress.
@StingyJack have you tested it? The solution in this answer logs only if the TheAPI() takes more than 1 second to complete. Otherwise it doesn't log.
I think showing adding/modifying current implementation with TimeProvider.CreateTimer would be beneficial. Also mentioning that Dispose()ing cancels the timer.
@IvanPetrov sure. I didn't mention the TimeProvider because I've never used it. Feel free to edit my answer and make these two improvements if you want.
@TheodorZoulias sure, not sure when I'd have the time to make it more bullet-proof since timers (rootedness etc) are a fun topic.
4

I'm not sure if it has already been proposed in part by one of the other answers. However, unless you want the log message to appear exactly at the time of the threshold, you can simply start a Stopwatch before the request. After the request is successful or failed, you log if Elapsed is more than the threshold.

1 Comment

On reflection this is really all I need. Thank you to everyone else, though as I have put some of the other approaches into my back pocket for future use.
3

Azure portal has metrics that will give you execution times and you can setup alerts for when execution times start to exceed thresholds. Trying to add this kind of monitoring and notification in your function seems like a lot of work.

If you were so inclined to do this in code, whenever your function was invoked, a second thread would have to be created that would check to see if the main thread's execution has exceeded your desired time. Trying to synchronize this and do it properly would get messy real quick.

If its just for logging the execution time, I would just emit the log statement once the function was complete.

1 Comment

For in code solutions based on Task, see On awaiting a task with a timeout in C# and WaitAsync(TimeSpan)
0

Create two competing tasks: the actual API call and a simple time delay representing your threshold.

int thresholdMs = 2000; // Your N milliseconds
var apiCallTask = HttpClient.GetAsync(url); // The API call task
var delayTask = Task.Delay(thresholdMs);    // The monitoring timer

Use Task.WhenAny to wait for the fastest task. If the delay finishes first, the API call has exceeded the threshold, and you log a warning.

var completedTask = await Task.WhenAny(apiCallTask, delayTask);

if (completedTask == delayTask)
{
    // LOG ENTRY: The threshold was breached. 
    // The apiCallTask is still running in the background.
    log.LogWarning("API call is slow, still waiting...");
}

Regardless of whether the warning was logged, you must always await the original apiCallTask to make sure the call finishes and its result is handled.

// Wait for the API call to finally finish
try
{
    var response = await apiCallTask; 
    // ... process final result
}
catch (Exception ex)
{
    // ... handle exceptions
}
New contributor
Leonplane is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.