Prerequisites

Option 1: Using HTTP Client

Create a new .NET project

Create a new .NET project. In your terminal, go to the directory where you want to create your project. Run the following command to create a new console app named AxiomLogs.

dotnet new console -n AxiomLogs

Install packages

Install the packages for your project. Use the Microsoft.AspNet.WebApi.Client package to make HTTP requests to the Axiom API. Run the following command to install the package:

dotnet add package Microsoft.AspNet.WebApi.Client

Configure the Axiom logger

Create a class to handle logging to Axiom. Create a new file named AxiomLogger.cs in your project directory with the following content:

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public static class AxiomLogger
{
    public static async Task LogToAxiom(string message, string logLevel)
    {
        // Create an instance of HttpClient to make HTTP requests
        var client = new HttpClient();
        
        // Specify the Axiom dataset name and construct the API endpoint URL
        var datasetName = "YOUR-DATASET-NAME"; // Replace with your actual dataset name
        var axiomUri = $"https://api.axiom.co/v1/datasets/{datasetName}/ingest";
        
        // Replace with your Axiom API token
        var apiToken = "YOUR-API-TOKEN"; // Ensure your API token is correct
        
        // Create an array of log entries, including the timestamp, message, and log level
        var logEntries = new[]
        {
            new
            {
                timestamp = DateTime.UtcNow.ToString("o"),
                message = message,
                level = logLevel
            }
        };
        
        // Serialize the log entries to JSON format using System.Text.Json.JsonSerializer
        var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(logEntries), Encoding.UTF8, "application/json");
        
        // Set the authorization header with the Axiom API token
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiToken);

        // Make a POST request to the Axiom API endpoint with the serialized log entries
        var response = await client.PostAsync(axiomUri, content);

        // Check the response status code
        if (!response.IsSuccessStatusCode)
        {
            // If the response is not successful, print the error details
            var responseBody = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"Failed to send log: {response.StatusCode}\n{responseBody}");
        }
        else
        {
            // If the response is successful, print "Log sent successfully."
            Console.WriteLine("Log sent successfully.");
        }
    }
}

Configure the main program

Now that the Axiom logger is in place, update the main program so it can be used. Open the Program.cs file and replace its contents with the following code:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Log the application startup event with an "INFO" log level
        await AxiomLogger.LogToAxiom("Application started", "INFO");

        // Call the SimulateOperations method to simulate various application operations
        await SimulateOperations();
        
        // Log the .NET runtime version information with an "INFO" log level
        await AxiomLogger.LogToAxiom($"CLR version: {Environment.Version}", "INFO");

        // Log the application shutdown event with an "INFO" log level
        await AxiomLogger.LogToAxiom("Application shutting down", "INFO");
    }

    static async Task SimulateOperations()
    {
        // Log the start of operations with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Starting operations", "DEBUG");

        // Log the database connection event with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Connecting to database", "DEBUG");
        await Task.Delay(500); // Simulated delay
        // Log the successful database connection with an "INFO" log level
        await AxiomLogger.LogToAxiom("Connected to database successfully", "INFO");

        // Log the user data retrieval event with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Retrieving user data", "DEBUG");
        await Task.Delay(1000);
        // Log the number of retrieved user records with an "INFO" log level
        await AxiomLogger.LogToAxiom("Retrieved 100 user records", "INFO");

        // Log the user preference update event with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Updating user preferences", "DEBUG");
        await Task.Delay(800);
        // Log the successful user preference update with an "INFO" log level
        await AxiomLogger.LogToAxiom("Updated user preferences successfully", "INFO");

        try
        {
            // Log the payment processing event with a "DEBUG" log level
            await AxiomLogger.LogToAxiom("Processing payments", "DEBUG");
            await Task.Delay(1500); 
            // Intentionally throw an exception to demonstrate error logging
            throw new Exception("Payment gateway unavailable");
        }
        catch (Exception ex)
        {
            // Log the payment processing failure with an "ERROR" log level
            await AxiomLogger.LogToAxiom($"Payment processing failed: {ex.Message}", "ERROR");
        }

        // Log the email notification sending event with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Sending email notifications", "DEBUG");
        await Task.Delay(1200);
        // Log the number of sent email notifications with an "INFO" log level
        await AxiomLogger.LogToAxiom("Sent 50 email notifications", "INFO");

        // Log the high memory usage detection with a "WARN" log level
        await AxiomLogger.LogToAxiom("Detected high memory usage", "WARN");
        await Task.Delay(500);
        // Log the memory usage normalization with an "INFO" log level
        await AxiomLogger.LogToAxiom("Memory usage normalized", "INFO");

        // Log the completion of operations with a "DEBUG" log level
        await AxiomLogger.LogToAxiom("Operations completed", "DEBUG");
    }
}

This code simulates various app operations and logs messages at different levels (DEBUG, INFO, WARN, ERROR) to Axiom.

Project file configuration

Ensure your axiomlogs.csproj file is configured with the package reference. The file should look like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
  </ItemGroup>

</Project>

Build and run the app

To build and run the app, go to the project directory in your terminal and run the following command:

dotnet build

dotnet run

This command builds the project and runs the app. You see the log messages being sent to Axiom, and the console displays Log sent successfully. for each log entry.

Option 2: Using Serilog

Install Serilog Packages

Add Serilog and the necessary extensions to your project. You need the Serilog, Serilog.Sinks.Http, and Serilog.Formatting.Json packages.

dotnet add package Serilog
dotnet add package Serilog.Sinks.Http
dotnet add package Serilog.Formatting.Json

