Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 137 additions & 4 deletions sdk/@launchdarkly/observability-dotnet/AspSampleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using LaunchDarkly.Observability;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Server;
Expand Down Expand Up @@ -35,6 +36,86 @@
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/recordexception", () =>
{
Observe.RecordException(new InvalidOperationException("this is a recorded exception"),
new Dictionary<string, object>
{
{ "key", "value" },
});
return "";
})
.WithName("GetRecordException")
.WithOpenApi();

app.MapGet("/recordmetrics", () =>
{
var random = new Random();

// Record a gauge metric (CPU usage percentage)
var cpuUsage = Math.Round(random.NextDouble() * 100, 2);
Observe.RecordMetric("cpu_usage_percent", cpuUsage, new Dictionary<string, object>
{
{ "environment", "development" },
{ "service", "asp-sample" }
});

// Record a counter with random value (requests processed)
var requestsProcessed = random.Next(1, 100);
Observe.RecordCount("requests_processed", requestsProcessed, new Dictionary<string, object>
{
{ "operation", "test" },
{ "status", "success" }
});

// Record an increment (counter with value 1)
Observe.RecordIncr("endpoint_hits", new Dictionary<string, object>
{
{ "endpoint", "/recordmetrics" },
{ "method", "GET" }
});

// Record a histogram value (request duration in seconds)
var requestDuration = Math.Round(random.NextDouble() * 2.0, 3); // 0-2 seconds
Observe.RecordHistogram("request_duration_seconds", requestDuration, new Dictionary<string, object>
{
{ "handler", "recordmetrics" },
{ "response_code", "200" }
});

// Record an up-down counter (active connections - positive)
var connectionDelta = random.Next(1, 10);
Observe.RecordUpDownCounter("active_connections", connectionDelta, new Dictionary<string, object>
{
{ "connection_type", "http" },
{ "region", "us-east-1" }
});

// Record another up-down counter with negative delta (queue items processed)
var queueDelta = -random.Next(1, 5);
Observe.RecordUpDownCounter("queue_size", queueDelta, new Dictionary<string, object>
{
{ "queue_name", "processing" },
{ "priority", "high" }
});

return new
{
message = "Metrics recorded successfully",
metrics = new
{
gauge = $"cpu_usage_percent: {cpuUsage}%",
counter = $"requests_processed: +{requestsProcessed}",
increment = "endpoint_hits: +1",
histogram = $"request_duration_seconds: {requestDuration}s",
upDownCounter1 = $"active_connections: +{connectionDelta}",
upDownCounter2 = $"queue_size: {queueDelta}"
}
};
})
.WithName("GetRecordMetrics")
.WithOpenApi();

