Writing Logs to an XML File with .NET - c#

I am storing logs in an xml file...
In a traditional straight text format approach, you would typically just have a openFile... then writeLine method...
How is it possible to add a new entry into the xml document structure, like you would just with the text file approach?

use an XmlWriter.
example code:
public class Quote
{
public string symbol;
public double price;
public double change;
public int volume;
}
public void Run()
{
Quote q = new Quote
{
symbol = "fff",
price = 19.86,
change = 1.23,
volume = 190393,
};
WriteDocument(q);
}
public void WriteDocument(Quote q)
{
var settings = new System.Xml.XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent= true
};
using (XmlWriter writer = XmlWriter.Create(Console.Out, settings))
{
writer.WriteStartElement("Stock");
writer.WriteAttributeString("Symbol", q.symbol);
writer.WriteElementString("Price", XmlConvert.ToString(q.price));
writer.WriteElementString("Change", XmlConvert.ToString(q.change));
writer.WriteElementString("Volume", XmlConvert.ToString(q.volume));
writer.WriteEndElement();
}
}
example output:
<Stock Symbol="fff">
<Price>19.86</Price>
<Change>1.23</Change>
<Volume>190393</Volume>
</Stock>
see
Writing with an XmlWriter
for more info.

One of the problems with writing a log file in XML format is that you can't just append lines to the end of the file, because the last line has to have a closing root element (for the XML to be valid)
This blog post by Filip De Vos demonstrates quite a good solution to this:
High Performance writing to XML Log files (edit: link now dead so removed)
Basically, you have two XML files linked together using an XML-include thing:
Header file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log [
<!ENTITY loglines SYSTEM "loglines.xml">
]>
<log>
&loglines;
</log>
Lines file (in this example, named loglines.xml):
<logline date="2007-07-01 13:56:04.313" text="start process" />
<logline date="2007-07-01 13:56:25.837" text="do something" />
<logline date="2007-07-01 13:56:25.853" text="the end" />
You can then append new lines to the 'lines file', but (most) XML parsers will be able to open the header file and read the lines correctly.
Filip notes that: This XML will not be parsed correctly by every XML parser on the planet. But all the parsers I have used do it correctly.

The big difference is the way you are thinking about your log data. In plain text files you are indeed just adding new lines. XML is a tree structure however, and you need to think about like such. What you are adding is probably another NODE, i.e.:
<log>
<time>12:30:03 PST</time>
<user>joe</user>
<action>login</action>
<log>
Because it is a tree what you need to ask is what parent are you adding this new node to. This is usually all defined in your DTD (Aka, how you are defining the structure of your data). Hopefully this is more helpful then just what library to use as once you understand this principle the interface of the library should make more sense.

Why reinvent the wheel? Use TraceSource Class (System.Diagnostics) with the XmlWriterTraceListener.

