Giter Site home page Giter Site logo

aws-embedded-metrics-dotnet's Introduction

aws-embedded-metrics-dotnet

Generate CloudWatch Metrics embedded within structured log events. The embedded metrics will be extracted so you can visualize and alarm on them for real-time incident detection. This allows you to monitor aggregated values while preserving the detailed event context that generated them.

Use Cases

  • Generate custom metrics across compute environments

    • Easily generate custom metrics from Lambda functions without requiring custom batching code, making blocking network requests or relying on 3rd party software.
    • Other compute environments (EC2, On-prem, ECS, EKS, and other container environments) are supported by installing the CloudWatch Agent.
  • Linking metrics to high cardinality context

    Using the Embedded Metric Format, you will be able to visualize and alarm on custom metrics, but also retain the original, detailed and high-cardinality context which is queryable using CloudWatch Logs Insights. For example, the library automatically injects environment metadata such as Lambda Function version, EC2 instance and image ids into the structured log event data.

Installation

  • Using the CLI:
dotnet add package Amazon.CloudWatch.EMF

Usage

To get a metric logger, you can instantiate it like so. MetricsLogger implements IDisposable. When the logger is disposed, it will write the metrics to the configured sink.

using (var logger = new MetricsLogger()) {
    logger.SetNamespace("Canary");
    var dimensionSet = new DimensionSet();
    dimensionSet.AddDimension("Service", "aggregator");
    logger.SetDimensions(dimensionSet);
    logger.PutMetric("ProcessingLatency", 100, Unit.MILLISECONDS,StorageResolution.STANDARD);
    logger.PutMetric("Memory.HeapUsed", "1600424.0", Unit.BYTES, StorageResolution.HIGH);
    logger.PutProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
    
}

Graceful Shutdown

In any environment, other than AWS Lambda, we recommend running an out-of-process agent (the CloudWatch Agent or FireLens / Fluent-Bit) to collect the EMF events. When using an out-of-process agent, this package will buffer the data asynchronously in process to handle any transient communication issues with the agent. This means that when the MetricsLogger gets flushed, data may not be safely persisted yet. To gracefully shutdown the environment, you can call shutdown on the environment's sink. This is an async call that should be awaited. A full example can be found in the examples directory.

var configuration = new Configuration
{
    ServiceName = "DemoApp",
    ServiceType = "ConsoleApp",
    LogGroupName = "DemoApp",
    EnvironmentOverride = Environments.EC2
};

var environment = new DefaultEnvironment(configuration);

using (var logger = new MetricsLogger()) {
    logger.SetNamespace("Canary");
    var dimensionSet = new DimensionSet();
    dimensionSet.AddDimension("Service", "aggregator");
    logger.SetDimensions(dimensionSet);
    logger.PutMetric("ProcessingLatency", 100, Unit.MILLISECONDS);
    logger.PutMetric("Memory.HeapUsed", "1600424.0", Unit.BYTES, StorageResolution.HIGH); 
    logger.PutProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
}

await environment.Sink.Shutdown();

ASP.Net Core

We offer a helper package for ASP.Net Core applications that can be used to simplify the onboarding process and provide default metrics.

See the example in examples/Amazon.CloudWatch.EMF.Examples.Web to create a logger that is hooked into the dependency injection framework and provides default metrics for each request. By adding some code to your Startup.cs file, you can get default metrics like the following. And of course, you can also emit additional custom metrics from your Controllers.

  1. Add the configuration to your Startup file.
public void ConfigureServices(IServiceCollection services) {
    // Add the necessary services. After this is done, you will have the
    // IMetricsLogger available for dependency injection in your
    // controllers
    services.AddEmf();
}
  1. Add middleware to add default metrics and metadata to each request.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Add middleware which will set metric dimensions based on the request routing
    app.UseEmfMiddleware();
}

Example

