Skip to content

Commit

Permalink
Merge pull request #1323 from qdraw/feature/202312_open_telemetry
Browse files Browse the repository at this point in the history
OpenTelemetry
  • Loading branch information
qdraw authored Jan 8, 2024
2 parents 75b8886 + d1bcf04 commit ad3246c
Show file tree
Hide file tree
Showing 34 changed files with 867 additions and 34 deletions.
98 changes: 98 additions & 0 deletions documentation/docs/developer-guide/logging/opentelemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Open Telemetry logging

- Scroll down to [setup](#setup) for the settings

## Why OpenTelemetry?

OpenTelemetry has emerged as a powerful and
standardized solution to address the challenges of observability in modern,
distributed systems.

OpenTelemetry is an open-source project that provides a set of APIs, libraries, agents,
and instrumentation to enable observability in cloud-native applications.

Observability, in the context of software systems,
refers to the ability to understand and measure how well a system is operating.
It involves collecting and analyzing data related to key aspects such as traces,
metrics, and logs. OpenTelemetry focuses on two primary pillars of observability:
tracing and metrics.

### Distributed Tracing:
OpenTelemetry allows developers to trace requests as they traverse
through various services in a distributed environment.
This helps identify performance bottlenecks, understand dependencies between services,
and diagnose issues across the entire application stack.
By providing a standardized way to instrument code and capture trace data,
OpenTelemetry simplifies the process of generating insights into the flow of requests in complex,
microservices-based architectures.

### Metrics Collection:
Monitoring the health and performance of applications involves the collection of metrics.
OpenTelemetry provides a consistent API for capturing metrics from different parts of an application.
Metrics such as latency, error rates, and resource utilization
can be collected and analyzed to gain a comprehensive view of application behavior.
This information is invaluable for proactive issue detection, capacity planning, and overall system optimization.

OpenTelemetry supports multiple programming languages and integrates seamlessly with various observability backends,
including popular solutions like Prometheus, Jaeger, and Grafana.

Its adaptability and community-driven development make it a versatile choice for organizations seeking
to implement observability in their applications, regardless of the technology stack they use.

In conclusion, OpenTelemetry plays a pivotal role in the modern software development landscape by providing
a standardized and extensible framework for observability.
Its ability to capture distributed traces and metrics empowers developers and operators to
gain deep insights into the performance and behavior of their applications,
facilitating efficient troubleshooting and continuous improvement.

## Setup
The following settings can be used:

### json configuration

use for example this file name: appsettings.machinename.json

### Order of appsettings patch files

1. You can use `appsettings.json` inside the application folder to set base settings.
The order of this files is used to get the values from the appsettings
- `/bin/Debug/net6.0/appsettings.patch.json`
- `/bin/Debug/net6.0/appsettings.default.json`
- `/bin/Debug/net6.0/appsettings.computername.patch.json`
- `/bin/Debug/net6.0/appsettings.json`
- `/bin/Debug/net6.0/appsettings.computername.json`

```json
{
"app" : {
"OpenTelemetry": {
"TracesEndpoint": "http://test",
"MetricsEndpoint": null,
"LogsEndpoint": null
}
}
}
```


### Environment variables

```bash
"app__OpenTelemetry__TracesEndpoint": "https://otlp.eu01.nr-data.net:4318/v1/traces",
"app__OpenTelemetry__MetricsEndpoint": "https://otlp.eu01.nr-data.net:4318/v1/metrics",
"app__OpenTelemetry__LogsEndpoint": "https://otlp.eu01.nr-data.net:4318/v1/logs",
"app__OpenTelemetry__Header": "api-key=EXAMPLE_KEY",
"app__OpenTelemetry__ServiceName": "starsky-dev",
```

### Use a valid url
The properties assume that the Url is valid. It should start with http or https

```
Unhandled exception. System.UriFormatException: Invalid URI: The format of the URI could not be determined.
at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind, UriCreationOptions& creationOptions)
at System.Uri..ctor(String uriString)
at starsky.foundation.webtelemetry.Extensions.OpenTelemetryExtension.<>c__DisplayClass0_0.
<AddOpenTelemetryMonitoring>b__4(OtlpExporterOptions o) in ..Extensions/OpenTelemetryExtension.cs:line 48
```

3 changes: 3 additions & 0 deletions documentation/docs/developer-guide/logging/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ sidebar_position: 20

# Logging

## Open Telemetry logging
See [Open Telemetry logging](opentelemetry.md)

## Desktop logs

For the desktop app the logs are stored in following location:
Expand Down
3 changes: 2 additions & 1 deletion history.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ Semantic Versioning 2.0.0 is from version 0.1.6+
## List of versions

## version 0.5.15 _(Unreleased)_ - 2024-01-? {#v0.5.15}
- no changes yet

- [x] (Added) _Back-end_ Add support for OpenTelemetry (server side only) (PR #1323)

## version 0.5.14 - 2023-12-29 {#v0.5.14}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,42 @@ public static List<string> Compare(AppSettings sourceIndexItem, object? updateOb

CompareMultipleSingleItems(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList);
CompareMultipleListDictionary(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList);
CompareMultipleObjects(propertyB, propertyInfoFromA, sourceIndexItem, updateObject, differenceList);

}
return differenceList;
}

