Skip to content

Commit

Permalink
Add otel endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelldi committed Dec 18, 2023
1 parent 22deb72 commit b01ca5b
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/dotnet/aspire-session-host/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace AspireSessionHost;

internal class Connection : IDisposable
internal sealed class Connection : IDisposable
{
private readonly LifetimeDefinition _lifetimeDef = new();
private readonly Lifetime _lifetime;
Expand Down
11 changes: 11 additions & 0 deletions src/dotnet/aspire-session-host/Otel/OtelEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace AspireSessionHost.Otel;

internal static class OtelEndpoints
{
internal static void MapOtelEndpoints(this IEndpointRouteBuilder routes)
{
routes.MapGrpcService<OtelLogService>();
routes.MapGrpcService<OtelMetricService>();
routes.MapGrpcService<OtelTraceService>();
}
}
20 changes: 20 additions & 0 deletions src/dotnet/aspire-session-host/Otel/OtelLogService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Grpc.Core;
using OpenTelemetry.Proto.Collector.Logs.V1;

namespace AspireSessionHost.Otel;

internal sealed class OtelLogService : LogsService.LogsServiceBase
{
public override Task<ExportLogsServiceResponse> Export(
ExportLogsServiceRequest request,
ServerCallContext context)
{
return Task.FromResult(new ExportLogsServiceResponse
{
PartialSuccess = new ExportLogsPartialSuccess
{
RejectedLogRecords = 0
}
});
}
}
20 changes: 20 additions & 0 deletions src/dotnet/aspire-session-host/Otel/OtelMetricService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Grpc.Core;
using OpenTelemetry.Proto.Collector.Metrics.V1;

namespace AspireSessionHost.Otel;

internal sealed class OtelMetricService : MetricsService.MetricsServiceBase
{
public override Task<ExportMetricsServiceResponse> Export(
ExportMetricsServiceRequest request,
ServerCallContext context)
{
return Task.FromResult(new ExportMetricsServiceResponse
{
PartialSuccess = new ExportMetricsPartialSuccess
{
RejectedDataPoints = 0
}
});
}
}
20 changes: 20 additions & 0 deletions src/dotnet/aspire-session-host/Otel/OtelTraceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Grpc.Core;
using OpenTelemetry.Proto.Collector.Trace.V1;

namespace AspireSessionHost.Otel;

internal sealed class OtelTraceService : TraceService.TraceServiceBase
{
public override Task<ExportTraceServiceResponse> Export(
ExportTraceServiceRequest request,
ServerCallContext context)
{
return Task.FromResult(new ExportTraceServiceResponse
{
PartialSuccess = new ExportTracePartialSuccess
{
RejectedSpans = 0
}
});
}
}
35 changes: 31 additions & 4 deletions src/dotnet/aspire-session-host/Program.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
using System.Globalization;
using System.Text.Json;
using AspireSessionHost;
using AspireSessionHost.Otel;
using Microsoft.AspNetCore.Server.Kestrel.Core;

ParentProcessWatchdog.StartNewIfAvailable();

var port = Environment.GetEnvironmentVariable("RIDER_RD_PORT");
if (port == null) throw new ApplicationException("Unable to find RIDER_RD_PORT variable");
var aspNetCoreUrlValue = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
if (aspNetCoreUrlValue == null) throw new ApplicationException("Unable to find ASPNETCORE_URLS variable");
if (!Uri.TryCreate(aspNetCoreUrlValue, UriKind.Absolute, out var aspNetCoreUrl))
throw new ApplicationException("ASPNETCORE_URLS is not a valid URI");

var connection = new Connection(int.Parse(port, CultureInfo.InvariantCulture));
var rdPortValue = Environment.GetEnvironmentVariable("RIDER_RD_PORT");
if (rdPortValue == null) throw new ApplicationException("Unable to find RIDER_RD_PORT variable");
if (!int.TryParse(rdPortValue, CultureInfo.InvariantCulture, out var rdPort))
throw new ApplicationException("RIDER_RD_PORT is not a valid port");

var otelPortValue = Environment.GetEnvironmentVariable("RIDER_OTEL_PORT");
if (otelPortValue == null) throw new ApplicationException("Unable to find RIDER_OTEL_PORT variable");
if (!int.TryParse(otelPortValue, CultureInfo.InvariantCulture, out var otelPort))
throw new ApplicationException("RIDER_OTEL_PORT is not a valid port");

var connection = new Connection(rdPort);
var sessionEventService = new SessionEventService();
await sessionEventService.Subscribe(connection);

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();

builder.Services.AddSingleton(connection);
builder.Services.AddSingleton(sessionEventService);
builder.Services.AddSingleton<SessionService>();
Expand All @@ -22,10 +38,21 @@
it.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
});

