diff --git a/docs/features/telemetry-enrichment.md b/docs/features/telemetry-enrichment.md index f1cc8488..84922d7d 100644 --- a/docs/features/telemetry-enrichment.md +++ b/docs/features/telemetry-enrichment.md @@ -86,8 +86,9 @@ Value: `0477E377-414D-47CD-8756-BCBE3DBE3ACB` **Usage** ```csharp +IServiceProvider serviceProvider = ... ILogger logger = new LoggerConfiguration() - .Enrich.WithCorrelationInfo() + .Enrich.WithCorrelationInfo(serviceProvider) .CreateLogger(); logger.Information("This event will be enriched with the correlation information"); @@ -112,8 +113,10 @@ The correlation information enricher allows you to specify the names of the log This is available on all extension overloads. By default the operation ID is set to `OperationId` and the transaction ID to `TransactionId`. ```csharp +IServiceProvider serviceProvider = ... ILogger logger = new LoggerConfiguration() .Enrich.WithCorrelationInfo( + serviceProvider, operationIdPropertyName: "MyOperationId", transactionIdPropertyName: "MyTransactionId") .CreateLogger(); diff --git a/docs/features/writing-different-telemetry-types.md b/docs/features/writing-different-telemetry-types.md index 416caf83..2c71d818 100644 --- a/docs/features/writing-different-telemetry-types.md +++ b/docs/features/writing-different-telemetry-types.md @@ -38,6 +38,7 @@ We provide support for the following dependencies: - [Azure Cosmos DB](#measuring-azure-cosmos-db-dependencies) - [Azure Event Hubs](#measuring-azure-event-hubs-dependencies) - [Azure IoT Hub](#measuring-azure-iot-hub-dependencies) +- [Azure Key Vault](#measuring-azure-key-vault-dependencies) - [Azure Search](#measuring-azure-search-dependencies) - [Azure Service Bus](#measuring-azure-service-bus-dependencies) - [Azure Table Storage](#measuring-azure-table-storage-dependencies) @@ -60,7 +61,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogBlobStorageDependency(accountName: "multimedia", containerName: "images", isSuccessful: true, startTime, durationMeasurement.Elapsed); +logger.LogBlobStorageDependency(accountName: "multimedia", containerName: "images", isSuccessful: true, startTime, durationMeasurement.Elapsed); // Output: "Dependency Azure blob multimedia named images in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -79,7 +80,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogCosmosSqlDependency(accountName: "administration", database: "docs", container: "purchases", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogCosmosSqlDependency(accountName: "administration", database: "docs", container: "purchases", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure DocumentDB docs/purchases named administration in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -96,7 +97,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogEventHubsDependency(namespaceName: "be.sensors.contoso", eventHubName: "temperature", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogEventHubsDependency(namespaceName: "be.sensors.contoso", eventHubName: "temperature", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure Event Hubs be.sensors.contoso named temerature in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -115,7 +116,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.logger.LogIotHubDependency(iotHubName: "sensors", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogIotHubDependency(iotHubName: "sensors", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -140,10 +141,29 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.logger.LogIotHubDependency(iotHubConnectionString: "Hostname=sensors;", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogIotHubDependency(iotHubConnectionString: "Hostname=sensors;", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` +### Measuring Azure Key Vault dependencies + +We allow you to measure Azure Key vault dependencies. + +**Example** + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new StopWatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.AzureKeyVaultDependency(vaultUri: "https://my-secret-store.vault.azure.net", secretName: "ServiceBus-ConnectionString", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure key vault get secret named https://my-secret-store.vault.azure.net in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + ### Measuring Azure Search dependencies We allow you to measure Azure Search depdendencies for cognetive services. @@ -157,7 +177,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogAzureSearchDependency(searchServiceName: "orders-search", operationName: "get-orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogAzureSearchDependency(searchServiceName: "orders-search", operationName: "get-orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure Search get-orders named orders-search in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ### Measuring Azure Service Bus dependencies @@ -173,7 +193,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogServiceBusQueueDependency(queueName: "ordersqueue", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogServiceBusQueueDependency(queueName: "ordersqueue", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure Service Bus Queue named ordersqueue in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -192,7 +212,7 @@ var durationMeasurement = new Stopwatch(); durationMeasurement.Start(); var startTime = DateTimeOffset.UtcNow; -_logger.LogTableStorageDependency(accountName: "orderAccount", tableName: "orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogTableStorageDependency(accountName: "orderAccount", tableName: "orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency Azure table orders named orderAccount in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -215,7 +235,7 @@ var startTime = DateTimeOffset.UtcNow; // Send request to dependant service var response = await httpClient.SendAsync(request); -_logger.LogHttpDependency(request, statusCode: response.StatusCode, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogHttpDependency(request, statusCode: response.StatusCode, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "HTTP Dependency requestbin.net for POST /r/ujxglouj completed with 200 in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` @@ -233,7 +253,7 @@ durationMeasurement.Start(); // Interact with database var products = await _repository.GetProducts(); -_logger.LogSqlDependency("sample-server", "sample-database", "my-table", "get-products", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogSqlDependency("sample-server", "sample-database", "my-table", "get-products", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "SQL Dependency sample-server for sample-database/my-table for operation get-products in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" ``` @@ -260,7 +280,7 @@ durationMeasurement.Start(); // Interact with database var products = await _repository.GetProducts(); -_logger.LogSqlDependency(connectionString, "my-table", "get-products", isSuccessful: true, measurement: measurement); +logger.LogSqlDependency(connectionString, "my-table", "get-products", isSuccessful: true, measurement: measurement); // Output: "SQL Dependency sample-server for sample-database/my-table for operation get-products in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" ``` @@ -278,7 +298,7 @@ durationMeasurement.Start(); string dependencyName = "SendGrid"; object dependencyData = "http://my.sendgrid.uri/" -_logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); // Output: "Dependency SendGrid http://my.sendgrid.uri/ in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" ``` @@ -298,7 +318,7 @@ durationMeasurement.Start(); /// Track dependency string dependencyName = "SendGrid"; object dependencyData = "https://my.sendgrid.uri/"; -_logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed, context: telemetryContext); +logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed, context: telemetryContext); ``` However, by using `DependencyMeasurement.Start()` we take care of the measuring aspect: @@ -312,7 +332,7 @@ using (var measurement = DependencyMeasurement.Start()) // Track dependency string dependencyName = "SendGrid"; object dependencyData = "https://my.sendgrid.uri/"; - _logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); + logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); } ``` @@ -327,12 +347,12 @@ try // Interact with SendGrid... // Done! - _logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); + logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); } catch (Exception exception) { - _logger.LogError(exception, "Failed to interact with SendGrid"); - _logger.LogDependency(dependencyName, dependencyData, isSuccessful: false, startTime: measurement, context: telemetryContext); + logger.LogError(exception, "Failed to interact with SendGrid"); + logger.LogDependency(dependencyName, dependencyData, isSuccessful: false, startTime: measurement, context: telemetryContext); } ``` @@ -373,6 +393,16 @@ logger.LogMetric("Invoice Received", 133.37, telemetryContext); Requests allow you to keep track of the HTTP requests that are performed against your API and what the response was that was sent out. +**Installation** + +If you want to track the `HttpRequest` and `HttpResponse` of an ASP.NET Core project, you'll have to install an additional package to include these ASP.NET Core dependencies: + +```shell +PM > Install-Package Arcus.Observability.Telemetry.AspNetCore +``` + +**Example** + Here is how you can keep track of requests: ```csharp diff --git a/docs/index.md b/docs/index.md index 60dd38d4..f29559ae 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,6 +39,7 @@ This is licensed under The MIT License (MIT). Which means that you can use, copy # Older Versions +- [v1.0](v1.0) - [v0.4](v0.4) - [v0.3](v0.3) - [v0.2.0](v0.2.0) diff --git a/docs/v2.0/features/correlation.md b/docs/v2.0/features/correlation.md new file mode 100644 index 00000000..04248789 --- /dev/null +++ b/docs/v2.0/features/correlation.md @@ -0,0 +1,172 @@ +--- +title: "Correlation" +layout: default +--- + +# Correlation + +`CorrelationInfo` provides a common set of correlation levels: + +- Transaction Id - ID that relates different requests together into a functional transaction. +- Operation Id - Unique ID information for a single request. + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Correlation +``` + +## What We Provide + +The `Arcus.Observability.Correlation` library provides a way to get access to correlation information across your application. +What it **DOES NOT** provide is how this correlation information is initially set. + +It uses the the Microsoft dependency injection mechanism to register an `ICorrelationInfoAccessor` and `ICorrelationInfoAccessor<>` implementation that is available. + +**Example** + +```csharp +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + // Adds operation and transaction correlation to the application, + // using the `DefaultCorrelationInfoAccessor` as `ICorrelationInfoAccessor` that stores the `CorrelationInfo` model internally. + services.AddCorrelation(); + } +} +``` +## Custom Correlation + +We register two interfaces during the registration of the correlation: `ICorrealtionInfoAccessor` and `ICorrelationInfoAccessor<>`. +The reason is because some applications require a custom `CorrelationInfo` model, and with using the generic interface `ICorrelationInfoAccessor<>` we can support this. + +**Example** + +```csharp +public class OrderCorrelationInfo : CorrelationInfo +{ + public string OrderId { get; } +} + +public class Startup +{ + public void ConfigureService(IServiceCollection services) + { + services.AddCorrelation(); + } +} +``` + +## Accessing Correlation Throughout the Application + +When a part of the application needs access to the correlation information, you can inject one of the two interfaces: + +```csharp +public class OrderService +{ + public OrderService(ICorrelationInfoAccessor accessor) + { + CorrelationInfo correlationInfo = accessor.CorrelationInfo; + } +} +``` + +Or, alternatively when using custom correlation: + +```csharp +public class OrderService +{ + public OrderService(ICorrelationInfoAccessor accessor) + { + OrderCorrelationInfo correlationInfo = accessor.CorrelationInfo; + } +} +``` + +## Configuration + +The library also provides a way configure some correlation specific options that you can later retrieve during get/set of the correlation information in your application. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddCorrelation(options => + { + // Configuration on the transaction ID (`X-Transaction-ID`) request/response header. + // --------------------------------------------------------------------------------- + + // Whether the transaction ID can be specified in the request, and will be used throughout the request handling. + // The request will return early when the `.AllowInRequest` is set to `false` and the request does contain the header (default: true). + options.Transaction.AllowInRequest = true; + + // Whether or not the transaction ID should be generated when there isn't any transaction ID found in the request. + // When the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header, no value will be available for the transaction ID; + // otherwise a GUID will be generated (default: true). + options.Transaction.GenerateWhenNotSpecified = true; + + // Whether to include the transaction ID in the response (default: true). + options.Transaction.IncludeInResponse = true; + + // The header to look for in the request, and will be set in the response (default: X-Transaction-ID). + options.Transaction.HeaderName = "X-Transaction-ID"; + + // The function that will generate the transaction ID, when the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header. + // (default: new `Guid`). + options.Transaction.GenerateId = () => $"Transaction-{Guid.NewGuid()}"; + + // Configuration on the operation ID (`RequestId`) response header. + // ---------------------------------------------------------------- + + // Whether to include the operation ID in the response (default: true). + options.Operation.IncludeInResponse = true; + + // The header that will contain the operation ID in the response (default: RequestId). + options.Operation.HeaderName = "RequestId"; + + // The function that will generate the operation ID header value. + // (default: new `Guid`). + options.Operation.GenerateId = () => $"Operation-{Guid.NewGuid()}"; + }); +} +``` + +Later in the application, the options can be retrieved by injecting the `IOptions` type. + +### Custom Configuration + +We also provide a way to provide custom configuration options when the application uses a custom correlation model. + +For example, with a custom correlation model: + +```csharp +public class OrderCorrelationInfo : CorrelationInfo +{ + public string OrderId { get; } +} +``` + +We could introduce an `OrderCorrelationInfoOptions` model: + +```csharp +public class OrderCorrelationInfoOptions : CorrelationInfoOptions +{ + public bool IncludeOrderId { get; set; } +} +``` + +This custom options model can then be included when registering the correlation: + +```csharp +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddCorrelation(options => options.IncludeOrderId = true); + } +} +``` + +[← back](/) diff --git a/docs/v2.0/features/making-telemetry-more-powerful.md b/docs/v2.0/features/making-telemetry-more-powerful.md new file mode 100644 index 00000000..e1c1798d --- /dev/null +++ b/docs/v2.0/features/making-telemetry-more-powerful.md @@ -0,0 +1,60 @@ +--- +title: "Making telemetry more powerful" +layout: default +--- + +# Making telemetry more powerful + +## Providing contextual information + +In order to make telemetry more powerful we **highly recommend providing contextual information around what the situation is of your application**. That's why every telemetry type that you can write, allows you to provide context in the form of a dictionary. + +```csharp +// Provide context around event +var telemetryContext = new Dictionary +{ + {"Customer", "Arcus"}, + {"OrderId", "ABC"}, +}; + +logger.LogEvent("Order Created", telemetryContext); +// Output: "Events Order Created (Context: [Customer, Arcus], [OrderId, ABC])" +``` + +By doing so, you'll be able to interact more efficient with your logs by filtering, searching, ... on it. + +We support this for all [telemetry types that you can write](/features/writing-different-telemetry-types). + +### Seeing the power in action + +Let's use an example - When measuring a metric you get an understanding of the count, in our case the number of orders received: + +```csharp +logger.LogMetric("Orders Received", 133); +// Log output: "Metric Orders Received: 133 (Context: )" +``` + +If we output this to Azure Application Insights as a metric similar to our example: +![Single-dimension Metric](./../media/single-dimensional-metric.png) + +However, you can very easily provide additional context, allowing you to get an understanding of the number of orders received and annotate it with the vendor information. + +```csharp +var telemetryContext = new Dictionary +{ + { "Customer", "Contoso"}, +}; + +logger.LogMetric("Orders Received", 133, telemetryContext); +// Log output: "Metric Orders Received: 133 (Context: [Customer, Contoso])" +``` + +The outputted telemetry will contain that information and depending on the sink that you are using it's even going to be more powerful. + +For example, when using Azure Application Insights your metric will evolve from a single-dimensional metric to multi-dimensional metrics allowing you to get the total number of orders, get the number of orders per vendor or filter the metric to one specific vendor. + +Here we are using our multi-dimensional metric and splitting it per customer to get more detailed insights: + +![Multi-dimension Metric](./../media/multi-dimensional-metrics.png) + +[← back](/) diff --git a/docs/v2.0/features/sinks/azure-application-insights.md b/docs/v2.0/features/sinks/azure-application-insights.md new file mode 100644 index 00000000..ef0eadd9 --- /dev/null +++ b/docs/v2.0/features/sinks/azure-application-insights.md @@ -0,0 +1,60 @@ +--- +title: "Azure Application Insights Sink" +layout: default +--- + +# Azure Application Insights Sink + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.Serilog.Sinks.ApplicationInsights +``` + +## What is it? + +The Azure Application Insights sink is an extension of the [official Application Insights sink](https://www.nuget.org/packages/Serilog.Sinks.ApplicationInsights/) that allows you to not only emit traces or events, but the whole Application Insights suite of telemetry types - Traces, Dependencies, Events, Requests & Metrics. + +You can easily configure the sink by providing the Azure Application Insights key: + +```csharp +ILogger logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.AzureApplicationInsights("") + .CreateLogger(); +``` + +Alternatively, you can override the default minimum log level to reduce amount of telemetry being tracked : + +```csharp +ILogger logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.AzureApplicationInsights("", restrictedToMinimumLevel: LogEventLevel.Warning) + .CreateLogger(); +``` + +## FAQ + +### Q: Why is it mandatory to provide an instrumentation key? + +While the native Azure Application Insights SDK does not enforce an instrumentation key we have chosen to make it mandatory to provide one. + +By doing this, we allow you to fail fast and avoid running your application with a misconfigured telemetry setup. If it would be optional, you could have it running for days/weeks only to notice you are not sending any telemetry when everything is on fire and you are in the dark. + +If you want to optionally use our sink when there is an instrumentation key, we recommend using this simple pattern: + +```csharp +var loggerConfig = new LoggerConfiguration() + .MinimumLevel.Debug(); + +if(string.IsNullOrEmpty(key) == false) +{ + loggerConfig.WriteTo.AzureApplicationInsights(key); +} + +ILogger logger = loggerConfig.CreateLogger(); +``` + +[← back](/) diff --git a/docs/v2.0/features/telemetry-enrichment.md b/docs/v2.0/features/telemetry-enrichment.md new file mode 100644 index 00000000..84922d7d --- /dev/null +++ b/docs/v2.0/features/telemetry-enrichment.md @@ -0,0 +1,302 @@ +--- +title: "Telemetry Enrichment" +layout: default +--- + +# Telemetry Enrichment + +We provide a variety of enrichers for Serilog: + +- [Application Enricher](#application-enricher) +- [Correlation Enricher](#correlation-enricher) +- [Kubernetes Enricher](#kubernetes-enricher) +- [Version Enricher](#version-enricher) + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.Serilog.Enrichers +``` + +## Application Enricher + +The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment) +that adds the application's component name to the log event as a log property with the name `ComponentName` and gives the opportiunity to choose the location from where the application 'instance' should be retrieved. + +**Example** +Name: `ComponentName` +Value: `My application component` + +**Usage** + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithComponentName("My application component") + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {ComponentName: My application component, MachineName: MyComputer} +``` + +Or, alternatively one can choose to use the Kubernetes information which our [Application Insights](./sinks/azure-application-insights) sink will prioritize above the `MachineName` when determining the telemetry `Cloud.RoleInstance`. + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithComponentName("My application component") + .Enrich.WithKubernetesInfo() + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {ComponentName: My application component, MachineName: MachineName: MyComputer, PodName: demo-app} +``` + +### Custom Serilog property names + +The application enricher allows you to specify the name of the log property that will be added to the log event during enrichment. +By default this is set to `ComponentName`. + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithComponentName( + componentName: "My application component", + propertyName: "MyComponentName") + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {MyComponentName: My application component, MachineName: MyComputer} +``` + +## Correlation Enricher + +The `Arcus.ObservabilityTelemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment) +that adds the `CorrelationInfo` information from the current context as log properties with the names `OperationId` and `TransactionId`. + +You can use your own `ICorrelationInfoAccessor` implementation to retrieve this `CorrelationInfo` model, +or use the default `DefaultCorrelationInfoAccessor` implementation that stores this model + +**Example** +Name: `OperationId` +Value: `52EE2C00-53EE-476E-9DAB-C1234EB4AD0B` + +Name: `TransactionId` +Value: `0477E377-414D-47CD-8756-BCBE3DBE3ACB` + +**Usage** + +```csharp +IServiceProvider serviceProvider = ... +ILogger logger = new LoggerConfiguration() + .Enrich.WithCorrelationInfo(serviceProvider) + .CreateLogger(); + +logger.Information("This event will be enriched with the correlation information"); +``` + +Or alternatively, with a custom `ICorrelationInfoAccessor`: + +```csharp +ICorrelationInfoAccessor myCustomAccessor = ... + +ILogger logger = new LoggerConfiguration() + .Enrich.WithCorrelationInfo(myCustomAccessor) + .CreateLogger(); + +logger.Information("This event will be enriched with the correlation information"); +// Output: This event will be enriched with the correlation information {OperationId: 52EE2C00-53EE-476E-9DAB-C1234EB4AD0B, TransactionId: 0477E377-414D-47CD-8756-BCBE3DBE3ACB} +``` + +### Custom Serilog property names + +The correlation information enricher allows you to specify the names of the log properties that will be added to the log event during enrichment. +This is available on all extension overloads. By default the operation ID is set to `OperationId` and the transaction ID to `TransactionId`. + +```csharp +IServiceProvider serviceProvider = ... +ILogger logger = new LoggerConfiguration() + .Enrich.WithCorrelationInfo( + serviceProvider, + operationIdPropertyName: "MyOperationId", + transactionIdPropertyName: "MyTransactionId") + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {MyOperationId: 52EE2C00-53EE-476E-9DAB-C1234EB4AD0B, MyTransactionId: 0477E377-414D-47CD-8756-BCBE3DBE3ACB} +``` + +## Kubernetes Enricher + +The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Kubernetes](https://kubernetes.io/) [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment) +that adds several machine information from the environment (variables). + +**Example** + +| Environment Variable | Log Property | +| ---------------------- | ------------ | +| `KUBERNETES_NODE_NAME` | NodeName | +| `KUBERNETES_POD_NAME` | PodName | +| `KUBERNETES_NAMESPACE` | Namespace | + +**Usage** + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithKubernetesInfo() + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {NodeName: demo-cluster, PodName: demo-app, Namespace: demo} +``` + +Here is an example of a Kubernetes YAML that provides the required environment variables: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-app + labels: + app: demo +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: demo-app + app.kubernetes.io/instance: instance + template: + metadata: + labels: + app.kubernetes.io/name: demo-app + app.kubernetes.io/instance: instance + spec: + containers: + - name: event-proxy + image: arcusazure/arcus-event-grid-proxy + env: + - name: ARCUS_EVENTGRID_TOPICENDPOINT + value: https://arcus.io + - name: ARCUS_EVENTGRID_AUTHKEY + valueFrom: + secretKeyRef: + name: secrets-order-consumer + key: servicebus-connectionstring + - name: KUBERNETES_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +``` + +### Custom Serilog property names + +The Kubernetes enricher allows you to specify the names of the log properties that will be added to the log event during enrichment. +By default the node name is set to `NodeName`, the pod name to `PodName`, and the namespace to `Namespace`. + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithKubernetesInfo( + nodeNamePropertyName: "MyNodeName", + podNamePropertyName: "MyPodName", + namespacePropertyName: "MyNamespace") + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {MyNodeName: demo-cluster, MyPodName: demo-app, MyNamespace: demo} +``` + +## Version Enricher + +The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment) +that adds (by default) the current runtime assembly version of the product to the log event as a log property with the name `version`. + +**Example** +Name: `version` +Value: `1.0.0-preview` + +**Usage** + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithVersion() + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {version: 1.0.0-preview} +``` + +### Custom application version + +The version enricher allows you to specify an `IAppVersion` instance that retrieves your custom application version, which will be used during enrichement. +By default this is set to the version of the current executing assembly. + +**Assembly version as application version** + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Register the `AssemblyAppVersion` instance to retrieve the application version from the assembly where the passed-along `Startup` type is located. + services.AddAssemblyAppVersion(); +} +``` + +**User-provided version** + +```csharp +IAppVersion appVersion = new MyCustomAppVersion("v0.1.0"); +ILogger logger = new LoggerConfiguration() + .Enrich.WithVersion(appVersion) + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {version: v0.1.0} +``` + +Or alternatively, you can choose to register the application version so you can use it in your application as well. + +```csharp +public void ConfigureServivces(IServiceCollection services) +{ + // Register the `MyApplicationVersion` instance to the registered services (using empty constructor). + services.AddAppVersion(); + + // Register the `MyApplicationVersion` instance using the service provider. + services.AddAppVersion(serviceProvider => + { + var logger = serviceProvider.GetRequiredService>(); + return new MyApplicationVersion(logger); + }); +} +``` + +Once the application version is registered, you can pass along the `IServiceProvider` instead to the Serilog configuration. + +```csharp +IServiceProvider serviceProvider = ... +ILogger logger = new LoggerConfiguration() + .Enrich.WithVersion(serviceProvider) + .CreateLogger(); +``` + +### Custom Serilog property names + +The version enricher allows you to specify the name of the property that will be added to the log event during enrichement. +By default this is set to `version`. + +```csharp +ILogger logger = new LoggerConfiguration() + .Enrich.WithVersion(propertyName: "MyVersion") + .CreateLogger(); + +logger.Information("Some event"); +// Output: Some event {MyVersion: 1.0.0-preview} +``` + +[← back](/) diff --git a/docs/v2.0/features/telemetry-filter.md b/docs/v2.0/features/telemetry-filter.md new file mode 100644 index 00000000..a9eb21de --- /dev/null +++ b/docs/v2.0/features/telemetry-filter.md @@ -0,0 +1,47 @@ +--- +title: "Telemetry Filters" +layout: default +--- + +# Telemetry Filters + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.Serilog.Filters +``` + +## Telemetry Type Filter + +This [Serilog filter](https://github.com/serilog/serilog/wiki/Enrichment) allows you to filter out different a specific type of telemetry. + +```csharp +ILogger logger = new LoggerConfiguration() + .WriteTo.AzureApplicationInsights("") + .Filter.With(TelemetryTypeFilter.On(TelemetryType.Events)) + .CreateLogger(); +``` + +The filter can also be used to reduce telemetry for multiple types by chaining them: + +```csharp +ILogger logger = new LoggerConfiguration() + .WriteTo.AzureApplicationInsights("") + .Filter.With(TelemetryTypeFilter.On(TelemetryType.Events)) + .Filter.With(TelemetryTypeFilter.On(TelemetryType.Dependency)) + .CreateLogger(); +``` + +Alternatively, you can explicitly specify if it should track telemetry or not based on the application configuration has to be tracked or not : + +```csharp +var trackDependencies = configuration["telemetry:depenencies:isEnabled"]; +ILogger logger = new LoggerConfiguration() + .WriteTo.AzureApplicationInsights("") + .Filter.With(TelemetryTypeFilter.On(TelemetryType.Dependency, isTrackingEnabled: bool.Parse(trackDependencies))) + .CreateLogger(); +``` + +[← back](/) diff --git a/docs/v2.0/features/writing-different-telemetry-types.md b/docs/v2.0/features/writing-different-telemetry-types.md new file mode 100644 index 00000000..2c71d818 --- /dev/null +++ b/docs/v2.0/features/writing-different-telemetry-types.md @@ -0,0 +1,425 @@ +--- +title: "Write different telemetry types" +layout: default +--- + +# Write different telemetry types + +Logs are a great way to gain insights, but sometimes they are not the best approach for the job. + +We provide the capability to track the following telemetry types on top of ILogger with good support on Serilog: + +- [Dependencies](#dependencies) +- [Events](#events) +- [Metrics](#metrics) +- [Requests](#requests) + +For most optimal output, we recommend using our [Azure Application Insights sink](/features/sinks/azure-application-insights). + +**We highly encourage to provide contextual information to all your telemetry** to make it more powerful and support this for all telemetry types. + +> :bulb: For sake of simplicity we have not included how to track contextual information, for more information see [our documentation](/features/making-telemetry-more-powerful). + +## Installation + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.Core +``` + +## Dependencies + +Dependencies allow you to track how your external dependencies are doing to give you insights on performance and error rate. + +We provide support for the following dependencies: + +- [Azure Blob Storage](#measuring-azure-blob-storage-dependencies) +- [Azure Cosmos DB](#measuring-azure-cosmos-db-dependencies) +- [Azure Event Hubs](#measuring-azure-event-hubs-dependencies) +- [Azure IoT Hub](#measuring-azure-iot-hub-dependencies) +- [Azure Key Vault](#measuring-azure-key-vault-dependencies) +- [Azure Search](#measuring-azure-search-dependencies) +- [Azure Service Bus](#measuring-azure-service-bus-dependencies) +- [Azure Table Storage](#measuring-azure-table-storage-dependencies) +- [Custom](#measuring-custom-dependencies) +- [HTTP](#measuring-http-dependencies) +- [SQL](#measuring-sql-dependencies) + +Since measuring dependencies can add some noise in your code, we've introduced `DependencyMeasurement` to make it simpler. ([docs](#making-it-easier-to-measure-dependencies)) + +### Measuring Azure Blob Storage dependencies + +We allow you to measure Azure Blob Storage dependencies. + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogBlobStorageDependency(accountName: "multimedia", containerName: "images", isSuccessful: true, startTime, durationMeasurement.Elapsed); +// Output: "Dependency Azure blob multimedia named images in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring Azure Cosmos DB dependencies + +We allow you to measure Azure Cosmos dependencies. + +Here is how you can report a dependency call: + +**Cosmos SQL** + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogCosmosSqlDependency(accountName: "administration", database: "docs", container: "purchases", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure DocumentDB docs/purchases named administration in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring Azure Event Hubs dependencies + +We allow you to measure Azure Event Hubs dependencies. + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogEventHubsDependency(namespaceName: "be.sensors.contoso", eventHubName: "temperature", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure Event Hubs be.sensors.contoso named temerature in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring Azure IoT Hub dependencies + +We allow you to measure Azure IoT Hub dependencies. + +**Example** + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogIotHubDependency(iotHubName: "sensors", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +Or, alternatively you can pass allong the IoT connection string itself so the host name will be selected for you. + +**Installation** + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.IoT +``` + +**Example** + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogIotHubDependency(iotHubConnectionString: "Hostname=sensors;", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring Azure Key Vault dependencies + +We allow you to measure Azure Key vault dependencies. + +**Example** + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new StopWatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.AzureKeyVaultDependency(vaultUri: "https://my-secret-store.vault.azure.net", secretName: "ServiceBus-ConnectionString", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure key vault get secret named https://my-secret-store.vault.azure.net in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring Azure Search dependencies + +We allow you to measure Azure Search depdendencies for cognetive services. + +Here is how you can report an Azure Search dependency: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogAzureSearchDependency(searchServiceName: "orders-search", operationName: "get-orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure Search get-orders named orders-search in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" + +### Measuring Azure Service Bus dependencies + +We allow you to measure Azure Service Bus dependencies for both queues & topics. + +Here is how you can report an Azure Service Bus Queue dependency: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogServiceBusQueueDependency(queueName: "ordersqueue", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure Service Bus Queue named ordersqueue in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +Note that we have an `LogServiceBusTopicDependency` to log dependency logs for an Azure Service Bus Topic and an `LogServiceBusDependency` to log Azure Service Bus logs where the entity type is not known. + +### Measuring Azure Table Storage Dependencies + +We allow you to measure Azure Table Storage dependencies. + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +logger.LogTableStorageDependency(accountName: "orderAccount", tableName: "orders", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure table orders named orderAccount in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring HTTP dependencies + +Here is how you can report a HTTP dependency: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Create request +var request = new HttpRequestMessage(HttpMethod.Post, "http://requestbin.net/r/ujxglouj") +{ + Content = new StringContent("{\"message\":\"Hello World!\"") +}; + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; +// Send request to dependant service +var response = await httpClient.SendAsync(request); + +logger.LogHttpDependency(request, statusCode: response.StatusCode, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "HTTP Dependency requestbin.net for POST /r/ujxglouj completed with 200 in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + +### Measuring SQL dependencies + +Here is how you can report a SQL dependency: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +var startTime = DateTimeOffset.UtcNow; +durationMeasurement.Start(); + +// Interact with database +var products = await _repository.GetProducts(); + +logger.LogSqlDependency("sample-server", "sample-database", "my-table", "get-products", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "SQL Dependency sample-server for sample-database/my-table for operation get-products in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" +``` + +Or alternatively, when one already got the SQL connection string, you can use the overload that takes this directly: + +**Installation** + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.Sql +``` + +**Example** + +```csharp +string connectionString = "Server=sample-server;Database=sample-database;User=admin;Password=123"; +var durationMeasurement = new Stopwatch(); + +// Start measuring +var startTime = DateTimeOffset.UtcNow; +durationMeasurement.Start(); + +// Interact with database +var products = await _repository.GetProducts(); + +logger.LogSqlDependency(connectionString, "my-table", "get-products", isSuccessful: true, measurement: measurement); +// Output: "SQL Dependency sample-server for sample-database/my-table for operation get-products in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" +``` + +### Measuring custom dependencies + +Here is how you can areport a custom depenency: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +var startTime = DateTimeOffset.UtcNow; +durationMeasurement.Start(); + +string dependencyName = "SendGrid"; +object dependencyData = "http://my.sendgrid.uri/" + +logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency SendGrid http://my.sendgrid.uri/ in 00:00:01.2396312 at 03/23/2020 09:32:02 +00:00 (Successful: True - Context: )" +``` + +### Making it easier to measure dependencies + +Measuring dependencies means you need to keep track of how long the action took and when it started. + +Here's a small example: + +```csharp +var durationMeasurement = new Stopwatch(); +var startTime = DateTimeOffset.UtcNow; +durationMeasurement.Start(); + +// Do action + +/// Track dependency +string dependencyName = "SendGrid"; +object dependencyData = "https://my.sendgrid.uri/"; +logger.LogDependency("SendGrid", dependencyData, isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed, context: telemetryContext); +``` + +However, by using `DependencyMeasurement.Start()` we take care of the measuring aspect: + +```csharp +// Start measuring +using (var measurement = DependencyMeasurement.Start()) +{ + // Do Action + + // Track dependency + string dependencyName = "SendGrid"; + object dependencyData = "https://my.sendgrid.uri/"; + logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); +} +``` + +Failures during the interaction with the tracked dependency can be controlled by passing `isSuccessful`: + +```csharp +string dependencyName = "SendGrid"; +object dependencyData = "https://my.sendgrid.uri"; + +try +{ + // Interact with SendGrid... + // Done! + + logger.LogDependency(dependencyName, dependencyData, isSuccessful: true, startTime: measurement, context: telemetryContext); +} +catch (Exception exception) +{ + logger.LogError(exception, "Failed to interact with SendGrid"); + logger.LogDependency(dependencyName, dependencyData, isSuccessful: false, startTime: measurement, context: telemetryContext); +} +``` + +## Events + +Events allow you to report custom events which are a great way to track business-related events. + +Here is how you can report an `Order Created` event: + +```csharp +logger.LogEvent("Order Created"); +// Output: "Events Order Created (Context: )" +``` + +### Security Events + +Some events are considered "security events" when they relate to possible malicious activity, authentication, input validation... + +Here is how an invalid `Order` can be reported: + +```csharp +loger.LogSecurityEvent("Invalid Order"); +// Output: "Events Invalid Order (Context: )" +``` + +## Metrics + +Metrics allow you to report custom metrics which allow you to give insights on application-specific metrics. + +Here is how you can report an `Invoice Received` metric: + +```csharp +logger.LogMetric("Invoice Received", 133.37, telemetryContext); +// Output: "Metric Invoice Received: 133.37 (Context: )" +``` + +## Requests + +Requests allow you to keep track of the HTTP requests that are performed against your API and what the response was that was sent out. + +**Installation** + +If you want to track the `HttpRequest` and `HttpResponse` of an ASP.NET Core project, you'll have to install an additional package to include these ASP.NET Core dependencies: + +```shell +PM > Install-Package Arcus.Observability.Telemetry.AspNetCore +``` + +**Example** + +Here is how you can keep track of requests: + +```csharp +// Determine calling tenant +string tenantName = "Unknown"; +if (httpContext.Request?.Headers?.ContainsKey("X-Tenant") == true) +{ + tenantName = httpContext.Request.Headers["X-Tenant"]; +} + +var stopWatch = Stopwatch.StartNew(); + +// Perform action that creates a response, in this case call next middleware in the chain. +await _next(httpContext); + +logger.LogRequest(httpContext.Request, httpContext.Response, stopWatch.Elapsed); +// Output: "HTTP Request GET http://localhost:5000//weatherforecast completed with 200 in 00:00:00.0191554 at 03/23/2020 10:12:55 +00:00 - (Context: )" +``` + +[← back](/) diff --git a/docs/v2.0/guidance/use-with-dotnet-and-functions.md b/docs/v2.0/guidance/use-with-dotnet-and-functions.md new file mode 100644 index 00000000..a5b4b610 --- /dev/null +++ b/docs/v2.0/guidance/use-with-dotnet-and-functions.md @@ -0,0 +1,109 @@ +--- +title: "Using Arcus & Serilog in .NET Core and/or Azure Functions" +layout: default +--- + +# Using Arcus & Serilog in .NET Core and/or Azure Functions + +When using Arcus Observability & Serilog it is mandatory that your application is configured correctly to ensure everything works smoothly and all features are working fine. + +We encourage you to follow the standard [Serilog instructions](https://github.com/serilog/serilog-aspnetcore#instructions) on setting your application up. + +Some aspects we would like to highlight are: + +- Make sure to call [`UseSerilog`](https://www.nuget.org/packages/Serilog.AspNetCore) when creating a `IHostBuilder` +- Remove default for logging including its configuration in `appsettings.json` *(if applicable)* + +If you cannot use `UseSerilog`, you can still configure it by using `AddSerilog` as a logging provider; but we recommend removing all other providers with `loggingBuilder.ClearProviders()` so that they don't interfer. + +## Setting up Serilog with Azure Functions + +Using Serilog with Azure Functions requires some guidance and we've made it a bit easier to use. + +Before we get started, install our NuGet package for Azure Functions: + +``` +PM > Install-Package -Name Arcus.Observability.Telemetry.AzureFunctions +``` + +Once that is done, you can configure Serilog during startup as following: + +```csharp +using Microsoft.Extensions.Logging; + +[assembly: FunctionsStartup(typeof(Startup))] +namespace Arcus.Samples.AzureFunction +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + var serviceProvider = builder.Services.BuildServiceProvider(); + var config = serviceProvider.GetRequiredService(); + var instrumentationKey = config.GetValue("APPINSIGHTS_INSTRUMENTATIONKEY"); + + var logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .Enrich.WithComponentName("Docker Hub Metrics Scraper") + .Enrich.WithVersion() + .WriteTo.Console() + .WriteTo.AzureApplicationInsights(instrumentationKey) + .CreateLogger(); + + builder.Services.AddLogging(loggingBuilder => + { + loggingBuilder.ClearProvidersExceptFunctionProviders(); + loggingBuilder.AddSerilog(logger); + }); + } + } +} +``` + +> :bulb: Not that we are using `ClearProvidersExceptFunctionProviders` instead of `ClearProviders` given Azure Functions requires some logging providers to be available. + +Here is an example of how you can use ILogger to write multi-dimensional metrics with Arcus. If Serilog would not be setup correctly (see above), it would only report the metric without the dimensions. + +```csharp +public class DockerHubMetricScraperFunction +{ + private readonly DockerHubClient _dockerHubClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public DockerHubMetricScraperFunction(DockerHubClient dockerHubClient, IConfiguration configuration, ILogger logger) + { + Guard.NotNull(dockerHubClient, nameof(dockerHubClient)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(logger, nameof(logger)); + + _dockerHubClient = dockerHubClient; + _configuration = configuration; + _logger = logger; + } + + [FunctionName("docker-hub-metric-scraper")] + public async Task Run([TimerTrigger("0 */15 * * * *")] TimerInfo timer) + { + _logger.LogInformation($"Starting to scrape Docker Hub metrics at {DateTime.UtcNow}"); + + var repoName = _configuration["DOCKER_HUB_REPO_NAME"]; + var imageName = _configuration["DOCKER_HUB_IMAGE_NAME"]; + + var pullCount = await _dockerHubClient.GetImageMetricsAsync(repoName, imageName); + + var contextualInformation = new Dictionary + { + {"Repo Name", repoName}, + {"Image Name", imageName}, + {"Image ID", $"{repoName}/{imageName}"} + }; + + _logger.LogMetric("Image Pulls", pullCount, contextualInformation); + } +} +``` + +[← back](/) \ No newline at end of file diff --git a/docs/v2.0/index.md b/docs/v2.0/index.md new file mode 100644 index 00000000..451b26b1 --- /dev/null +++ b/docs/v2.0/index.md @@ -0,0 +1,45 @@ +--- +title: "Home" +layout: default +--- + +[![NuGet Badge](https://buildstats.info/nuget/Arcus.Observability.Correlation?packageVersion=2.0.0)](https://www.nuget.org/packages/Arcus.Observability.Correlation/2.0.0) + +# Installation + +The Arcus.Observability.Correlation can be installed via NuGet: + +```shell +PM > Install-Package Arcus.Observability.Correlation --Version 2.0.0 +``` + +For more granular packages we recommend reading the documentation. + +# Features + +- [Making telemetry more powerful](/features/making-telemetry-more-powerful) by making it simple to provide contextual information +- [Writing different telemetry types](/features/writing-different-telemetry-types) - Go beyond logs with our `ILogger` extensions for Dependencies, Events, Requests & Metrics. +- [Correlation](/features/correlation) - A common set of correlation levels. +- Telemetry + - [Enrichment](/features/telemetry-enrichment) - A set of enrichers to improve telemetry information. + - [Filters](/features/telemetry-filter) - A set of filters to control telemetry flow with. +- Sinks + - [Azure Application Insights](/features/sinks/azure-application-insights) - Flow Traces, Dependencies, Events, Requests & Metrics information to Azure Application Insights + +# Guidance + +- [Using Arcus & Serilog in .NET Core and/or Azure Functions](/guidance/use-with-dotnet-and-functions.md) + +# License +This is licensed under The MIT License (MIT). Which means that you can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the web application. But you always need to state that Codit is the original author of this web application. + +*[Full license here](https://github.com/arcus-azure/arcus.observability/blob/master/LICENSE)* + +# Older Versions + +- [v1.0](../v1.0) +- [v0.4](../v0.4) +- [v0.3](../v0.3) +- [v0.2.0](../v0.2.0) +- [v0.1.1](../v0.1.1) +- [v0.1.0](../v0.1.0)