• 用c#读取并分析sql2005日志


    用过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条数据之后把表清空

    1. usemaster
    2. go
    3. createdatabasedbLogTest
    4. go
    5. usedbLogTest
    6. go
    7. createtablelog_test(idint,codechar(10),namevarchar(20),datedatetime,memovarchar(100))
    8. insertintolog_testselect100,'id001','jinjazz',getdate(),'剪刀'
    9. insertintolog_testselect65549,'id002','游客',getdate()-1,'这家伙很懒,没有设置昵称'
    10. insertintolog_testselect-999,'id003','这家伙来自火星',getdate()-1000,'a'
    11. deletefromlog_test
    12. --usemaster
    13. --go
    14. --dropdatabasedbLogTest


    2、我们最终的目的是要找到被我们删掉的数据

    3、分析日志的c#代码:我已经尽量详细的写了注释

    1. usingSystem;
    2. usingSystem.Collections.Generic;
    3. usingSystem.Text;
    4. namespaceConsoleApplication21
    5. {
    6. classProgram
    7. {
    8. ///<summary>
    9. ///分析sql2005日志,找回被delete的数据,引用请保留以下信息
    10. ///作者:jinjazz(csdn的剪刀)
    11. ///作者blog:http://blog.csdn.net/jinjazz
    12. ///</summary>
    13. ///<paramname="args"></param>
    14. staticvoidMain(string[]args)
    15. {
    16. using(System.Data.SqlClient.SqlConnectionconn=newSystem.Data.SqlClient.SqlConnection())
    17. {
    18. conn.ConnectionString="server=localhost;uid=sa;pwd=sqlgis;database=dbLogTest";
    19. conn.Open();
    20. using(System.Data.SqlClient.SqlCommandcommand=conn.CreateCommand())
    21. {
    22. //察看dbo.log_test对象的sql日志
    23. command.CommandText=@"SELECTallocunitname,operation,[RowLogContents0]asr0,[RowLogContents1]asr1
    24. from::fn_dblog(null,null)
    25. whereallocunitnamelike'dbo.log_test%'and
    26. operationin('LOP_INSERT_ROWS','LOP_DELETE_ROWS')";
    27. System.Data.SqlClient.SqlDataReaderreader=command.ExecuteReader();
    28. //根据表字段的顺序建立字段数组
    29. Datacolumn[]columns=newDatacolumn[]
    30. {
    31. newDatacolumn("id",System.Data.SqlDbType.Int),
    32. newDatacolumn("code",System.Data.SqlDbType.Char,10),
    33. newDatacolumn("name",System.Data.SqlDbType.VarChar),
    34. newDatacolumn("date",System.Data.SqlDbType.DateTime),
    35. newDatacolumn("memo",System.Data.SqlDbType.VarChar)
    36. };
    37. //循环读取日志
    38. while(reader.Read())
    39. {
    40. byte[]data=(byte[])reader["r0"];
    41. try
    42. {
    43. //把二进制数据结构转换为明文
    44. TranslateData(data,columns);
    45. Console.WriteLine("数据对象{1}的{0}操作:",reader["operation"],reader["allocunitname"]);
    46. foreach(Datacolumncincolumns)
    47. {
    48. Console.WriteLine("{0}={1}",c.Name,c.Value);
    49. }
    50. Console.WriteLine();
    51. }
    52. catch
    53. {
    54. //to-do...
    55. }
    56. }
    57. reader.Close();
    58. }
    59. conn.Close();
    60. }
    61. Console.WriteLine("************************日志分析完成");
    62. Console.ReadLine();
    63. }
    64. //自定义的column结构
    65. publicclassDatacolumn
    66. {
    67. publicstringName;
    68. publicSystem.Data.SqlDbTypeDataType;
    69. publicshortLength=-1;
    70. publicobjectValue=null;
    71. publicDatacolumn(stringname,System.Data.SqlDbTypetype)
    72. {
    73. Name=name;
    74. DataType=type;
    75. }
    76. publicDatacolumn(stringname,System.Data.SqlDbTypetype,shortlength)
    77. {
    78. Name=name;
    79. DataType=type;
    80. Length=length;
    81. }
    82. }
    83. ///<summary>
    84. ///sql二进制结构翻译,这个比较关键,测试环境为sql2005,其他版本没有测过。
    85. ///</summary>
    86. ///<paramname="data"></param>
    87. ///<paramname="columns"></param>
    88. staticvoidTranslateData(byte[]data,Datacolumn[]columns)
    89. {
    90. //我只根据示例写了Char,DateTime,Int三种定长度字段和varchar一种不定长字段,其余的有兴趣可以自己补充
    91. //这里没有暂时没有考虑Null和空字符串两种情况,以后会补充。
    92. //引用请保留以下信息:
    93. //作者:jinjazz
    94. //sql的数据行二进制结构参考我的blog
    95. //http://blog.csdn.net/jinjazz/archive/2008/08/07/2783872.aspx
    96. //行数据从第5个字节开始
    97. shortindex=4;
    98. //先取定长字段
    99. foreach(Datacolumncincolumns)
    100. {
    101. switch(c.DataType)
    102. {
    103. caseSystem.Data.SqlDbType.Char:
    104. //读取定长字符串,需要根据表结构指定长度
    105. c.Value=System.Text.Encoding.Default.GetString(data,index,c.Length);
    106. index+=c.Length;
    107. break;
    108. caseSystem.Data.SqlDbType.DateTime:
    109. //读取datetime字段,sql为8字节保存
    110. System.DateTimedate=newDateTime(1900,1,1);
    111. //前四位1/300秒保存
    112. intsecond=BitConverter.ToInt32(data,index);
    113. date=date.AddSeconds(second/300);
    114. index+=4;
    115. //后四位1900-1-1的天数
    116. intdays=BitConverter.ToInt32(data,index);
    117. date=date.AddDays(days);
    118. index+=4;
    119. c.Value=date;
    120. break;
    121. caseSystem.Data.SqlDbType.Int:
    122. //读取int字段,为4个字节保存
    123. c.Value=BitConverter.ToInt32(data,index);
    124. index+=4;
    125. break;
    126. default:
    127. //忽略不定长字段和其他不支持以及不愿意考虑的字段
    128. break;
    129. }
    130. }
    131. //跳过三个字节
    132. index+=3;
    133. //取变长字段的数量,保存两个字节
    134. shortvarColumnCount=BitConverter.ToInt16(data,index);
    135. index+=2;
    136. //接下来,每两个字节保存一个变长字段的结束位置,
    137. //所以第一个变长字段的开始位置可以算出来
    138. shortstartIndex=(short)(index+varColumnCount*2);
    139. //第一个变长字段的结束位置也可以算出来
    140. shortendIndex=BitConverter.ToInt16(data,index);
    141. //循环变长字段列表读取数据
    142. foreach(Datacolumncincolumns)
    143. {
    144. switch(c.DataType)
    145. {
    146. caseSystem.Data.SqlDbType.VarChar:
    147. //根据开始和结束位置,可以算出来每个变长字段的值
    148. c.Value=System.Text.Encoding.Default.GetString(data,startIndex,endIndex-startIndex);
    149. //下一个变长字段的开始位置
    150. startIndex=endIndex;
    151. //获取下一个变长字段的结束位置
    152. index+=2;
    153. endIndex=BitConverter.ToInt16(data,index);
    154. break;
    155. default:
    156. //忽略定长字段和其他不支持以及不愿意考虑的字段
    157. break;
    158. }
    159. }
    160. //获取完毕
    161. }
    162. }
    163. }

    4、更改你的sql连接字符串后运行以上代码,会看到如下输出信息:

    1. 数据对象dbo.log_test的LOP_INSERT_ROWS操作:
    2. id=100
    3. code=id001
    4. name=jinjazz
    5. date=2008-8-718:14:03
    6. memo=剪刀
    7. 数据对象dbo.log_test的LOP_INSERT_ROWS操作:
    8. id=65549
    9. code=id002
    10. name=游客
    11. date=2008-8-618:14:03
    12. memo=这家伙很懒,没有设置昵称
    13. 数据对象dbo.log_test的LOP_INSERT_ROWS操作:
    14. id=-999
    15. code=id003
    16. name=这家伙来自火星
    17. date=2005-11-1118:14:03
    18. memo=a
    19. 数据对象dbo.log_test的LOP_DELETE_ROWS操作:
    20. id=100
    21. code=id001
    22. name=jinjazz
    23. date=2008-8-718:14:03
    24. memo=剪刀
    25. 数据对象dbo.log_test的LOP_DELETE_ROWS操作:
    26. id=65549
    27. code=id002
    28. name=游客
    29. date=2008-8-618:14:03
    30. memo=这家伙很懒,没有设置昵称
    31. 数据对象dbo.log_test的LOP_DELETE_ROWS操作:
    32. id=-999
    33. code=id003
    34. name=这家伙来自火星
    35. date=2005-11-1118:14:03
    36. memo=a
    37. ************************日志分析完成


    试验成功~~

  • 相关阅读:
    使用secureCRT连接VMware-Ubuntukylin虚拟机
    java使用POI jar包读写xls文件
    SimpleDateFormat 相关用法
    ORACLE之表
    ORACLE之PACKAGE-游标变量
    PHP多进程学习(三)__代码案例来了解父进程与子进程的执行顺序
    PHP多进程学习(二)__fork起多个子进程,父进程的阻塞与非阻塞
    PHP多进程学习(二)__来初步了解一下PHP多进程及简单demo
    Python学习【三】
    Python学习【二】
  • 原文地址:https://www.cnblogs.com/gc2013/p/3831371.html
Copyright © 2020-2023  润新知