Anixe.IO.Appmetrics 3.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Anixe.IO.Appmetrics --version 3.0.1                
NuGet\Install-Package Anixe.IO.Appmetrics -Version 3.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Anixe.IO.Appmetrics" Version="3.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Anixe.IO.Appmetrics --version 3.0.1                
#r "nuget: Anixe.IO.Appmetrics, 3.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Anixe.IO.Appmetrics as a Cake Addin
#addin nuget:?package=Anixe.IO.Appmetrics&version=3.0.1

// Install Anixe.IO.Appmetrics as a Cake Tool
#tool nuget:?package=Anixe.IO.Appmetrics&version=3.0.1                

Appmetrics

Anixe.IO.Appmetrics allows you to quickly integrate your asp .net core application with graylog. The module produces json object in GELF format with fields describing inbound communication (incoming HTTP request into the application) and outbound communication (any IO operation made by the application)

How to integrate

add in csproj as a package reference

<PackageReference Include="Anixe.IO.Appmetrics" Version="2.0.11" />

add section to appsettings.json

{
  ...
  "Appmetrics" : {
    "LogSender" : {
      "Channel": "NLog" // External, NLog, UdpClient
    },
    "Middleware": { // configuration for inbound HTTP requests
      "TenantFieldName": "prof", // url query parameter or routing data or http context's items
      "RequestId" : {
        "QueryFieldName": "rid", // url query parameter
        "HeaderFieldName": "X-RequestId", // request header parameter
        "AddToResponse": true // will add the value to the response header
      },
      "MethodIdIdFieldName": "mid", // HTTP context's item name
      "IncludeMethods": [],
      "ExcludeMethods": [ // omit these requests with HTTP method
        "OPTIONS",
        "HEAD"
      ],
      "IncludeUrls": [],
      "ExcludeUrls": [ // do not log for these incoming requests URLs to avoid measuring healthchecks
        "/ping",
        "$health$", //$..$ to match all urls which contains health
        "/status$" // ...$ to match all urls which start with /status
      ]
    },
    "HttpClient": { // configuration for outbound HTTP requests
      "TimeoutCustomMessage": "Timeout occurred", // override OperationCancelledException and TaskCancelledException with this message
      "IncludeUrls": [],
      "ExcludeUrls": [ // do not log for these outbound urls to avoid measuring healthchecks
        "/_cluster/health"
      ]
    },
    "Defaults" : { // each message will have these defaults
      "application_name": "geows-be",
      "src": "geows-be", // can be something else e.g. module from the monolith (plugin name, startup). Useful in case of several different metrics outside the convention scope
      "platform": "shared-svc",
      "product": "geo"
    }
  }
}

register middleware for inbound HTTP requests and handling exceptions in DI container in Startup.cs and additional http client handler for collecting metrics from outbound HTTP request, all you need it to define HttpClient sub-section in Appmetrics section in appsettings.json and use HttpClientFactory for http client registration; otherwise only middleware will be registered.

    public void ConfigureServices(IServiceCollection services)
    {
      services.UseAnixeAppmetrics(this.config, opts => // use this to provide metrics for inbound HTTP requests and unhandled exception
      {
        // override any option here
        opts.Middleware.HostName = "my machine"; /* providing machine name is on your own */
        opts.Middleware.ServerIp = "127.0.0.1"; /* providing machine name is on your own */
      })
    }

Register NLog target or built-in UDP sender

for NLog just set up appsettings.json

{
  ...
  "Appmetrics" : {  
    "LogSender" : {
      "Channel": "NLog"
    },
  ...
  }
}

then use build-in anixe-appmetrics layout formatter and define an NLog rule with appmetrics name

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true"
      autoReload="false"
      internalLogLevel="Warn"
      internalLogFile="internal-nlog.txt">

  <extensions>
    <add assembly="Anixe.IO.Appmetrics"/>
  </extensions>

   <targets>
    <target xsi:type="Console" name="appmetrics-console" layout="${anixe-appmetrics}" />
    <target xsi:type="Network" address="udp://graylog.com:5558" name="appmetrics" layout="${anixe-appmetrics}" />
  </targets>

  <rules>
    
    <logger name="appmetrics-console" minlevel="Debug" writeTo="appmetrics-console" final="true">
      <filters defaultAction="Ignore">
        <when condition="equals('${environment:ASPNETCORE_ENVIRONMENT}','Development')" action="Log"/>
      </filters>
    </logger>
    
    <logger name="appmetrics" minlevel="Debug" writeTo="appmetrics" final="true">
      <filters defaultAction="Ignore">
        <when condition="not equals('${environment:ASPNETCORE_ENVIRONMENT}','Development')" action="Log"/>
      </filters>
    </logger>
  </rules>
</nlog>

for build-in UdpSender just set up appsettings.json

