views:

289

answers:

2

Need logging to:

  1. Rolling file, to avoid 1 big log file.
  2. CSV format for easier look up.

I can see EntLib (5.0) have Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener to log to rolling log file.

To make the log entries look like a CSV row, I can change the Logging.Formatters.TextFormatter.Template to put double quote around the values. Also change the Listener's Footer and Header to nothing, so they won't be output.

Under normal circumstance, this would give me a well formed CSV file. However if a token value in the Template contain double quote, this would not be escaped, hence the log file become an invalid CSV file.

Is there any way to resolve this?

Is there any alternative solutions to this problem?

A: 

I don't think there is any "silver bullet" solution short of writing your own formatter.

You will need to worry about double quotes and new lines. Any of those will throw off the formatting.

I think the only properties that you have to worry about those characters for is the Message, Title, and any ExtendedProperties you are using. I recommend writing a thin wrapper or facade around the Write method where you escape those properties to ensure that you have a properly formatted file. i.e. escape any double quotes and replace new lines with a space.

Tuzo
Yeah, I eventually had to write a wrapper around the Logger (already a facade!!!), since I can't bother writing my own formatter that can support all those tokens in the template and configuration element along with it, which I find very useful of the Textformatter.I'll accept this as an answer for now.Thank you.
Tinminator
A: 

See http://msdn.microsoft.com/en-us/library/ff650608.aspx. Turn out adding a custom formatter is not that hard, I added a CSVTextFormattter to only take care of massaging the message and extended properties, which works for me. Notice I use the bult-in TextFormatter to do all the heavy lifting.

Sample Config:

<loggingConfiguration name="" tracingEnabled="true" defaultCategory="General">
...
    <formatters>
      <add type="<your namespace>.CSVTextFormatter, <your dll>"
          template="{timestamp(local)},{severity},{category},{message},{property(ActivityId)},{eventid},{win32ThreadId},{threadName},{dictionary({key} - {value}{newline})}"
          name="CSV Text Formatter" />
    </formatters>...
</loggingConfiguration>

The class is something like this:

Public Class CSVTextFormatter
    Implements ILogFormatter

    Private Const csTemplateAttributeName As String = "template"

    Private moTextFormatter As TextFormatter
    Private Property TextFormatter() As TextFormatter
        Get
            Return moTextFormatter
        End Get
        Set(ByVal value As TextFormatter)
            moTextFormatter = value
        End Set
    End Property

    Private moConfigData As System.Collections.Specialized.NameValueCollection
    Private Property ConfigData() As System.Collections.Specialized.NameValueCollection
        Get
            Return moConfigData
        End Get
        Set(ByVal value As System.Collections.Specialized.NameValueCollection)
            moConfigData = value
            If moConfigData.AllKeys.Contains(csTemplateAttributeName) Then
                TextFormatter = New TextFormatter(moConfigData(csTemplateAttributeName))
            Else
                TextFormatter = New TextFormatter()
            End If
        End Set
    End Property

    Public Sub New()
        TextFormatter = New TextFormatter()
    End Sub

    Public Sub New(ByVal configData As System.Collections.Specialized.NameValueCollection)
        Me.ConfigData = configData
    End Sub

    Public Function Format(ByVal log As Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry) As String Implements Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.ILogFormatter.Format
        Dim oLog As Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry = log.Clone()
        With oLog
            .Message = NormalizeToCSVValue(.Message)
            For Each sKey In .ExtendedProperties.Keys
                Dim sValue As String = TryCast(.ExtendedProperties(sKey), String)
                If Not String.IsNullOrEmpty(sValue) Then
                    .ExtendedProperties(sKey) = NormalizeToCSVValue(sValue)
                End If
            Next
        End With
        Return TextFormatter.Format(oLog)
    End Function

    Private Shared Function NormalizeToCSVValue(ByVal text As String) As String
        Dim bWrapLogText = False
        Dim oQualifiers = New String() {""""}
        For Each sQualifier In oQualifiers
            If text.Contains(sQualifier) Then
                text = text.Replace(sQualifier, String.Format("""{0}""", sQualifier))
                bWrapLogText = True
            End If
        Next
        Dim oDelimiters = New String() {",", vbLf, vbCr, vbCrLf}
        If text.Contains(oDelimiters) Then
            bWrapLogText = True
        End If
        If bWrapLogText Then
            text = String.Format("""{0}""", text)
        End If
        Return text
    End Function

End Class
Tinminator