Sorry to post a answer for old thread. i developed the same long time ago. here i like to share my full code for logger saved log data in xml file date wise.
logger class code
using System.IO;
using System.Xml;
using System.Threading;
public class BBALogger
{
public enum MsgType
{
Error ,
Info
}
public static BBALogger Instance
{
get
{
if (_Instance == null)
{
lock (_SyncRoot)
{
if (_Instance == null)
_Instance = new BBALogger();
}
}
return _Instance;
}
}
private static BBALogger _Instance;
private static object _SyncRoot = new Object();
private static ReaderWriterLockSlim _readWriteLock = new ReaderWriterLockSlim();
private BBALogger()
{
LogFileName = DateTime.Now.ToString("dd-MM-yyyy");
LogFileExtension = ".xml";
LogPath= Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Log";
}
public StreamWriter Writer { get; set; }
public string LogPath { get; set; }
public string LogFileName { get; set; }
public string LogFileExtension { get; set; }
public string LogFile { get { return LogFileName + LogFileExtension; } }
public string LogFullPath { get { return Path.Combine(LogPath, LogFile); } }
public bool LogExists { get { return File.Exists(LogFullPath); } }
public void WriteToLog(String inLogMessage, MsgType msgtype)
{
_readWriteLock.EnterWriteLock();
try
{
LogFileName = DateTime.Now.ToString("dd-MM-yyyy");
if (!Directory.Exists(LogPath))
{
Directory.CreateDirectory(LogPath);
}
var settings = new System.Xml.XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
StringBuilder sbuilder = new StringBuilder();
using (StringWriter sw = new StringWriter(sbuilder))
{
using (XmlWriter w = XmlWriter.Create(sw, settings))
{
w.WriteStartElement("LogInfo");
w.WriteElementString("Time", DateTime.Now.ToString());
if (msgtype == MsgType.Error)
w.WriteElementString("Error", inLogMessage);
else if (msgtype == MsgType.Info)
w.WriteElementString("Info", inLogMessage);
w.WriteEndElement();
}
}
using (StreamWriter Writer = new StreamWriter(LogFullPath, true, Encoding.UTF8))
{
Writer.WriteLine(sbuilder.ToString());
}
}
catch (Exception ex)
{
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
public static void Write(String inLogMessage, MsgType msgtype)
{
Instance.WriteToLog(inLogMessage, msgtype);
}
}
Calling or using this way
BBALogger.Write("pp1", BBALogger.MsgType.Error);
BBALogger.Write("pp2", BBALogger.MsgType.Error);
BBALogger.Write("pp3", BBALogger.MsgType.Info);
MessageBox.Show("done");
may my code help you and other :)

Without more information on what you are doing I can only offer some basic advice to try.
There is a method on most of the XML objects called "AppendChild". You can use this method to add the new node you create with the log comment in it. This node will appear at the end of the item list. You would use the parent element of where all the log nodes are as the object to call on.
Hope that helps.

XML needs a document element (Basically top level tag starting and ending the document).
This means a well formed XML document need have a beginning and end, which does not sound very suitable for logs, where the current "end" of the log is continously extended.
Unless you are writing batches of self contained logs where you write everything to be logged to one file in a short period of time, I'd consider something else than XML.
If you are writing a log of a work-unit done, or a log that doesn't need to be inspected until the whole thing has finished, you could use your approach though - simply openfile, write the log lines, close the file when the work unit is done.

For editing an xml file, you could also use LINQ. You can take a look on how here:
http://www.linqhelp.com/linq-tutorials/adding-to-xml-file-using-linq-and-c/

Related

FlatFile library, delimited layout, wrong parsing when multiple fields are empty at the end of the row

We use in some of our applications the FlatFile library (https://github.com/forcewake/FlatFile) to parse some files delimited with separator (";"), since a lot of time without problems.
We faced yesterday a problem receiving files having multiple fields empty at the end of the row.
I replicated the problem with short console application to show and permit you to verify in a simple way:
using FlatFile.Delimited;
using FlatFile.Delimited.Attributes;
using FlatFile.Delimited.Implementation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FlatFileTester
{
class Program
{
static void Main(string[] args)
{
var layout = GetLayout();
var factory = new DelimitedFileEngineFactory();
using (MemoryStream ms = new MemoryStream())
using (FileStream file = new FileStream(#"D:\shared\dotnet\FlatFileTester\test.csv", FileMode.Open, FileAccess.Read))
{
byte[] bytes = new byte[file.Length];
file.Read(bytes, 0, (int)file.Length);
ms.Write(bytes, 0, (int)file.Length);
var flatFile = factory.GetEngine(layout);
ms.Position = 0;
List<TestObject> records = flatFile.Read<TestObject>(ms).ToList();
foreach(var record in records)
{
Console.WriteLine(string.Format("Id=\"{0}\" - DescriptionA=\"{1}\" - DescriptionB=\"{2}\" - DescriptionC=\"{3}\"", record.Id, record.DescriptionA, record.DescriptionB, record.DescriptionC));
}
}
Console.ReadLine();
}
public static IDelimitedLayout<TestObject> GetLayout()
{
IDelimitedLayout<TestObject> layout = new DelimitedLayout<TestObject>()
.WithDelimiter(";")
.WithQuote("\"")
.WithMember(x => x.Id)
.WithMember(x => x.DescriptionA)
.WithMember(x => x.DescriptionB)
.WithMember(x => x.DescriptionC)
;
return layout;
}
}
[DelimitedFile(Delimiter = ";", Quotes = "\"")]
public class TestObject
{
[DelimitedField(1)]
public int Id { get; set; }
[DelimitedField(2)]
public string DescriptionA { get; set; }
[DelimitedField(3)]
public string DescriptionB { get; set; }
[DelimitedField(4)]
public string DescriptionC { get; set; }
}
}
This is an example of file:
1;desc1;desc1;desc1
2;desc2;desc2;desc2
3;desc3;;desc3
4;desc4;desc4;
5;desc5;;
So the first 4 rows are parsed as expected:
All fields with values in the first and second row
empty string for third field of third row
empty string for fouth field of fourth row
in the fifth row we expect empty string on third and fourth field, like this:
Id=5
DescriptionA="desc5"
DescriptionB=""
DescriptionC=""
instead we receive this:
Id=5
DescriptionA="desc5"
DescriptionB=";" // --> THE SEPARATOR!!!
DescriptionC=""
We can't understand if is a problem of configuration, bug of the library, or some other problem in the code...
Anyone have some similar experiences with this library, or can note some problem in the code above not linked with the library but causing the error...?
I took a look and debug the source code of the open source library: https://github.com/forcewake/FlatFile.
It seems there's a problem, in particular in this case, in witch there are 2 empty fields, at the end of a row, the bug take effects on the field before the last of the row.
I opened an issue for this libray, hoping some contributor of the library could invest some time to investigate, and, if it is so, to fix: https://github.com/forcewake/FlatFile/issues/80
For now we decided to fix the wrong values of the list, something like:
string separator = ",";
//...
//...
//...
records.ForEach(x => {
x.DescriptionC = x.DescriptionC.Replace(separator, "");
});
For our case, anyway, it make not sense to have a character corresponding to the separator as value of that field...
...even if it would be better to have bug fixing of the library

How to make an xml look simpler and also to make it simple to retrieve values using c#?

<reportParameterTestSettings>
<Report uri="/standard/Project-Iteration-WorkItem-MultiFT">
<ReportParameters>
<name>WorkItemType</name>
<name>CurrentUser</name>
<name>SelectedDate</name>
<name>TopIteration</name>
<name>TopTeam</name>
<name>ExecutionIteration</name>
<name>ShowIteration</name>
<name>SelectedExecutionIteration</name>
<name>SelectedShowIteration</name>
<name>ExecutionStartDate</name>
<name>ShowEndDate</name>
<name>Holiday</name>
<name>JobRole</name>
<name>EnabledFeature</name>
</ReportParameters>
</Report>
</reportParameterTestSettings>
How can i make this xml such that is it doesn't have redundant tags such as name. Also this example consists of only one Report. In reality there would be more than 20 reports. I was thinking of adding all parameters as one string comma separated and then doing a split on it. Is there a better way to do it?
Are you creating this file? A little confused by what you're asking but you should be able to do something like this
use XML Serialization
using System.XML.Serialization
public class reportParameterTestSettings
{
[XmlElement("Report")]
public List<ReportParameters> Report { get; set; }
[XmlAttribute("Uri")]
public string Uri { get; set; }
}
public class ReportParameters
{
public string WorkItemType { get; set; }
public string CurrentUser { get; set; }
...etc ...etc
}
Write it to an xml file
reportParameterTestSettings data = new reportParameterTestSettings();
//add your items to data
XmlSerializer writer = new XmlSerializer(typeof(reportParameterTestSettings));
using (FileStream file = File.Create(#"c:\file.xml"))
{
writer.Serialize(file, data);
}
Reading your xml file
reportParameterTestSettings data;
XmlSerializer reader = new XmlSerializer(typeof(reportParameterTestSettings));
using (FileStream file = File.OpenRead(#"c:\file.xml"))
{
data = reader.Deserialize(file) as reportParameterTestSettings;
}
You should then end up with a file that looks like
<reportParameterTestSettings>
<Report Uri=stuff>
<ReportParameters>
<WorkItemType>Stuff here</WorkItemType>
<CurrentUser>Stuff here</CurrentUser>
...etc ...etc
</ReportParameters>
</Report>
<Report Uri=stuff>
<ReportParameters>
<WorkItemType>Stuff here</WorkItemType>
<CurrentUser>Stuff here</CurrentUser>
...etc ...etc
</ReportParameters>
</Report>
</reportParameterTestSettings>
The newboston community has some great tutorials on xml in C#:
starting with this tutorial move on:http://thenewboston.org/watch.php?cat=15&number=109
I think your current structure is simple enough already. In fact it's very similar to having multiple rows in a database table.
In terms of querying your xml, I recommend looking at linq to xml http://msdn.microsoft.com/en-us/library/bb387061.aspx

Deserialize XML file

I have an XML file containing 4 different strings, but I am having trouble deserializing the file. Could someone help me with this?
I looked online for answers, but none of them worked, I'm not sure what to do about it.
Here is the XML file I am trying to deserialize:
<?xml version="1.0" encoding="utf-8"?>
<saveData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<strFolder1>1st Location</strFolder1>
<strFolder2>2nd Location</strFolder2>
<strTabName>newTab0</strTabName>
<strTabText>Main</strTabText>
</saveData>
var ser = new XmlSerializer(typeof(saveData));
var obj = (saveData)ser.Deserialize(stream);
public class saveData
{
public string strFolder1;
public string strFolder2;
public string strTabName;
public string strTabText;
}
I'd recommend looking at XmlReader. Some other approaches are easier in different ways, but you can build anything from XmlReader. Such as:
while(rdr.Read())
if(rdr.NodeType == XmlNodeType.Element)
switch(rdr.LocalName)
{
case "strFolder1":
strFolder1 = rdr.ReadContentAsString();
break;
case "strFolder2":
strFolder2 = rdr.ReadContentAsString();
break;
case "strTabName":
strTabName = rdr.ReadContentAsString();
break;
case "strTabText":
strTabText = rdr.ReadContentAsString();
break;
}
(We could take some short-cuts if guaranteed the ordering, I did it the hard way to show that the hard way isn't that hard).
Using XmlDocument, XmlSerializer and XDocument are easier in a lot of cases, but I recommend learning this first because it'll handle everything and is never less efficient. If you learn it first the worse that'll happen is you do a bit more work than necessary to end up with something a bit more efficient than strictly necessary (you'll do a micro-optimisation out of ignorance of the simpler ways). If you learn the others first the worse that'll happen is you do a lot more work than necessary to end up with something a lot less efficient than needed.
namespace Cars1
{
public class saveData
{
public string strFolder1;
public string strFolder2;
public string strTabName;
public string strTabText;
}
[Serializable]
class Program
{
static void Main(string[] args)
{
saveData obj = new saveData();
FileStream fopen = new FileStream("abc.xml", FileMode.Open);
XmlSerializer x = new XmlSerializer(obj.GetType());
StreamReader read_from_xml = new StreamReader(fopen);
obj = (saveData)x.Deserialize(read_from_xml);
Console.WriteLine(obj.strFolder1 + "=>" + obj.strFolder2 + "=>" + obj.strTabName+"=>"+obj.strTabText);
Console.ReadKey();
fopen.Close();
}
}
}

OFX File parser in C#

I am looking for an OFX file parser library in C#. I have search the web but there seems to be none. Does anyone know of any good quality C# OFX file parser. I need to process some bank statements files which are in OFX format.
Update
I have managed to find a C# library for parsing OFX parser.
Here is the link ofx sharp. This codebase seems to be the best case to startup my solution.
I tried to use the ofx sharp library, but realised it doesn't work is the file is not valid XML ... it seems to parse but has empty values ...
I made a change in the OFXDocumentParser.cs where I first fix the file to become valid XML and then let the parser continue. Not sure if you experienced the same issue?
Inside of the method:
private string SGMLToXML(string file)
I added a few lines first to take file to newfile and then let the SqmlReader process that after the following code:
string newfile = ParseHeader(file);
newfile = SGMLToXMLFixer.Fix_SONRS(newfile);
newfile = SGMLToXMLFixer.Fix_STMTTRNRS(newfile);
newfile = SGMLToXMLFixer.Fix_CCSTMTTRNRS(newfile);
//reader.InputStream = new StringReader(ParseHeader(file));
reader.InputStream = new StringReader(newfile);
SGMLToXMLFixer is new class I added into the OFXSharp library. It basically scans all the tags that open and verifies it has a closing tag too.
namespace OFXSharp
{
public static class SGMLToXMLFixer
{
public static string Fix_SONRS(string original)
{ .... }
public static string Fix_STMTTRNRS(string original)
{ .... }
public static string Fix_CCSTMTTRNRS(string original)
{ .... }
private static string Fix_Transactions(string file, string transactionTag, int lastIdx, out int lastIdx_new)
{ .... }
private static string Fix_Transactions_Recursive(string file_modified, int lastIdx, out int lastIdx_new)
{ .... }
}
}
Try http://www.codeproject.com/KB/aspnet/Ofx_to_DataSet.aspx. The code uses Framework 3.5 and transforms an ofx into a dataset, this may help with what you're trying to do.

How can I find the position where a string is malformed XML (in C#)?

I'm writing a lightweight XML editor, and in cases where the user's input is not well formed, I would like to indicate to the user where the problem is, or at least where the first problem is. Does anyone know of an existing algorithm for this? If looking at code helps, if I could fill in the FindIndexOfInvalidXml method (or something like it), this would answer my question.
using System;
namespace TempConsoleApp
{
class Program
{
static void Main(string[] args)
{
string text = "<?xml version=\"1.0\"?><tag1><tag2>Some text.</taagg2></tag1>";
int index = FindIndexOfInvalidXml(text);
Console.WriteLine(index);
}
private static int FindIndexOfInvalidXml(string theString)
{
int index = -1;
//Some logic
return index;
}
}
}
I'd probably just cheat. :) This will get you a line number and position:
string s = "<?xml version=\"1.0\"?><tag1><tag2>Some text.</taagg2></tag1>";
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
try
{
doc.LoadXml(s);
}
catch(System.Xml.XmlException ex)
{
MessageBox.Show(ex.LineNumber.ToString());
MessageBox.Show(ex.LinePosition.ToString());
}
Unless this is an academic exercise, I think that writing your own XML parser is probably not the best way to go about this. I would probably check out the XmlDocument class within the System.Xml namespace and try/catch exceptions for the Load() or LoadXml() methods. The exception's message property should contain info on where the error occurred (row/col numbers) and I suspect it'd be easier to use a regular expression to extract those error messages and the related positional info.
You should be able to simply load the string into an XmlDocument or an XmlReader and catch XmlException. The XmlException class has a LineNumber property and a LinePosition property.
You can also use XmlValidatingReader if you want to validate against a schema in addition to checking that a document is well-formed.
You'd want to load the string into an XmlDocument object via the load method and then catch any exceptions.
public bool isValidXml(string xml)
{
System.Xml.XmlDocument xDoc = null;
bool valid = false;
try
{
xDoc = new System.Xml.XmlDocument();
xDoc.loadXml(xmlString);
valid = true;
}
catch
{
// trap for errors
}
return valid;
}

Categories