{
  ...
  "Appmetrics" : {  
    "LogSender" : {
      "Channel": "UdpClient"
    },
  ...
  }
}

and expect IUdpClient instance from DI container as a dependency. The IUdpClient instance is a thread-safe singleton.

public class MyCustomLogger
{
  private readonly IUdpClient client;

  public MyCustomLogger(IUdpClient client)
  {
    this.client = client;  
  }
}

for your custom log sender just set up in appsettings.json

{
  ...
  "Appmetrics" : {  
    "LogSender" : {
      "Channel": "External"
    },
  ...
  }
}

and register your own IUdpClient implementation in DI container. And thats all, it registers default convention made from Appmetrics middleware, filter and http client delegating handler. You don't need to add any additional code.

How to get access to log message within asp .net core processing pipeline

Expect IMessage in HttpContext.Items[nameof(IMessage)]. This example describes a controller which uses HttpContext.Items[nameof(IMessage)]

  public class LocationsController : ControllerBase
  {
    private readonly ILocationsService locations;

    public LocationsController(ILocationsService locations)
    {
      this.locations = locations;
    }

    [Route("locations")]
    [HttpGet]
    public async Task<IActionResult> GetAllAsync([FromQuery]LocationsRequestModel request)
    {
      var mgs = this.HttpContext.Items[nameof(IMessage)] as IMessage;
      var retval = await locations.FindAllAsync(request, this.HttpContext.RequestAborted);
      msg.AddPair("no", retval.locations?.Count ?? 0);
      Response.Headers.Add("X-Total", retval.total.GetValueOrDefault().ToString());
      Response.Headers.Add("X-Total-Unfiltered", retval.total_unfiltered.GetValueOrDefault().ToString());
      return Ok(retval);
    }
  }

How to manually prepare log message then send it outside asp .net core processing pipeline.

Expect IMetricsCollector instance which has several logging method. Each method will send log to the graylog after method call ends. This example describes a hosted service.

  public class TaskQueueHostedService : BackgroundService
  {
    private readonly IBackgroundTaskQueue queue;
    private readonly IMetricsCollector metricsCollector;

    public TaskQueueHostedService(
      IBackgroundTaskQueue queue, 
      IMetricsCollector metricsCollector)
    {
      this.metricsCollector = metricsCollector;
      this.queue = queue;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      Consts.Trace.Value.Info("Starting TaskQueueHostedService");
      while (!stoppingToken.IsCancellationRequested)
      {
        try
        {
          var job = await this.queue.DequeueAsync(stoppingToken);
          await this.metricsCollector.LogAsync(LogInvokeTask, (job, stoppingToken));
        }
        catch (Exception ex)
        {
          Consts.Trace.Value.Fatal(ex);
        }
      }
      Consts.Trace.Value.Info("Finished TaskQueueHostedService");
    }

    private async Task LogInvokeTask(IMessage msg, ((string jobName, Func<CancellationToken, Task<int>> task) job, CancellationToken token) state)
    {
      var (job, token) = state;
      msg.src = Consts.JobFacility;
      msg.transaction_name = job.jobName;
      var itemsAffected = await job.task?.Invoke(token);
      msg.AddPair("no", itemsAffected);
    }
  }

How to integrate with Resfinity Ops

define Middleware section in Appmetrics in appsettings.json like the below:

    "Middleware": {
      "TenantFieldName": "profile",  // this field will be displayed as contract
      "RequestId": {
        "QueryFieldName": "rid",
        "HeaderFieldName": "rid",
        "AddToResponse": true
      },
      "TransactionNameField": "controller",
      "MethodIdIdFieldName": "mid",
      ...

define defaults in appsettings as in example:

"Defaults": {
    "application_name": "campers_booking_api", // this will be displayed as facility
    "src": "campers",      // this value will be used to match with provider stats
    "platform": "campers",
    "product": "CAMPER"    // needed to determine the correct GrayLog stream and will be displayed as product
}

optionally, you can add extra data that will be displayed Resfinity Ops:

IMessage msg = HttpContext.Items[nameof(IMessage)];
msg.AddPair("df", "2021-11-01T09:30");  // will be displayed in dates section
msg.AddPair("dt", "2021-12-01T09:30")); // will be displayed in dates section
msg.AddPair("pl", "LAX");               // will be displayed in destination section
msg.AddPair("dl", "NYC");               // will be displayed in destination section
msg.AddPair("no", 1);                   // will be displayed in dump in items_count property

Configuration fields explanation

Example log in GELF format

