Custom Database Trace Listener - Issue getting customData to the database

Topics: Exception Handling Application Block, General discussion, Logging Application Block
Nov 30, 2012 at 3:16 PM

I am following the example 'Custom Database Trace Listener Sample' found here http://whirelez1.appspot.com/entlib.codeplex.com/wikipage?title=Sample%20Projects&referringTitle=Home

I have downloaded and rebuilt the solution.

I was able to add the listener to my sample project.

The issue is that in my sample project, i create the logEntry and then write it out via the following

                CustomLogEntry logEntry = new CustomLogEntry()                {                    Categories = new string[] { "Log To Database" },                    Message = "This is the message to log!",                    Severity = System.Diagnostics.TraceEventType.Information,                    CustomData = "My Custom Data"                };

Logger.Write(logEntry, "Log to Database");

 

I am able to step through the solution in debug mode from my code into the sample provided above.  What I am noticing however is customData in the database is always null.

Working backwards it calls

        private int ExecuteWriteLogStoredProcedure(LogEntry logEntry, Data.Database db, DbTransaction transaction)

and within this method it casts the logEntry to CustomLogEntry.  At this point it is always null and as such customdata sent to the database is always null.  This is the problem.


            // Add custom data            CustomLogEntry customLogEntry = logEntry as CustomLogEntry;
            if (customLogEntry != null)            {                customData = customLogEntry.CustomData;            } 

 

I noticed that the entry point is TraceData (line 85) calling...
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)

The customlogentry is suppose to be coming in via the object data.  If i look at what is coming it, it does not contain the property customdata at all and so any cast to customlogentry results in null. 

The question is WHY is customdata property missing if i send in see above above 
Logger.Write(logEntry, "Log to Database"); where logEntry is of type CustomLogEntry.

Very confused here and getting frustrated.

I appreciate any help.

 

 

Nov 30, 2012 at 3:47 PM

There is only one overload for Write that accepts a LogEntry and that is: Logger.Write(LogEntry log).  If you call any other overload such as Logger.Write(logEntry, "Log to Database") then the closest matching overload that the compiler will resolve to is: Logger.Write(object message, string category).  In all the object overloads a ToString() is performed on the object and the result set as the Message property of a new LogEntry instance created within the application block.  That is why the custom LogEntry type is lost.

So, the solution is to use the Logger.Write(LogEntry log) overload which is fine because the category (or categories) will be set on the LogEntry object directly and not have to be passed in to the Write method.  

Just call: Logger.Write(logEntry);

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Nov 30, 2012 at 7:40 PM
Edited Nov 30, 2012 at 11:09 PM

Hi Randy.

If I execute the following code it only logs to the event log (and not to the database without the custom data, i believe it is a result of one of the special sources).  

In addition, when executing the following Logger.Write(logEntry) it does not call CustomDatabaseTraceListener at all while if I call Logger.Write(logEntry, "Log To Database") it does in fact enter the custom listener.  I don't understand why calling Logger.Wite(logEntry) it doesn't even enter the customdatabasetracelistener.    I also do not see any overridden method in the customdatabasetracelistener to accept Logger.Write(LogEntry log).  It seems that there should be no?
This code is from the site listed above.  I have included the code of the customdatabasetracelistener below to ensure we are talking about the same thing.

Help is very much appreciated.

 

                CustomLogEntry logEntry = new CustomLogEntry()
                {
                    Categories = new string[] { "Log To Database" },
                    Message = "This is the message to log!",
                    Severity = System.Diagnostics.TraceEventType.Information,
                    CustomData = "My Custom Data"
                };
 Logger.Write(logEntry);

 

I must be missing something here.  I am close but something not quite right.

 

 

 

custom database listener code

 

//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters;
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Database.Configuration;
using Data = Microsoft.Practices.EnterpriseLibrary.Data;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
using System.Text;
using System.Xml.Linq;
using CustomDatabaseTraceListener.Configuration;