cd examples/Amazon.CloudWatch.EMF.Web
▶ export AWS_EMF_ENVIRONMENT=Local
▶ dotnet run
▶ curl http://localhost:5000
{"TraceId":"0HM6EKOBA2CPJ:00000001","Path":"/","StatusCode":"404"}
▶ curl http://localhost:5000/weatherForecast
{
  "_aws": {
    "Timestamp": 1617649416374,
    "CloudWatchMetrics": [
      {
        "Namespace": "WeatherApp",
        "Metrics": [
          { "Name": "Temperature", "Unit": "None" },
          { "Name": "Time", "Unit": "Milliseconds" }
        ],
        "Dimensions": [
          [ "Controller", "Action" ],
          [ "Controller", "Action", "StatusCode" ]
        ]
      }
    ]
  },
  "TraceId": "|f6eec800-4652f86aef0c7219.",
  "Path": "/WeatherForecast",
  "Controller": "WeatherForecast",
  "Action": "Get",
  "StatusCode": "200",
  "Temperature": -10,
  "Time": 189
}

API

MetricsLogger

The MetricsLogger is the interface you will use to publish embedded metrics.

  • MetricsLogger PutMetric(string key, double value)
  • MetricsLogger PutMetric(string key, double value, Unit unit)
  • MetricsLogger PutMetric(string key, double value, StorageResolution storageResolution)
  • MetricsLogger PutMetric(string key, double value, Unit unit, StorageResolution storageResolution)

Adds a new metric to the current logger context. Multiple metrics using the same key will be appended to an array of values. Multiple metrics cannot have the same key but different storage resolutions. Same metric cannot have different storage resolutions otherwise a InvalidMetricException will be thrown. The Embedded Metric Format supports a maxumum of 100 metrics per key.

Metrics must meet CloudWatch Metrics requirements, otherwise a InvalidMetricException will be thrown. See MetricDatum for valid values.

Storage Resolution.

An OPTIONAL value representing the storage resolution for the corresponding metric. Setting this to High specifies this metric as a high-resolution metric, so that CloudWatch stores the metric with sub-minute resolution down to one second. Setting this to Standard specifies this metric as a standard-resolution metric, which CloudWatch stores at 1-minute resolution. If a value is not provided, then a default value of Standard is assumed. See Cloud Watch High-Resolution metrics

Example:

// Standard Resolution Example
logger.PutMetric("ProcessingLatency", 101, Unit.MILLISECONDS);

// High Resolution Example
logger.PutMetric("Memory.HeapUsed", "1600424.0", Unit.BYTES, StorageResolution.HIGH);
  • MetricsLogger PutProperty(string key, object value)

Adds or updates the value for a given property on this context. This value is not submitted to CloudWatch Metrics but is searchable by CloudWatch Logs Insights. This is useful for contextual and potentially high-cardinality data that is not appropriate for CloudWatch Metrics dimensions.

Example:

logger.PutProperty("AccountId", "123456789");
logger.PutProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");

Dictionary<string, object> payLoad = new Dictionary<string, object>
{
  { "sampleTime", 123456789 },
  { "temperature", 273.0 },
  { "pressure", 101.3 }
};
logger.PutProperty("Payload", payLoad);
  • MetricsLogger PutDimensions(DimensionSet dimensions)

Adds a new set of dimensions that will be associated with all metric values.

WARNING: Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values). If the cardinality of a particular value is expected to be high, you should consider using setProperty instead.

Dimensions must meet CloudWatch Dimensions requirements, otherwise a InvalidDimensionException will be thrown. See Dimensions for valid values.

Example:

DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
logger.PutDimensions(dimensionSet);
  • MetricsLogger SetDimensions(params DimensionSet[] dimensionSets)
  • MetricsLogger SetDimensions(bool useDefault, params DimensionSet[] dimensionSets)

Explicitly override all dimensions. This will remove the default dimensions unless useDefault is set to true.

WARNING:Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values). If the cardinality of a particular value is expected to be high, you should consider using setProperty instead.

Dimensions must meet CloudWatch Dimensions requirements, otherwise a InvalidDimensionException will be thrown. See Dimensions for valid values.

Examples:

DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
logger.SetDimensions(true, dimensionSet); // Will preserve default dimensions
DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
logger.SetDimensions(dimensionSet); // Will remove default dimensions
  • MetricsLogger ResetDimensions(bool useDefault)

Explicitly clear all custom dimensions. Set useDefault to true to keep using the default dimensions.

  • MetricsLogger SetNamespace(string logNamespace)

