DFSR Debug Analysis with Message Analyzer – Part 6, Parsing Specific Multi-line Messages

This post continues the series that started here.

Last time I discussed parsing multi-line messages but the way I handled them was to append everything into a generic MessageText field. In this post I want to examine the handling I’ve used for specific multi-line messages where the extended logging has additional field data. Let’s start with an example of such a message –

20120522 11:01:55.858 2600 JOIN 1201 Join::SubmitUpdate LDB Updating ID Record:
+ fid 0x200000000F503
+ usn 0x600d5e48
+ uidVisible 1
+ filtered 0
+ journalWrapped 0
+ slowRecoverCheck 0
+ pendingTombstone 0
+ internalUpdate 0
+ dirtyShutdownMismatch 0
+ meetInstallUpdate 0
+ meetReanimated 0
+ recUpdateTime 20120522 00:31:11.500 GMT
+ present 1
+ nameConflict 0
+ attributes 0x20
+ ghostedHeader 0
+ data 0
+ gvsn {5442ADD7-04C7-486B-B665-2CB036997A67}-v899394
+ uid {5442ADD7-04C7-486B-B665-2CB036997A67}-v598754
+ parent {8A6CF487-2D5A-456C-A235-09F312D631C8}-v1
+ fence Default (3)
+ clockDecrementedInDirtyShutdown 0
+ clock 20120522 00:31:11.500 GMT (0x1cd37b230245303)
+ createTime 20120516 00:30:40.306 GMT
+ csId {8A6CF487-2D5A-456C-A235-09F312D631C8}
+ hash 3C379B81-28E4F2A8-52F091A5-0D4CFAB8
+ similarity 2E233A14-253F363F-3E110515-22120C04
+ name file0000004161.txt
+

For the sake of simplicity, I’ll edit the example to –

20120522 11:01:55.858 2600 JOIN 1201 Join::SubmitUpdate LDB Updating ID Record:
+ fid 0x200000000F503
+ name file0000004161.txt

This will make the code samples that follow less verbose. In practice I’ve extended these to deal with all fields.

Custom Methods

I’ve discovered that Open Parser Notation (OPN) has no contains method. This would be useful for checking MessageText to see when I’m parsing a particular message type. I’ve created a contains method and dropped it at the end of my parser file –

bool Contains(this string str, string subStr)
{
return str.IndexOf(subStr) >= 0;
}

Utility Parsers

Looking at the multi-line continuation of the message, I can see that it occurs as name-value pairs. I’ll need to create what I refer to as a utility parser to handle this. The utility parser will never be invoked against a standalone message but I’ll call on it in my multi-line parser code to extract these name-value pairs. Once again, I drop this code at the end of my parser file –

message name_value_pair with
EntryInfo { Regex = @"\+\s*(?<n>[A-Za-z]+)\s*(?<v>.*)", Priority = 2 } : LogEntry
{
string n;
string v;
}

The RegEx looks for

  1. A + at the start of the line followed by any number of spaces – \+\s*
  2. One or more alpha characters which are injected into the “n” string – (?<n>[A-Za-z]+)
  3. Any number of spaces – \s*
  4. Any characters which are injected into the “v” string – (?<v>.*)

Custom Data Type

When I parse these name-value pairs I’ll want to inject them into a data type for that type of message. I define it like this –

type ldbupdateinfo
{
optional string fid;
optional string name;

    bool parse(string line, LineContext context, any message m)
{
line = line.Trim();
var nvp = Decode<name_value_pair>(line, context);

if (nvp == null) return false;

switch (nvp.n)
{
// cases for an LDB change
case "fid" => fid = (nvp.v as string);
case "name" => name = (nvp.v as string);
default => return false;
}

return true;
}
}

Here my custom data type, ldbupdateinfo has two optional string fields – fid and name.

type ldbupdateinfo
{
optional string fid;
optional string name;

It has a parse method which calls the name_value_pair utility parser and attempts to populate the fid and name fields. If it is successful it returns true to indicate the line is parsed or false if unsuccessful –

    bool parse(string line, LineContext context, any message m)
{
line = line.Trim();
var nvp = Decode<name_value_pair>(line, context);

if (nvp == null) return false;

switch (nvp.n)
{
// cases for an LDB change
case "fid" => fid = (nvp.v as string);
case "name" => name = (nvp.v as string);
default => return false;
}

return true;
}

Modify Multi-line Parser

The multi-line parser from last post contained code –

else
{
if (line.Count > 0 && line[0] == '+')
{
MessageText = MessageText + " " + (line.Segment(1)).Trim();
return true;
}
else
{
return false;
}
}

I modify this to handle our specific multi-line message –

else
{
// line starting with "+" is considered continuation
// of the current message
if (line.Count > 0 && line[0] == '+')
{
if (MessageText.Contains("LDB Updating ID Record:"))
{
// if there isn't already an ldbupdateinfo node, create one
var ldbui = LDBUpdateInformation == nothing ? new ldbupdateinfo() : LDBUpdateInformation as ldbupdateinfo;

// attempt to parse this line as an ldbupdateinfo node and extract name value pairs
if (!ldbui.parse(line, context, this))
{
// if the line could not be parsed as a name-value pair
// just append the whole line to the MessageText field
// and confirm the line is parsed
MessageText = MessageText + " " + (line.Segment(1)).Trim();
return true;
}

LDBUpdateInformation = ldbui;
return true;
}
else()
{
MessageText = MessageText + " " + (line.Segment(1)).Trim();
return true;
}
}
else
{
return false;
}
}

And lastly, add an optional LDBUpdateInformation field to the message definition –

uint Thread;
string ModuleID;
uint SourceCodeLine;
string Namespace;
string Class;
string Method;
string Level;
string MessageText;
optional ldbupdateinfo LDBUpdateInformation;

Result

After saving all of these changes to the parser file (and expanding code to include all fields for the message type), a multi-line message of this type is parsed as follows –

MA10

Progress Report

To make it easier for you to consume what I’ve done so far, here’s a version of my parser that includes everything discussed to this point –

DFSRDebug-01.config

Next Up

Dealing with Message Analyzer Limitations