🚀 Executive Summary

TL;DR: Azure Functions frequently suffer from missing or inconsistent logs due to restrictive default `host.json` configurations, leading to silent failures and difficult debugging. This problem can be solved by implementing strategies ranging from quick `host.json` tweaks to adopting Dependency Injection with `ILogger` for structured control, or integrating Serilog for advanced, enterprise-grade observability.

🎯 Key Takeaways

  • The `host.json` file dictates logging behavior in Azure Functions, controlling categories like `Function` and `Microsoft` and their minimum `logLevel`, which often defaults to `Warning` and silently discards `Information` or `Debug` logs.
  • The recommended ‘grown-up’ approach for sustainable logging is to use standard .NET Dependency Injection to inject an `ILogger` instance, allowing for decoupled business logic and fine-grained control over log levels for specific application components.
  • For ultimate control and structured logging, especially in critical, high-volume applications, integrating a dedicated library like Serilog enables custom properties (e.g., Correlation ID), JSON-based formatting, and multi-sink output (e.g., Application Insights and Seq).

Struggling with missing or inconsistent logs in your Azure Functions? Darian Vance, a Lead Cloud Architect, shares his battle-tested strategies to fix logging issues, from quick host.json hacks to enterprise-grade structured logging.

From the Trenches: My Unfiltered Guide to Azure Function Logging

It was 2 AM. A critical payment processing function, ProcessStripeWebhook-prod, was failing silently. Alarms were quiet, but our finance team was seeing failed transactions pile up. I dove into Application Insights, firing off Kusto queries like a madman. traces | where message contains "paymentId"… nothing. exceptions… empty. It felt like shouting into the void. After a frantic 30 minutes, we found the culprit: a single, swallowed exception, invisible because the default logging configuration decided it wasn’t ‘important’ enough to report. That night, I swore I’d never let default logging configurations burn me again.

If you’re reading this, you’ve probably felt that pain. You sprinkle log.LogInformation("Processing item X") everywhere, but when you go to App Insights, half of it is missing. You’re not crazy; the default setup is just not intuitive. Let’s fix it.

The “Why”: The Root of the Logging Chaos

The core problem lies in a disconnect between how the Azure Functions runtime wants to log and how modern .NET applications are built. The system is controlled by the host.json file, which defines logging categories and their minimum log levels (like Information, Warning, Error).

There are two key categories you’re fighting with:

  • Function: This is the catch-all for logs emitted directly from your function’s code, especially when using the static context.log.
  • Function.<YourFunctionName>: A more specific category for a particular function.
  • Microsoft: Logs from the underlying Azure and .NET frameworks. These are often noisy and set to Warning by default.

By default, the log level is often set to Warning. This means any LogInformation or LogDebug calls you make are silently thrown away before they ever leave the server. You *think* you’re logging, but the configuration is actively working against you.

The Fixes: From Duct Tape to a New Engine

I’ve seen this problem dozens of times. Here are the three ways we tackle it at TechResolve, depending on the situation.

Solution 1: The ‘Get Me Out of Jail’ host.json Tweak

This is the quick and dirty fix. It’s not elegant, but when production is on fire and you need visibility *right now*, this is your go-to. The goal here is to just open the floodgates and see everything.

You do this by editing your host.json to be extremely permissive. You’re telling the runtime, “I don’t care what category it is, just show me everything at an Information level or higher.”


{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    },
    "logLevel": {
      "default": "Information",
      "Function": "Information",
      "Host.Results": "Information"
    }
  }
}

Warning: This approach can get very noisy and may increase your Application Insights costs due to the sheer volume of logs. It’s a great temporary debugging tool, but I wouldn’t leave it this way in a high-traffic production environment.

Solution 2: The ‘Grown-Up’ Way with Dependency Injection

This is the permanent, sustainable fix and the way all our new projects are built. Instead of relying on the static context.log, you should be using standard .NET Dependency Injection (DI) to inject an ILogger instance. This decouples your business logic from the Functions runtime and gives you much finer-grained control.

Step 1: Use `ILogger` in your Function class.

Instead of passing `ILogger log` as a method parameter, inject it into the constructor. This is the standard pattern in ASP.NET Core and it works beautifully here.


