高级跟踪






4.92/5 (9投票s)
本文展示了如何在运行时轻松地从 .NET 应用程序获取信息,以及如何将其存储到您选择的目标(本例中为数据库)。
引言
本文的目的是展示如何轻松地从运行中的应用程序获取信息,以及如何通过极少的开发将其存储到您选择的目标,从而使您更轻松地查询可用信息并协助诊断实时系统问题。我们将从通过示例基本了解跟踪开始,然后转向高级跟踪的概念。
跟踪提供了一种检测运行中的应用程序的方法。它帮助您跟踪代码执行,并且您可以在执行期间从代码中发出信息。让我们尝试构建一个小型应用程序来了解跟踪的好处。从应用程序代码的角度来看,我们最关心的两个重要类是 Debug 和 Trace,两者都在 System.Diagnostics 命名空间
中,前者主要用于调试版本,后者用于应用程序的发布版本。
要了解 .NET Framework 中的跟踪,让我们使用最基本的应用程序,它会向用户问好,这是每个程序员都喜欢的,Hello World!
using System;
using System.Diagnostics;
public class BasicTracingSample
{
public static void Main()
{
Trace.WriteLine("Program execution started...");
Console.WriteLine("Enter your name :");
string strName = Console.ReadLine();
Console.WriteLine(string.Format("Hello {0}!", strName));
Trace.WriteLine(string.Format("Greeted User {0}", strName));
Trace.WriteLine("Program finished...");
Console.ReadLine();
}
}
并使用应用程序配置文件为上述程序定义跟踪设置,如下所示
编译上述应用程序并使用以下命令执行它
请注意,您必须在编译时定义 TRACE 常量才能使跟踪生效。在使用 Visual Studio 中的项目时,您可以通过“生成”选项卡下的项目属性页来实现此目的,如下所示
现在让我们执行应用程序。您会注意到,作为执行的一部分,它创建了一个日志文件,内容如下
总而言之,使用应用程序配置文件和代码中的 Trace.WriteLine
方法调用,我们能够将信息从执行中的应用程序获取到日志文件。在本例中,信息被记录到日志文件是因为我们选择了 TextWriterTraceListener
。如果我们使用 .NET Framework 提供的 EventLogTraceListener
,它将开始记录到 Windows 事件查看器。因此,您可以通过选择 .NET Framework 提供的跟踪侦听器来选择自己的日志目标。
跟踪方法
Trace 类提供了以下方法,可在应用程序执行期间用于发出信息
Write
WriteLine
TraceInformation
TraceWarning
TraceError
这些方法是跟踪框架 API,供您的应用程序简单地调用相关的 Trace
方法,配置的 TraceListener
将负责将信息转储到其已知目标。这些不同的方法有助于确定跟踪类型和该类型的附加信息,以便跟踪侦听器可以相应地记录它。例如,在 EventLogTraceListener
的情况下,它将记录类型,即信息、警告或错误作为“Type
”列中的值,以便您可以轻松地对其进行过滤。它基本上是为了让跟踪侦听器实现类能够最好地利用传递给它的信息。这些方法还使用为跟踪侦听器定义的跟踪开关来决定是否记录跟踪。让我们了解跟踪开关。
跟踪开关
下表定义了可能的跟踪开关以及它们如何影响跟踪输出
枚举值 | 整数值 | 显示(或写入指定输出目标)的消息类型 |
关闭 | 0 | 无 |
Error(错误) | 1 | 仅错误消息 |
警告 | 2 | 警告消息和错误消息 |
Info(信息) | 3 | 信息消息、警告消息和错误消息 |
Verbose(详细) | 4 | 详细消息、信息消息、警告消息和错误消息 |
来源:http://msdn.microsoft.com/en-us/library/aa983740(v=vs.71).aspx
跟踪开关为您提供了进一步过滤您感兴趣的相关信息的能力。在应用程序的不同阶段,您可能希望在不同的开关值之间切换以控制输出。例如,当一个新应用程序首次上线时,您可能希望将跟踪开关设置为 Verbose,以便捕获所有信息,但一旦应用程序稳定一段时间并且您不再有兴趣记录从应用程序中生成的所有信息时,那么您可以将跟踪开关设置为 Warning,依此类推。最棒的是,您可以通过修改应用程序配置文件来配置所有这些,而无需重新编译应用程序。
需要更多
到目前为止一切顺利,但我们需要更多!您可以选择使用 .NET Framework 中可用的侦听器,例如 ConsoleTraceListener
、TextWriterTraceListener
、EventLogTraceListener
、XmlWriterTraceListener
等。所有这些现有的跟踪侦听器对于一些小型应用程序来说都非常好,但对于大型分布式应用程序,您需要一种更强大的方法来从执行中的应用程序收集和处理信息(*从中获得任何好处*)。.NET 框架提供的跟踪侦听器记录到不适合大多数分布式应用程序的目标,因为功能分散在多个应用程序、运行在不同服务器庄园中的多个组件上,并且您的应用程序正在记录大量信息,这在大型分布式应用程序中通常是这种情况。在文本文件或事件查看器中搜索信息或对相关信息进行分组变得非常困难。让我们以最常见的需求为例,假设您想根据事务编号或其某个其他标识符搜索为特定事务记录的所有信息。从文本文件或事件查看器中提取此类信息并不容易,因为您正在寻找的信息会散布在整个文件/事件查看器中,从而难以提取您真正需要的信息。
雪上加霜的是,您需要制定自己的方法来维护这些目标,例如定期将日志文件移动到某些备份存储等等。基本上,我们需要一个跟踪解决方案,它
- 支持将来自多个应用程序的所有信息记录到中央目标
- 提供以简单方式查询信息的能力,就像数据库表一样(选择、过滤、排序、分组等)
- 提供一种维护日志目标简单方法
当我想到跟踪解决方案的这些要求时,立即的反应是将日志记录到数据库表中。如果我们可以将应用程序的跟踪信息记录到数据库表中,那么它就满足了上述所有要求。
自定义跟踪侦听器来拯救!
自定义跟踪侦听器来拯救!跟踪为 .NET 应用程序提供了一个可插拔框架,用于在执行期间记录信息。通过可插拔,我的意思是您可以添加自定义跟踪侦听器并提供自定义实现,以说明您想要在哪里以及如何记录信息。例如,您可以编写一个自定义跟踪侦听器,将信息转储到数据库中的表中。
使用数据库表作为日志目标有几个主要优点,例如
- 它提供了使用所有 DBMS 构造来查询、过滤和汇总可用信息的能力。它与任何其他事务表一样好,您可以以最佳方式使用可用信息。
- 使用数据库表,维护变得更加容易,您可以通过简单地编写查询来删除包含不需要信息的行。您可以轻松识别包含不需要数据的行并选择性地删除它们。另外,您还可以安排一个数据库作业,删除超过 3 个月的信息等。以及许多其他类似的可能方法,就像您通常使用数据库表一样。
使用数据库表作为日志目标有一些缺点,因此,在以下情况下不建议使用此方法:
- 您的应用程序在很短的时间内发出大量信息,因为它会在极短的时间内导致大量数据库命中,并可能影响数据库性能、网络带宽,进而影响您的应用程序性能。
- 访问数据库存在任何类型的延迟,例如网络延迟,因为它可能会使本来就很慢的网络负担过重,导致核心业务目的的网络资源利用不足,并可能影响应用程序和网络的整体性能。
在决定使用数据库表作为跟踪目标之前,您应该测量应用程序相对于数据库的性能,并考虑上述因素。此外,由于您的应用程序可以使用多个跟踪侦听器,因此您可以根据需要决定在这些可用的跟踪侦听器之间切换,并充分利用它们的功能来诊断问题,而且您也无需重新编译应用程序。您可以从配置文件本身控制应用程序的跟踪侦听器,但对于某些类型的应用程序,您可能需要重新启动应用程序才能使配置文件中的跟踪侦听器更改生效。
让我们继续设计一个自定义数据库跟踪侦听器,然后实现它。
设计考虑因素
让我们设计一个表,我们将所有跟踪条目记录到其中
tracelog | |||
主键 | traceid | int | 标识 |
message | xml | ||
type | varchar | 默认值“Verbose” | |
componentname | varchar | 默认值 NULL | |
loggeddate | datetime | 默认值 getdate() |
让我们强调这个自定义数据库跟踪侦听器应支持的关键要求
- 能够正确地对来自应用程序的信息进行分类,而不是将其全部作为长
string
的一部分转储(并且您不确定一个部分在哪里结束,另一个部分在哪里开始,因为您的眼睛需要慢慢扫描才能看清楚)。我们需要像 XML 树一样的东西,其中每个元素都表示可以轻松识别和搜索的特定信息。我们将表中消息列的数据类型定义为 XML,并让跟踪侦听器实现负责将传入的信息转换为 XML
string
- 能够在配置文件中定义数据库的连接字符串。
这可以作为应用程序配置文件中跟踪侦听器上已支持的
initializeData
属性的一部分进行定义,并且我们还将为此目的支持一个附加属性,即connectionString
。 - 它应提供在应用程序/服务级别为跟踪条目分配标识符的能力,以便您可以过滤/分组来自同一个应用程序的条目。
跟踪侦听器实现将支持一个附加属性,即
componentName
,可以在应用程序配置文件中定义,用于单独标识来自应用程序的所有跟踪条目。此属性定义的值将存储在表的componentname
列中。 - 无需重新启动应用程序即可切换跟踪级别(例如在 Windows 服务的情况下)。
跟踪侦听器实现将支持一个附加属性,即布尔类型的
refreshTraceConfig
,用于决定跟踪是否需要刷新跟踪开关值。 - 如果在自定义跟踪侦听器执行期间发生任何错误,这些错误应单独记录到文本文件中。
自定义数据库跟踪侦听器实现
让我们通过查看实际代码来了解我们如何实现为我们设定的目标。下面是使用数据库表作为目标的自定义跟踪侦听器的完整源代码
using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Xml.Linq;
using System.Threading;
using System.Linq;
namespace AdvancedTracing
{
// Delegate definition for the real method responsible for logging
internal delegate void LogIt(string message, string type, string componentName);
// Delegate for DBTraceFailed event
public delegate void DBTraceFailedHandler (string exceptionText);
// IMPORTANT!!!IMPORTANT!!!IMPORTANT!!!IMPORTANT!!!
// --------------------------------------------------
// Dont write any debugging statements anywhere in this
// class such as Debug.WriteLine or Trace.WriteLine
// simply because it would go into an endless loop
// --------------------------------------------------
public class DBTraceListener : TraceListener
{
public event DBTraceFailedHandler DBTraceFailed;
#region Constants
private const string STORED_PROC_NAME = "prc_writetraceentry";
private const string STORED_PROC_MESSAGE_PARAM_NAME = "@message";
private const string STORED_PROC_MESSAGETYPE_PARAM_NAME = "@type";
private const string STORED_PROC_COMPONENTNAME_PARAM_NAME = "@componentname";
private const string TRACE_SWITCH_NAME = "DBTraceSwitch";
private const string TRACE_SWITCH_DESCRIPTION =
"Trace switch defined in config file for configuring trace output to database";
// Not defining it as readonly string so that in future it could come
// from an external source and we can provide initializer for it
private static readonly string DEFAULT_TRACE_TYPE = "Verbose";
#endregion
#region Class Data
// Database connection object
private SqlConnection _cn;
// Database command object
private SqlCommand _cm;
// Connection string for database
private string _connectionString;
// Flag for DBTraceListener object disposal status
private bool _disposed = false;
// Trace Switch object for controlling trace output, defaulting to Verbose
private TraceSwitch TraceSwitch =
new TraceSwitch(TRACE_SWITCH_NAME, TRACE_SWITCH_DESCRIPTION, DEFAULT_TRACE_TYPE);
// Delegate to point to the method which would do actual operation of logging
private LogIt workerMethod;
// Component Name
private string _componentName;
// Lock object
private object _traceLockObject = new object();
private object _fileLockObject = new object();
// Timer to refresh trace configuration information
private Timer _traceSwitchTimer;
// Flag to indicate whether trace configuration data needs to be refreshed
private bool _refreshTraceConfig = false;
#endregion
#region Properties
public override bool IsThreadSafe
{
// TODO: We are logging to database and the core method responsible
// for this places lock on the core code responsible for executing
// database command to ensure that only one thread can access it at
// a time. Considering this, we can really return true for this
// property but before doing that, just need to do some testing.
get { return false; }
}
public string ConnectionString
{
get
{
if ( string.IsNullOrEmpty(this._connectionString))
{
this.LoadAttributes();
}
return this._connectionString;
}
set { this._connectionString = value; }
}
public string ComponentName
{
get
{
if (string.IsNullOrEmpty(this._componentName))
{
this.LoadAttributes();
}
return this._componentName;
}
set { this._componentName = value; }
}
/// <summary>
/// Setting this property to True would refresh Trace configuration
/// data from system.diagnostics section in your application configuration
/// file. It is important to note that it would only refresh trace configuration
/// data and not entire configuration file. For example, while your application
/// is running, if you change one of the appSettings values, it would not be
/// refreshed but changing trace switch value would be refreshed.
/// </summary>
public bool RefreshTraceConfig
{
get
{
this.LoadAttributes();
return this._refreshTraceConfig;
}
set
{
if (value)
{
// Refresh trace section every 15 minutes
if (!this._refreshTraceConfig)
{
// i.e. If timer is not already active
this._refreshTraceConfig = true;
this._traceSwitchTimer = new Timer(new TimerCallback(RefreshSwitch),
null, new TimeSpan(0, 15, 0), new TimeSpan(0, 15, 0));
}
}
else
{
// If timer is active, stop it
this._refreshTraceConfig = false;
this._traceSwitchTimer.Dispose();
this._traceSwitchTimer = null;
}
}
}
#endregion
#region Methods
void RefreshSwitch(object o)
{
// Trace.Refresh call is not expected to throw any exception, but if it DOES
// catch the exception and do nothing
try
{
if (this.RefreshTraceConfig)
{
Trace.Refresh();
}
}
catch(Exception ex)
{
this.WriteLine(
string.Format("Trace.Refresh failed with following exception: {0}, ", ex.ToString()),
"Error",
"DBTraceListener"
);
this.WriteEntryToInternalLog(string.Format
("Trace.Refresh failed with following exception: {0}, ", ex.ToString()));
}
}
private void WriteEntryToInternalLog(string msg)
{
lock (this._fileLockObject)
{
try
{
File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + @"\DBTraceListener.log",
string.Format("{0}{1}: {2}", Environment.NewLine, DateTime.Now.ToString(), msg));
}
catch
{
// Do nothing
}
}
}
/// <summary>
/// Another method useful for testing if DBTraceListener is
/// able to establish connection to database
/// </summary>
/// <returns>void</returns>
public bool TestDBConnection()
{
try
{
using (SqlConnection cn = new SqlConnection(this.ConnectionString))
{
cn.Open();
}
return true;
}
catch
{
// In case of any exception just return false
return false;
}
}
internal bool ShouldLogTrace(TraceEventType eventType)
{
bool shouldLog = true;
switch (eventType)
{
case TraceEventType.Critical:
case TraceEventType.Error:
shouldLog = this.TraceSwitch.TraceError;
break;
case TraceEventType.Warning:
shouldLog = this.TraceSwitch.TraceWarning;
break;
case TraceEventType.Information:
shouldLog = this.TraceSwitch.TraceInfo;
break;
case TraceEventType.Start:
case TraceEventType.Stop:
case TraceEventType.Suspend:
case TraceEventType.Resume:
case TraceEventType.Transfer:
case TraceEventType.Verbose:
shouldLog = this.TraceSwitch.TraceVerbose;
break;
}
return shouldLog;
}
#region TraceEvent
public override void TraceEvent(TraceEventCache eventCache,
string source, TraceEventType eventType, int id)
{
this.TraceEvent(eventCache, source, eventType, id, string.Empty);
}
public override void TraceEvent(TraceEventCache eventCache,
string source, TraceEventType eventType, int id, string message)
{
XElement msg;
try
{
if (!this.ShouldLogTrace(eventType))
return;
if (eventType == TraceEventType.Critical ||
eventType == TraceEventType.Error ||
eventType == TraceEventType.Warning)
{
msg = new XElement("TraceLog",
new XElement("Message", message),
new XElement("Id", id),
new XElement("CallStack", eventCache.Callstack.ToString()),
new XElement("ThreadId", eventCache.ThreadId),
new XElement("ProcessId", eventCache.ProcessId)
);
}
else
{
msg = new XElement("TraceLog",
new XElement("Message", message));
}
this.WriteLineInternal(msg.ToString(), eventType.ToString(), null);
}
catch(Exception ex)
{
this.WriteLine(
string.Format("AdvancedTracing::DBTraceListener - Trace.TraceEvent failed
with following exception: {0}, for message {1} ", ex.ToString(), message),
"Error",
"DBTraceListener"
);
this.WriteEntryToInternalLog(string.Format("Trace.TraceEvent failed
with following exception: {0}", ex.ToString()));
}
}
public override void TraceEvent(TraceEventCache eventCache, string source,
TraceEventType eventType, int id, string format, params object[] args)
{
XElement msg;
try
{
if (!this.ShouldLogTrace(eventType))
return;
if (eventType == TraceEventType.Critical ||
eventType == TraceEventType.Error ||
eventType == TraceEventType.Warning)
{
msg = new XElement("TraceLog",
new XElement("Message", string.Format(format, args)),
new XElement("Id", id),
new XElement("CallStack", eventCache.Callstack.ToString()),
new XElement("ThreadId", eventCache.ThreadId),
new XElement("ProcessId", eventCache.ProcessId)
);
}
else
{
msg = new XElement("TraceLog",
new XElement("Message", string.Format(format, args)));
}
this.WriteLineInternal(msg.ToString(), eventType.ToString(), null);
}
catch (Exception ex)
{
this.WriteLine(
string.Format("AdvancedTracing::DBTraceListener - Trace.TraceEvent failed
with following exception: {0}, for message {1}", ex.ToString(), format),
"Error",
"DBTraceListener"
);
this.WriteEntryToInternalLog(string.Format("Trace.TraceEvent failed with
following exception: {0}", ex.ToString()));
}
}
#endregion
#region TraceTransfer
public override void TraceTransfer(TraceEventCache eventCache, string source,
int id, string message, Guid relatedActivityId)
{
try
{
if (this.ShouldLogTrace(TraceEventType.Transfer))
{
XElement msg = new XElement("TraceLog",
new XElement("Message", message),
new XElement("Source", source),
new XElement("Id", id),
new XElement("RelatedActivityId", relatedActivityId.ToString()),
new XElement("CallStack", eventCache.Callstack.ToString()),
new XElement("ThreadId", eventCache.ThreadId),
new XElement("ProcessId", eventCache.ProcessId));
this.WriteLine(msg.ToString(), TraceEventType.Verbose.ToString(), null);
}
}
catch (Exception ex)
{
this.WriteLine(
string.Format("AdvancedTracing::DBTraceListener - Trace.TraceTransfer failed
with following exception: {0}, for message {1} ", ex.ToString(), message),
"Error",
"DBTraceListener"
);
this.WriteEntryToInternalLog(string.Format("Trace.TraceTransfer failed with
following exception: {0}", ex.ToString()));
}
}
#endregion
public override string ToString()
{
return string.Format("DBTraceListener for Component: {0} using ConnectionString: {1}",
this.ComponentName, this.ConnectionString);
}
#region Write Methods
public override void Write(object o)
{
if (o != null)
{
this.WriteLine(o.ToString(), null);
}
}
public override void Write(string message)
{
this.WriteLine(message, null);
}
public override void Write(object o, string category)
{
if (o != null)
{
this.WriteLine(o.ToString(), category);
}
}
public override void Write(string message, string category)
{
this.WriteLine(message, category);
}
#endregion
#region WriteLine Methods
public override void WriteLine(object o)
{
if (o != null)
{
this.WriteLine(o.ToString(), null);
}
}
public override void WriteLine(object o, string category)
{
if (o != null)
{
this.WriteLine(o.ToString(), category);
}
}
public override void WriteLine(string message)
{
this.WriteLine(message, null);
}
override public void WriteLine(string message, string category)
{
try
{
if (!this.ShouldLogTrace(TraceEventType.Verbose))
return;
// IMPORTANT!!!!
// DO NOT WRITE ANY Debug.WriteLine or Trace.WriteLine statements in this method
XElement msg = new XElement("TraceLog",
new XElement("Message", message));
this.WriteLineInternal(msg.ToString(), category, null);
}
catch (Exception ex)
{
this.WriteEntryToInternalLog(string.Format
("WriteLine failed with following exception: {0}", ex.ToString()));
}
}
/// <summary>
/// This is a non-standard WriteLine method i.e. Trace class does not provide
/// a WriteLine method taking three parameters. It is used by internal implementation
/// of this class to provide functionality to log a different component name,
/// **primarily aimed towards helping in debugging some particular scenario**
/// </summary>
/// <param name="message" />
/// <param name="type" />
/// <param name="componentName" />
public void WriteLine(string message, string category, string componentName)
{
try
{
if (!this.ShouldLogTrace(TraceEventType.Verbose))
return;
// IMPORTANT!!!!
// DO NOT WRITE ANY Debug.WriteLine or Trace.WriteLine statements in this method
XElement msg = new XElement("TraceLog",
new XElement("Message", message));
this.WriteLineInternal(msg.ToString(), category, componentName);
}
catch (Exception ex)
{
this.WriteEntryToInternalLog(string.Format
("WriteLine failed with following exception: {0}", ex.ToString()));
}
}
#endregion
private void WriteLineInternal(string message, string category, string componentName)
{
// Perform the actual operation of logging **asynchronously**
workerMethod = SaveLogEntry;
workerMethod.BeginInvoke(message, category, componentName, null, null);
}
private void SaveLogEntry(string message, string category, string componentName)
{
// IMPORTANT!!!!
// DO NOT WRITE ANY Debug.WriteLine or Trace.WriteLine statements in this method
lock (_traceLockObject)
{
try
{
// save trace message to database
if (this._cn.State == ConnectionState.Broken ||
this._cn.State == ConnectionState.Closed)
{
this._cn.ConnectionString = this.ConnectionString;
this._cn.Open();
}
this._cm.Parameters[STORED_PROC_MESSAGE_PARAM_NAME].Value = message;
this._cm.Parameters[STORED_PROC_MESSAGETYPE_PARAM_NAME].Value = category;
if (string.IsNullOrEmpty(componentName))
{
// No value provided by caller. Look for the value defined
// in application configuration file
if (string.IsNullOrEmpty(this.ComponentName))
{
this._cm.Parameters[STORED_PROC_COMPONENTNAME_PARAM_NAME].Value =
DBNull.Value;
}
else
{
this._cm.Parameters[STORED_PROC_COMPONENTNAME_PARAM_NAME].Value =
this.ComponentName;
}
}
else
{
// Specific value provided by caller for this specific trace/log
// Need to use the same
this._cm.Parameters[STORED_PROC_COMPONENTNAME_PARAM_NAME].Value = componentName;
}
this._cm.ExecuteNonQuery();
}
catch (Exception ex)
{
// Raise event to let others know, just in case
// someone interested
if (this.DBTraceFailed != null)
{
DBTraceFailed(ex.ToString());
}
// Write entry to internal log file
this.WriteEntryToInternalLog(ex.ToString());
}
finally
{
// Nothing to dispose in case of exception
}
}
}
protected override string[] GetSupportedAttributes()
{
return new string[] { "connectionString", "componentName", "refreshTraceConfig" };
}
private void LoadAttributes()
{
if (Attributes.ContainsKey("connectionString"))
{
this.ConnectionString = this.Attributes["connectionString"];
}
if (Attributes.ContainsKey("componentName"))
{
this.ComponentName = this.Attributes["componentName"];
}
if (Attributes.ContainsKey("refreshTraceConfig"))
{
bool val;
bool.TryParse(this.Attributes["refreshTraceConfig"], out val);
this.RefreshTraceConfig = val;
}
}
protected override void Dispose(bool disposing)
{
if (!this._disposed)
{
if (disposing)
{
if (this._cn != null)
this._cn.Dispose();
if (this._cm != null)
this._cm.Dispose();
if (this._traceSwitchTimer != null)
this._traceSwitchTimer.Dispose();
}
this._disposed = true;
}
this._cm = null;
this._cn = null;
base.Dispose(disposing);
}
#endregion
#region Constructors
public DBTraceListener() : this(string.Empty) { }
public DBTraceListener(string initializeData)
: base(initializeData)
{
// Initialize connection object
this._cn = new SqlConnection();
this._cn.ConnectionString = initializeData;
this.ConnectionString = initializeData;
try
{
this._cn.Open();
}
catch(Exception ex)
{
// Write to internal
this.WriteEntryToInternalLog(string.Format("Could not connect to database from
the provided connection string. Exception: {0}", ex.ToString()));
// Let the caller know that this listener object cannot do its
// work because it cannot establish connection to database
//
// Since Tracing framework is initialized by CLR, you would
// in all likelihood get Could not create type... error
throw;
}
// Setup command object
this._cm = this._cn.CreateCommand();
this._cm.CommandText = STORED_PROC_NAME;
this._cm.CommandType = CommandType.StoredProcedure;
this._cm.Parameters.Add(new SqlParameter(STORED_PROC_MESSAGE_PARAM_NAME, DBNull.Value));
this._cm.Parameters.Add(new SqlParameter(STORED_PROC_MESSAGETYPE_PARAM_NAME, DBNull.Value));
this._cm.Parameters.Add(new SqlParameter
(STORED_PROC_COMPONENTNAME_PARAM_NAME, DBNull.Value));
}
#endregion
}
}
以上代码是自定义数据库跟踪侦听器所需的内容。您需要运行额外的数据库脚本来创建所需的表和存储过程,脚本如下
-- tracelog table
------------------------------------------------------------
if exists (select * from sys.tables where [name]='tracelog')
begin
print 'table tracelog already exists.'
end
else
begin
print 'creating table tracelog...'
create table [dbo].[tracelog](
[traceid] [bigint] identity(1,1) not null,
[message] [xml] null,
[type] [varchar](20) null default ('Verbose'),
[componentname] [varchar](200) null default (null),
[loggeddate] [datetime] null default (getdate()),
primary key clustered
(
[traceid] asc
)with (ignore_dup_key = off) on [primary]
) on [primary]
print 'table tracelog created.';
end
go
-- stored procedure to write trace log entry
------------------------------------------------------------
if exists (select * from sys.objects where object_id = object_id(N'[dbo].[prc_writetraceentry]') _
and type in (N'p', N'pc'))
drop procedure [dbo].[prc_writetraceentry]
go
create proc prc_writetraceentry
@message xml = null,
@type varchar(100) = 'Verbose',
@componentname varchar(200) = null
as
begin
insert tracelog([message], type, componentname)
values(@message, @type, @componentname)
end
go
-- add index to speed up data retrieval in case the table
-- gets too big
------------------------------------------------------------
create nonclustered index [idx_tracelog_loggeddate] on [dbo].[tracelog]
(
[loggeddate] desc,
[componentname] asc,
[traceid] asc
)
go
现在,让我们修改应用程序配置文件并添加新创建的跟踪侦听器。您需要按如下方式添加跟踪侦听器
并执行应用程序,您将在数据库的 tracelog
表中看到以下内容
总而言之,自定义数据库跟踪侦听器的显著特点如下
- 将跟踪条目作为 XML 数据写入数据库表
- 提供附加列以使用源应用程序/组件标识条目
- 将数据库跟踪侦听器实现中生成的错误写入文本文件
在未来的某个帖子中(我不喜欢说下一个帖子,因为我的下一个帖子可能会写一些完全不同的东西),我们将尝试研究高级跟踪技术,例如代码注入,将代码注入到程序集中,这些代码将在每个方法执行开始时记录附加信息,以便您可以在应用程序中执行每个方法时对其进行跟踪。
祝您编码愉快!!
Vande Mataram!
(向祖国致敬)
附言:除了撰写博客外,我还在 Twitter 上分享技巧、链接等。我的 Twitter 用户名是:@girishjjain