Skip to Content
New release 11.5 available 🎉
LicensingReportingCustom Reports

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.

  1. Open your preferred Git client or the command line.
  2. Clone the GitHub repository by executing the following command:
git clone https://github.com/babelfornet/console-custom-report-example.git
  1. Once the repository is cloned, navigate to the project directory:
cd console-custom-report-example
  1. 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:

  1. BabelReporting Initialization: The example code initializes a BabelReporting object 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; }
  1. 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.

  2. Custom Report Class: The HackDetectionReport class inherits from Report and defines custom properties for tracking security events. It uses ReportPlot to 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(); } }
  1. BeforeSendReport Event: The example subscribes to the BeforeSendReport event, which allows you to add additional properties to reports before they are sent. In this event handler, custom properties like cmdline (command line) and username (current user’s name) are added.

  2. Sending the Report: The SendAsync method 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

MethodDescription
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

PropertyDescription
ArgumentFieldThe field name in data objects to use as X-axis values (categories)
ValueFieldThe 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:

  1. Validation: Report name and content are validated
  2. Decryption: Encrypted content is decrypted using the configured key
  3. Parsing: Properties are extracted from the content
  4. Storage: Report is stored in the database with metadata
  5. Indexing: Properties are indexed for searchability
  6. Webhooks: ReportCreated events 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-31

Best 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"); }

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 */; }
Last updated on