Wednesday, January 23, 2008

Create logs in database with EntLib

INTRODUCTION
This lab is demonstrating Logging Block of Enterprise Library 3.0. Specifically it sets up
a logging to database framework. The following 5 steps are all needed for this job.

STEP 1: Set the configure entries
The sample configuration is as follow.

<configSections>
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.9.9.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=2.9.9.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</configSections>

<loggingConfiguration name="Logging Application Block" tracingEnabled="true"
defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">
<listeners>
<add databaseInstanceName="Connection String" writeLogStoredProcName="WriteLog"
addCategoryStoredProcName="AddCategory" formatter="Text Formatter"
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Database.Configuration.FormattedDatabaseTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=2.9.9.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
traceOutputOptions="None" type="Microsoft.Practices.EnterpriseLibrary.Logging.Database.FormattedDatabaseTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=2.9.9.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
name="Database Trace Listener" />
</listeners>
<formatters>
<add template="Timestamp: {timestamp}&#xD;&#xA;Message: {message}&#xD;&#xA;Category: {category}&#xD;&#xA;Priority: {priority}&#xD;&#xA;EventId: {eventid}&#xD;&#xA;Severity: {severity}&#xD;&#xA;Title:{title}&#xD;&#xA;Machine: {machine}&#xD;&#xA;Application Domain: {appDomain}&#xD;&#xA;Process Id: {processId}&#xD;&#xA;Process Name: {processName}&#xD;&#xA;Win32 Thread Id: {win32ThreadId}&#xD;&#xA;Thread Name: {threadName}&#xD;&#xA;Extended Properties: {dictionary({key} - {value}&#xD;&#xA;)}"
type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.9.9.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
name="Text Formatter" />
</formatters>
<categorySources>
<add switchValue="All" name="General">
<listeners>
<add name="Database Trace Listener" />
</listeners>
</add>
</categorySources>
<specialSources>
<allEvents switchValue="All" name="All Events" />
<notProcessed switchValue="All" name="Unprocessed Category" />
<errors switchValue="All" name="Logging Errors &amp; Warnings">
<listeners>
<add name="Database Trace Listener" />
</listeners>
</errors>
</specialSources>
</loggingConfiguration>

<connectionStrings>
<add name="Connection String" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database1.mdf;Integrated Security=True;User Instance=True"
providerName="System.Data.SqlClient" />
</connectionStrings>



STEP 2: COPY AND CREATE REFERENCE FOR ENTLIB 3.0
Create a folder EntLib3 under the website and copy the assmplies, the add reference for the website.

The assemblies and files used:
Microsoft.Practices.EnterpriseLibrary.Common.xml
Microsoft.Practices.EnterpriseLibrary.Common.dll
Microsoft.Practices.EnterpriseLibrary.Data.xml
Microsoft.Practices.EnterpriseLibrary.Data.dll
Microsoft.Practices.EnterpriseLibrary.Logging.Database.xml
Microsoft.Practices.EnterpriseLibrary.Logging.Database.dll
Microsoft.Practices.EnterpriseLibrary.Logging.xml
Microsoft.Practices.EnterpriseLibrary.Logging.dll
Microsoft.Practices.ObjectBuilder.dll


STEP 3: THE CODE SAMPLE FOR WRITING INTO LOG

// import EntLib assembly's namespace
using Microsoft.Practices.EnterpriseLibrary.Logging;

// write an log entry by the Logger class
Logger.Write("Test","General");


STEP 4: CREATE BACKEND DATABASE STRUCTURE
There are three tables need to be created:

- Category
- CategoryLog
- Log