private static void CompareMultipleObjects(PropertyInfo propertyB, PropertyInfo propertyInfoFromA, AppSettings sourceIndexItem, object updateObject, List<string> differenceList)
{
if (propertyInfoFromA.PropertyType == typeof(OpenTelemetrySettings) && propertyB.PropertyType == typeof(OpenTelemetrySettings))
{
var oldObjectValue = (OpenTelemetrySettings?)propertyInfoFromA.GetValue(sourceIndexItem, null);
var newObjectValue = (OpenTelemetrySettings?)propertyB.GetValue(updateObject, null);
CompareOpenTelemetrySettingsObject(propertyB.Name, sourceIndexItem, oldObjectValue, newObjectValue, differenceList);
}
}

private static void CompareOpenTelemetrySettingsObject(string propertyName, AppSettings? sourceIndexItem,
OpenTelemetrySettings? oldKeyValuePairStringStringValue,
OpenTelemetrySettings? newKeyValuePairStringStringValue, ICollection<string> differenceList)
{
if ( oldKeyValuePairStringStringValue == null ||
newKeyValuePairStringStringValue == null ||
// compare lists
JsonSerializer.Serialize(oldKeyValuePairStringStringValue) ==
JsonSerializer.Serialize(newKeyValuePairStringStringValue) ||
// default options
JsonSerializer.Serialize(newKeyValuePairStringStringValue) ==
JsonSerializer.Serialize(new OpenTelemetrySettings()))
{
return;
}

sourceIndexItem?.GetType().GetProperty(propertyName)?.SetValue(sourceIndexItem, newKeyValuePairStringStringValue, null);
differenceList.Add(propertyName.ToLowerInvariant());
}

private static void CompareMultipleSingleItems(PropertyInfo propertyB,
PropertyInfo propertyInfoFromA,
AppSettings sourceIndexItem, object updateObject,
Expand Down
37 changes: 37 additions & 0 deletions starsky/starsky.foundation.platform/Models/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,9 @@ private string AssemblyDirectoryReplacer(string value)
/// </summary>
public bool? ExiftoolSkipDownloadOnStartup { get; set; } = false;

public OpenTelemetrySettings OpenTelemetry { get; set; } =
new OpenTelemetrySettings();

/// <returns>AppSettings duplicated</returns>
/// <summary>
/// Duplicate this item in memory. AND remove _databaseConnection
Expand Down Expand Up @@ -927,9 +930,43 @@ public AppSettings CloneToDisplay()
}
}

ReplaceOpenTelemetryData(appSettings);

return appSettings;
}

private static void ReplaceOpenTelemetryData(AppSettings appSettings)
{
if ( appSettings.OpenTelemetry == null )
{
return;
}

if (!string.IsNullOrEmpty(appSettings.OpenTelemetry.Header) )
{
appSettings.OpenTelemetry.Header =
CloneToDisplaySecurityWarning;
}

if (!string.IsNullOrEmpty(appSettings.OpenTelemetry.MetricsHeader) )
{
appSettings.OpenTelemetry.MetricsHeader =
CloneToDisplaySecurityWarning;
}

if (!string.IsNullOrEmpty(appSettings.OpenTelemetry.LogsHeader) )
{
appSettings.OpenTelemetry.LogsHeader =
CloneToDisplaySecurityWarning;
}

if (!string.IsNullOrEmpty(appSettings.OpenTelemetry.TracesHeader) )
{
appSettings.OpenTelemetry.TracesHeader =
CloneToDisplaySecurityWarning;
}
}