namespace CustomDatabaseTraceListener
{
    /// <summary>
    /// A <see cref="System.Diagnostics.TraceListener"/> that writes to a database, formatting the output with an <see cref="ILogFormatter"/>.
    /// </summary>
    [ConfigurationElementType(typeof(CustomDatabaseTraceListenerData))]
    public class CustomDatabaseTraceListener : FormattedTraceListenerBase
    {
        string writeLogStoredProcName = String.Empty;
        string addCategoryStoredProcName = String.Empty;
        Data.Database database;

        /// <summary>
        /// Initializes a new instance of <see cref="CustomDatabaseTraceListener"/>.
        /// </summary>
        /// <param name="database">The database for writing the log.</param>
        /// <param name="writeLogStoredProcName">The stored procedure name for writing the log.</param>
        /// <param name="addCategoryStoredProcName">The stored procedure name for adding a category for this log.</param>
        /// <param name="formatter">The formatter.</param>        
        public CustomDatabaseTraceListener(
            Data.Database database,
            string writeLogStoredProcName,
            string addCategoryStoredProcName,
            ILogFormatter formatter)
            : base(formatter)
        {
            this.writeLogStoredProcName = writeLogStoredProcName;
            this.addCategoryStoredProcName = addCategoryStoredProcName;
            this.database = database;
        }

        /// <summary>
        /// The Write method 
        /// </summary>
        /// <param name="message">The message to log</param>
        public override void Write(string message)
        {
            ExecuteWriteLogStoredProcedure(0, 5, TraceEventType.Information, string.Empty, DateTime.Now, string.Empty,
                                            string.Empty, string.Empty, string.Empty, null, null, message, database, null);
        }

        /// <summary>
        /// The WriteLine method.
        /// </summary>
        /// <param name="message">The message to log</param>
        public override void WriteLine(string message)
        {
            Write(message);
        }


        /// <summary>
        /// Delivers the trace data to the underlying database.
        /// </summary>
        /// <param name="eventCache">The context information provided by <see cref="System.Diagnostics"/>.</param>
        /// <param name="source">The name of the trace source that delivered the trace data.</param>
        /// <param name="eventType">The type of event.</param>
        /// <param name="id">The id of the event.</param>
        /// <param name="data">The data to trace.</param>
///this one gets called if i do Logger.Write(logEntry, "Log To Database") but data is of type LogEntry not CustomLog
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
        {
            if ((this.Filter == null) || this.Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null))
            {
                if (data is LogEntry)
                {
                    LogEntry logEntry = data as LogEntry;
                    if (ValidateParameters(logEntry))
                        ExecuteStoredProcedure(logEntry);
                }
                else if (data is string)
                {
                    Write(data as string);
                }
                else
                {
                    base.TraceData(eventCache, source, eventType, id, data);
                }
            }
        }

        /// <summary>
        /// Declare the supported attributes for <see cref="CustomDatabaseTraceListener"/>
        /// </summary>
        protected override string[] GetSupportedAttributes()
        {
            return new string[4] { "formatter", "writeLogStoredProcName", "addCategoryStoredProcName", "databaseInstanceName" };
        }

        /// <summary>
        /// Validates that enough information exists to attempt executing the stored procedures
        /// </summary>
        /// <param name="logEntry">The LogEntry to validate.</param>
        /// <returns>A Boolean indicating whether the parameters for the LogEntry configuration are valid.</returns>
        private bool ValidateParameters(LogEntry logEntry)
        {
            bool valid = true;

            if (writeLogStoredProcName == null ||
                writeLogStoredProcName.Length == 0)
            {
                return false;
            }

            if (addCategoryStoredProcName == null ||
                addCategoryStoredProcName.Length == 0)
            {
                return false;
            }

            return valid;
        }

        /// <summary>
        /// Executes the stored procedures
        /// </summary>
        /// <param name="logEntry">The LogEntry to store in the database</param>
        private void ExecuteStoredProcedure(LogEntry logEntry)
        {
            using (DbConnection connection = database.CreateConnection())
            {
                connection.Open();
                try
                {
                    using (DbTransaction transaction = connection.BeginTransaction())
                    {
                        try
                        {
                            int logID = Convert.ToInt32(ExecuteWriteLogStoredProcedure(logEntry, database, transaction));
                            ExecuteAddCategoryStoredProcedure(logEntry, logID, database, transaction);
                            transaction.Commit();
                        }
                        catch
                        {
                            transaction.Rollback();
                            throw;
                        }

                    }
                }
                finally
                {
                    connection.Close();
                }
            }
        }