public class MyHttpTrigger
{
    private readonly ILogger<MyHttpTrigger> _logger;
    private readonly IMyService _myService;

    // Inject the logger and your services here
    public MyHttpTrigger(ILogger<MyHttpTrigger> logger, IMyService myService)
    {
        _logger = logger;
        _myService = myService;
    }

    [FunctionName("MyHttpTrigger")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
    {
        _logger.LogInformation("C# HTTP trigger function processed a request.");
        _myService.DoWork();
        return new OkObjectResult("Success");
    }
}

Step 2: Configure `host.json` intelligently.

Now, your logs will come from the category `Function.MyHttpTrigger.User` or your custom service’s namespace. This allows you to set different log levels for different parts of your application.


{
  "version": "2.0",
  "logging": {
    "logLevel": {
      "default": "Warning", // Keep the default restrictive
      "Function": "Information", // Log our function entry/exit points
      "TechResolve.Services": "Debug" // Get detailed logs from our custom services
    }
  }
}

This is the best of both worlds: you reduce noise from the framework (by keeping `default` at `Warning`) but get rich, detailed logs from the code you actually care about.

Solution 3: The ‘Ultimate Control’ with Structured Logging (Serilog)

Sometimes, even the built-in ILogger isn’t enough. You need to enforce a structured, JSON-based logging format, add custom properties to every log message (like a Correlation ID or a User ID), and maybe even send logs to multiple places at once (e.g., App Insights and a separate Seq instance for deep analysis).

For this, we bring in a dedicated logging library like Serilog. It completely takes over the logging pipeline and gives you ultimate power.

Setting it up involves a bit more code in your `Program.cs` (for Isolated Worker) or `Startup.cs` (for In-Process), but the payoff is huge.


// Example for an Isolated Worker Function in Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .UseSerilog((context, services, loggerConfiguration) => loggerConfiguration
        .ReadFrom.Configuration(context.Configuration)
        .WriteTo.Console()
        .WriteTo.ApplicationInsights(services.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces)
        .Enrich.FromLogContext()
        .Enrich.WithProperty("ApplicationName", "MyCriticalFunctionApp") // Add custom properties!
    )
    .Build();

host.Run();

Pro Tip: This is my ‘nuclear’ option. It introduces an external dependency and a bit more complexity. I only use this for our most critical, high-volume applications where we absolutely cannot afford to lose a single piece of diagnostic information, like on our `prod-billing-engine-func`. For most standard APIs or data processing jobs, Solution 2 is more than sufficient.

Summary: Choosing Your Weapon

Navigating Azure Function logging can be a nightmare, but it doesn’t have to be. Here’s how I decide which path to take:

Solution Best For Complexity My Take
1. The Quick Fix Emergency debugging; quick PoCs. Low A lifesaver in a pinch, but clean it up later. It’s tech debt.
2. DI & `ILogger` All new projects; the default standard. Medium This should be your go-to. It’s robust, maintainable, and follows best practices.
3. Structured (Serilog) Complex, high-stakes applications. High Unmatched power and observability, but don’t use a sledgehammer to crack a nut.

Stop fighting the defaults. Take control of your logging configuration. Your future self, debugging an issue at 2 AM, will thank you for it.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ Why are my Azure Function `LogInformation` messages not appearing in Application Insights?

Your `host.json` configuration likely has the `logLevel` for categories like `Function` or `default` set to `Warning` or higher, causing `Information` and `Debug` logs to be silently discarded before they are sent to Application Insights.

âť“ How do the different Azure Function logging solutions compare in terms of complexity and benefits?

The ‘Quick Fix’ (`host.json` tweak) is low complexity, best for emergency debugging but can be noisy. Dependency Injection with `ILogger` is medium complexity, robust, and the recommended standard for new projects. Structured logging with Serilog is high complexity, offering unmatched power and observability for critical, high-stakes applications.

âť“ What is a common implementation pitfall when logging in Azure Functions and how can it be avoided?

A common pitfall is relying on default `host.json` settings or the static `context.log`, which often leads to missing or inconsistent logs. This can be avoided by explicitly configuring `host.json` log levels and adopting Dependency Injection with `ILogger` for better control, maintainability, and clear log categories.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading