Introduction
Logging is one of the most crucial aspects of building and operating scalable applications. As microservices evolve and systems become distributed, structured logging becomes a superpower for debugging, monitoring, and tracing.
With the rise of .NET Aspire, Microsoft’s orchestration and development layer for building cloud-native distributed applications, logging and observability are now first-class citizens.
In this post, we'll walk through how to implement structured logging using .NET Aspire orchestration for GraphQL APIs powered by HotChocolate.
Why Structured Logging?
Unlike traditional log messages, structured logs:
- Use key-value pairs, making them machine-readable.
- Are easier to filter, query, and correlate.
- Integrate well with tools like Seq, Grafana Loki, or ELK stacks.
- Help trace requests across distributed services.
Prerequisites
Assuming that you have these things already installed and ready in your machine -
- .NET 8/9 SDK
- Visual Studio IDE
- Existing WebAPI project
In this article, I'll be using graphql-dot repository to demonstrate the structured logging in .NET aspire for graphql APIs. You can fork this repository and clone it to see the demonstration.
My GraphQL project
I have used the chillicream's hotchocolate library for implementing graphql APIs. I used REST webapi template and then converted it into graphql compatible project. You can see the required packages needed, services and middlewares required to run the graphql server. Here is how I have implemented them -
My project's structure
- In launchsettings.json : The application url, launch url and other launch related details are mentioned in the project.
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchUrl": "api",
"launchBrowser": false,
"applicationUrl": "http://localhost:5158",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchUrl": "api",
"launchBrowser": false,
"applicationUrl": "https://localhost:7075;http://localhost:5158",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
- Weather Query: This is the file where queries in graphql are implemented. Please note that for this demo, I have implemented only query in graphql. You can find out how we can implement mutation and subscription from the github repository or you can drop a message in the comments.
using JetBrains.Annotations;
namespace graphql_dotnet.GraphQL.Queries;
[PublicAPI]
[ExtendObjectType(OperationTypeNames.Query)]
public class WeatherQuery
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public IEnumerable<WeatherForecast> GetWeatherForecast(
[Service] ILogger<WeatherQuery> logger,
int days = 5)
{
//logger for structured loggin for later
logger.LogInformation("Getting weather forecast for {Days} days at {Time}", days, DateTime.UtcNow);
// Limit days to prevent excessive data
days = Math.Max(1, Math.Min(days, 30));
return Enumerable.Range(1, days).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
- Program.cs : I configured it for the graphql apis -
a. The ConfigureServices(builder.Configuration, builder.Environment)
tells about the services and middlewares written in the ProgramConfiguration.cs
file.
b. The schema
of IRequestExecutor
type tells us about the schema of graphql. I'm writing it in GraphqlDotNet.graphql
file.
c. app.MapDefaultEndpoints()
configures default health check endpoints for the app.
- ProgramConfiguration.cs: Here I have implemented the services, configurations and DI for the graphql project -
a. services.AddGraphQl(configuration)
adds the Graphql related configurations and necessary services.
b. We can also add our DbContext
here.
c. Also dependency injection can be done here.
- ProgramConfiguration.Graphql.cs: Dependency resolvers, Graphql server and other things related to graphql are done here -
Adding queries -
End of Graphql project!
.NET Aspire orchestration
It is very simple to add .NET Aspire orchestration via Visual Studio. I'm using Visual Studio 2022.
Step 1: Right click on the WebApi project > Add > .NET Aspire Orchestration Support...
Step 2: Select the Aspire version. I have selected v9.3
Step 3: Add the project prefix if you want or keep as it is and press ok.
It will take sometime to configure on its own.
Step 4: Right click on the AppHost project generated and select Set as startup project
.
In your AppHost
's Program.cs
now you should be able to see -
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.graphql_dotnet>("graphql-dotnet");
builder.Build().Run();
Otel based magic (Extras)
If you open your Extensions.cs
in the ServiceDefaults library project, you can see how beautifully .NET Aspire is taking care of the logging part -
Once, the .NET Aspire setup is done, now let's run and see the magic.
It is now up and running at port 17178
and my nitro(bananacakepop) is up and running at https://localhost:7075/api/
The console logs from the graphQL APIs. We can even customize these logs according to our needs -
The structured logs from the Graphql-dotnet
-
Traces of our GraphQL APIs -
Metrics of our GraphQL APIs -
Yay! we just used .NET Aspire orchestration for our graphql APIs. I hope you were also setup the structured logging for your project(s). If you face any issues, let's discuss them in the comments.
Let connect on LinkedIn and checkout my GitHub repos:
Top comments (2)
Great explanation with implementation!!!
Thanks Sahil. Let's try deploying this in EC2 AWS.