private static void ReplaceAppSettingsPublishProfilesCloneToDisplay(AppSettingsPublishProfiles value)
{
if ( !string.IsNullOrEmpty(value.Path) && value.Path != AppSettingsPublishProfiles.GetDefaultPath() )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace starsky.foundation.platform.Models;

public class OpenTelemetrySettings
{
public string Header { get; set; }

public string ServiceName { get; set; }
public string TracesEndpoint { get; set; }
public string TracesHeader { get; set; }

public string MetricsEndpoint { get; set; }
public string MetricsHeader { get; set; }

public string LogsEndpoint { get; set; }
public string LogsHeader { get; set; }

public string GetServiceName()
{
return string.IsNullOrWhiteSpace(ServiceName)
? "Starsky"
: ServiceName ;
}

public string GetLogsHeader()
{
return string.IsNullOrWhiteSpace(LogsHeader)
? Header
: LogsHeader;
}

public string GetMetricsHeader()
{
return string.IsNullOrWhiteSpace(MetricsHeader)
? Header
: MetricsHeader;
}

public string GetTracesHeader()
{
return string.IsNullOrWhiteSpace(TracesHeader)
? Header
: TracesHeader;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using starsky.foundation.platform.Models;

[assembly: InternalsVisibleTo("starskytest")]
namespace starsky.foundation.webtelemetry.Extensions;

public static class OpenTelemetryExtension
{
/// <summary>
/// Add Metrics & Monitoring for OpenTelemetry
/// </summary>
/// <param name="services">collection service</param>
/// <param name="appSettings">to use for OpenTelemetry keys and info</param>
public static void AddOpenTelemetryMonitoring(
this IServiceCollection services, AppSettings appSettings)
{
var telemetryBuilder = services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceNamespace: appSettings.OpenTelemetry.GetServiceName(),
serviceName: appSettings.OpenTelemetry.GetServiceName(),
serviceVersion: Assembly.GetEntryAssembly()?.GetName().Version
?.ToString(),
serviceInstanceId: Environment.MachineName
).AddAttributes(new Dictionary<string, object>
{
{
"deployment.environment",
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty
}
}));

if ( !string.IsNullOrWhiteSpace(appSettings.OpenTelemetry.TracesEndpoint) )
{
telemetryBuilder.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation(o => o.Filter = FilterPath)
.AddOtlpExporter(
o =>
{
o.Endpoint =
new Uri(appSettings.OpenTelemetry.TracesEndpoint);
o.Protocol = OtlpExportProtocol.HttpProtobuf;
o.Headers = appSettings.OpenTelemetry.GetTracesHeader();
}
).SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(appSettings.OpenTelemetry.GetServiceName())
)
);
}

if ( string.IsNullOrWhiteSpace(
appSettings.OpenTelemetry.MetricsEndpoint) )
{
return;
}

telemetryBuilder.WithMetrics(metrics =>
metrics.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(
o =>
{
o.Endpoint = new Uri(appSettings.OpenTelemetry.MetricsEndpoint);
o.Protocol = OtlpExportProtocol.HttpProtobuf;
o.Headers = appSettings.OpenTelemetry.GetMetricsHeader();
})
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(appSettings.OpenTelemetry.GetServiceName())
)
);
}

internal static bool FilterPath(HttpContext context)
{
if ( (context.Request.Path.Value?.EndsWith("/realtime") == true ||
context.Request.Path.Value?.EndsWith("/api/health") == true ||
context.Request.Path.Value?.EndsWith("/api/health/details") == true ||
context.Request.Path.Value?.EndsWith("/api/open-telemetry/trace") == true)
&& context.Response.StatusCode == 200)
{
return false;
}

if ( context.Request.Path.Value?.EndsWith("/api/index") == true
&& context.Response.StatusCode == 401)
{
return false;
}

return true;
}
}
Loading

1 comment on commit ad3246c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.