Commit: e88cfdd
Parent: e6960c8

Add tracing

Mårten Åsberg committed on 2026-06-04 at 09:45
BfiMonitor.csproj +6 -0
diff --git a/BfiMonitor.csproj b/BfiMonitor.csproj
index 9536a52..241c8b5 100644
@@ -31,6 +31,12 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="11.0.0-preview.4.26230.115" />
<PackageReference Include="Microsoft.Playwright" Version="1.60.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="11.0.0-preview.4.26230.115" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Quartz" Version="1.15.1-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.18.1" />
</ItemGroup>
</Project>
BfiScreeningCheckerJob.cs +18 -0
diff --git a/BfiScreeningCheckerJob.cs b/BfiScreeningCheckerJob.cs
index 5b982ff..0a69ffd 100644
@@ -1,3 +1,5 @@
using System.Diagnostics;
using BfiMonitor;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Playwright;
@@ -14,6 +16,8 @@ internal sealed class BfiScreeningCheckerJob(
{
public async Task Execute(IJobExecutionContext context)
{
using var activity = Tracing.StartCheckerJob();
var latestHtml = await repository.LatestHtml(context.CancellationToken);
logger.LogLatestHtml(latestHtml);
@@ -27,6 +31,8 @@ internal sealed class BfiScreeningCheckerJob(
if (resultsElement is null)
{
logger.LogNoResultsElementFound(opts.Selector);
activity?.SetTag("EndReason", "NoResultsElementFound");
activity?.SetStatus(ActivityStatusCode.Ok);
return;
}
var currentHtml = await resultsElement.InnerHTMLAsync();
@@ -35,6 +41,8 @@ internal sealed class BfiScreeningCheckerJob(
if (currentHtml == latestHtml)
{
logger.LogHtmlNotUpdated();
activity?.SetTag("EndReason", "HtmlNotUpdated");
activity?.SetStatus(ActivityStatusCode.Ok);
return;
}
logger.LogHtmlUpdated();
@@ -51,6 +59,16 @@ internal sealed class BfiScreeningCheckerJob(
await context.Scheduler.ScheduleJob(trigger, context.CancellationToken);
logger.LogSmsScheduled(phoneNumber);
}
activity?.SetTag("EndReason", "RanToCompletion");
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.SetTag("EndReason", "Exception");
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
throw;
}
finally
{
OpenTelemetryExtensions.cs +53 -0
diff --git a/OpenTelemetryExtensions.cs b/OpenTelemetryExtensions.cs
new file mode 100644
index 0000000..8847176
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace BfiMonitor;
public static class OpenTelemetryExtensions
{
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder
.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddHttpClientInstrumentation().AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing
.AddSource(builder.Environment.ApplicationName)
.AddHttpClientInstrumentation()
.AddQuartzInstrumentation()
.AddSource(Tracing.ActivitySource.Name);
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
return builder;
}
}
Program.cs +3 -0
diff --git a/Program.cs b/Program.cs
index 3afaa34..851b504 100644
@@ -1,3 +1,4 @@
using BfiMonitor;
using HuaweiWifiSms.Grpc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -7,6 +8,8 @@ using Quartz;
var builder = Host.CreateApplicationBuilder(args);
builder.ConfigureOpenTelemetry();
builder.Services.AddOptions<MonitorOptions>().BindConfiguration("Monitor");
builder.Services.AddGrpcClient<SmsSender.SmsSenderClient>(
ScreeningRepository.cs +11 -4
diff --git a/ScreeningRepository.cs b/ScreeningRepository.cs
index 7a95791..68dd1e1 100644
@@ -1,3 +1,4 @@
using BfiMonitor;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
@@ -13,29 +14,35 @@ internal sealed class ScreeningRepository
private void Initialize()
{
const string sql =
"CREATE TABLE IF NOT EXISTS DetectedHtml (Id INTEGER PRIMARY KEY AUTOINCREMENT, Html TEXT NOT NULL, DetectedAt TEXT NOT NULL)";
using var activity = Tracing.StartInitializeScreeningsDatabase(sql);
using var connection = new SqliteConnection(connectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText =
"CREATE TABLE IF NOT EXISTS DetectedHtml (Id INTEGER PRIMARY KEY AUTOINCREMENT, Html TEXT NOT NULL, DetectedAt TEXT NOT NULL)";
command.CommandText = sql;
command.ExecuteNonQuery();
}
public async Task<string?> LatestHtml(CancellationToken cancellationToken)
{
const string sql = "SELECT Html FROM DetectedHtml ORDER BY DetectedAt DESC LIMIT 1";
using var activity = Tracing.StartLatestHtml(sql);
await using var connection = new SqliteConnection(connectionString);
await connection.OpenAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = "SELECT Html FROM DetectedHtml ORDER BY DetectedAt DESC LIMIT 1";
command.CommandText = sql;
return await command.ExecuteScalarAsync(cancellationToken) as string;
}
public async Task InsertNewDetection(string html, DateTimeOffset detectedAt)
{
const string sql = "INSERT INTO DetectedHtml (Html, DetectedAt) VALUES ($html, $at)";
using var activity = Tracing.StartInsertNewDetection(sql);
await using var connection = new SqliteConnection(connectionString);
await connection.OpenAsync();
using var command = connection.CreateCommand();
command.CommandText = "INSERT INTO DetectedHtml (Html, DetectedAt) VALUES ($html, $at)";
command.CommandText = sql;
command.Parameters.AddWithValue("$html", html);
command.Parameters.AddWithValue("$at", detectedAt.ToString("O"));
await command.ExecuteNonQueryAsync();
SendSmsJob.cs +23 -8
diff --git a/SendSmsJob.cs b/SendSmsJob.cs
index 9a899b9..235186c 100644
@@ -1,3 +1,5 @@
using System.Diagnostics;
using BfiMonitor;
using HuaweiWifiSms.Grpc;
using Microsoft.Extensions.Logging;
using Quartz;
@@ -11,18 +13,31 @@ internal sealed class SendSmsJob(SmsSender.SmsSenderClient smsSender, ILogger<Se
var phoneNumber = context.MergedJobDataMap.GetString("phoneNumber")!;
var message = context.MergedJobDataMap.GetString("message")!;
var response = await smsSender.SendSmsAsync(
new SmsRequest { RecipientPhoneNumber = phoneNumber, Content = message },
cancellationToken: context.CancellationToken
);
using var activity = Tracing.StartSendSms(phoneNumber);
if (response.Status is SmsStatus.Success)
try
{
logger.LogSmsSent(phoneNumber);
var response = await smsSender.SendSmsAsync(
new SmsRequest { RecipientPhoneNumber = phoneNumber, Content = message },
cancellationToken: context.CancellationToken
);
if (response.Status is SmsStatus.Success)
{
logger.LogSmsSent(phoneNumber);
activity?.SetStatus(ActivityStatusCode.Ok);
}
else
{
logger.LogSmsFailed(phoneNumber, response.Status);
activity?.SetStatus(ActivityStatusCode.Error, $"Failure response {response.Status}");
}
}
else
catch (Exception ex)
{
logger.LogSmsFailed(phoneNumber, response.Status);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
throw;
}
}
}
Tracing.cs +22 -0
diff --git a/Tracing.cs b/Tracing.cs
new file mode 100644
index 0000000..987bd76
@@ -0,0 +1,22 @@
using System.Diagnostics;
namespace BfiMonitor;
internal static class Tracing
{
internal static ActivitySource ActivitySource { get; } = new("BfiMonitor");
public static Activity? StartInitializeScreeningsDatabase(string sql) =>
ActivitySource.StartActivity("InitializeScreeningsDatabase", ActivityKind.Client)?.SetTag("Sql", sql);
public static Activity? StartLatestHtml(string sql) =>
ActivitySource.StartActivity("LatestHtml", ActivityKind.Client)?.SetTag("Sql", sql);
public static Activity? StartInsertNewDetection(string sql) =>
ActivitySource.StartActivity("InsertNewDetection", ActivityKind.Client)?.SetTag("Sql", sql);
public static Activity? StartSendSms(string phoneNumber) =>
ActivitySource.StartActivity("SendSms", ActivityKind.Internal)?.SetTag("PhoneNumber", phoneNumber);
public static Activity? StartCheckerJob() => ActivitySource.StartActivity("CheckerJob", ActivityKind.Internal);
}