Configure Serilog

In your Program.cs or a startup configuration file, set up Serilog to use the HTTP sink. Configure the sink to point to the Axiom ingestion API endpoint.

using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Http(
        requestUri: "https://api.axiom.co/v1/datasets/YOUR-DATASET-NAME/ingest",
        textFormatter: new Serilog.Formatting.Json.JsonFormatter(),
        httpClient: new HttpClient
        {
            DefaultRequestHeaders =
            {
                { "Authorization", "Bearer YOUR-API-TOKEN" }
            }
        })
    .CreateLogger();

class Program
{
    static async Task Main(string[] args)
    {
        Log.Information("Application started");
        await SimulateOperations();
        Log.Information($"CLR version: {Environment.Version}");
        Log.Information("Application shutting down");
    }

    static async Task SimulateOperations()
    {
        Log.Debug("Starting operations");
        Log.Debug("Connecting to database");
        await Task.Delay(500); // Simulated delay
        Log.Information("Connected to database successfully");
        Log.Debug("Retrieving user data");
        await Task.Delay(1000);
        Log.Information("Retrieved 100 user records");
        Log.Debug("Updating user preferences");
        await Task.Delay(800);
        Log.Information("Updated user preferences successfully");

        try
        {
            Log.Debug("Processing payments");
            await Task.Delay(1500);
            throw new Exception("Payment gateway unavailable");
        }
        catch (Exception ex)
        {
            Log.Error($"Payment processing failed: {ex.Message}");
        }

        Log.Debug("Sending email notifications");
        await Task.Delay(1200);
        Log.Information("Sent 50 email notifications");
        Log.Warning("Detected high memory usage");
        await Task.Delay(500);
        Log.Information("Memory usage normalized");
        Log.Debug("Operations completed");
    }
}

Project file configuration

Ensure your axiomlogs.csproj file is configured with the package references. The file should look like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Serilog" Version="2.10.0" />
    <PackageReference Include="Serilog.Sinks.Http" Version="5.0.0" />
    <PackageReference Include="Serilog.Formatting.Json" Version="3.1.0" />
  </ItemGroup>

</Project>

Build and run the app

To build and run the app, go to the project directory in your terminal and run the following commands:

dotnet build
dotnet run

This command builds the project and runs the app. You see the log messages being sent to Axiom.

Option 3: Using NLog

Install NLog Packages

You need NLog and potentially an extension for HTTP targets.

dotnet add package NLog
dotnet add package NLog.Web.AspNetCore

Configure NLog

Set up NLog by creating an NLog.config file or configuring it programmatically. Here is an example configuration for NLog using an HTTP target:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <targets>
        <target xsi:type="BufferingWrapper"
                name="allLogs"
                flushTimeout="5000">
            <target xsi:type="WebClient"
                    name="http"
                    url="https://api.axiom.co/v1/datasets/YOUR-DATASET-NAME/ingest"
                    method="POST"
                    header="Authorization: Bearer YOUR-API-TOKEN"
                    layout="${longdate}|${level:uppercase=true}|${message} ${exception:format=toString,Data}">
            </target>
        </target>
    </targets>
    <rules>
        <logger name="*" minlevel="Trace" writeTo="allLogs" />
    </rules>
</nlog>

Configure the main program

Update the main program to use NLog. In your Program.cs file:

using NLog;
using NLog.Web;

var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();

class Program
{
    static async Task Main(string[] args)
    {
        logger.Info("Application started");
        await SimulateOperations();
        logger.Info($"CLR version: {Environment.Version}");
        logger.Info("Application shutting down");
    }

    static async Task SimulateOperations()
    {
        logger.Debug("Starting operations");
        logger.Debug("Connecting to database");
        await Task.Delay(500); // Simulated delay
        logger.Info("Connected to database successfully");
        logger.Debug("Retrieving user data");
        await Task.Delay(1000);
        logger.Info("Retrieved 100 user records");
        logger.Debug("Updating user preferences");
        await Task.Delay(800);
        logger.Info("Updated user preferences successfully");

        try
        {
            logger.Debug("Processing payments");
            await Task.Delay(1500);
            throw new Exception("Payment gateway unavailable");
        }
        catch (Exception ex)
        {
            logger.Error($"Payment processing failed: {ex.Message}");
        }

        logger.Debug("Sending email notifications");
        await Task.Delay(1200);
        logger.Info("Sent 50 email notifications");
        logger.Warn("Detected high memory usage");
        await Task.Delay(500);
        logger.Info("Memory usage normalized");
        logger.Debug("Operations completed");
    }
}

Project file configuration

Ensure your axiomlogs.csproj file is configured with the package references. The file should look like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NLog" Version="4.7.12" />
    <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.3" />
  </ItemGroup>

</Project>

Build and run the app

To build and run the app, go to the project directory in your terminal and run the following commands:

dotnet build
dotnet run

This command builds the project and runs the app. You should see the log messages being sent to Axiom.

Best practices for logging

To make your logging more effective, consider the following best practices:

  • Include relevant information such as user IDs, request details, and system state in your log messages to provide context when investigating issues.
  • Use different log levels (DEBUG, INFO, WARN, ERROR) to categorize the severity and importance of log messages. This allows you to filter and analyze logs more effectively
  • Use structured logging formats like JSON to make it easier to parse and analyze log data

Conclusion

This guide covers the steps to send logs from a C# .NET app to Axiom. By following these instructions and adhering to logging best practices, you can effectively monitor your app, diagnose issues, and gain valuable insights into its behavior.