{
  "version": "1.1",
  "host": "wm-anx-macbook.local",
  "level": 6,
  "timestamp": 1562766590.96176,
  "_application_name": "geows-be",
  "_transaction_name": "locationproducts", // path in request url
  "_user": "Mozilla/5.0", // User Agent
  "_src": "geows-be",
  "_rid": "5680338139043881971", // can be passed from Http request to chain request in one transaction flow or autogenerated if not present
  "_mid": "4732771268302683729", // is always autogenerated to distinguish individual requests
  "_con": "mpg", // tenant or client-id - something to detect the client
  "_st": "OK", // return status (OK or ERR)
  "_p": "geo", // product name, several applications can have the same product name
  "_env": "development", // environment
  "_platform": "shared-svc", // platform name, several products can be bound within one platform
  "_host_ip": "192.168.195.81", // IP of host machine
  "_rq_mth": "GET", // HTTP request method
  "_rq_url": "http://localhost:5000/locationproducts?lang=de&prof=mpg&gp=IT.LI.PSA&product=gegs", // full request url
  "_rs_code": 200, // HTTP response code
  "_client_ip": "::1", // client IP, it also checks for X-Forwared-For ip collection
  "_http__locations_geocodes__count_tt": 97.821, // outbound httpclient requests
  "_http__locations_geocodes__search_tt": 52.019,
  "_http__all_products_product__search_tt": 27.514,
  "tx_bytes": 123, // bytes send by http client
  "tx_bytes": 123, // bytes received from http client
  "rx_bytes": 3625,
  "_tt": 391.937, // HTTP request time taken
  "_ttc": 128.025, // time taken spent awaiting for IO operation to complete
  "_ttp": 263.912, // HTTP request CPU bound time taken (tt - ttc)
  "short_message": "-" // - for OK, error message for ERR status
} 

Example error log in GELF format

{
  "version": "1.1",
  "host": "wm-anx-macbook.local",
  "level": 6,
  "timestamp": 1562766590.96176,
  "_application_name": "geows-be",
  "_transaction_name": "locationproducts",
  "_user": "Mozilla/5.0",
  "_src": "geows-be",
  "_rid": "5680338139043881971",
  "_mid": "4732771268302683729",
  "_con": "mpg",
  "_st": "ERR",
  "_p": "geo",
  "_env": "development",
  "_platform": "shared-svc",
  "_host_ip": "192.168.195.81",
  "_rq_mth": "GET",
  "_rq_url": "http",
  "_rs_code": 500,
  "_client_ip": "::1",
  "_http__locations_geocodes__count_tt": 97.821,
  "_http__locations_geocodes__search_tt": 52.019,
  "_http__all_products_product__search_tt": 27.514,
  "_tt": 391.937,
  "_ttc": 128.025,
  "_ttp": 263.912,
  "short_message": "Object reference not set to an instance of an object."
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Anixe.IO.Appmetrics:

Package Downloads
Anixe.IO.Appmetrics.Extensions.SqlDB

Package Description

Anixe.IO.Appmetrics.Extensions.Mongodb

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.0.0 2,751 1/8/2024
3.0.1 615 12/4/2023
3.0.0 3,689 12/20/2022
2.1.1 4,053 1/21/2022
2.1.0 2,824 8/31/2021
2.0.20 786 7/8/2021
2.0.19 2,874 3/15/2021
2.0.18 842 1/27/2021
2.0.17 581 11/17/2020
2.0.16 1,173 10/21/2020
2.0.15 1,838 7/9/2020
2.0.13 1,030 4/27/2020
2.0.12 617 2/26/2020
2.0.11 1,315 12/11/2019
2.0.10 1,337 9/5/2019
2.0.9 566 9/2/2019
2.0.8 2,409 9/2/2019
2.0.7 551 8/28/2019
2.0.6 356 8/27/2019
2.0.5 342 8/27/2019
2.0.4 519 8/27/2019
2.0.3 347 8/23/2019
2.0.2 554 7/25/2019
2.0.1 442 7/23/2019
2.0.0 983 7/22/2019
1.0.1 710 7/15/2019
1.0.0 805 7/14/2019

# Anixe.IO.Appmetrics CHANGELOG

## 3.0.1 - 2023-12-05

- Fix poor precision of big values in custom fields of type long when sending using Gelf
- Fix typo of parameter name in IMessage.AddIO method
- Fix NullReferenceException when resolving transaction name using only HttpClient.BaseAddress without url in HttpRequestMessage
- Update Anixe.IO to 3.1.0

## 3.0.0 - 2022-12-15

- Drop support of .NET older than .NET 6
- Update NLog to 5.1.0
- Changed nullability of AppmetricsOptions.BeforeControllerAction

## 2.1.1 - 2022-01-21

- fix MethodId value was not used from httpContext (if exists) and was always generated a new one
- Update NLog to 4.7.13

## 2.1.0 - 2021-08-31

- Update NLog to 4.7.11
- Update Anixe.IO to 2.1.0

## 2.0.20 - 2021-07-08

### Added

- License information in package metadata