        /// <summary>
        /// Executes the WriteLog stored procedure
        /// </summary>
        /// <param name="eventId">The event id for this LogEntry.</param>
        /// <param name="priority">The priority for this LogEntry.</param>
        /// <param name="severity">The severity for this LogEntry.</param>
        /// <param name="title">The title for this LogEntry.</param>
        /// <param name="timeStamp">The timestamp for this LogEntry.</param>
        /// <param name="machineName">The machine name for this LogEntry.</param>
        /// <param name="appDomainName">The appDomainName for this LogEntry.</param>
        /// <param name="processId">The process id for this LogEntry.</param>
        /// <param name="processName">The processName for this LogEntry.</param>
        /// <param name="managedThreadName">The managedthreadName for this LogEntry.</param>
        /// <param name="win32ThreadId">The win32threadID for this LogEntry.</param>
        /// <param name="message">The message for this LogEntry.</param>
        /// <param name="db">An instance of the database class to use for storing the LogEntry</param>
        /// <returns>An integer for the LogEntry Id</returns>
        private int ExecuteWriteLogStoredProcedure(int eventId, int priority, TraceEventType severity, string title, DateTime timeStamp,
                                                    string machineName, string appDomainName, string processId, string processName,
                                                    string managedThreadName, string win32ThreadId, string message, Data.Database db,
                                                    string customData)
        {
            DbCommand cmd = db.GetStoredProcCommand(writeLogStoredProcName);

            db.AddInParameter(cmd, "eventID", DbType.Int32, eventId);
            db.AddInParameter(cmd, "priority", DbType.Int32, priority);
            db.AddParameter(cmd, "severity", DbType.String, 32, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, severity.ToString());
            db.AddParameter(cmd, "title", DbType.String, 256, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, title);
            db.AddInParameter(cmd, "timestamp", DbType.DateTime, timeStamp);
            db.AddParameter(cmd, "machineName", DbType.String, 32, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, machineName);
            db.AddParameter(cmd, "AppDomainName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, appDomainName);
            db.AddParameter(cmd, "ProcessID", DbType.String, 256, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, processId);
            db.AddParameter(cmd, "ProcessName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, processName);
            db.AddParameter(cmd, "ThreadName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, managedThreadName);
            db.AddParameter(cmd, "Win32ThreadId", DbType.String, 128, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, win32ThreadId);
            db.AddParameter(cmd, "message", DbType.String, 1500, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, message);
            db.AddInParameter(cmd, "formattedmessage", DbType.String, message);

            // Custom parameters added here
            db.AddParameter(cmd, "customData", DbType.String, 512, ParameterDirection.Input, true, 0, 0, null, DataRowVersion.Default, customData);

            db.AddOutParameter(cmd, "LogId", DbType.Int32, 4);

            db.ExecuteNonQuery(cmd);
            int logId = Convert.ToInt32(cmd.Parameters[cmd.Parameters.Count - 1].Value, CultureInfo.InvariantCulture);
            return logId;
        }

        /// <summary>
        /// Executes the WriteLog stored procedure
        /// </summary>
        /// <param name="logEntry">The LogEntry to store in the database.</param>
        /// <param name="db">An instance of the database class to use for storing the LogEntry</param>
        /// <param name="transaction">The transaction that wraps around the execution calls for storing the LogEntry</param>
        /// <returns>An integer for the LogEntry Id</returns>
        private int ExecuteWriteLogStoredProcedure(LogEntry logEntry, Data.Database db, DbTransaction transaction)
        {
            DbCommand cmd = db.GetStoredProcCommand(writeLogStoredProcName);

            db.AddInParameter(cmd, "eventID", DbType.Int32, logEntry.EventId);
            db.AddInParameter(cmd, "priority", DbType.Int32, logEntry.Priority);
            db.AddParameter(cmd, "severity", DbType.String, 32, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.Severity.ToString());
            db.AddParameter(cmd, "title", DbType.String, 256, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.Title);
            db.AddInParameter(cmd, "timestamp", DbType.DateTime, logEntry.TimeStamp);
            db.AddParameter(cmd, "machineName", DbType.String, 32, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.MachineName);
            db.AddParameter(cmd, "AppDomainName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.AppDomainName);
            db.AddParameter(cmd, "ProcessID", DbType.String, 256, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.ProcessId);
            db.AddParameter(cmd, "ProcessName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.ProcessName);
            db.AddParameter(cmd, "ThreadName", DbType.String, 512, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.ManagedThreadName);
            db.AddParameter(cmd, "Win32ThreadId", DbType.String, 128, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.Win32ThreadId);
            db.AddParameter(cmd, "message", DbType.String, 1500, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, logEntry.Message);

            if (Formatter != null)
                db.AddInParameter(cmd, "formattedmessage", DbType.String, Formatter.Format(logEntry));
            else
                db.AddInParameter(cmd, "formattedmessage", DbType.String, logEntry.Message);

            string customData = null;

            // Add custom data
            CustomLogEntry customLogEntry = logEntry as CustomLogEntry;

            if (customLogEntry != null)
            {
                customData = customLogEntry.CustomData;
            }

            db.AddParameter(cmd, "customData", DbType.String, 512, ParameterDirection.Input, true, 0, 0, null, DataRowVersion.Default, customData);

            db.AddOutParameter(cmd, "LogId", DbType.Int32, 4);

            db.ExecuteNonQuery(cmd, transaction);
            int logId = Convert.ToInt32(cmd.Parameters[cmd.Parameters.Count - 1].Value, CultureInfo.InvariantCulture);
            return logId;
        }

        /// <summary>
        /// Executes the AddCategory stored procedure
        /// </summary>
        /// <param name="logEntry">The LogEntry to store in the database.</param>
        /// <param name="logID">The unique identifer for the LogEntry as obtained from the WriteLog Stored procedure.</param>
        /// <param name="db">An instance of the database class to use for storing the LogEntry</param>
        /// <param name="transaction">The transaction that wraps around the execution calls for storing the LogEntry</param>
        private void ExecuteAddCategoryStoredProcedure(LogEntry logEntry, int logID, Data.Database db, DbTransaction transaction)
        {
            foreach (string category in logEntry.Categories)
            {
                DbCommand cmd = db.GetStoredProcCommand(addCategoryStoredProcName);
                db.AddInParameter(cmd, "categoryName", DbType.String, category);
                db.AddInParameter(cmd, "logID", DbType.Int32, logID);
                db.ExecuteNonQuery(cmd, transaction);
            }
        }
    }
}

Dec 2, 2012 at 6:01 AM

> I also do not see any overridden method in the customdatabasetracelistener to accept Logger.Write(LogEntry log).  It seems that there should be no?

No.  The Logging Application block processes the request and invokes the TraceData method of the configured trace listeners.

My recommendation would be ensure that the console application quick start with the project is working for you -- it should be provided that the database scripts are run and the connection information is correct.  Then modify your application with similar settings.  If it doesn't work then use the exact same settings as the quickstart and then make small changes testing as you go to ensure that it is working at every step of the way.  Then you can see when it stops working and you will know what changed to make it not work.  (This approach can get you through a myriad of issues.)

If that still doesn't work then post your configuration information.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Dec 4, 2012 at 2:16 AM

Ok so some improvement.

I took pieces from the sample and incorporated into my sample application.  I narrowed it down to some difference in the loggingConfiguration section in the web.config but I physically could not see the difference between the bad one and the good one.  It was almost like there was a hidden character (even after using file compares I couldn't identify the wacky character causing the configuration to not work.  Kills me).

In the end I could not find the difference and I started using the good copy of the loggingConfiguration section.  So this seems to have been resolved.

thanks