Sets the CloudWatch namespace that extracted metrics should be published to. If not set, a default value of aws-embedded-metrics will be used. Namespaces must meet CloudWatch Namespace requirements, otherwise a InvalidNamespaceException will be thrown. See Namespace for valid values.

Example:

logger.SetNamespace("MyApplication")
  • MetricsLogger SetTimestamp(DateTime dateTime)

Sets the timestamp of the metrics. If not set, the current time of the client will be used. Timestamp must meet CloudWatch requirements, otherwise a InvalidTimestampException will be thrown. See Timestamps for valid values.

Example:

 logger.SetTimestamp(DateTime.Now);
  • Flush()

Flushes the current MetricsContext to the configured sink and resets all properties and metric values. The namespace and default dimensions will be preserved across flushes. Custom dimensions are preserved by default, but this behavior can be changed by setting FlushPreserveDimensions = false on the metrics logger.

Examples:

flush();  // default dimensions and custom dimensions will be preserved after each flush()
logger.FlushPreserveDimensions = false;
flush();  // only default dimensions will be preserved after each flush()
logger.FlushPreserveDimensions = false;
logger.ResetDimensions(false);  // default dimensions are disabled; no dimensions will be preserved after each flush()
logger.Flush();

aws-embedded-metrics-dotnet's People

Contributors

cmobrien-godaddy avatar deepikakaushal39 avatar dependabot[bot] avatar dkaushal1-godaddy avatar gordonpn avatar himtanaya avatar jaredcnance avatar markkuhn avatar meshwa19 avatar ntatikonda-godaddy avatar rayabagi avatar tarkatronic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-embedded-metrics-dotnet's Issues

ASP.NET Helpers

Is there any way to ignore logging specific controller actions or status codes? If you try to use this on a server behind an NLB, the TCP health checks just create a bunch of useless 404 responses that all get logged.

The EMF Web Extension Methods Are Not in Nuget Package

The advertised extension methods in the EMF.Web namespace are not part of the Amazon.CloudWatch.EMF nuget package (since it's a different project and I don't see it published). This means the example with .UseEmfMiddleware(); and services.AddEmf(); don't work. It's simple enough to copy the code into your project, but wasn't expecting to need to do that (and didn't specify in the readme). In the example code, it all works because you're still in the EMF namespace, but consumers of the library can't access it.

EC2/ECS Metadata Endpoint checks block Lambda function

We are not short-circuiting environment detection on env var checks. The impact of this is that the flush time on Lambda functions will be extended by the network I/O which will block function response and drive up latency.

MetricsLogger cannot send metrics in parallel

We found that if multiple tasks/threads use the same MetricsLogger and run in parallel, the metrics sent by this MetricsLogger could be messed up by each other.

I have a sample code below

...
public static void SendMetricLog(this IMetricsLogger metricsLogger, string metricsNamespace, 
params (string metricName, double value, Unit unit)[] metricValues)
{
     metricsLogger.SetNamespace(metricsNamespace);
     metricValues.ToList().ForEach(metricValue =>
      {
            metricsLogger.PutMetric(metricValue.metricName, metricValue.value, metricValue.unit);
      });
      metricsLogger.Flush();
}

public static async Task TaskA(IMetricsLogger metricsLogger)
{
      var failedCount = await makeRequestsToServiceAAsync();
      metricsLogger.SendMetricLog("namespaceA", ("RequestAErrors", failedCount, Unit.COUNT));
}

public static async Task TaskB(IMetricsLogger metricsLogger)
{
      var failedCount = await makeRequestsToServiceBAsync();
      metricsLogger.SendMetricLog("namespaceB", ("RequestBErrors", failedCount, Unit.COUNT));
}

...
using var logger = new MetricsLogger();
await Task.WhenAll(TaskA(logger), TaskB(logger));
...

It turns out that the metrics which should be sent to namespaceA are sporadically sent to namespaceB.

Am I missing the doc about the usage of MetricsLogger in the above case or is it a bug in this SDK?

Target netstandard2.1

Are there plans to target netstandard? Trying to integrate this package into my service and my team has expressed concern about having to change our target to netcoreapp3.1.

Disallow duplicate dimension sets

Related issues:

Currently duplicate dimension set are being allowed:
Example:

