لاگ کردن رویداد ها و مدیریت خطاها در Asp.NET
اگر شما هم یک برنامه نویس وب هستید چه تازه کار و چه حرفه ای با خطاهای مختلفی روبرو خواهید شد ، شما ناگزیر هستید این خطا ها را مدیریت کنید در غیر این صورت حیات پروژه وب شما در خطر خواهد افتاد .
روش های متعددی برای مدیریت خطا وجود دارد و برنامه نویسان به اقتضای سلیقه و پروژه روش های مختلفی را پیاده سازی می کنند ، بنده در این مقاله یک روش مدیریت خطا را که در چند پروژه مهم از آن استفاده کردم ارائه خواهم داد ، این روش برای پروژه های بزرگ و حجم زیاد داده به خوبی پاسخگو خواهد بود .
علاوه بر لاگ کردن خطا ها شما می توانید تمامی اتفاقاتی که در وب سایت می افتد را لاگ کنید ، از ورود کاربر گرفته تا هر عملی که توسط کاربر انجام می شود ، این اطلاعات به مدیر سایت کمک زیادی خواهد کرد .
قبل از هرچیز بد نیست بدانید که به هیچ وجه نباید متن خطا را به کاربران سایت نمایش دهید ، این یکی از مهمترین اصول امنیت در وب سایت شماست ، دلیل آن هم این است که ممکن است در متن خطا اطلاعاتی باشد که به وسیله این اطلاعات سایت شما به راحتی هک شود .
بنابراین پس از بارگزاری پروژه و نهایی سازی آن ابتدا خاصیت mode تگ CustomeErrors را برابر Off یا RemoteOnly قرار دهید :
<customErrors mode="Off"></customErrors>
و خاصیت debug تگ Compilation را نیز false نمایید :
<compilation debug="false"></compilation>
سناریو کار پس از بروز یک خطا به این صورت است که ابتدا متن خطا را لاگ (Log) کرده و سپس کاربر را به یک صفحه Html هدایت می کنیم ، در این صفحه شما می توانید از کاربر عذر خواهی کنید و اطمینان دهید که متن خطا برای مدیران سایت ارسال شده و در اولین فرصت خطا بر طرف خواهد شد .
لاگ نمودن خطا در دو حالت قابل انجام است :
- در فایل Global.asax
- در بلاک Try , Catch
در این دو حالت متن خطا ذخیره شده و بسته به سیاست شما پروژه به کار خود ادامه داده یا به یک صفحه پیغام هدایت می شود .
نکته اساسی نحوه ذخیره لاگ می باشد ، بسیاری از برنامه نویسان لاگ ها را در فایل های متنی ذخیره می کنند ولی تجربه به بنده نشان داده که بهترین محل برای ذخیره لاگ دیتابیس می باشد .
پیشنهاد می کنم یک دیتابیس جداگانه برای لاگ ایجاد کنید ، دلیل این امر این است که پس از مرور زمان این دیتابیس بزرگ شده و جابجایی و بکاپ گرفتن از آن مشکل خواهد شد بنابراین در صورتی که جدول لاگ جزئی از دیتابیس اصلی پروژه شما باشد به مشکل بزرگی بر می خورید .
یکی از مهمترین مضایای استفاده از دیتابیس قابلیت Query گرفتن از آن می باشد ، شما براحتی با یک QUery ساده می توانید خطا ها را با یک شرط خاص بدست آورید ، یا تمام کارهایی که یک کاربر انجام داده است را لیست نمایید .
یک دیتابیس به نام Log ساخته و جدول TBLLogs را در آن ایجاد کنید :
CREATE TABLE [dbo].[TBLLogs](
[LogID] [bigint] IDENTITY(1,1) NOT NULL,
[UserID] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LogType] [tinyint] NOT NULL,
[LogSource] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LogName] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LogText] [nvarchar](max) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[URL] [nvarchar](500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[IPAddress] [nvarchar](15) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Browser] [nvarchar](15) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[AddDate] [datetime] NOT NULL CONSTRAINT [DF_TBLLogs_AddDate] DEFAULT (getdate()),
CONSTRAINT [PK_TBLLogs] PRIMARY KEY CLUSTERED
(
[LogID] DESC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
توضیح فیلد ها :
LogID : کد واحد خطا که به صورت اتوماتیک ایجاد شده و فیلد کلید جدول می باشد
UserID : کد کاربری می باشد ، در صورت پروژه شما سیستم ورود و تشخیص هویت داشته باشد کد کاربری را در آن ذخیره می کنید در غیر این صورت آن را 0 وارد کنید
LogType : نوع لاگ را مشخص می کند : 1- لاگ رویداد ها 2- لاگ خطاها
LogSource : منبع رخداد خطا را در این فیلد ذخیره می کنیم ، منبع رخداد می تواند ساب روتین مربوط یا حتی نام صفحه یا ماژول باشد
LogName : در صورتی که لاگ رویداد ها را ذخیره می کنید نام لاگ برای شما بسیار مفید خواهد بود مثلا "AddNewUser" یک نام برای لاگ درج یک کاربر جدید است
LogText : متن کامل لاگ یا متن کامل خطا در این فیل ذخیره می شود
URL : آدرس صفحه در این فیلد ذخیره می شود تا بعدا متوجه شوید که در کدام صفحه این اتفاق افتاده است
IPAddress: آیپی کاربر در این فیل ذخیره می شود
Browser : نوع مرورگر کاربر در این فیلد ذخیره می شود
AddDate : تاریخ و زمان درج رکورد هم در این فیلد ذخیره خواهد شد
و همچنین یک Stored Procedure برای درج رکورد به صورت زیر ایجاد نمایید :
CREATE PROCEDURE [dbo].[AddNewLog]
(
@UserID nvarchar(50),
@LogType tinyint,
@LogSource nvarchar(50),
@LogName nvarchar(50),
@URL nvarchar(500),
@LogText nvarchar(MAX),
@IPAddress nvarchar(15),
@Browser nvarchar(15)
)
AS
SET NOCOUNT OFF;
INSERT INTO TBLLogs
(UserID, LogType, LogSource, LogName, URL, LogText,IPAddress,Browser)
VALUES (@UserID,@LogType,@LogSource,@LogName,@URL,@LogText,@IPAddress,@Browser)
کار ما با بانک اطلاعاتی تقریبا به پایان رسیده ، به سراغ پروژه رفته و یک کلاس به نام Logger در آن به صورت زیر ایجاد می کنیم :
using System;
using System.Web;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Configuration;
using System.Net.Mail;
using System.Net;
public class Logger
{
public enum LogType
{
EventLog = 1,
ErrorLog = 2
}
public static void AddNewLog(LogType logType, string logSource, string logName, string logText)
{
HttpContext currentContext = HttpContext.Current;
string userId = "0";
string url = currentContext.Request.Url.PathAndQuery.ToString();
string ipAddress = currentContext.Request.UserHostAddress;
string browser = currentContext.Request.Browser.Browser + " " + HttpContext.Current.Request.Browser.Version;
bool maiErrors = Convert.ToBoolean(ConfigurationManager.AppSettings["MailErrors"]);
if (currentContext.User.Identity.IsAuthenticated)
userId = currentContext.User.Identity.Name;
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["LogConStr"].ConnectionString))
{
SqlCommand cmd = new SqlCommand("AddNewLog", con);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@UserID", userId);
cmd.Parameters.AddWithValue("@LogType", logType);
cmd.Parameters.AddWithValue("@LogSource", logSource);
cmd.Parameters.AddWithValue("@LogName", logName);
cmd.Parameters.AddWithValue("@URL", url);
cmd.Parameters.AddWithValue("@LogText", logText);
cmd.Parameters.AddWithValue("@IPAddress", ipAddress);
cmd.Parameters.AddWithValue("@Browser", browser);
try
{
con.Open();
cmd.ExecuteNonQuery();
}
finally
{
con.Close();
}
}
if (logType == LogType.ErrorLog && maiErrors)
{
SendMail(logName, string.Format("UserID:{0} <br> LogSource:{1} <br> LogName:{2} <br> Url:{3} <br> LogText:{4} <br> IpAddress:{5} <br> Browser:{6}",
userId, logSource, logName, url, logText, ipAddress, browser));
}
}
private static void SendMail(string subject, string body)
{
SmtpClient MyMail = new SmtpClient();
MailMessage MyMsg = new MailMessage();
MyMail.Host = "mail.yourdomain.com";
MyMsg.To.Add(new MailAddress("ReceiverEmailAddres"));
MyMsg.Subject = subject;
MyMsg.SubjectEncoding = Encoding.UTF8;
MyMsg.IsBodyHtml = true;
MyMsg.From = new MailAddress("SenderEmailAddresss");
MyMsg.BodyEncoding = Encoding.UTF8;
MyMsg.Body = body;
MyMail.UseDefaultCredentials = false;
NetworkCredential MyCredentials = new NetworkCredential("SenderEmailAddress", "SenderEmailPassword");
MyMail.Credentials = MyCredentials;
try
{
MyMail.Send(MyMsg);
}
catch
{
}
}
}
کلاس فوق از دو متد ساده تشکیل شده است
متد AddNewLog وظیفه ایجاد اطلاعات و ذخیره آن در بانک را انجام می دهد ، در صورتی که مقدار MailErrors در وبکانفیگ true باشد متن خطا به صورت ایمیل هم ارسال خواهد شد .
دقت نمایید که رشته اتصال (ConnectionString) در وبکانفیگ ذخیره شده است :
<connectionStrings>
<add connectionString="Data Source=(local);Initial Catalog=Log;Integrated Security=True" name="LogConStr" providerName="System.Data.SqlClient" />
</connectionStrings>
و مقدار MailErrors برای فعال و غیر فعال کردن ارسال ایمیل :
<appSettings>
<add key="MailErrors" value="false" />
</appSettings>
اکنون شما در هر جایی می توانید از این متد استفاده نمایید ، اولین نمونه استفاده آن ذخیره لاگ خطا ها در Global.asax می باشد ،اگر در پروژه شما فایل این فایل وجود ندارد آن را از طریق Add new Item و سپس Global Application class انتخاب و به پروژه اضافه نمایید ، سپس در رویداد Application_Error از متد فوق به صورت زیر استفاده می کنیم :
void Application_Error(object sender, EventArgs e)
{
Logger.AddNewLog(Logger.LogType.ErrorLog, sender.ToString(), "Error", Server.GetLastError().Message);
Response.Redirect("Error.htm");
}
همانطور که ملاحظه می کنید پس از لاگ کردن خطا کاربر به صفحه Error.html هدایت خواهد شد .
برای بیشتر روشن شدن مطلب به مثال زیر توجه کنید :
یک صفحه تستی به پروژه اضافه کردیم .
4 باتن بر روی صفحه قرار دادیم :
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button1" />
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button2" />
<asp:Button ID="Button3" runat="server" OnClick="Button3_Click" Text="Error Button" />
<asp:Button ID="Button4" runat="server" OnClick="Button4_Click" Text="Catch Button" />
و رویداد هر کدام را هندل نمودیم :
protected void Button1_Click(object sender, EventArgs e)
{
// Log event
Logger.AddNewLog(Logger.LogType.EventLog, sender.ToString(), "Button1", "Button 1 Was Clicked!");
}
protected void Button2_Click(object sender, EventArgs e)
{
// Log event
Logger.AddNewLog(Logger.LogType.EventLog, sender.ToString(), "Button2", "Button 2 Was Clicked!");
}
protected void Button3_Click(object sender, EventArgs e)
{
// It Makes Error
string test = Request["test"].ToString();
}
protected void Button4_Click(object sender, EventArgs e)
{
try
{
object test = null;
Response.Write(test.ToString());
// Log event
Logger.AddNewLog(Logger.LogType.ErrorLog, sender.ToString(), "Button4", "Button 4 Was Clicked!");
}
catch (Exception exp)
{
// Log error
Logger.AddNewLog(Logger.LogType.ErrorLog, sender.ToString(), "Button4", exp.Message);
}
}
در رویداد مربوط به کلیک شدن Button1 و Button2 فقط کلیک شدن آن لاگ شده است ، در رویداد مربوط به کلیک شدن Button3 یک کد اشتباه که باعث بروز Null Exception می شود نوشتیم ، اگر بر روی این باتن کلیک کنید سیستم شما را به صفحه Error.html هدایت کرده و لاگ خطا ذخیره خواهد شد
به همین ترتیب در مورد Button4 نیز یک کد که باعث بروز خطا می شود نوشتیم ولی در این بار با استفاده از بلاک try و catch خودمان خطا را هندل کرده و به صورت دلخواه لاگ کردیم ، با این روش کاربر به صفحه Error.html هدایت نشد .
فایل کامل مقاله را میتوانید از این لینک دانلود نمایید