builder.WebHost.ConfigureKestrel(it =>
{
it.ListenLocalhost(aspNetCoreUrl.Port);
it.ListenLocalhost(otelPort, options =>
{
options.Protocols = HttpProtocols.Http2;
options.UseHttps();
});
});

var app = builder.Build();

app.UseWebSockets();

app.MapSessionEndpoints();
app.MapOtelEndpoints();

app.Run();
app.Run();
2 changes: 1 addition & 1 deletion src/dotnet/aspire-session-host/SessionEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace AspireSessionHost;

internal class SessionEventService : IDisposable
internal sealed class SessionEventService : IDisposable
{
private readonly LifetimeDefinition _lifetimeDef = new();

Expand Down
2 changes: 1 addition & 1 deletion src/dotnet/aspire-session-host/SessionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace AspireSessionHost;

internal class SessionService(Connection connection)
internal sealed class SessionService(Connection connection)
{
private const string TelemetryServiceName = "OTEL_SERVICE_NAME";

Expand Down
9 changes: 7 additions & 2 deletions src/dotnet/aspire-session-host/aspire-session-host.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="JetBrains.RdFramework" Version="2023.3.3" />
<Protobuf Include="**/*.proto" GrpcServices="Server"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.59.0"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
<PackageReference Include="JetBrains.RdFramework" Version="2023.3.3"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.intellij.openapi.rd.util.lifetime
import com.intellij.openapi.rd.util.startOnUiAsync
import com.jetbrains.rider.debugger.DotNetProgramRunner
import com.jetbrains.rider.run.DotNetProcessRunProfileState
import com.jetbrains.rider.util.NetUtils
import me.rafaelldi.aspire.sessionHost.AspireSessionHostConfig
import me.rafaelldi.aspire.sessionHost.AspireSessionHostRunner
import org.jetbrains.concurrency.Promise
Expand All @@ -38,24 +39,26 @@ class AspireHostProgramRunner : DotNetProgramRunner() {
val dotnetProcessState = state as? DotNetProcessRunProfileState
?: throw CantRunException("Unable to execute RunProfileState: $state")
val token = dotnetProcessState.dotNetExecutable.environmentVariables[DEBUG_SESSION_TOKEN]
val port = dotnetProcessState.dotNetExecutable.environmentVariables[DEBUG_SESSION_PORT]
val aspNetPort = dotnetProcessState.dotNetExecutable.environmentVariables[DEBUG_SESSION_PORT]
?.substringAfter(':')
?.toInt()
if (token == null || port == null)
if (token == null || aspNetPort == null)
throw CantRunException("Unable to find token or port")
LOG.trace("Found token $token and port $port")
LOG.trace("Found token $token and port $aspNetPort")

val runProfileName = environment.runProfile.name
val isDebug = environment.executor.id == DefaultDebugExecutor.EXECUTOR_ID

val sessionHostLifetime = environment.project.lifetime.createNested()

val sessionHostRunner = AspireSessionHostRunner.getInstance()
val otelPort = NetUtils.findFreePort(77800)
val config = AspireSessionHostConfig(
token,
runProfileName,
isDebug,
port
aspNetPort,
otelPort
)
LOG.trace("Aspire session host config: $config")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ data class AspireSessionHostConfig(
val id: String,
val projectName: String,
val isDebug: Boolean,
val aspNetPort: Int
val aspNetPort: Int,
val otelPort: Int
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class AspireSessionHostRunner {
private val LOG = logger<AspireSessionHostRunner>()

private const val ASPNETCORE_URLS = "ASPNETCORE_URLS"
private const val RIDER_OTEL_PORT = "RIDER_OTEL_PORT"
private const val RIDER_PARENT_PROCESS_PID = "RIDER_PARENT_PROCESS_PID"
private const val RIDER_RD_PORT = "RIDER_RD_PORT"
}
Expand Down Expand Up @@ -72,6 +73,7 @@ class AspireSessionHostRunner {
.withEnvironment(
mapOf(
ASPNETCORE_URLS to "http://localhost:${hostConfig.aspNetPort}/",
RIDER_OTEL_PORT to hostConfig.otelPort.toString(),
RIDER_RD_PORT to "${protocol.wire.serverPort}",
RIDER_PARENT_PROCESS_PID to ProcessHandle.current().pid().toString()
)
Expand Down Expand Up @@ -153,7 +155,8 @@ class AspireSessionHostRunner {
sessionLifetime,
sessionEvents,
hostConfig.projectName,
hostConfig.isDebug
hostConfig.isDebug,
hostConfig.otelPort
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
private val LOG = logger<AspireSessionRunner>()

private const val ASPIRE_SUFFIX = "Aspire"
private const val OTEL_EXPORTER_OTLP_ENDPOINT = "OTEL_EXPORTER_OTLP_ENDPOINT"

Check warning on line 47 in src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionRunner.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "OTEL_EXPORTER_OTLP_ENDPOINT" is never used
}

private val commandChannel = Channel<RunSessionCommand>(Channel.UNLIMITED)
Expand All @@ -55,7 +56,8 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
val sessionLifetime: Lifetime,
val sessionEvents: Channel<AspireSessionEvent>,
val hostName: String,
val isHostDebug: Boolean
val isHostDebug: Boolean,
val otelPort: Int
)

init {
Expand All @@ -67,7 +69,8 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
it.sessionLifetime,
it.sessionEvents,
it.hostName,
it.isHostDebug
it.isHostDebug,
it.otelPort
)
}
}
Expand All @@ -84,7 +87,8 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
sessionLifetime: Lifetime,
sessionEvents: Channel<AspireSessionEvent>,
hostName: String,
isHostDebug: Boolean
isHostDebug: Boolean,
otelPort: Int
) {
LOG.info("Starting a session for the project ${sessionModel.projectPath}")

Expand All @@ -93,7 +97,7 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
return
}

val configuration = getOrCreateConfiguration(sessionModel, hostName)
val configuration = getOrCreateConfiguration(sessionModel, hostName, otelPort)
if (configuration == null) {
LOG.warn("Unable to find or create run configuration for the project ${sessionModel.projectPath}")
return
Expand Down Expand Up @@ -178,7 +182,11 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
}
}

private fun getOrCreateConfiguration(session: SessionModel, hostName: String): RunnerAndConfigurationSettings? {
private fun getOrCreateConfiguration(
session: SessionModel,
hostName: String,
otelPort: Int
): RunnerAndConfigurationSettings? {
val projects = project.solution.runnableProjectsModel.projects.valueOrNull
if (projects == null) {
LOG.warn("Runnable projects model doesn't contain projects")
Expand All @@ -205,24 +213,40 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {

if (existingConfiguration != null) {
LOG.info("Found existing run configuration ${existingConfiguration.name}")
return updateConfiguration(existingConfiguration, runnableProject, session)
return updateConfiguration(
existingConfiguration,
runnableProject,
session,
otelPort
)
} else {
LOG.info("Creating a new run configuration $configurationName")
return createConfiguration(
configurationType,
configurationName,
runManager,
runnableProject,
session,
hostName,
otelPort
)
}

LOG.info("Creating a new run configuration $configurationName")
return createConfiguration(configurationType, configurationName, runManager, runnableProject, session, hostName)
}

private fun updateConfiguration(
existingConfiguration: RunnerAndConfigurationSettings,
runnableProject: RunnableProject,
session: SessionModel,
otelPort: Int
): RunnerAndConfigurationSettings {
existingConfiguration.apply {
(configuration as DotNetProjectConfiguration).apply {
parameters.projectFilePath = runnableProject.projectFilePath
parameters.projectKind = runnableProject.kind
parameters.programParameters = ParametersListUtil.join(session.args?.toList() ?: emptyList())
parameters.envs = session.envs?.associate { it.key to it.value } ?: emptyMap()
val envs = session.envs?.associate { it.key to it.value }?.toMutableMap() ?: mutableMapOf()
// envs[OTEL_EXPORTER_OTLP_ENDPOINT] = "https://localhost:$otelPort"
parameters.envs = envs
}
}

Expand All @@ -237,7 +261,8 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
runManager: RunManager,
runnableProject: RunnableProject,
session: SessionModel,
hostName: String
hostName: String,
otelPort: Int
): RunnerAndConfigurationSettings {
val factory = configurationType.factory
val defaultConfiguration =
Expand All @@ -246,7 +271,9 @@ class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
parameters.projectFilePath = runnableProject.projectFilePath
parameters.projectKind = runnableProject.kind
parameters.programParameters = ParametersListUtil.join(session.args?.toList() ?: emptyList())
parameters.envs = session.envs?.associate { it.key to it.value } ?: emptyMap()
val envs = session.envs?.associate { it.key to it.value }?.toMutableMap() ?: mutableMapOf()
// envs[OTEL_EXPORTER_OTLP_ENDPOINT] = "https://localhost:$otelPort"
parameters.envs = envs
}
isActivateToolWindowBeforeRun = false
isFocusToolWindowBeforeRun = false
Expand Down

0 comments on commit b01ca5b

Please sign in to comment.