Log記錄 - 前言
我們之前已經寫過數篇討論檔案上傳下載的文章,如「檔案上傳下載筆記」,不過還有加強的空間,例如今天的主題「Log記錄」。Log問題,在以前,可大可小,例如,我們寫網頁,可以什麼都不做,IIS還是會幫我們留一些記錄,我們還可以使用超強大Log流量分析軟體 AWStats來分析產出美美的報表。
但今日,個人資料處理法已經快要正式立法完成,我們必須讓走過我們的系統的使用者都留下Log記錄,以防日後舉證之需要。但不經整理的IIS Log記錄,法官應該是不可能接受,一是太大,二是太亂。類似 AWStats 的Log流量分析軟體,整理後的資訊,對於法律實際上效用有限,所以我們必須除了IIS Log記錄外,最好還能提供整個使用者的Log記錄。
Log記錄的方式
要留下Log記錄,基本方向可以有:- System Log記錄
- File Log記錄
- Database Log記錄
- Log Server
File Log記錄指的是透過 System.IO 寫一支程式,將你要的Log記錄到一個純文字檔,例如, FileLogs.txt,因為程式自己寫,你能控制什麼是你要,什麼是你不要,與System Log記錄相比,可以免除太大、太亂的問題。檔案太大還是有可能,這部分可能要寫程式自動處理,或是手動處理。
Database Log記錄,即將Log記錄直接記錄在Database之中,與System Log記錄與File Log記錄相較,Database本來就是儲存與管理資料所在地,而且與撰寫發送Windows Log記錄事件檢視器程式碼和File Log記錄程式碼來看,資料庫 Log記錄程式碼簡單多了。。
Log Server一般而言,是當伺服器群成長到一定數量,會架一台專門處理Log記錄的硬體伺服器,例如,Linux平台的Syslog,Windows下的Kiwi Syslog Server來架設,或是買專門機架硬體式Log Server,來統一收集、分析、管理這些Server Log記錄。
真是可大可小。
以下,我會使用 ASP.NET MVC來實作一個Database Log記錄範例。
Database Log記錄 - Schema準備
針對我們檔案管理系統部分,我們會有兩張 Table,一張是記錄有權限的人員做了什麼動作,一張是記錄使用者下載了那些資料。KKBruceFileLog Schema
這是檔案下載的Log記錄表,要注意的只有 HiterIP 長度為 50,為什訂那麼長?我們事先把 IPv6 考慮進去。這樣,不管我們的 .NET Framwork 程式是抓到 IPv4 或 IPv6 ,記錄進資料庫都不會有問題。-- 請切換至你的資料庫 -- USE [KKBruce] GO /****** Object: Table [dbo].[KKBruceFileLog] Script Date: 11/08/2011 14:31:52 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[KKBruceFileLog]( [FilesLogId] [int] IDENTITY(1,1) NOT NULL, [FileId] [int] NOT NULL, [HiterIP] [nvarchar](50) NOT NULL, [CreateTime] [datetime2](7) NOT NULL, CONSTRAINT [PK_FilesLogs] PRIMARY KEY CLUSTERED ( [FilesLogId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /* 我的資料表裡有外來鍵及其他設定, 你可以依需求設定,單純測試不需要。 */ /* EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'檔案編號' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'KKBruceFileLog', @level2type=N'COLUMN',@level2name=N'FileId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'檔案下載Log表' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'KKBruceFileLog' GO ALTER TABLE [dbo].[KKBruceFileLog] WITH CHECK ADD CONSTRAINT [FK_FilesLogs_Files] FOREIGN KEY([FileId]) REFERENCES [dbo].[ProductFile] ([FileId]) GO ALTER TABLE [dbo].[KKBruceFileLog] CHECK CONSTRAINT [FK_FilesLogs_Files] GO ALTER TABLE [dbo].[KKBruceFileLog] ADD CONSTRAINT [DF_FilesLogs_HitIPv4] DEFAULT (N'下載者IPv4') FOR [HiterIP] GO */
KKBruceEmployeeLog Schema
人員Log記錄表,記錄人員操作的任何動作,我們記錄登入人員的Id、動作、說明、IP、時間等資訊。另外補充,為了與 Entity Framework 有比較配合度,日期時間建議使用 datetime2 格式。-- 請切換至你的資料庫 -- USE [KKBruce] GO /****** Object: Table [dbo].[KKBruceEmployeeLog] Script Date: 11/08/2011 14:44:35 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[KKBruceEmployeeLog]( [EmployeeLogID] [int] IDENTITY(1,1) NOT NULL, [EmployeeId] [int] NOT NULL, [ActionName] [nvarchar](250) NOT NULL, [Description] [nvarchar](max) NOT NULL, [ClientIP] [nvarchar](50) NOT NULL, [CreateTime] [datetime2](7) NOT NULL, CONSTRAINT [PK_KKBruceEmployeesLogs] PRIMARY KEY CLUSTERED ( [EmployeeLogID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /* 我的資料表裡有外來鍵及其他設定, 你可以依需求設定,單純測試不需要。 */ /* EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'人員Log記錄檔' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'KKBruceEmployeeLog' GO ALTER TABLE [dbo].[KKBruceEmployeeLog] WITH CHECK ADD CONSTRAINT [FK_EmployeesLogs_Employees] FOREIGN KEY([EmployeeId]) REFERENCES [dbo].[KKBruceEmployee] ([EmployeeId]) GO ALTER TABLE [dbo].[KKBruceEmployeeLog] CHECK CONSTRAINT [FK_EmployeesLogs_Employees] GO */
ASP.NET MVC實作Log記錄
如果拿ASP.NET與ASP.NET MVC比較,要在 ASP.NET MVC裡實作Log記錄,老實說,沒有太大難度。因為ASP.NET MVC架構上非常容易實作與執行這一類程式碼。ASP.NET麻煩了些。什麼「這一類程式碼」?即需要「大量且重覆使用的程式碼」。在ASP.NET MVC裡有個機制稱 ActionFilter,就是專門在處理這種事,透過 ActionFilter 機制,我們可以很容易且簡單的去重覆使用我們的過濾器。因為我們要寫Log記錄,我們可以稱我們寫的為 Log ActionFilter、Log過濾器。
實作人員Log記錄
- 新增一目錄「ActionFilters」→ 新增一類別「EmployeeLogAttribute.vb」→ 繼承「ActionFilterAttribute 屬性類別」→ 覆寫「OnActionExecuting 副程式」。
EmployeeLogAttribute.vb
Public Class EmployeeLogAttribute Inherits ActionFilterAttribute ' 由開發人員設定傳入相關Action的說明 Public Property Description() As String Public Sub New() End Sub Public Overrides Sub OnActionExecuting(filterContext As ActionExecutingContext) ' 有使用者登入訊息 If filterContext.HttpContext.User IsNot Nothing Then Using db As New KKBruceEntities ' 設定Log相關資訊 Dim log As New KKBruceEmployeeLog() With { .EmployeeId = (From e In db.KKBruceEmployee Where e.Account = filterContext.HttpContext.User.Identity.Name Select e.EmployeeId).First(), .ActionName = filterContext.RouteData.Values("controller") & "/" & _ filterContext.RouteData.Values("action"), .Description = Me.Description, .ClientIP = filterContext.HttpContext.Request.UserHostAddress, .CreateTime = DateTime.Now.ToString() } ' 寫入資料庫 db.KKBruceEmployeeLog.Add(log) db.SaveChanges() ' 邀免Log成長太大,保留六個月的Log ' --- 補充 --- ' 這一段會造成大量的Scan,Log越大,效能影響越明顯, ' 原因在每個人員的每一個操作,我們就會執行一次這段程式, ' 可在 Database 設定 Index、減少保留日期 … 來處理, ' 或改使用排程來週期性的刪除。 ' EF 效能考量,可參考MSDN: ' http://msdn.microsoft.com/zh-tw/library/cc853327.aspx Dim DeleteLogs = From logs In db.KKBruceEmployeeLog Where logs.CreateTime <= DateTime.Now.AddMonths(-6) Select logs ' EF批次相關資料: ' 1. http://geeks.ms/blogs/unai/archive/2008/07/17/multiple-entity-updates-with-entity-framework-ef-fetch-updates.aspx ' 2. http://www.dotblogs.com.tw/wadehuang36/archive/2010/08/20/entityframeworkmultipleupdateanddelete.aspx For Each DelLog In DeleteLogs db.Entry(DelLog).State = EntityState.Deleted Next db.SaveChanges() End Using End If End Sub End Class
如果你想測試這一段程式碼,你還必須建立會員登入相關資料表及程式碼才會運作。你可以先
- 註解第12 ( filterContext.HttpContext.User )、18 ( EmployeeId )、55 ( End If )行;
- 還有 KKBruceEmployeeLog Schema 裡的 EmployeeId 也必須把 NOT NULL 關鍵字移除。
使用Log ActionFilter
針對我們寫好的Log ActionFilter,使用有非常簡單,開啟你要設定的 Controller,然後在Action上設定 Log ActionFilter屬性。上傳檔案Log ActionFilter設定
#Region "上傳" <HttpPost()> <EmployeeLog(Description:="上傳檔案")> Function Create(upfile As HttpPostedFileBase) As ActionResult ' 略,請參考之前文章 Return View() End Function <HttpPost()> <EmployeeLog(Description:="上傳多個檔案")> Function CreateMultiFile(form As FormCollection) As ActionResult ' 略,請參考之前文章 Return View() End Function #End Region
有進到 HttpPost 代表有Submit,我們只需一一為相關Action設定好EmployeeLog及說明,然後測試一下相關動作,再到資料庫或另寫一頁顯示Log記錄的頁面查詢,應該就能看到如下Log記錄:
EmployeeLogID EmployeeId ActionName Description ClientIP CreateTime 1 1 Account/LogOff 登出系統 ::1 2011-06-30 11:22:49.0000000 2 1 Account/Index 查看員工資料 ::1 2011-06-30 11:23:56.0000000 3 1 Account/Edit 修改人員資料 ::1 2011-06-30 11:24:16.0000000 4 1 Account/Index 查看員工資料 ::1 2011-06-30 11:24:16.0000000 5 1 Account/Edit 修改人員資料 ::1 2011-06-30 11:24:28.0000000
檔案下載的Log ActionFilter
一般檔案下載是匿名的,當我們把 EmployeeLogAttribute.vb 裡取得會員資料相關程式碼註解後,就差不多是檔案下載的 Log ActionFilter,把「設定Log相關資訊」及Entity Framework對應的「資料表名稱」改一改即可。然後,設定到檔案下載相關 Controller/Action 去,即可。就算是採會員制,上面那個Log ActionFilter一樣寫好了,就拿去用吧!
一樣的東西,留給你去做。^_^
您好,謝謝您的文章對我有所啟發。
回覆刪除請教一下,我想用log來記錄網站後台操作過程,例如:查詢、新增、修改、刪除等。
為了日後查詢,我決定使用Database Log記錄的方式。
不過,這些記錄可能一天之內就會有上千筆,這樣一來資料成長的速度相當的可怕。
不知道依您的經驗,如何處理比較適當?
我本來想在網站的資料庫裡直接建立一個log資料表來記錄
但是,想到記錄的成長速度,我怕資料庫肥大後,會影響網站的其他資料讀取速度
所以,我想到的方式是用SQLite來建立Log資料庫
然後每個月建立一個新的資料表來記錄該月的Log
這樣會不會太瑣碎了?還是一個資料表到底,反正用SQL語法來查詢過濾即可得到資料?
一天“上千筆”,這對MS SQL Server是小菜中小菜吧 XD
刪除如果可以,可以把Log存放至獨立資料庫中,這樣就不會影響正式資料庫。 :D
Bruce大您好:
回覆刪除想請教您如果要做的功能是紀錄『每篇文章的瀏覽量』並依照瀏覽量大小做排序,您會建議使用MSSQL存取資料嗎? 目前的想法是記錄IP若是在2小時內有瀏覽過文章,就不寫入資料庫,反之則寫入,想請教您會如何處理呢?
目前是擔心一個頁面要呈現多筆文章,若是每篇文章都有上百或上千筆瀏覽數,不知道效能上要如何做處理比較好呢?
請看上一篇的回答。
刪除