/****** Object: Table [dbo].[Category] Script Date: 01/16/2008 00:12:59 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Category](
[CategoryID] [int] IDENTITY(1,1) NOT NULL,
[CategoryName] [nvarchar](64) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY CLUSTERED
(
[CategoryID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]


/****** Object: Table [dbo].[CategoryLog] Script Date: 01/16/2008 00:17:56 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CategoryLog](
[CategoryLogID] [int] IDENTITY(1,1) NOT NULL,
[CategoryID] [int] NOT NULL,
[LogID] [int] NOT NULL,
CONSTRAINT [PK_CategoryLog] PRIMARY KEY CLUSTERED
(
[CategoryLogID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
USE [C:\EXAMPLES2\ENTERPRISE LIBRARY\LOGGING\LOGTODATABASE\LOGTODATABASE\APP_DATA\DATABASE1.MDF]
GO
ALTER TABLE [dbo].[CategoryLog] WITH CHECK ADD CONSTRAINT [FK_CategoryLog_Category] FOREIGN KEY([CategoryID])
REFERENCES [dbo].[Category] ([CategoryID])
GO
ALTER TABLE [dbo].[CategoryLog] WITH CHECK ADD CONSTRAINT [FK_CategoryLog_Log] FOREIGN KEY([LogID])
REFERENCES [dbo].[Log] ([LogID])

/****** Object: Table [dbo].[Log] Script Date: 01/16/2008 00:18:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Log](
[LogID] [int] IDENTITY(1,1) NOT NULL,
[EventID] [int] NULL,
[Priority] [int] NOT NULL,
[Severity] [nvarchar](32) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Title] [nvarchar](256) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Timestamp] [datetime] NOT NULL,
[MachineName] [nvarchar](32) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[AppDomainName] [nvarchar](512) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[ProcessID] [nvarchar](256) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[ProcessName] [nvarchar](512) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[ThreadName] [nvarchar](512) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Win32ThreadId] [nvarchar](128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Message] [nvarchar](1500) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[FormattedMessage] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT [PK_Log] PRIMARY KEY CLUSTERED
(
[LogID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

STEP 5: CREATE THE ACCESSING STORED PROCEDURES
They are:
- AddCategory
- ClearLogs
- InsertCategoryLog
- WriteLog

--AddCategory
CREATE PROCEDURE [dbo].[AddCategory]
-- Add the parameters for the function here
@CategoryName nvarchar(64),
@LogID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @CatID INT
SELECT @CatID = CategoryID FROM Category WHERE CategoryName = @CategoryName
IF @CatID IS NULL
BEGIN
INSERT INTO Category (CategoryName) VALUES(@CategoryName)
SELECT @CatID = @@IDENTITY
END

EXEC InsertCategoryLog @CatID, @LogID

RETURN @CatID
END

--ClearLogs
CREATE PROCEDURE [dbo].[ClearLogs]
AS
BEGIN
SET NOCOUNT ON;

DELETE FROM CategoryLog
DELETE FROM [Log]
DELETE FROM Category
END

--InsertCategoryLog
CREATE PROCEDURE [dbo].[InsertCategoryLog]
@CategoryID INT,
@LogID INT
AS
BEGIN
SET NOCOUNT ON;

DECLARE @CatLogID INT
SELECT @CatLogID FROM CategoryLog WHERE CategoryID=@CategoryID and LogID = @LogID
IF @CatLogID IS NULL
BEGIN
INSERT INTO CategoryLog (CategoryID, LogID) VALUES(@CategoryID, @LogID)
RETURN @@IDENTITY
END
ELSE RETURN @CatLogID
END

--WriteLog
CREATE PROCEDURE [dbo].[WriteLog]
(
@EventID int,
@Priority int,
@Severity nvarchar(32),
@Title nvarchar(256),
@Timestamp datetime,
@MachineName nvarchar(32),
@AppDomainName nvarchar(512),
@ProcessID nvarchar(256),
@ProcessName nvarchar(512),
@ThreadName nvarchar(512),
@Win32ThreadId nvarchar(128),
@Message nvarchar(1500),
@FormattedMessage ntext,
@LogId int OUTPUT
)
AS

INSERT INTO [Log] (
EventID,
Priority,
Severity,
Title,
[Timestamp],
MachineName,
AppDomainName,
ProcessID,
ProcessName,
ThreadName,
Win32ThreadId,
Message,
FormattedMessage
)
VALUES (
@EventID,
@Priority,
@Severity,
@Title,
@Timestamp,
@MachineName,
@AppDomainName,
@ProcessID,
@ProcessName,
@ThreadName,
@Win32ThreadId,
@Message,
@FormattedMessage)

SET @LogID = @@IDENTITY
RETURN @LogID

Other issues:
1. Add more categories
It may be important to define a number of categories for logs
this can be done by the configuration file.


<categorySources>
<add switchValue="All" name="General">
<listeners>
<add name="Database Trace Listener"/>
</listeners>
</add>
<add switchValue="All" name="MyCategory">
<listeners>
<add name="Database Trace Listener"/>
</listeners>
</add>
<add switchValue="All" name="MyCategory2">
<listeners>
<add name="Database Trace Listener"/>
</listeners>
</add>
</categorySources>



Once new categorySources defined, we can use it in code where to write a log:

// indicate which category that this log belongs to
Logger.Write("This is my test", "MyCategory2");

the EntLib code will create database record for the categories and the mapping records between logs and categories.

2. define custom fields
if we are not only logging a message, we'd like to log information composed by a number of fields, we can use ExtendedProperties.

LogEntry ent = new LogEntry();
ent.ExtendedProperties.Add("key1", "value1");
ent.ExtendedProperties.Add("key2", "value2");
ent.Categories.Add("MyCategory2");
Logger.Write(ent);


these collection will be serialized to a string and stored to FormattedMessage column of Log table.
the format of the string is defined by a text formatter in configure file:

<formatters>
<add template="Timestamp: {timestamp}&#xD;&#xA;
Message:{message}&#xD;&#xA;
Category: {category}&#xD;&#xA;
Priority: {priority}&#xD;&#xA;
EventId: {eventid}&#xD;&#xA;
Severity: {severity}&#xD;&#xA;
Title:{title}&#xD;&#xA;
Machine: {machine}&#xD;&#xA;
Application Domain: {appDomain}&#xD;&#xA;
Process Id: {processId}&#xD;&#xA;
Process Name: {processName}&#xD;&#xA;
Win32 Thread Id: {win32ThreadId}&#xD;&#xA;
Thread Name: {threadName}&#xD;&#xA;
Extended Properties: {dictionary({key} - {value}&#xD;&#xA;)}"

type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter,
Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null"
name="Text Formatter" />