Skip to content

Instantly share code, notes, and snippets.

@pedroinfo
Last active December 9, 2025 13:32
Show Gist options
  • Select an option

  • Save pedroinfo/126fe2597253d7e087af1ff93f35210d to your computer and use it in GitHub Desktop.

Select an option

Save pedroinfo/126fe2597253d7e087af1ff93f35210d to your computer and use it in GitHub Desktop.
WebFormsOTELLogger
LogService.LogBusiness(
action: "ButtonClicked",
page: "/Clientes.aspx",
userId: "123",
entityId: "456",
details: "User clicked the SAVE button"
);
LogService.LogBusiness(
action: "PageOpened",
page: "/Pedidos.aspx",
userId: "789",
details: "User opened the orders page"
);
try
{
throw new Exception("Test exception");
}
catch (Exception ex)
{
LogService.LogError(ex);
}
Output
{"timestamp":"2025-12-09T15:30:12.345Z","severity_text":"Info","severity_number":9,"body":"ButtonClicked","attributes":{"eventType":"BUSINESS","action":"ButtonClicked","page":"/Clientes.aspx","userId":"123","entityId":"456","details":"User clicked the SAVE button"}}
{"timestamp":"2025-12-09T15:31:00.000Z","severity_text":"Info","severity_number":9,"body":"OK","attributes":{"eventType":"HEARTBEAT"}}
{"timestamp":"2025-12-09T15:32:11.456Z","severity_text":"Error","severity_number":17,"body":"Test exception","attributes":{"eventType":"UnhandledException","details":"stacktrace here"}}
using System;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
HeartbeatService.Start();
}
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
LogService.LogError(ex);
Server.ClearError();
}
}
using System;
using System.Data.SqlClient;
using System.Timers;
namespace WebFormsOTELLogger.Logging
{
public static class HeartbeatService
{
private static Timer _heartbeatTimer;
private static DateTime _applicationStartTime = DateTime.UtcNow;
private static string _connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
private static int _heartbeatIntervalSeconds;
private static bool _initialized = false;
// --------------------------------------------------------------------
// Public method to explicitly start heartbeat
// --------------------------------------------------------------------
public static void Start()
{
if (_initialized) return; // prevent multiple starts
int.TryParse(System.Configuration.ConfigurationManager.AppSettings["HeartbeatIntervalSeconds"], out _heartbeatIntervalSeconds);
if (_heartbeatIntervalSeconds <= 0) _heartbeatIntervalSeconds = 60;
_heartbeatTimer = new Timer(_heartbeatIntervalSeconds * 1000);
_heartbeatTimer.AutoReset = true;
_heartbeatTimer.Elapsed += (s, e) => SendHeartbeat();
_heartbeatTimer.Start();
_initialized = true;
}
// --------------------------------------------------------------------
// Internal method to send heartbeat
// --------------------------------------------------------------------
private static void SendHeartbeat()
{
bool dbHealthy = false;
bool appAlive = true; // basic health: heartbeat running
try
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
dbHealthy = true;
}
}
catch
{
dbHealthy = false;
}
var uptime = DateTime.UtcNow - _applicationStartTime;
var attributes = new
{
eventType = "HEARTBEAT",
serviceName = "WebFormsOTELLogger",
environment = "prod",
instanceId = Environment.MachineName,
uptimeSeconds = uptime.TotalSeconds,
dbHealthy,
appAlive,
version = "1.0.0"
};
LogService.WriteOtelLogPublic(OTelSeverity.Info, "Heartbeat", attributes);
}
}
}
using System;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;
namespace WebFormsOTELLogger.Logging
{
public static class LogService
{
private static JavaScriptSerializer _json = new JavaScriptSerializer();
private static string _logDirectory;
private static string _archiveDirectory;
private static int _logRetentionDays;
private static bool _initialized = false;
// --------------------------------------------------------------------
// Public method to initialize log service
// --------------------------------------------------------------------
public static void Start()
{
if (_initialized) return;
_logDirectory = HttpContext.Current.Server.MapPath(
System.Configuration.ConfigurationManager.AppSettings["LogDirectory"]
);
_archiveDirectory = Path.Combine(_logDirectory, "archive");
if (!int.TryParse(System.Configuration.ConfigurationManager.AppSettings["LogRetentionDays"], out _logRetentionDays))
_logRetentionDays = 90;
Directory.CreateDirectory(_logDirectory);
Directory.CreateDirectory(_archiveDirectory);
CleanupOldLogs();
_initialized = true;
}
// --------------------------------------------------------------------
// Log an unhandled exception
// --------------------------------------------------------------------
public static void LogError(Exception ex)
{
var attributes = new
{
eventType = "UnhandledException",
details = ex.ToString()
};
WriteOtelLog(OTelSeverity.Error, ex.Message, attributes);
}
// --------------------------------------------------------------------
// Log a business event
// --------------------------------------------------------------------
public static void LogBusiness(string action, string page = "", string userId = "", string entityId = "", string details = "")
{
var attributes = new
{
eventType = "BUSINESS",
action,
page,
userId,
entityId,
details
};
WriteOtelLog(OTelSeverity.Info, action, attributes);
}
// --------------------------------------------------------------------
// Public method for HeartbeatService to write OTEL log
// --------------------------------------------------------------------
public static void WriteOtelLogPublic(OTelSeverity severity, string body, object attributes)
{
WriteOtelLog(severity, body, attributes);
}
// --------------------------------------------------------------------
// Internal method to write OTEL JSON log with daily rotation
// --------------------------------------------------------------------
private static void WriteOtelLog(OTelSeverity severity, string body, object attributes)
{
if (!_initialized) return;
try
{
var logObj = new
{
timestamp = DateTime.UtcNow.ToString("o"),
severity_text = severity.ToSeverityText(),
severity_number = (int)severity,
body,
attributes
};
string currentFile = Path.Combine(_logDirectory, "current.log");
File.AppendAllText(currentFile, _json.Serialize(logObj) + Environment.NewLine);
RotateDaily(currentFile);
}
catch
{
// Prevent logging errors from breaking the application
}
}
// --------------------------------------------------------------------
// Rotate logs daily into archive folder
// --------------------------------------------------------------------
private static void RotateDaily(string currentFile)
{
if (!File.Exists(currentFile)) return;
DateTime lastWrite = File.GetLastWriteTime(currentFile);
if (lastWrite.Date < DateTime.Now.Date)
{
string archiveFile = Path.Combine(_archiveDirectory, lastWrite.ToString("yyyy-MM-dd") + ".log");
if (File.Exists(archiveFile))
File.Delete(archiveFile);
File.Move(currentFile, archiveFile);
}
}
// --------------------------------------------------------------------
// Cleanup logs older than configured retention days
// --------------------------------------------------------------------
private static void CleanupOldLogs()
{
foreach (var file in Directory.GetFiles(_archiveDirectory, "*.log"))
{
if (File.GetLastWriteTime(file) < DateTime.Now.AddDays(-_logRetentionDays))
File.Delete(file);
}
}
}
}
// OTEL severity levels (main levels only)
public enum OTelSeverity
{
Trace = 1,
Debug = 5,
Info = 9,
Warn = 13,
Error = 17,
Fatal = 21
}
// Extension method to map severity enum to text
public static class OTelSeverityExtensions
{
public static string ToSeverityText(this OTelSeverity sev)
{
switch (sev)
{
case OTelSeverity.Trace: return "TRACE";
case OTelSeverity.Debug: return "DEBUG";
case OTelSeverity.Info: return "INFO";
case OTelSeverity.Warn: return "WARN";
case OTelSeverity.Error: return "ERROR";
case OTelSeverity.Fatal: return "FATAL";
default: return "INFO";
}
}
}
<configuration>
<appSettings>
<!-- Log directory (relative or absolute path) -->
<add key="LogDirectory" value="~/App_Data/Logs" />
<!-- Heartbeat interval in seconds -->
<add key="HeartbeatIntervalSeconds" value="60" />
<!-- Number of days to keep archived logs -->
<add key="LogRetentionDays" value="90" />
</appSettings>
</configuration>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment