我用c#寫了一個測試樣例,分析了int,char,datetime和varchar的日誌情況而且沒有考慮null和空字符串的保存,希望感興趣的朋友能和我一起交流打造屬於自己的日誌分析工具

用過logExplorer的朋友都會被他強悍的功能吸引,我寫過一篇詳細的操作文檔可以參考
http://blog.csdn.net/jinjazz/archive/2008/05/19/2459692.aspx

我們可以自己用開發工具來實現sql日誌的讀取,這個應用還是很酷的,具體思路

1、首先要了解一個沒有公開的系統函數::fn_dblog,他可以讀取sql日誌,並返回二進制的行數據
2、然後要了解sql的二進制數據是如何存儲的,這個可以參考我的blog文章
http://blog.csdn.net/jinjazz/archive/2008/08/07/2783872.aspx
3、用自己擅長的開發工具來分析數據,得到我們需要的信息

我用c#寫了一個測試樣例,分析了int,char,datetime和varchar的日誌情況而且沒有考慮null和空字符串的保存,希望感興趣的朋友能和我一起交流打造屬於自己的日誌分析工具

詳細的試驗步驟以及代碼如下:

1、首先建立sqlserver的測試環境,我用的sql2005,這個過程不能保證在之前的版本中運行
以下sql語句會建立一個dbLogTest數據庫,並建立一張log_test表,然後插入3條數據之後把表清空

use master
go
create database dbLogTest
go
use  dbLogTest
go
create table log_test(id int ,code char(10),name varchar(20),date datetime,memo varchar(100))
insert into log_test select 100, 'id001','jinjazz',getdate(),'剪刀'
insert into log_test select 65549,'id002','遊客',getdate()-1,'這傢伙很懶,沒有設置暱稱'
insert into log_test select -999,'id003','這傢伙來自火星',getdate()-1000,'a'

delete from log_test

--use master
--go
--drop database dbLogTest
 

2、我們最終的目的是要找到被我們刪掉的數據

3、分析日誌的c#代碼:我已經儘量詳細的寫了註釋

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication21
{
    class Program
    {
        /// <summary>
        /// 分析sql2005日誌,找回被delete的數據,引用請保留以下信息
        /// 作者:jinjazz (csdn的剪刀)
        /// 作者blog:http://blog.csdn.net/jinjazz
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection())
            {
                conn.ConnectionString = "server=localhost;uid=sa;pwd=sqlgis;database=dbLogTest";
                conn.Open();
                using (System.Data.SqlClient.SqlCommand command = conn.CreateCommand())
                {
                    //察看dbo.log_test對象的sql日誌
                    command.CommandText = @"SELECT allocunitname,operation,[RowLog Contents 0] as r0,[RowLog Contents 1]as r1
                                from::fn_dblog (null, null)  
                                where allocunitname like 'dbo.log_test%'and
                                operation in('LOP_INSERT_ROWS','LOP_DELETE_ROWS')";

                    System.Data.SqlClient.SqlDataReader reader = command.ExecuteReader();
                    //根據表字段的順序建立字段數組
                    Datacolumn[] columns = new Datacolumn[]
                        {
                            new Datacolumn("id", System.Data.SqlDbType.Int),
                            new Datacolumn("code", System.Data.SqlDbType.Char,10),
                            new Datacolumn("name", System.Data.SqlDbType.VarChar),
                            new Datacolumn("date", System.Data.SqlDbType.DateTime),
                            new Datacolumn("memo", System.Data.SqlDbType.VarChar)
                        };
                    //循環讀取日誌
                    while (reader.Read())
                    {
                        byte[] data = (byte[])reader["r0"];
                       
                        try
                        {
                            //把二進制數據結構轉換爲明文
                            TranslateData(data, columns);
                            Console.WriteLine("數據對象{1}的{0}操作:", reader["operation"], reader["allocunitname"]);
                            foreach (Datacolumn c in columns)
                            {
                                Console.WriteLine("{0} = {1}", c.Name, c.Value);
                            }
                            Console.WriteLine();
                        }
                        catch
                        {
                            //to-do...
                        }
                       
                    }
                    reader.Close();
                }
                conn.Close();
            }
            Console.WriteLine("************************日誌分析完成");
            Console.ReadLine();
        }
        //自定義的column結構
        public class Datacolumn
        {
            public string Name;
            public System.Data.SqlDbType DataType;
            public short Length = -1;
            public object Value = null;
            public Datacolumn(string name, System.Data.SqlDbType type)
            {
                Name = name;
                DataType = type;
            }
            public Datacolumn(string name,System.Data.SqlDbType type,short length)
            {
                Name = name;
                DataType = type;
                Length = length;
            }
        }
        /// <summary>
        /// sql二進制結構翻譯,這個比較關鍵,測試環境爲sql2005,其他版本沒有測過。
        /// </summary>
        /// <param name="data"></param>
        /// <param name="columns"></param>
        static void TranslateData(byte[] data, Datacolumn[] columns)
        {
            //我只根據示例寫了Char,DateTime,Int三種定長度字段和varchar一種不定長字段,其餘的有興趣可以自己補充
            //這裏沒有暫時沒有考慮Null和空字符串兩種情況,以後會補充。

            //引用請保留以下信息:
            //作者:jinjazz
            //sql的數據行二進制結構參考我的blog
            //http://blog.csdn.net/jinjazz/archive/2008/08/07/2783872.aspx
            //行數據從第5個字節開始
            short index = 4;
            //先取定長字段
            foreach (Datacolumn c in columns)
            {
                switch (c.DataType)
                {
                    case System.Data.SqlDbType.Char:
                        //讀取定長字符串,需要根據表結構指定長度
                        c.Value = System.Text.Encoding.Default.GetString(data,index,c.Length);
                        index += c.Length;
                        break;
                    case System.Data.SqlDbType.DateTime:
                        //讀取datetime字段,sql爲8字節保存
                        System.DateTime date = new DateTime(1900, 1, 1);
                        //前四位1/300秒保存
                        int second = BitConverter.ToInt32(data, index);
                        date = date.AddSeconds(second/300);
                        index += 4;
                        //後四位1900-1-1的天數
                        int days = BitConverter.ToInt32(data, index);
                        date=date.AddDays(days);
                        index += 4;
                        c.Value = date;
                        break;
                    case System.Data.SqlDbType.Int:
                        //讀取int字段,爲4個字節保存
                        c.Value = BitConverter.ToInt32(data, index);
                        index += 4;
                        break;
                   default:
                       //忽略不定長字段和其他不支持以及不願意考慮的字段
                        break;
                }
            }
            //跳過三個字節
            index += 3;
            //取變長字段的數量,保存兩個字節
            short varColumnCount = BitConverter.ToInt16(data, index);
            index += 2;
            //接下來,每兩個字節保存一個變長字段的結束位置,
            //所以第一個變長字段的開始位置可以算出來
            short startIndex =(short)( index + varColumnCount * 2);
            //第一個變長字段的結束位置也可以算出來
            short endIndex = BitConverter.ToInt16(data, index);
            //循環變長字段列表讀取數據
            foreach (Datacolumn c in columns)
            {
                switch (c.DataType)
                {
                    case System.Data.SqlDbType.VarChar:
                        //根據開始和結束位置,可以算出來每個變長字段的值
                        c.Value =System.Text.Encoding.Default.GetString(data, startIndex, endIndex - startIndex);
                        //下一個變長字段的開始位置
                        startIndex = endIndex;
                        //獲取下一個變長字段的結束位置
                        index += 2;
                        endIndex = BitConverter.ToInt16(data, index);
                        break;
                    default:
                        //忽略定長字段和其他不支持以及不願意考慮的字段
                        break;
                }
            }
            //獲取完畢
        }
    }
}

4、更改你的sql連接字符串後運行以上代碼,會看到如下輸出信息:

數據對象dbo.log_test的LOP_INSERT_ROWS操作:
id = 100
code = id001
name = jinjazz
date = 2008-8-7 18:14:03
memo = 剪刀

數據對象dbo.log_test的LOP_INSERT_ROWS操作:
id = 65549
code = id002
name = 遊客
date = 2008-8-6 18:14:03
memo = 這傢伙很懶,沒有設置暱稱

數據對象dbo.log_test的LOP_INSERT_ROWS操作:
id = -999
code = id003
name = 這傢伙來自火星
date = 2005-11-11 18:14:03
memo = a

數據對象dbo.log_test的LOP_DELETE_ROWS操作:
id = 100
code = id001
name = jinjazz
date = 2008-8-7 18:14:03
memo = 剪刀

數據對象dbo.log_test的LOP_DELETE_ROWS操作:
id = 65549
code = id002
name = 遊客
date = 2008-8-6 18:14:03
memo = 這傢伙很懶,沒有設置暱稱

數據對象dbo.log_test的LOP_DELETE_ROWS操作:
id = -999
code = id003
name = 這傢伙來自火星
date = 2005-11-11 18:14:03
memo = a

************************日誌分析完成


試驗成功~~


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jinjazz/archive/2008/08/07/2783909.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章