Custom Reports
In addition to built-in exception and license reports, Babel Licensing allows you to create custom reports tailored to your application’s specific needs. Custom reports enable you to send any structured data to your Babel Licensing Service for centralized collection, analysis, and monitoring.
Overview
Custom reports are useful for:
- Security Events: Detect and report potential hacking attempts, unauthorized access, or suspicious behavior
- Application Metrics: Track custom application performance or usage metrics
- Business Events: Report significant business events or milestones
- Diagnostics: Send custom diagnostic data for troubleshooting
- Audit Trails: Create audit logs for compliance requirements
Console Application Example
To get started with a practical example, clone the console-custom-report-example repository which demonstrates how to create and send custom reports using Babel Reporting functionality.
- Open your preferred Git client or the command line.
- Clone the GitHub repository by executing the following command:
git clone https://github.com/babelfornet/console-custom-report-example.git- Once the repository is cloned, navigate to the project directory:
cd console-custom-report-example- You are now ready to explore and run the example code.
The console-custom-report-example demonstrates how to integrate the Babel Licensing client library into your application to create and send custom reports to the Babel Licensing Service. Here’s a breakdown of the code:
- BabelReporting Initialization: The example code initializes a
BabelReportingobject and configures it with the service URL and client identifier.
private BabelReporting CreateReportingClient()
{
var reporting = new BabelReporting();
reporting.Configuration.ClientId = ClientId;
reporting.Configuration.ServiceUrl = ServiceUrl;
reporting.Configuration.UseHttp(http => {
http.Timeout = TimeSpan.FromSeconds(3);
http.Handler = new HttpClientHandler() {
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
});
reporting.BeforeSendReport += (s, e) => {
e.Report.Properties.Add("cmdline", Environment.CommandLine);
e.Report.Properties.Add("username", Environment.UserName);
};
return reporting;
}-
User Key: The example code uses a user key (
UserKey) to authorize the client to send reports. Make sure to replace it with a valid user key. To generate a user key please refer to the Floating License or License Activation paragraphs. -
Custom Report Class: The
HackDetectionReportclass inherits fromReportand defines custom properties for tracking security events. It usesReportPlotto create interactive charts that are rendered in the web application:
class HackDetectionReport : Report
{
public string HackingType { get; set; } = "unknown";
public string AttackMethod { get; set; } = "unknown";
public string HackDetails { get; set; } = "no_details";
public HackDetectionReport() : base("HackDetection") { }
public override void Build()
{
// Add custom hack detection information
Properties.Add("machineName", Environment.MachineName);
Properties.Add("hackingType", HackingType);
Properties.Add("attackMethod", AttackMethod);
Properties.Add("hackDetails", HackDetails);
// Add attack distribution pie chart
var attackDistribution = new ReportPlot(ReportPlotType.Pie, "Attack Type Distribution")
.SetHeight(400)
.AddSeries("Attacks", new List<object>
{
new { type = "SQL Injection", count = 45 },
new { type = "XSS", count = 32 },
new { type = "CSRF", count = 18 },
new { type = "Path Traversal", count = 12 },
new { type = "Brute Force", count = 8 }
});
attackDistribution.Series[0].ArgumentField = "type";
attackDistribution.Series[0].ValueField = "count";
Properties.Add("attackDistribution", attackDistribution.ToDictionary());
// Add attack timeline line chart
var attackTimeline = new ReportPlot(ReportPlotType.Line, "Attack Attempts Over Time")
.SetAxisTitles("Time", "Attack Count")
.SetHeight(350)
.AddSeries("Today", new List<object>
{
new { hour = "00:00", attempts = 5 },
new { hour = "04:00", attempts = 3 },
new { hour = "08:00", attempts = 15 },
new { hour = "12:00", attempts = 28 },
new { hour = "16:00", attempts = 22 },
new { hour = "20:00", attempts = 18 }
})
.AddSeries("Yesterday", new List<object>
{
new { hour = "00:00", attempts = 4 },
new { hour = "04:00", attempts = 2 },
new { hour = "08:00", attempts = 12 },
new { hour = "12:00", attempts = 20 },
new { hour = "16:00", attempts = 25 },
new { hour = "20:00", attempts = 15 }
});
attackTimeline.Series[0].ArgumentField = "hour";
attackTimeline.Series[0].ValueField = "attempts";
attackTimeline.Series[1].ArgumentField = "hour";
attackTimeline.Series[1].ValueField = "attempts";
Properties.Add("attackTimeline", attackTimeline.ToDictionary());
// Add severity bar chart
var severityChart = new ReportPlot(ReportPlotType.Bar, "Attack Severity Levels")
.SetAxisTitles("Severity", "Incidents")
.SetHeight(350)
.AddSeries("Last 24h", new List<object>
{
new { severity = "Critical", incidents = 8 },
new { severity = "High", incidents = 25 },
new { severity = "Medium", incidents = 42 },
new { severity = "Low", incidents = 30 }
});
severityChart.Series[0].ArgumentField = "severity";
severityChart.Series[0].ValueField = "incidents";
Properties.Add("severityChart", severityChart.ToDictionary());
// Add IP source area chart
var ipSourceChart = new ReportPlot(ReportPlotType.Area, "Attack Sources by Region")
.SetAxisTitles("Hour", "Unique IPs")
.SetHeight(350)
.AddSeries("Asia", new List<object>
{
new { hour = "00:00", ips = 12 },
new { hour = "06:00", ips = 18 },
new { hour = "12:00", ips = 25 },
new { hour = "18:00", ips = 22 }
})
.AddSeries("Europe", new List<object>
{
new { hour = "00:00", ips = 8 },
new { hour = "06:00", ips = 15 },
new { hour = "12:00", ips = 20 },
new { hour = "18:00", ips = 18 }
})
.AddSeries("Americas", new List<object>
{
new { hour = "00:00", ips = 5 },
new { hour = "06:00", ips = 10 },
new { hour = "12:00", ips = 15 },
new { hour = "18:00", ips = 12 }
});
ipSourceChart.Series[0].ArgumentField = "hour";
ipSourceChart.Series[0].ValueField = "ips";
ipSourceChart.Series[1].ArgumentField = "hour";
ipSourceChart.Series[1].ValueField = "ips";
ipSourceChart.Series[2].ArgumentField = "hour";
ipSourceChart.Series[2].ValueField = "ips";
Properties.Add("ipSourceChart", ipSourceChart.ToDictionary());
base.Build();
}
}-
BeforeSendReport Event: The example subscribes to the
BeforeSendReportevent, which allows you to add additional properties to reports before they are sent. In this event handler, custom properties likecmdline(command line) andusername(current user’s name) are added. -
Sending the Report: The
SendAsyncmethod sends the custom report to the Babel Licensing Service:
private async Task SendHackDetectionReportAsync()
{
var hackReport = new HackDetectionReport
{
HackingType = "SQL Injection",
AttackMethod = "Malicious Input",
HackDetails = "Detected SQL keywords in user input"
};
await _reporting.SendAsync(UserKey, hackReport);
Console.WriteLine("HackDetectionReport sent.");
}
Custom Report Example
The console-custom-report-example provides a practical demonstration of how to create custom report types and send them to the Babel Licensing Service. By integrating custom reports into your applications, you can track security events, application metrics, business events, and any other structured data relevant to your application.
Creating a Custom Report
To create a custom report, inherit from the Report base class:
using Babel.Licensing.Reports;
public class HackDetectionReport : Report
{
// Custom properties for your report
public string HackingType { get; set; } = "unknown";
public string AttackMethod { get; set; } = "unknown";
public string HackDetails { get; set; } = "no_details";
// Constructor: specify report name
public HackDetectionReport() : base("HackDetection")
{
}
// Build method: populate report properties
public override void Build()
{
// Add custom properties to the report
Properties.Add("machine_name", Environment.MachineName);
Properties.Add("hacking_type", HackingType);
Properties.Add("attack_method", AttackMethod);
Properties.Add("detection_details", HackDetails);
Properties.Add("detected_at", DateTime.UtcNow);
// Call base to generate final JSON structure
base.Build();
}
}Report Base Class
The Report class provides the foundation for all reports:
Key Properties
- Name: Unique identifier for the report type (e.g., “HackDetection”, “PerformanceMetrics”)
- Content: JSON-serialized report data (generated by
Build()) - Version: Report format version (currently “1.0”)
- Date: Report creation timestamp
- Encrypted: Flag indicating if content is encrypted
- Properties: Dictionary for custom key-value data
Key Methods
- Build(): Override this to populate your custom properties
- Create(string report, string password): Deserialize a report from JSON
- FromException(Exception ex, ExceptionReportOptions options): Factory method for exception reports
Sending Custom Reports
Once you’ve created your custom report class, send it using BabelReporting:
using Babel.Licensing.Services;
// Initialize reporting service
var reporting = new BabelReporting();
reporting.Configuration.ServiceUrl = "https://licensing.example.com";
reporting.Configuration.ClientId = "MyApplication";
// Create and configure your custom report
var hackReport = new HackDetectionReport
{
HackingType = "SQL Injection",
AttackMethod = "Malicious Input",
HackDetails = "Detected SQL keywords in user input: ' OR '1'='1"
};
// Send the report
var result = await reporting.SendAsync(userKey, hackReport);
if (result.Success)
{
Console.WriteLine($"Report sent: {result.ReportUid}");
}
else
{
Console.WriteLine($"Failed: {result.Error?.Message}");
}Real-World Examples
Example 1: Security Event Report
Detect and report potential security threats:
public class SecurityEventReport : Report
{
public string EventType { get; set; }
public string Severity { get; set; }
public string SourceIP { get; set; }
public string UserAgent { get; set; }
public string RequestPath { get; set; }
public Dictionary<string, string> AdditionalData { get; set; }
public SecurityEventReport() : base("SecurityEvent")
{
AdditionalData = new Dictionary<string, string>();
}
public override void Build()
{
Properties.Add("event_type", EventType);
Properties.Add("severity", Severity);
Properties.Add("source_ip", SourceIP);
Properties.Add("user_agent", UserAgent);
Properties.Add("request_path", RequestPath);
Properties.Add("timestamp", DateTime.UtcNow);
Properties.Add("machine_id", GetMachineId());
// Add all additional data
foreach (var kvp in AdditionalData)
{
Properties.Add(kvp.Key, kvp.Value);
}
base.Build();
}
private string GetMachineId()
{
// Implementation to get unique machine identifier
return Environment.MachineName;
}
}
// Usage
public async Task ReportSuspiciousLogin(string ipAddress, string userAgent)
{
var report = new SecurityEventReport
{
EventType = "SuspiciousLogin",
Severity = "High",
SourceIP = ipAddress,
UserAgent = userAgent,
RequestPath = "/api/auth/login"
};
report.AdditionalData.Add("failed_attempts", "5");
report.AdditionalData.Add("time_window", "60s");
await _reporting.SendAsync(_userKey, report);
}Example 2: Performance Metrics Report
Track application performance metrics:
public class PerformanceMetricsReport : Report
{
public double AverageResponseTime { get; set; }
public double PeakResponseTime { get; set; }
public int TotalRequests { get; set; }
public int FailedRequests { get; set; }
public double CpuUsage { get; set; }
public double MemoryUsage { get; set; }
public TimeSpan Uptime { get; set; }
public PerformanceMetricsReport() : base("PerformanceMetrics")
{
}
public override void Build()
{
Properties.Add("average_response_time_ms", AverageResponseTime);
Properties.Add("peak_response_time_ms", PeakResponseTime);
Properties.Add("total_requests", TotalRequests);
Properties.Add("failed_requests", FailedRequests);
Properties.Add("error_rate", CalculateErrorRate());
Properties.Add("cpu_usage_percent", CpuUsage);
Properties.Add("memory_usage_mb", MemoryUsage);
Properties.Add("uptime_hours", Uptime.TotalHours);
Properties.Add("collection_time", DateTime.UtcNow);
base.Build();
}
private double CalculateErrorRate()
{
if (TotalRequests == 0) return 0;
return (double)FailedRequests / TotalRequests * 100;
}
}
// Usage with periodic reporting
public class MetricsCollector
{
private readonly BabelReporting _reporting;
private readonly Timer _timer;
private readonly PerformanceMonitor _monitor;
public MetricsCollector(BabelReporting reporting, string userKey)
{
_reporting = reporting;
_monitor = new PerformanceMonitor();
// Report metrics every hour
_timer = new Timer(async _ =>
{
var metrics = _monitor.GetCurrentMetrics();
var report = new PerformanceMetricsReport
{
AverageResponseTime = metrics.AvgResponseTime,
PeakResponseTime = metrics.PeakResponseTime,
TotalRequests = metrics.TotalRequests,
FailedRequests = metrics.FailedRequests,
CpuUsage = metrics.CpuUsage,
MemoryUsage = metrics.MemoryUsage,
Uptime = metrics.Uptime
};
await _reporting.SendAsync(userKey, report);
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
}
}Example 3: Business Event Report
Track important business events:
public class BusinessEventReport : Report
{
public string EventCategory { get; set; }
public string EventAction { get; set; }
public string UserId { get; set; }
public string EntityType { get; set; }
public string EntityId { get; set; }
public decimal? TransactionAmount { get; set; }
public Dictionary<string, object> Metadata { get; set; }
public BusinessEventReport() : base("BusinessEvent")
{
Metadata = new Dictionary<string, object>();
}
public override void Build()
{
Properties.Add("event_category", EventCategory);
Properties.Add("event_action", EventAction);
Properties.Add("user_id", UserId);
Properties.Add("entity_type", EntityType);
Properties.Add("entity_id", EntityId);
Properties.Add("timestamp", DateTime.UtcNow);
if (TransactionAmount.HasValue)
{
Properties.Add("transaction_amount", TransactionAmount.Value);
}
// Add metadata
foreach (var kvp in Metadata)
{
Properties.Add(kvp.Key, kvp.Value);
}
base.Build();
}
}
// Usage
public async Task ReportPurchase(string userId, string orderId, decimal amount)
{
var report = new BusinessEventReport
{
EventCategory = "Sales",
EventAction = "Purchase",
UserId = userId,
EntityType = "Order",
EntityId = orderId,
TransactionAmount = amount
};
report.Metadata.Add("payment_method", "CreditCard");
report.Metadata.Add("currency", "USD");
await _reporting.SendAsync(_userKey, report);
}Example 4: Diagnostic Report
Send detailed diagnostic information:
public class DiagnosticReport : Report
{
public string Component { get; set; }
public string Status { get; set; }
public string ErrorMessage { get; set; }
public Dictionary<string, string> ConfigurationSettings { get; set; }
public Dictionary<string, string> HealthChecks { get; set; }
public DiagnosticReport() : base("Diagnostic")
{
ConfigurationSettings = new Dictionary<string, string>();
HealthChecks = new Dictionary<string, string>();
}
public override void Build()
{
Properties.Add("component", Component);
Properties.Add("status", Status);
Properties.Add("error_message", ErrorMessage ?? "None");
Properties.Add("timestamp", DateTime.UtcNow);
Properties.Add("application_version", GetApplicationVersion());
// Add configuration settings
var configJson = JsonSerializer.Serialize(ConfigurationSettings);
Properties.Add("configuration", configJson);
// Add health check results
var healthJson = JsonSerializer.Serialize(HealthChecks);
Properties.Add("health_checks", healthJson);
base.Build();
}
private string GetApplicationVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
// Usage
public async Task ReportDatabaseConnectionIssue()
{
var report = new DiagnosticReport
{
Component = "Database",
Status = "Error",
ErrorMessage = "Connection timeout after 30 seconds"
};
report.ConfigurationSettings.Add("connection_string", "Server=***;Database=***");
report.ConfigurationSettings.Add("timeout", "30");
report.ConfigurationSettings.Add("retry_count", "3");
report.HealthChecks.Add("database_ping", "Failed");
report.HealthChecks.Add("dns_resolution", "Success");
report.HealthChecks.Add("network_connectivity", "Success");
await _reporting.SendAsync(_userKey, report);
}Adding Environment and System Information
You can enhance your custom reports with environment and system information using the built-in collectors:
using Babel.Licensing.Reports;
using Babel.Licensing.Reports.Collectors;
public class EnhancedCustomReport : Report
{
private readonly EnvironmentReportOptions _envOptions;
private readonly SystemReportOptions _sysOptions;
public string CustomData { get; set; }
public EnhancedCustomReport() : base("EnhancedCustom")
{
// Configure what environment/system info to collect
_envOptions = new EnvironmentReportOptions
{
CollectApplicationInformation = true,
CollectLoadedAssemblies = true,
CollectEnvironmentVariables = false
};
_sysOptions = new SystemReportOptions
{
CollectOsInformation = true,
CollectProcessorInformation = true,
CollectMemoryInformation = true
};
}
public override void Build()
{
// Add your custom properties
Properties.Add("custom_data", CustomData);
// Add environment information
var envCollector = new EnvironmentCollector(Properties, _envOptions);
envCollector.Collect();
// Add system information
var sysCollector = new SystemCollector(Properties, _sysOptions);
sysCollector.Collect();
base.Build();
}
}Adding Charts to Reports
Custom reports can include interactive charts that are automatically rendered in the Babel Licensing web application. Use the ReportPlot class to create visualizations of your report data.
Chart Types
The ReportPlotType enumeration supports four chart types:
- Pie: Display distribution data as a pie chart
- Line: Show trends over time with line charts
- Bar: Compare categorical data with bar charts
- Area: Visualize cumulative data with area charts
Creating a Pie Chart
Pie charts are ideal for showing distribution of categories:
using Babel.Licensing.Reports;
// Create a pie chart for attack type distribution
var attackDistribution = new ReportPlot(ReportPlotType.Pie, "Attack Type Distribution")
.SetHeight(400)
.AddSeries("Attacks", new List<object>
{
new { type = "SQL Injection", count = 45 },
new { type = "XSS", count = 32 },
new { type = "CSRF", count = 18 },
new { type = "Path Traversal", count = 12 },
new { type = "Brute Force", count = 8 }
});
// Configure which fields to use for the chart
attackDistribution.Series[0].ArgumentField = "type"; // Category labels
attackDistribution.Series[0].ValueField = "count"; // Values
// Add to report properties
Properties.Add("attackDistribution", attackDistribution.ToDictionary());Creating a Line Chart
Line charts are perfect for showing trends over time:
// Create a line chart comparing attack attempts over time
var attackTimeline = new ReportPlot(ReportPlotType.Line, "Attack Attempts Over Time")
.SetAxisTitles("Time", "Attack Count")
.SetHeight(350)
.AddSeries("Today", new List<object>
{
new { hour = "00:00", attempts = 5 },
new { hour = "04:00", attempts = 3 },
new { hour = "08:00", attempts = 15 },
new { hour = "12:00", attempts = 28 },
new { hour = "16:00", attempts = 22 },
new { hour = "20:00", attempts = 18 }
})
.AddSeries("Yesterday", new List<object>
{
new { hour = "00:00", attempts = 4 },
new { hour = "04:00", attempts = 2 },
new { hour = "08:00", attempts = 12 },
new { hour = "12:00", attempts = 20 },
new { hour = "16:00", attempts = 25 },
new { hour = "20:00", attempts = 15 }
});
// Configure field mappings for each series
attackTimeline.Series[0].ArgumentField = "hour";
attackTimeline.Series[0].ValueField = "attempts";
attackTimeline.Series[1].ArgumentField = "hour";
attackTimeline.Series[1].ValueField = "attempts";
Properties.Add("attackTimeline", attackTimeline.ToDictionary());Creating a Bar Chart
Bar charts work well for comparing discrete categories:
// Create a bar chart for severity levels
var severityChart = new ReportPlot(ReportPlotType.Bar, "Attack Severity Levels")
.SetAxisTitles("Severity", "Incidents")
.SetHeight(350)
.AddSeries("Last 24h", new List<object>
{
new { severity = "Critical", incidents = 8 },
new { severity = "High", incidents = 25 },
new { severity = "Medium", incidents = 42 },
new { severity = "Low", incidents = 30 }
});
severityChart.Series[0].ArgumentField = "severity";
severityChart.Series[0].ValueField = "incidents";
Properties.Add("severityChart", severityChart.ToDictionary());Creating an Area Chart
Area charts are useful for showing cumulative data across multiple series:
// Create an area chart for attack sources by region
var ipSourceChart = new ReportPlot(ReportPlotType.Area, "Attack Sources by Region")
.SetAxisTitles("Hour", "Unique IPs")
.SetHeight(350)
.AddSeries("Asia", new List<object>
{
new { hour = "00:00", ips = 12 },
new { hour = "06:00", ips = 18 },
new { hour = "12:00", ips = 25 },
new { hour = "18:00", ips = 22 }
})
.AddSeries("Europe", new List<object>
{
new { hour = "00:00", ips = 8 },
new { hour = "06:00", ips = 15 },
new { hour = "12:00", ips = 20 },
new { hour = "18:00", ips = 18 }
})
.AddSeries("Americas", new List<object>
{
new { hour = "00:00", ips = 5 },
new { hour = "06:00", ips = 10 },
new { hour = "12:00", ips = 15 },
new { hour = "18:00", ips = 12 }
});
// Configure field mappings for all series
foreach (var series in ipSourceChart.Series)
{
series.ArgumentField = "hour";
series.ValueField = "ips";
}
Properties.Add("ipSourceChart", ipSourceChart.ToDictionary());ReportPlot API Reference
Constructor
new ReportPlot(ReportPlotType type, string title)Methods
| Method | Description |
|---|---|
SetHeight(int height) | Sets the chart height in pixels |
SetAxisTitles(string xAxis, string yAxis) | Sets the X and Y axis labels |
AddSeries(string name, List<object> data) | Adds a data series to the chart |
ToDictionary() | Converts the chart to a dictionary for report properties |
Series Properties
| Property | Description |
|---|---|
ArgumentField | The field name in data objects to use as X-axis values (categories) |
ValueField | The field name in data objects to use as Y-axis values (measurements) |
Complete Example with Multiple Charts
Here’s a complete custom report class that includes multiple visualizations:
public class SecurityDashboardReport : Report
{
public SecurityDashboardReport() : base("SecurityDashboard") { }
public override void Build()
{
// Basic properties
Properties.Add("generated_at", DateTime.UtcNow);
Properties.Add("machine_name", Environment.MachineName);
// Pie chart: Attack distribution
var attackTypes = new ReportPlot(ReportPlotType.Pie, "Attack Types")
.SetHeight(400)
.AddSeries("Types", GetAttackTypeData());
attackTypes.Series[0].ArgumentField = "type";
attackTypes.Series[0].ValueField = "count";
Properties.Add("attackTypes", attackTypes.ToDictionary());
// Line chart: Attacks over time
var timeline = new ReportPlot(ReportPlotType.Line, "Attack Timeline")
.SetAxisTitles("Time", "Attacks")
.SetHeight(350)
.AddSeries("Attacks", GetTimelineData());
timeline.Series[0].ArgumentField = "time";
timeline.Series[0].ValueField = "count";
Properties.Add("timeline", timeline.ToDictionary());
// Bar chart: Severity breakdown
var severity = new ReportPlot(ReportPlotType.Bar, "Severity Distribution")
.SetAxisTitles("Level", "Count")
.SetHeight(350)
.AddSeries("Severity", GetSeverityData());
severity.Series[0].ArgumentField = "level";
severity.Series[0].ValueField = "count";
Properties.Add("severity", severity.ToDictionary());
base.Build();
}
private List<object> GetAttackTypeData() => new List<object>
{
new { type = "SQL Injection", count = 45 },
new { type = "XSS", count = 32 },
new { type = "CSRF", count = 18 }
};
private List<object> GetTimelineData() => new List<object>
{
new { time = "08:00", count = 15 },
new { time = "12:00", count = 28 },
new { time = "16:00", count = 22 }
};
private List<object> GetSeverityData() => new List<object>
{
new { level = "Critical", count = 8 },
new { level = "High", count = 25 },
new { level = "Medium", count = 42 }
};
}When this report is viewed in the Babel Licensing web application, all charts are rendered as interactive visualizations, allowing you to analyze your custom report data visually.
Report Encryption
Protect sensitive data in your custom reports with encryption:
var reporting = new BabelReporting();
reporting.Configuration.EncryptionKey = "your-secret-encryption-key";
var report = new SecurityEventReport
{
EventType = "DataBreach",
Severity = "Critical",
// ... sensitive data
};
// Report will be automatically encrypted before transmission
var result = await reporting.SendAsync(userKey, report);The server must be configured with the same encryption key to decrypt the report.
Customizing Before Send
Modify any report (including custom reports) before it’s sent:
reporting.BeforeSendReport += (s, e) =>
{
// Add global properties to all reports
e.Report.Properties.Add("environment", "production");
e.Report.Properties.Add("region", "us-east-1");
e.Report.Properties.Add("deployment_id", DeploymentId);
// Add conditional logic
if (e.Report.Name == "SecurityEvent")
{
e.Report.Properties.Add("security_version", SecurityFrameworkVersion);
}
// Validate before sending
if (!IsValidReport(e.Report))
{
e.Cancel = true; // Don't send invalid reports
}
};Report Structure
All reports follow a consistent JSON structure:
{
"name": "HackDetection",
"version": "1.0",
"date": "2025-01-22T10:30:00Z",
"encrypted": false,
"content": {
"properties": {
"machine_name": "DESKTOP-ABC123",
"hacking_type": "SQL Injection",
"attack_method": "Malicious Input",
"detection_details": "Detected SQL keywords in user input",
"detected_at": "2025-01-22T10:30:00Z"
}
}
}Server-Side Processing
When your custom report arrives at the Babel Licensing Service:
- Validation: Report name and content are validated
- Decryption: Encrypted content is decrypted using the configured key
- Parsing: Properties are extracted from the content
- Storage: Report is stored in the database with metadata
- Indexing: Properties are indexed for searchability
- Webhooks:
ReportCreatedevents are dispatched for external integrations
You can query custom reports through the Babel Licensing Service API:
GET /api/reports?name=HackDetection&from=2025-01-01&to=2025-01-31Best Practices
1. Choose Meaningful Report Names
Use descriptive, consistent names for your report types:
// GOOD: Clear and specific
public class UserAuthenticationFailureReport : Report
{
public UserAuthenticationFailureReport() : base("UserAuthFailure") { }
}
// BAD: Generic and unclear
public class CustomReport1 : Report
{
public CustomReport1() : base("Report1") { }
}2. Include Timestamps
Always include timestamps for temporal analysis:
public override void Build()
{
Properties.Add("timestamp", DateTime.UtcNow);
Properties.Add("event_time", eventOccurredAt);
base.Build();
}3. Add Context Information
Include enough context to make the report actionable:
Properties.Add("user_id", userId);
Properties.Add("session_id", sessionId);
Properties.Add("request_id", requestId);
Properties.Add("correlation_id", correlationId);4. Use Structured Data
Prefer structured data over freeform text for better analysis:
// GOOD: Structured
Properties.Add("response_time_ms", 1250);
Properties.Add("status_code", 200);
Properties.Add("endpoint", "/api/users");
// BAD: Unstructured
Properties.Add("message", "Request to /api/users took 1250ms and returned 200");5. Handle Errors Gracefully
Don’t let report failures impact application functionality:
try
{
var report = new CustomReport { /* ... */ };
await _reporting.SendAsync(_userKey, report);
}
catch (Exception ex)
{
// Log error but don't throw
_logger.LogWarning(ex, "Failed to send custom report");
}6. Batch Related Events
Instead of sending many individual reports, batch related events:
public class BatchEventReport : Report
{
public List<EventData> Events { get; set; } = new List<EventData>();
public BatchEventReport() : base("BatchEvent") { }
public override void Build()
{
Properties.Add("event_count", Events.Count);
Properties.Add("events", JsonSerializer.Serialize(Events));
Properties.Add("batch_time", DateTime.UtcNow);
base.Build();
}
}7. Sanitize Sensitive Data
Never include passwords, API keys, or personally identifiable information:
public override void Build()
{
// GOOD: Sanitized
Properties.Add("username", SanitizeUsername(username));
Properties.Add("email", HashEmail(email));
// BAD: Sensitive data
// Properties.Add("password", password); // NEVER DO THIS
// Properties.Add("credit_card", cardNumber); // NEVER DO THIS
base.Build();
}
private string SanitizeUsername(string username)
{
// Return first 3 chars + asterisks
if (username.Length <= 3) return "***";
return username.Substring(0, 3) + new string('*', username.Length - 3);
}Complete Example
Here’s a complete example combining custom reports with proper error handling and configuration:
using Babel.Licensing.Reports;
using Babel.Licensing.Services;
using Microsoft.Extensions.Logging;
public class ApplicationReportingService
{
private readonly BabelReporting _reporting;
private readonly ILogger<ApplicationReportingService> _logger;
private readonly string _userKey;
public ApplicationReportingService(
IConfiguration configuration,
ILogger<ApplicationReportingService> logger)
{
_logger = logger;
_userKey = configuration["Licensing:UserKey"];
_reporting = new BabelReporting();
_reporting.Configuration.ServiceUrl = configuration["Licensing:ServiceUrl"];
_reporting.Configuration.ClientId = "EquiTrack v2.0";
_reporting.Configuration.EncryptionKey = configuration["Licensing:EncryptionKey"];
_reporting.BeforeSendReport += OnBeforeSendReport;
_reporting.AfterSendReport += OnAfterSendReport;
}
private void OnBeforeSendReport(object sender, BeforeSendReportEventArgs e)
{
// Add global context to all reports
e.Report.Properties["application_version"] = GetApplicationVersion();
e.Report.Properties["environment"] = GetEnvironment();
}
private void OnAfterSendReport(object sender, AfterSendReportEventArgs e)
{
if (e.Error != null)
{
_logger.LogError(e.Error, "Failed to send {ReportType} report", e.Report.Name);
}
else
{
_logger.LogInformation("Sent {ReportType} report: {ReportId}",
e.Report.Name, e.Result.ReportUid);
}
}
public async Task ReportSecurityEvent(
string eventType,
string severity,
string details)
{
try
{
var report = new SecurityEventReport
{
EventType = eventType,
Severity = severity,
SourceIP = GetClientIpAddress(),
UserAgent = GetUserAgent(),
RequestPath = GetCurrentRequestPath()
};
report.AdditionalData["details"] = details;
var result = await _reporting.SendAsync(_userKey, report);
if (!result.Success)
{
_logger.LogWarning("Security event report failed: {Error}",
result.Error?.Message);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception sending security event report");
}
}
private string GetApplicationVersion() =>
Assembly.GetExecutingAssembly().GetName().Version.ToString();
private string GetEnvironment() =>
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
private string GetClientIpAddress() => /* implementation */;
private string GetUserAgent() => /* implementation */;
private string GetCurrentRequestPath() => /* implementation */;
}Related Topics
- Exception Reports - Built-in exception reporting
- License Reports - Track license usage
- Floating License - Generate user keys for reporting