Skip to content

Commit

Permalink
feat(janitor): add otlp logger for janitor (#1686)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue(s)

- #1616 

## Verification

- [ ] **Your** code builds clean without any errors or warnings
- [ ] Manual testing done (required)
- [ ] Relevant automated test added (if you find this hard, leave it and
we'll help out)

## Documentation

- [ ] Documentation is updated (either in `docs`-directory, Altinnpedia
or a separate linked PR in
[altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if
applicable)
  • Loading branch information
arealmaas authored Jan 15, 2025
1 parent 687e35e commit 2e1656b
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using Azure.Monitor.OpenTelemetry.Exporter;
using Npgsql;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Diagnostics;
using System.Globalization;
using Serilog;
using Serilog.Configuration;
using Serilog.Sinks.OpenTelemetry;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Digdir.Domain.Dialogporten.Janitor.Common.Extensions;

internal static class OpenTelemetryExtensions
{
private const string OtelExporterOtlpEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT";
private const string OtelExporterOtlpProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL";

public static IServiceCollection AddDialogportenTelemetry(
this IServiceCollection services,
IConfiguration configuration,
IHostEnvironment environment)
{
if (!Uri.IsWellFormedUriString(configuration[OtelExporterOtlpEndpoint], UriKind.Absolute))
return services;

var otlpProtocol = configuration[OtelExporterOtlpProtocol]?.ToLowerInvariant() switch
{
"grpc" => OtlpExportProtocol.Grpc,
"http/protobuf" => OtlpExportProtocol.HttpProtobuf,
_ => throw new ArgumentException($"Unsupported protocol: {configuration[OtelExporterOtlpProtocol]}")
};

var endpoint = new Uri(configuration[OtelExporterOtlpEndpoint]!);

return services.AddOpenTelemetry()
.ConfigureResource(resource =>
{
resource.AddService(
serviceName: configuration["OTEL_SERVICE_NAME"] ?? environment.ApplicationName);
})
.WithTracing(tracing =>
{
if (environment.IsDevelopment())
{
tracing.SetSampler(new AlwaysOnSampler());
}

tracing
.AddHttpClientInstrumentation(o =>
{
o.RecordException = true;
o.FilterHttpRequestMessage = _ =>
{
var parentActivity = Activity.Current?.Parent;
if (parentActivity != null && parentActivity.Source.Name.Equals("Azure.Core.Http", StringComparison.Ordinal))
{
return false;
}
return true;
};
})
.AddEntityFrameworkCoreInstrumentation()
.AddNpgsql()
.AddFusionCacheInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri(endpoint, "/v1/traces");
options.Protocol = otlpProtocol;
});
})
.WithMetrics(metrics =>
{
metrics.AddRuntimeInstrumentation()
.AddHttpClientInstrumentation();

var appInsightsConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
if (!string.IsNullOrEmpty(appInsightsConnectionString))
{
metrics.AddAzureMonitorMetricExporter(options =>
{
options.ConnectionString = appInsightsConnectionString;
});
}
else
{
metrics.AddOtlpExporter(options =>
{
options.Endpoint = new Uri(endpoint, "/v1/metrics");
options.Protocol = otlpProtocol;
});
}
})
.Services;
}

public static LoggerConfiguration OpenTelemetryOrConsole(this LoggerSinkConfiguration writeTo, HostBuilderContext context)
{
var otelEndpoint = context.Configuration[OtelExporterOtlpEndpoint];
var otelProtocol = context.Configuration[OtelExporterOtlpProtocol];

return otelEndpoint switch
{
null =>
writeTo.Console(formatProvider: CultureInfo.InvariantCulture),
not null when Uri.IsWellFormedUriString(otelEndpoint, UriKind.Absolute) =>
writeTo.OpenTelemetry(ConfigureOtlpSink(otelEndpoint, ParseOtlpProtocol(otelProtocol))),
_ => throw new InvalidOperationException($"Invalid otel endpoint: {otelEndpoint}")
};
}

public static LoggerConfiguration TryWriteToOpenTelemetry(this LoggerConfiguration config)
{
var otelEndpoint = Environment.GetEnvironmentVariable(OtelExporterOtlpEndpoint);
var otelProtocol = Environment.GetEnvironmentVariable(OtelExporterOtlpProtocol);

if (otelEndpoint is null || !Uri.IsWellFormedUriString(otelEndpoint, UriKind.Absolute))
{
return config;
}

try
{
var protocol = ParseOtlpProtocol(otelProtocol);
return config.WriteTo.OpenTelemetry(ConfigureOtlpSink(otelEndpoint, protocol));
}
catch (ArgumentException)
{
return config;
}
}

private static OtlpProtocol ParseOtlpProtocol(string? protocol)
{
return protocol?.ToLowerInvariant() switch
{
"grpc" => OtlpProtocol.Grpc,
"http/protobuf" => OtlpProtocol.HttpProtobuf,
_ => throw new ArgumentException($"Unsupported OTLP protocol: {protocol}")
};
}

private static Action<OpenTelemetrySinkOptions> ConfigureOtlpSink(string endpoint, OtlpProtocol protocol) =>
options =>
{
options.Endpoint = endpoint;
options.Protocol = protocol;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
<PackageReference Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.1.1" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.2.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.10.0-beta.1" />
<PackageReference Include="OpenTelemetry" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.10.0" />
<PackageReference Include="Npgsql.OpenTelemetry" Version="9.0.2" />
<PackageReference Include="ZiggyCreatures.FusionCache.OpenTelemetry" Version="1.4.1" />
</ItemGroup>

<ItemGroup>
Expand Down
13 changes: 5 additions & 8 deletions src/Digdir.Domain.Dialogporten.Janitor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
using Digdir.Domain.Dialogporten.Infrastructure;
using Digdir.Domain.Dialogporten.Janitor;
using Digdir.Domain.Dialogporten.Janitor.Common.Extensions;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -14,12 +15,10 @@
// Using two-stage initialization to catch startup errors.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Warning()
.Enrich.FromLogContext()
.Enrich.WithEnvironmentName()
.Enrich.FromLogContext()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.WriteTo.ApplicationInsights(
TelemetryConfiguration.CreateDefault(),
TelemetryConverter.Traces)
.TryWriteToOpenTelemetry()
.CreateBootstrapLogger();

try
Expand Down Expand Up @@ -53,12 +52,10 @@ static void BuildAndRun(string[] args)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithEnvironmentName()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.WriteTo.ApplicationInsights(
services.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces));
.WriteTo.OpenTelemetryOrConsole(context));

builder.Services
.AddDialogportenTelemetry(builder.Configuration, builder.Environment)
.AddApplication(builder.Configuration, builder.Environment)
.AddInfrastructure(builder.Configuration, builder.Environment)
.WithoutPubSubCapabilities()
Expand Down

0 comments on commit 2e1656b

Please sign in to comment.