app.MapGet("/weatherforecast", () =>
{
var isMercury =
Expand All @@ -52,10 +133,62 @@
.WithName("GetWeatherForecast")
.WithOpenApi();

app.MapGet("/crash", () =>
{
throw new NotImplementedException();
}).WithName("Crash").WithOpenApi();
app.MapGet("/recordlog", () =>
{
var random = new Random();
var logMessages = new[]
{
"User authentication successful",
"Database connection established",
"Cache miss occurred, falling back to database",
"API rate limit approaching threshold",
"Background job completed successfully"
};

var severityLevels = new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error, LogLevel.Debug };

var message = logMessages[random.Next(logMessages.Length)];
var severity = severityLevels[random.Next(severityLevels.Length)];

Observe.RecordLog(message, severity, new Dictionary<string, object>
{
{ "component", "asp-sample-app" },
{ "endpoint", "/recordlog" },
{ "timestamp", DateTime.UtcNow.ToString("O") },
{ "user_id", Guid.NewGuid().ToString() }
});

return new
{
message = "Log recorded successfully",
log_entry = new
{
message,
severity = severity.ToString(),
component = "asp-sample-app"
}
};
})
.WithName("GetRecordLog")
.WithOpenApi();

app.MapGet("/manualinstrumentation", () =>
{
using (Observe.StartActivity("manual-instrumentation", ActivityKind.Internal,
new Dictionary<string, object> { { "test", "attribute" } }))
{
var enableMetrics = client.BoolVariation("enableMetrics",
Context.New(ContextKind.Of("request"), Guid.NewGuid().ToString()));

if (!enableMetrics) return "Manual instrumentation completed";
Observe.RecordIncr("manual_instrumentation_calls");
return "Manual instrumentation completed with metrics enabled";
}
})
.WithName("GetManualInstrumentation")
.WithOpenApi();

app.MapGet("/crash", () => { throw new NotImplementedException(); }).WithName("Crash").WithOpenApi();

app.Run();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace LaunchDarkly.Observability
{
internal static class DefaultNames
{
public const string MeterName = "launchdarkly-plugin-default-metrics";
public const string ActivitySourceName = "launchdarkly-plugin-default-activity";
public const string DefaultLoggerName = "launchdarkly-plugin-default-logger";
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using LaunchDarkly.Observability.Otel;
using LaunchDarkly.Logging;
using LaunchDarkly.Observability.Logging;
using LaunchDarkly.Observability.Otel;
using LaunchDarkly.Observability.Sampling;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
Expand All @@ -21,8 +23,6 @@ namespace LaunchDarkly.Observability
/// </summary>
public static class ObservabilityExtensions
{
// Used for metrics when a service name is not specified.
private const string DefaultMetricsName = "launchdarkly-plugin-default-metrics";
private const OtlpExportProtocol ExportProtocol = OtlpExportProtocol.HttpProtobuf;
private const int FlushIntervalMs = 5 * 1000;
private const int MaxExportBatchSize = 10000;
Expand All @@ -33,6 +33,27 @@ public static class ObservabilityExtensions
private const string LogsPath = "/v1/logs";
private const string MetricsPath = "/v1/metrics";


private class LdObservabilityHostedService : IHostedService
{
private readonly ObservabilityConfig _config;
private readonly ILoggerProvider _loggerProvider;

public LdObservabilityHostedService(ObservabilityConfig config, IServiceProvider provider)
{
_loggerProvider = provider.GetService<ILoggerProvider>();
_config = config;
}

public Task StartAsync(CancellationToken cancellationToken)
{
Observe.Initialize(_config, _loggerProvider);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

private static async Task GetSamplingConfigAsync(CustomSampler sampler, ObservabilityConfig config)
{
using (var samplingClient = new SamplingConfigClient(config.BackendUrl))
Expand All @@ -56,10 +77,11 @@ private static IEnumerable<KeyValuePair<string, object>> GetResourceAttributes(O

if (!string.IsNullOrWhiteSpace(config.Environment))
{
attrs.Add(new KeyValuePair<string, object>("deployment.environment.name", config.Environment));
attrs.Add(
new KeyValuePair<string, object>(AttributeNames.DeploymentEnvironment, config.Environment));
}

attrs.Add(new KeyValuePair<string, object>("highlight.project_id", config.SdkKey));
attrs.Add(new KeyValuePair<string, object>(AttributeNames.ProjectId, config.SdkKey));

return attrs;
}
Expand Down Expand Up @@ -116,7 +138,7 @@ internal static void AddLaunchDarklyObservabilityWithConfig(this IServiceCollect
}).WithMetrics(metrics =>
{
metrics.SetResourceBuilder(resourceBuilder)
.AddMeter(config.ServiceName ?? DefaultMetricsName)
.AddMeter(config.ServiceName ?? DefaultNames.MeterName)
.AddRuntimeInstrumentation()
.AddProcessInstrumentation()
.AddHttpClientInstrumentation()
Expand All @@ -128,6 +150,11 @@ internal static void AddLaunchDarklyObservabilityWithConfig(this IServiceCollect
Protocol = ExportProtocol
})));
});

// Attach a hosted service which will allow us to get a logger provider instance from the built
// service collection.
services.AddHostedService((serviceProvider) =>
new LdObservabilityHostedService(config, serviceProvider));
}

/// <summary>
Expand Down
Loading
Loading