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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,6 @@ src/Garage.FeatureFlags/flagsapi
# Python virtual environments
src/Garage.ChatService/.venv/
src/Garage.ChatService/prompts.egg-info/

# Playwright test artifacts
.playwright-mcp
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
</ItemGroup>
<ItemGroup Label="Community Packages">
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.Flagd" Version="13.4.0" />
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector" Version="13.4.0" />
<PackageVersion Include="Scalar.Aspire" Version="0.11.2" />
</ItemGroup>
<ItemGroup Label="Microsoft Packages">
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ aspire run
- Web Frontend: [https://localhost:7070](https://localhost:7070)
- API Service: [https://localhost:7071](https://localhost:7071)
- Aspire Dashboard: [https://localhost:15888](https://localhost:15888)
- Grafana (LGTM): [http://localhost:3000](http://localhost:3000)

### Provisioned Grafana Dashboards

Grafana dashboard provisioning is configured in the AppHost and mounted into the LGTM container.

- Provider config: `src/Garage.AppHost/grafana/provisioning/dashboards/custom.yaml`
- Dashboard JSON folder: `src/Garage.AppHost/grafana/provisioning/dashboards/custom/`

To add an external dashboard:

1. Export or download the dashboard JSON.
2. Place it in `src/Garage.AppHost/grafana/provisioning/dashboards/custom/`.
3. Restart Aspire (`aspire run`) so LGTM reloads provisioned dashboards.
4. Open Grafana and navigate to the **OpenFeature** folder.

The starter dashboard includes datasource placeholder variables you can reuse in your panels:

- `$metrics_ds` (Prometheus)
- `$logs_ds` (Loki)
- `$traces_ds` (Tempo)

### 6. OpenAPI and Scalar Documentation (Development)

Expand Down
1 change: 1 addition & 0 deletions src/Garage.AppHost/Garage.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Aspire.Hosting.JavaScript" />
<PackageReference Include="Aspire.Hosting.Python" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.Flagd" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector" />
<PackageReference Include="Scalar.Aspire" />
</ItemGroup>

Expand Down
40 changes: 38 additions & 2 deletions src/Garage.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

var migrations = apiService.AddEFMigrations("api-migrations", "Garage.ApiModel.Data.GarageDbContext");

var webFrontend = builder.AddJavaScriptApp("web", "../Garage.Web/").WithBrowserLogs();
var webFrontend = builder.AddJavaScriptApp("web", "../Garage.Web/");
Comment thread
askpt marked this conversation as resolved.

// Add Python chat service using Uvicorn (FastAPI/ASGI)
var chatService = builder.AddUvicornApp("chatservice", "../Garage.ChatService/", "main:app")
Expand All @@ -59,16 +59,41 @@
var flagsApi = builder.AddGoApp("flagsapi", "../Garage.FeatureFlags/")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf")
.PublishAsDockerFile();

if (!builder.ExecutionContext.IsPublishMode)
{
// Add local Grafana provisioning path for dashboards
var grafanaProvisioningPath = Path.Combine(builder.AppHostDirectory, "grafana", "provisioning", "dashboards");
if (!Directory.Exists(grafanaProvisioningPath))
{
throw new DirectoryNotFoundException($"Grafana provisioning path not found: {grafanaProvisioningPath}");
}

Comment thread
askpt marked this conversation as resolved.
// Add Grafana LGTM stack (Loki, Tempo, Prometheus, Pyroscope, Grafana)
var lgtm = builder.AddContainer("lgtm", "grafana/otel-lgtm", "latest")
.WithHttpEndpoint(port: 3000, targetPort: 3000, name: "grafana")
.WithBindMount(grafanaProvisioningPath, "/otel-lgtm/grafana/conf/provisioning/dashboards", isReadOnly: true)
.WithExternalHttpEndpoints();
Comment thread
askpt marked this conversation as resolved.

// Add collector for OpenTelemetry signals
var collector = builder.AddOpenTelemetryCollector("opentelemetry-collector")
.WithConfig("otel/config.yaml")
.WithExternalHttpEndpoints()
.WithAppForwarding()
.WaitFor(lgtm);

// Local development: flagd reads from host filesystem via bind mount
var flagsPath = Path.Combine(builder.AppHostDirectory, "flags", "flagd.json");
flagd.WithBindFileSync(Path.GetDirectoryName(flagsPath)!);

flagd = flagd.WaitFor(collector);
chatService = chatService.WaitFor(collector);
apiService = apiService.WaitFor(collector);
migrations = migrations.WaitFor(collector);

flagsApi = flagsApi
.WaitFor(collector)
.WithEnvironment("FLAGS_FILE_PATH", flagsPath)
.WaitFor(flagd);

Expand All @@ -81,6 +106,16 @@
var tunnel = builder.AddDevTunnel("tunnel")
.WithReference(flagd)
.WithAnonymousAccess();

// Browser telemetry is sent directly from the browser to the collector, so it
// must target the collector's HTTP OTLP endpoint (port 4318) which has CORS
// configured — browsers can't use gRPC. Setting the protocol to "http" makes
// WithAppForwarding route to the collector's "http" endpoint instead of "grpc".
webFrontend = webFrontend
.WithBrowserLogs()
.WithOtlpExporter()
.WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", "http")
.WaitFor(collector);
}
else
{
Expand All @@ -98,6 +133,7 @@

flagsApi = flagsApi
.WithReference(flagsBlobs)
.WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf")
.WithEnvironment("AZURE_STORAGE_ACCOUNT", accountName)
.WithEnvironment("FLAGS_BLOB_CONTAINER", "flags")
.WithEnvironment("FLAGS_BLOB_NAME", "flagd.json");
Expand Down
12 changes: 12 additions & 0 deletions src/Garage.AppHost/grafana/provisioning/dashboards/custom.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: 1

providers:
- name: "OpenFeature Dashboards"
orgId: 1
folder: "OpenFeature"
type: file
disableDeletion: false
allowUiUpdates: true
options:
path: /otel-lgtm/grafana/conf/provisioning/dashboards/custom
foldersFromFilesStructure: false
Loading
Loading