var ds1 = new DimensionSet();
ds1.AddDimension("Region", "us-west-1");
var ds2 = new DimensionSet();
ds2.AddDimension("Region", "us-west-2");

metrics.SetDimensions(ds1);
metrics.PutDimensions(ds2);

metrics.PutMetric("Metric1", 1.0);
metrics.Flush();

This creates

{
  "_aws": {
    "Timestamp": 1660330702000,
    "CloudWatchMetrics": [
      {
        "Namespace": "aws-embedded-metrics",
        "Metrics": [
          {
            "Name": "Metric1",
            "Unit": "None"
          }
        ],
        "Dimensions": [
          [
            "Region"
          ],
          [
            "Region"
          ]
        ]
      }
    ],
    "LogGroupName": "DemoApp"
  },
  "Region": "us-west-1",
  "LogGroupName": "DemoApp",
  "Metric1": 1
}

Instead of:

{
  "_aws": {
    "Timestamp": 1660330702000,
    "CloudWatchMetrics": [
      {
        "Namespace": "aws-embedded-metrics",
        "Metrics": [
          {
            "Name": "Metric1",
            "Unit": "None"
          }
        ],
        "Dimensions": [
          [
            "Region"
          ]
        ]
      }
    ],
    "LogGroupName": "DemoApp"
  },
  "Region": "us-west-2",
  "LogGroupName": "DemoApp",
  "Metric1": 1
}

Support resetting custom dimensions and related enhancements

Related issue:

This is the equivalent enhancements related to resetting custom dimensions in the Dotnet library. Similar APIs and features should be implemented in all libraries for consistency. Specifially, the enhancements include:

  • Provide a ResetDimensions(bool useDefault) API for resetting dimensions
  • Provide a feature flag to control whether Flush() should preserve custom dimensions after each execution
  • Provide a setDimensions(bool useDefault, params DimensionSet[] dimensionSets) API to control the behavior of default dimensions while setting new dimensions.

Undocumented MetricsContext

I see a MetricsContext class in the package, which appears to be undocumented. Can you please explain how this should be used?

Add CI/CD

  • PR builds
  • Release Build that ships to NuGet -- Pending API Key for Amazon.* NuGet namespace
  • Dashboards
  • Code Pipeline
  • Canary

Method not found: 'Amazon.CloudWatch.EMF.Logger.MetricsLogger Amazon.CloudWatch.EMF.Logger.IMetricsLogger.PutMetric(System.String, Double, Amazon.CloudWatch.EMF.Model.Unit)'

We are using :

  • Amazon.CloudWatch.EMF 2.1.0
  • Amazon.CloudWatch.EMF.Web 1.0.0
  • ASP.NET Core 6.0 / .NET 6.0

I then add UseEmf and UseEmfMiddleware we are getting the following error when making any call to our API:

