Helatra.GelfReceiver
2.0.3.17
dotnet add package Helatra.GelfReceiver --version 2.0.3.17
NuGet\Install-Package Helatra.GelfReceiver -Version 2.0.3.17
<PackageReference Include="Helatra.GelfReceiver" Version="2.0.3.17" />
<PackageVersion Include="Helatra.GelfReceiver" Version="2.0.3.17" />
<PackageReference Include="Helatra.GelfReceiver" />
paket add Helatra.GelfReceiver --version 2.0.3.17
#r "nuget: Helatra.GelfReceiver, 2.0.3.17"
#:package Helatra.GelfReceiver@2.0.3.17
#addin nuget:?package=Helatra.GelfReceiver&version=2.0.3.17
#tool nuget:?package=Helatra.GelfReceiver&version=2.0.3.17
Helatra.GelfReceiver
All-in-one GELF log collection, indexing, and analytics package for .NET 9
Receive GELF 1.1 messages over UDP, index them to OpenSearch, query logs programmatically, and expose a ready-to-use REST API — all from a single NuGet package.
Your App (.NET)
│
▼
┌──────────────────────────────┐
│ Helatra.GelfReceiver │
│ │
│ UDP :12201 ──► Buffer │
│ │ │
│ ▼ │
│ OpenSearch Sink │──────► OpenSearch Cluster
│ │
│ REST API ◄── Query Client │──────► Search / Analytics
└──────────────────────────────┘
What Does This Package Do?
Problem: You have applications (NGINX, HAProxy, custom services) that emit GELF (Graylog Extended Log Format) messages over UDP. You need to collect, store, search, and analyze these logs — but without running a full Graylog stack.
Solution: Helatra.GelfReceiver replaces the entire Graylog pipeline with a single NuGet package:
| Capability | Traditional Stack | With This Package |
|---|---|---|
| Receive GELF UDP | Graylog Server | AddGelfReceiver() |
| Store & Index | Elasticsearch + Graylog | OpenSearch (direct) |
| Search Logs | Graylog UI / API | Embedded REST API |
| Analytics | Graylog Dashboards | Built-in summary & breakdown endpoints |
| Integration | External system | Native .NET DI, runs in your app |
Result: One line of code replaces an entire infrastructure component.
Installation
Prerequisites
OpenSearch is required for log storage and querying. Install on your server:
# Ubuntu/Debian
sudo apt-get update && sudo apt-get install -y opensearch
# Or with Docker
docker run -d --name opensearch \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "DISABLE_SECURITY_PLUGIN=true" \
opensearchproject/opensearch:2.18.0
NuGet Package
# From Helatra Nexus repository
dotnet add package Helatra.GelfReceiver \
--source https://artifact.helatra.com/repository/nuget-hosted/index.json
Or add to your .csproj:
<PackageReference Include="Helatra.GelfReceiver" Version="2.0.0" />
If you haven't registered the Nexus source yet:
dotnet nuget add source https://artifact.helatra.com/repository/nuget-hosted/index.json \
--name Helatra \
--username jenkins \
--password YOUR_PASSWORD \
--store-password-in-clear-text
Quick Start
Minimal Setup (3 lines)
// Program.cs
using Helatra.GelfReceiver.Extensions;
var builder = WebApplication.CreateBuilder(args);
// 1. Register GELF receiver + OpenSearch sink + query client
builder.Services.AddGelfReceiver(builder.Configuration);
// 2. Add controllers with embedded log analytics API
builder.Services.AddControllers()
.AddGelfReceiverControllers(); // Serves at: /api/v1/logs
var app = builder.Build();
app.MapControllers();
app.Run();
That's it. Your application now:
- Listens for GELF messages on UDP port 12201
- Indexes them into OpenSearch with daily rolling indices
- Exposes 5 REST endpoints for log search and analytics
With Custom Route Prefix
// If your frontend expects a specific route:
builder.Services.AddControllers()
.AddNewtonsoftJson()
.AddGelfReceiverControllers("api/v1/LogAnalytics");
// Endpoints: /api/v1/LogAnalytics/get-table-all, /summary, etc.
Configuration
appsettings.json
{
"GelfReceiver": {
"Port": 12201,
"BindAddress": "0.0.0.0",
"BufferSize": 500,
"FlushIntervalSeconds": 5,
"MaxMessagesPerSecond": 0,
"ChunkTimeoutSeconds": 5,
"MaxPendingChunks": 1000,
"OpenSearch": {
"Enabled": true,
"Url": "https://localhost:9200",
"Username": "admin",
"Password": "admin",
"IndexPrefix": "gelf-logs",
"NumberOfShards": 1,
"NumberOfReplicas": 0,
"RetentionDays": 7,
"SkipCertificateValidation": true
}
}
}
Configuration Reference
GelfReceiverOptions
| Option | Type | Default | Description |
|---|---|---|---|
Port |
int |
12201 |
UDP port to listen on (GELF standard) |
BindAddress |
string |
127.0.0.1 |
Bind address. Use 0.0.0.0 to accept from all interfaces |
BufferSize |
int |
500 |
Max messages to buffer before flushing to OpenSearch |
FlushIntervalSeconds |
int |
5 |
Force flush after N seconds even if buffer isn't full |
MaxMessagesPerSecond |
int |
0 |
Rate limit (0 = unlimited) |
ChunkTimeoutSeconds |
int |
5 |
Timeout for multi-chunk GELF message reassembly |
MaxPendingChunks |
int |
1000 |
Max pending chunk assemblies (prevents memory exhaustion) |
OpenSearchSinkOptions
| Option | Type | Default | Description |
|---|---|---|---|
Enabled |
bool |
false |
Enable OpenSearch indexing |
Url |
string |
https://localhost:9200 |
OpenSearch endpoint URL |
Username |
string |
"" |
Authentication username |
Password |
string |
"" |
Authentication password |
IndexPrefix |
string |
gelf-logs |
Index name prefix. Creates: {prefix}-2026.03.10 |
NumberOfShards |
int |
1 |
Primary shards per daily index |
NumberOfReplicas |
int |
0 |
Replica shards per daily index |
RetentionDays |
int |
7 |
Auto-delete indices older than N days (ISM policy) |
SkipCertificateValidation |
bool |
true |
Skip TLS validation (for self-signed certs) |
Manual Configuration (without appsettings.json)
builder.Services.AddGelfReceiver(
receiver =>
{
receiver.Port = 12201;
receiver.BindAddress = "0.0.0.0";
receiver.BufferSize = 1000;
receiver.FlushIntervalSeconds = 3;
},
openSearch =>
{
openSearch.Enabled = true;
openSearch.Url = "https://opensearch.internal:9200";
openSearch.Username = "admin";
openSearch.Password = "StrongPassword123!";
openSearch.IndexPrefix = "waf-logs";
openSearch.RetentionDays = 30;
});
API Endpoints
When AddGelfReceiverControllers() is called, these endpoints are automatically available:
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
{prefix}/get-table-all |
Required | Paginated log search with advanced filtering |
GET |
{prefix}/summary?range=3600 |
Required | Dashboard summary (total, blocked, errors, top attackers) |
GET |
{prefix}/blocked-count?range=3600 |
Required | Blocked request count widget |
GET |
{prefix}/attack-breakdown?range=3600 |
Required | Attack type distribution (SQL Injection, XSS, etc.) |
GET |
{prefix}/status |
Anonymous | OpenSearch engine health check |
POST /get-table-all — Paginated Search
Server-side pagination with filtering, designed for AG Grid / data table integration.
Request:
POST /api/v1/logs/get-table-all
{
"maxResultCount": 20,
"skipCount": 0,
"orderBy": "timestamp desc",
"keywordSearch": "",
"searchExpressionList": [
{
"propName": "remoteAddr",
"searchOperator": 0,
"value1": "192.168.1.100"
},
{
"propName": "status",
"searchOperator": 7,
"value1": "399"
},
{
"propName": "range",
"searchOperator": 0,
"value1": "86400"
}
]
}
Response:
{
"totalCount": 1542,
"items": [
{
"id": "abc123",
"timestamp": "2026-03-10T14:30:00Z",
"source": "nginx-waf",
"message": "GET /api/users HTTP/1.1",
"level": 6,
"facility": "nginx",
"remoteAddr": "192.168.1.100",
"requestUri": "/api/users",
"requestMethod": "GET",
"status": 403,
"httpHost": "app.example.com",
"httpUserAgent": "Mozilla/5.0",
"rule": "sqli-detect",
"eventType": "waf_block"
}
]
}
Search Operators
| Value | Name | Lucene Query | Example |
|---|---|---|---|
0 |
Equal | field:value |
status:403 |
1 |
NotEqual | NOT field:value |
NOT status:200 |
2 |
FullLike | field:*value* |
request_uri:*admin* |
3 |
NotFullLike | NOT field:*value* |
NOT request_uri:*css* |
4 |
StartWith | field:value* |
remote_addr:192.168.* |
5 |
EndWith | field:*value |
request_uri:*.php |
6 |
LessThan | field:<value |
status:<400 |
7 |
GreaterThan | field:>value |
status:>399 |
8 |
LessOrEqual | field:<=value |
status:<=299 |
9 |
GreaterOrEqual | field:>=value |
status:>=500 |
12 |
Blank | NOT _exists_:field |
Field is empty |
13 |
NotBlank | _exists_:field |
Field has value |
Searchable Fields
| Frontend Name | OpenSearch Field | Description |
|---|---|---|
timestamp |
@timestamp |
Log timestamp |
source |
host |
Source hostname |
message |
short_message |
Log message |
level |
level |
Syslog level (0-7) |
facility |
facility |
Log facility |
remoteAddr |
remote_addr |
Client IP address |
requestUri |
request_uri |
HTTP request path |
requestMethod |
request_method |
HTTP method |
status |
status |
HTTP status code |
httpHost |
http_host |
Target hostname |
httpUserAgent |
http_user_agent |
Client user agent |
rule |
rule |
WAF rule that triggered |
eventType |
event_type |
Event type (access, waf_block, etc.) |
GET /summary — Dashboard Analytics
curl -H "Authorization: Bearer TOKEN" \
"https://app.example.com/api/v1/logs/summary?range=3600"
Response:
{
"totalLogs": 15420,
"blockedRequests": 342,
"errorRequests": 89,
"topAttackers": [
{ "ip": "45.33.32.156", "count": 128 },
{ "ip": "104.236.198.48", "count": 67 }
],
"topTargetedUris": [
{ "uri": "/wp-login.php", "count": 203 },
{ "uri": "/admin/config.php", "count": 87 }
]
}
GET /attack-breakdown — WAF Attack Distribution
curl -H "Authorization: Bearer TOKEN" \
"https://app.example.com/api/v1/logs/attack-breakdown?range=86400"
Response:
{
"SQL Injection": 156,
"XSS": 89,
"Path Traversal": 42,
"Scanner Detection": 234,
"Protocol Attack": 18
}
GET /status — Health Check (Anonymous)
curl "https://app.example.com/api/v1/logs/status"
Response:
{
"engine": "OpenSearch",
"status": "green"
}
Query Client (Programmatic Access)
Inject IGelfLogQueryClient anywhere in your application for direct log access:
using Helatra.GelfReceiver.Query;
public class SecurityDashboardService
{
private readonly IGelfLogQueryClient _logClient;
public SecurityDashboardService(IGelfLogQueryClient logClient)
{
_logClient = logClient;
}
// Search for blocked requests from a specific IP
public async Task<GelfLogSearchResult> GetBlockedFromIp(string ip)
{
return await _logClient.SearchAsync(
query: $"remote_addr:{ip} AND event_type:waf_block",
rangeSeconds: 86400, // last 24 hours
limit: 100,
offset: 0,
sortField: "@timestamp",
sortOrder: "desc");
}
// Get real-time summary for monitoring
public async Task<GelfLogSummary> GetRealtimeSummary()
{
var summary = await _logClient.GetSummaryAsync(rangeSeconds: 300); // last 5 min
// summary.TotalLogs
// summary.BlockedRequests
// summary.ErrorRequests
// summary.TopAttackers → List<GelfTopAttacker> { Ip, Count }
// summary.TopTargetedUris → List<GelfTopUri> { Uri, Count }
return summary;
}
// Get attack breakdown for reporting
public async Task<Dictionary<string, int>> GetDailyAttackReport()
{
return await _logClient.GetAttackBreakdownAsync(rangeSeconds: 86400);
// { "SQL Injection": 156, "XSS": 89, "Scanner Detection": 234, ... }
}
}
Query Syntax
The search query uses Lucene query string syntax:
# Exact match
status:403
# Wildcard
request_uri:/admin*
# Range
status:>=400 AND status:<500
# Boolean
remote_addr:192.168.* AND NOT event_type:access
# Field exists
_exists_:rule
# Free text (searches short_message)
"SQL injection detected"
Custom Sink
Implement IGelfSink to add your own log processing alongside OpenSearch:
using Helatra.GelfReceiver.Models;
using Helatra.GelfReceiver.Processing;
public class SlackAlertSink : IGelfSink
{
public string Name => "SlackAlert";
public async Task HandleBatchAsync(IReadOnlyList<GelfMessage> messages, CancellationToken ct)
{
var critical = messages.Where(m => m.Level <= 3); // Emergency, Alert, Critical, Error
foreach (var msg in critical)
{
var ip = msg.GetString("_remote_addr");
var uri = msg.GetString("_request_uri");
var rule = msg.GetString("_rule");
// Send to Slack, PagerDuty, etc.
}
}
}
// Register in DI:
builder.Services.AddSingleton<IGelfSink, SlackAlertSink>();
Metrics & Monitoring
Access receiver metrics via DI:
using Helatra.GelfReceiver.Processing;
public class HealthCheckService
{
private readonly GelfReceiverMetrics _metrics;
public HealthCheckService(GelfReceiverMetrics metrics)
{
_metrics = metrics;
}
public object GetMetrics()
{
return _metrics.GetSnapshot();
// {
// "received": 150000,
// "flushed": 149800,
// "dropped": 0,
// "pending": 200,
// "sinkErrors": { "OpenSearch": 3 }
// }
}
}
GELF Message Format
The receiver accepts standard GELF 1.1 messages. Custom fields are prefixed with _:
{
"version": "1.1",
"host": "nginx-waf-01",
"short_message": "GET /api/users HTTP/1.1",
"timestamp": 1741612800.000,
"level": 6,
"_remote_addr": "192.168.1.100",
"_request_uri": "/api/users",
"_request_method": "GET",
"_status": 200,
"_http_host": "app.example.com",
"_http_user_agent": "Mozilla/5.0",
"_rule": "",
"_event_type": "access"
}
Syslog Levels
| Level | Name | Description |
|---|---|---|
| 0 | Emergency | System is unusable |
| 1 | Alert | Immediate action required |
| 2 | Critical | Critical conditions |
| 3 | Error | Error conditions |
| 4 | Warning | Warning conditions |
| 5 | Notice | Normal but significant |
| 6 | Info | Informational messages |
| 7 | Debug | Debug-level messages |
NGINX GELF Output Example
Configure NGINX to send access logs as GELF:
# nginx.conf
log_format gelf_json escape=json
'{'
'"version":"1.1",'
'"host":"$hostname",'
'"short_message":"$request",'
'"timestamp":$msec,'
'"level":6,'
'"_remote_addr":"$remote_addr",'
'"_request_uri":"$request_uri",'
'"_request_method":"$request_method",'
'"_status":$status,'
'"_http_host":"$host",'
'"_http_user_agent":"$http_user_agent",'
'"_body_bytes_sent":$body_bytes_sent,'
'"_request_time":$request_time'
'}';
access_log syslog:server=127.0.0.1:12201 gelf_json;
Test with netcat
echo '{"version":"1.1","host":"test","short_message":"Hello GELF","level":6}' \
| gzip | nc -u -w1 127.0.0.1 12201
Architecture
┌──────────────┐
│ GELF Source │ (NGINX, HAProxy, Apps)
└──────┬───────┘
│ UDP :12201
▼
┌─────────────────────┐
│ GelfUdpListener │ Decompression (GZIP/ZLIB)
│ GelfChunkAssembler │ Multi-chunk reassembly
└──────────┬──────────┘
│ GelfMessage
▼
┌─────────────────────┐
│ GelfMessageBuffer │ Channel-based batching
│ (bounded, 10K cap) │ Backpressure: DropOldest
└──────────┬──────────┘
│ IReadOnlyList<GelfMessage>
▼
┌──────────────────────────────┐
│ IGelfSink[] │
│ ┌──────────────────────┐ │
│ │ OpenSearchSink │ │──► OpenSearch Cluster
│ │ (bulk index, ISM) │ │ Daily: gelf-logs-2026.03.10
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ Your Custom Sink │ │──► Slack, DB, File...
│ └──────────────────────┘ │
└──────────────────────────────┘
┌──────────────────────────────┐
│ IGelfLogQueryClient │
│ OpenSearchLogQueryClient │──► OpenSearch (query)
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ GelfLogController │
│ POST /get-table-all │──► Paginated search
│ GET /summary │──► Analytics
│ GET /attack-breakdown │──► WAF distribution
│ GET /status │──► Health check
└──────────────────────────────┘
Key Design Decisions
| Decision | Rationale |
|---|---|
| Singleton services | Single UDP listener, single buffer — no resource duplication |
| Channel-based buffer | System.Threading.Channels for lock-free producer/consumer |
| BoundedChannel + DropOldest | Backpressure protection — never blocks the UDP listener |
| Daily rolling indices | {prefix}-yyyy.MM.dd — efficient time-range queries |
| ISM auto-retention | OpenSearch automatically deletes indices older than RetentionDays |
| IApplicationModelConvention | Route prefix configurable at startup, no hardcoded paths |
| IGelfSink interface | Extensible — add custom sinks without modifying the package |
Full Integration Example
A complete ASP.NET Core Web API with GELF receiver, JWT auth, and log analytics:
using Helatra.GelfReceiver.Extensions;
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidIssuer = "your-app",
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Auth:SecretKey"]!))
};
});
// Controllers + GELF log analytics API
builder.Services.AddControllers()
.AddGelfReceiverControllers("api/v1/logs");
// GELF Receiver — starts UDP listener as background service
builder.Services.AddGelfReceiver(builder.Configuration);
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
{
"Auth": {
"SecretKey": "your-secret-key-here"
},
"GelfReceiver": {
"Port": 12201,
"BindAddress": "0.0.0.0",
"OpenSearch": {
"Enabled": true,
"Url": "https://opensearch:9200",
"Username": "admin",
"Password": "admin",
"IndexPrefix": "app-logs",
"RetentionDays": 30
}
}
}
Requirements
- .NET 9.0 or later
- OpenSearch 2.x (for sink and query features)
- ASP.NET Core (for embedded API controller)
License
Proprietary - Helatra
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.Extensions.Configuration.Binder (>= 9.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Options (>= 9.0.8)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.8)
- OpenSearch.Client (>= 1.8.0)
- System.Text.Json (>= 9.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.