A lightweight, self-hosted structured logging tool designed to run in Azure App Service with Azure Table Storage as the backend.
- 🔍 Log Viewer - Query and search logs with a dark-themed UI
- 📊 Dashboard - Log volume, error/warning trends, and per-application breakdown
- 🤖 AI log investigation - Built-in MCP server for querying logs from Claude Code, VS Code, and other AI tools
- 📱 Application Management - Create and manage applications with API keys
- 🔐 Secure - Entra ID authentication for UI, API keys for ingestion
- 💰 Cost-effective - Uses Azure Table Storage (pennies/month)
- 🧹 Auto-cleanup - Configurable retention with automatic partition deletion
Log volume over time with error/warning counts and a per-application breakdown.
Search and filter structured logs by application, level, and time range.
Create and manage applications and their ingestion API keys.
- .NET 10 SDK
- Azure Storage Account (or Azurite for local development)
- Azure Entra ID app registration (for production)
# Using npm
npm install -g azurite
azurite --silent --location ./azurite --debug ./azurite/debug.log
# Or using Docker
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azuriteFor local development, the app uses UseDevelopmentStorage=true by default (see appsettings.Development.json).
For Entra ID authentication in development, you can either:
- Set up an Entra ID app registration and update
appsettings.Development.json - Temporarily disable authentication for testing
cd src/SignalYard.Web
dotnet runNavigate to https://localhost:5001 (or the URL shown in the console).
# Create resource group
az group create --name rg-signalyard --location australiaeast
# Create storage account
az storage account create \
--name stsignalyard \
--resource-group rg-signalyard \
--location australiaeast \
--sku Standard_LRS
# Create App Service Plan
az appservice plan create \
--name asp-signalyard \
--resource-group rg-signalyard \
--sku B1 \
--is-linux
# Create Web App
az webapp create \
--name app-signalyard \
--resource-group rg-signalyard \
--plan asp-signalyard \
--runtime "DOTNETCORE:9.0"- Go to Azure Portal → Entra ID → App registrations
- Create a new registration:
- Name:
SignalYard - Supported account types: Single tenant (or as needed)
- Redirect URI:
https://your-app.azurewebsites.net/signin-oidc
- Name:
- Note the Application (client) ID and Directory (tenant) ID
- Under Authentication, add the redirect URI for your app
# Get storage connection string
CONN_STRING=$(az storage account show-connection-string \
--name stsignalyard \
--resource-group rg-signalyard \
--query connectionString -o tsv)
# Set configuration
az webapp config appsettings set \
--name app-signalyard \
--resource-group rg-signalyard \
--settings \
"ConnectionStrings__TableStorage=$CONN_STRING" \
"AzureAd__TenantId=YOUR_TENANT_ID" \
"AzureAd__ClientId=YOUR_CLIENT_ID"dotnet publish -c Release
az webapp deploy --resource-group rg-signalyard --name app-signalyard --src-path ./publish.zip- Navigate to the Applications page
- Click "Add Application"
- Enter a name and description
- Copy the generated API key (shown only once!)
Install the SignalYard sink:
dotnet add package Serilog.Sinks.SignalYardConfigure your application — pass the SignalYard server URL and your API key:
Log.Logger = new LoggerConfiguration()
.WriteTo.SignalYard(
serverUrl: "https://your-signalyard.azurewebsites.net",
apiKey: "sy_your_api_key_here")
.CreateLogger();
Log.Information("User {Username} logged in from {IpAddress}", "john.doe", "10.0.0.1");
Log.CloseAndFlush(); // flush buffered events on shutdownThe sink formats events as CLEF, batches them, and posts to /api/events/raw with the
X-Api-Key header for you. See Serilog.Sinks.SignalYard
for configuration options (batch size, flush period, minimum level).
- Navigate to the Log Viewer (home page)
- Select an application
- Choose a date range
- Optionally filter by log level
- Click Search
- Use the search box to filter results client-side
SignalYard exposes a Model Context Protocol server at /mcp so AI
tools such as Claude Code and VS Code can investigate logs across all applications. It is
read-only and gated by a single global "investigator" API key — separate from the per-application
sy_ ingestion keys.
Provide a strong random secret via the Mcp__ApiKey environment variable (double underscore maps to
the Mcp:ApiKey configuration value). Leaving it unset disables the endpoint (every request is
rejected).
# Azure App Service
az webapp config appsettings set \
--name app-signalyard \
--resource-group rg-signalyard \
--settings "Mcp__ApiKey=$(openssl rand -hex 32)"For local development, set it in user-secrets or appsettings.Development.json:
{ "Mcp": { "ApiKey": "dev-mcp-key" } }The key is sent in the X-Api-Key header (an Authorization: Bearer <key> header also works).
Claude Code:
claude mcp add --transport http signalyard https://your-signalyard.azurewebsites.net/mcp \
--header "X-Api-Key: <your-mcp-key>"VS Code (.vscode/mcp.json):
{
"servers": {
"signalyard": {
"type": "http",
"url": "https://your-signalyard.azurewebsites.net/mcp",
"headers": { "X-Api-Key": "<your-mcp-key>" }
}
}
}| Tool | Description |
|---|---|
list_applications |
List all applications (name, description, enabled, retention). |
query_logs |
Query log entries for one or all applications over a time range. Params: application, level, from, to, maxResults (default 200), search, includeProperties. Defaults to the last 24 hours. |
get_log_stats |
Aggregate counts by level, over time, and per application. Params: application, from, to, bucketMinutes. |
searchis a post-filter over the returned page (up tomaxResults), not a full-range search — Table Storage has no server-side text search. Narrow the time range for exhaustive results.
| Setting | Description | Default |
|---|---|---|
InstanceName |
Overrides the "SignalYard" label in the browser tab title and header, to tell instances apart | SignalYard |
ConnectionStrings:TableStorage |
Azure Storage connection string | - |
TableStorage:AccountName |
Storage account name (for managed identity) | - |
Mcp:ApiKey |
Global key for the /mcp investigation endpoint (unset = disabled) |
- |
AzureAd:TenantId |
Entra ID tenant ID | - |
AzureAd:ClientId |
Entra ID application ID | - |
RetentionCleanup:StartupDelayMinutes |
Delay before first cleanup run | 60 |
RetentionCleanup:IntervalHours |
Hours between cleanup runs | 24 |
SignalYard/
├── src/
│ ├── SignalYard.Core/ # Domain layer
│ │ ├── Entities/ # Table Storage entities
│ │ ├── Models/ # DTOs and request/response models
│ │ └── Services/ # Business logic services
│ └── SignalYard.Web/ # ASP.NET Core MVC application
│ ├── Auth/ # API key authentication
│ ├── Controllers/ # MVC controllers
│ ├── Views/ # Razor views
│ ├── Endpoints/ # Minimal API ingestion endpoints
│ └── Services/ # Background services
└── tests/
├── SignalYard.Tests/ # Unit & integration tests (xUnit)
└── SignalYard.Playwright/ # End-to-end browser tests (Playwright + NUnit)
- PartitionKey:
{ApplicationName}_{YearMonth}(e.g.,MyApp_202501) - RowKey:
{InvertedTicks}_{Guid}(newest first)
- PartitionKey:
Application - RowKey: Application name
- PartitionKey:
ApiKey - RowKey: SHA256 hash of API key
MIT