An unhandled exception has occurred while executing the request.
System.MissingMethodException: Method not found: 'Amazon.CloudWatch.EMF.Logger.MetricsLogger Amazon.CloudWatch.EMF.Logger.IMetricsLogger.PutMetric(System.String, Double, Amazon.CloudWatch.EMF.Model.Unit)'.
at Amazon.CloudWatch.EMF.Web.ApplicationBuilderExtensions.<>c__DisplayClass1_0.<b__0>d.MoveNext()
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at Amazon.CloudWatch.EMF.Web.ApplicationBuilderExtensions.<>c__DisplayClass1_0.b__0(HttpContext context, Func`1 next)
at Microsoft.AspNetCore.Builder.UseExtensions.<>c__DisplayClass0_1.b__1(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

This looks to be caused by #48

It should be resolved just by rebuilding and publishing the .Web against 2.1.0.

I've worked around the issue by just copying the source for the .Web package into my codebase.

PutDimensions Doesn't Work

In an ASP.NET web application, I'm using the EMF middleware. I want to add dimensions besides Action, Status, and controller, so I'm creating a DimensionSet and calling PutDimensions.

DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("AZ-ID", Utils.GetAZId());
dimensionSet.AddDimension("Region", Utils.GetRegion());
dimensionSet.AddDimension("InstanceId", Utils.GetInstanceId());

metrics.PutDimensions(dimensionSet);

This doesn't work. In fact, any attempt to use PutDimensions or PutProperty inside a controller action results in nothing being logged. If I don't call these methods on the IMetricsLogger object (the one that's injected into the constructor), then logs are produced when a controller action is called. Finally, on the IMetricsLogger object that's injected into the controller, using SetNamespace in the constructor isn't honored for the log object created by the middleware, but calling a method like PutProperty is.

I believe you should be able to set the namespace (not always use the default of the ServiceName) and add dimensions and properties inside a controller action while using the EMF middleware.

asp.net Use EMF for both Controllers and IHostedService

I'm trying to use EMF for both controllers in my web app as well as an IHostedService that does a background refresh of a local cache. The asp.net helpers add the IMetricsLogger dependency as a scoped service. IHostedServices are singletons. When trying to set up a new MetricsLogger() in the hosted service, it looks like it's thrashing configs provided by the singleton elements like IEnvironmentProvider. Is there a way to setup a completely separate MetricsLogger that does not use any of the configs or other resources used in the controller actions?

Audit Environment Detection

When deploying the canary to ECS, environment detection did not work out of the box as expected. The default sink was stdout.

Memory leak with specific configuration

When using the example configuration as stated in the README, the library fails to send all its logs to the CloudWatch agent.

This has caused the canary's heap usage to increase until crashing and is therefore blocking the release of v2.0.0.

Screen Shot 2022-09-20 at 4 43 42 PM

Can `Timestamp` in metadata be overwritten?

By calling this SDK to send EMF logs, it seems we cannot define the timestamp of the metrics.
The timestamp is generated at

[JsonProperty]
[JsonConverter(typeof(UnixMillisecondDateTimeConverter))]
internal DateTime Timestamp { get; set; }
, and I don't find the way to overwrite it.

However, we could assign Timestamp as wanted, if we construct the EMF log by our own, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Generation_PutLogEvents.html.

MetaData may not be serialized correctly when JsonConvert.DefaultSettings has been changed.

The serialization of the MetaData class is impacted by changing the default JsonSerializerSettings. This may lead to the json no longer being in valid EMF. This was found using Amazon.CloudWatch.EMF 2.1.0.

var jsonSettings = new JsonSerializerSettings
{
	ContractResolver = new DefaultContractResolver
	{
		NamingStrategy = new CamelCaseNamingStrategy()
	}
};

JsonConvert.DefaultSettings = () => jsonSettings;

using var metrics = new MetricsLogger();
var dimensions = new DimensionSet("Dim1", "abc");
metrics.PutMetric("Count", 1, Unit.COUNT);

Actual output:

{"_aws":{"timestamp":1699986206875,"cloudWatchMetrics":[{"Namespace":"aws-embedded-metrics","Metrics":[{"Name":"Count","Unit":"Count"}],"Dimensions":[["ServiceName","ServiceType"]]}]},"ServiceName":"Unknown","ServiceType":"Unknown","Count":1.0}

Expected output:

{"_aws":{"Timestamp":1699986206875,"CloudWatchMetrics":[{"Namespace":"aws-embedded-metrics","Metrics":[{"Name":"Count","Unit":"Count"}],"Dimensions":[["ServiceName","ServiceType"]]}]},"ServiceName":"Unknown","ServiceType":"Unknown","Count":1.0}

Setting dimensions as null breaks metrics logger

I had this code:

class MyMetricsLogger
{
    private readonly MetricsLogger _metricsLogger = new MetricsLogger();
    public void LogMetric(string key, double value, DimensionSet dimensionSet = null)
    {
        _metricsLogger.SetDimensions(dimensionSet);
        _metricsLogger.PutMetric(key, value);
        _metricsLogger.Flush();
    }
}

Invoking MetricsLogger.SetDimensions with null will break the MetricsLogger. When it next tries to log any metrics there is a NullReferenceException thrown from

   at Amazon.CloudWatch.EMF.Model.MetricDirective.<>c.<get_AllDimensionKeys>b__20_0(DimensionSet s)

There is a simple workaround, avoid using null when calling SetDimensions.

if (dimensionSet != null)
{
    _metricsLogger.SetDimensions(dimensionSet);
}
else
{
    _metricsLogger.SetDimensions();
}

It is a weakness of the package design for me to have to do this, the null check should be handled gracefully within the package itself.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.