Project

Profile

Help

Trouble When I use Console.SetOut and Console.SetError

Added by Daniel Langdon over 10 years ago

I really don't know what to do at this point. I tried to submit a bug report, but the system rejected it with a very generic error message.

This is in regard to this issue that I previously described on StackOverflow: http://stackoverflow.com/questions/19549190/what-to-do-when-console-setout-doesnt-work

I see that when errors occur in Saxon, messages are automatically printed to the Console. However, when I use Console.SetOut and Console.SetError to redirect these messages to another destination, they still appear on the Console.

In the following example, the Console.SetOut and Console.SetError methods are used to set Console.Out to a TextWriter object that simply records, but does not display the output. The messages are meant to be later output at the end of the program.

The result of running the program is that the error messages appear at the beginning of the output instead of at the end of the output. When stepping thru the code in the debugger, it can clearly be seen that the messages are not being passed to my TextWriter class.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Xml;

namespace SaxonOutputBug { class Program { static void Main(string[] args) { // Cache the default Console TextWriter so we can use it again later. TextWriter defaultWriter = Console.Out;

        // Create a RecordingTextWriter and set it to be used for Console.Out and Console.Error
        RecordingTextWriter writer = new RecordingTextWriter();
        Console.SetOut(writer);
        Console.SetError(writer);            
     
        // Perform some operations that will write to the Console, which has been set to the RecordingTextWriter.
        performOperation();
        
        // Now that everything is done, set Console.Out back to the default value and display the result:
        Console.SetOut(defaultWriter);
        Console.WriteLine("The RecordingTextWriter object recorded the following string:\n" + writer.Record.ToString());            
    }

    private static void performOperation()
    {
        // Will record to the RecordingTextWriter class
        Console.WriteLine("I'm writing on the default Console.  Next, I want to see what Saxon writes on the default Console.");

        // Next, create some Saxon classes and make them dump errors on the Console.
        try
        {
            Saxon.Api.Processor processor = new Saxon.Api.Processor();
            XmlDocument invalidXsltDocument = new XmlDocument();
            invalidXsltDocument.LoadXml(@"<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""><xsl:template match=""invalidFunctionCall()""></xsl:template></xsl:stylesheet>");
            Saxon.Api.XdmNode xsltInput = processor.NewDocumentBuilder().Build(invalidXsltDocument);
            Saxon.Api.XsltCompiler xsltCompiler = processor.NewXsltCompiler();
            Saxon.Api.XsltExecutable xsltExecutable = xsltCompiler.Compile(xsltInput);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("Oh no!  There was an error!");
            Console.Error.WriteLine(ex.ToString());
        }
    }
}

public class RecordingTextWriter : TextWriter
{
    public StringBuilder Record { get; private set; }

    public RecordingTextWriter()
    {
        Record = new StringBuilder();
    }

    // Override Write and WriteLine so that the values recorded go to the Record StringBuilder.
    public override void Write(string value)
    {
        Record.Append(value);            
    }
    public override void WriteLine(string value)
    {
        Record.AppendLine(value);
    }
    public override void WriteLine()
    {
        Record.AppendLine();
    }

    public override Encoding Encoding
    {
        get { throw new NotImplementedException(); }
    }
}

}


Replies (16)

Please register to reply

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Thanks for reporting the problem you have discovered. I am currently investigating it now.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Hi Daniel,

Please may you confirm which Saxon version you are using. In Saxon 9.5.1.3 I am getting the expected outcome:

The RecordingTextWriter object recorded the following string:
I'm writing on the default Console.  Next, I want to see what Saxon writes on th
e default Console.
Oh no!  There was an error!
System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.Throw(String res, String arg)
   at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace)
   at System.Xml.XmlDocument.Load(XmlReader reader)
   at System.Xml.XmlDocument.LoadXml(String xml)
   at SaxonOutputBug.Program.performOperation() in C:\Users\ond1\Documents\Visual Studio 2010\Projects\QueryProject\DanielLangdon\DanielLangdon\Program.cs:line
36

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

Thanks for your response to my report, O'Nell.

First of all, it appears that I'm using Saxon 9.5.1.2, so I'm one minor version behind the version you tested with.

Second, I'm not getting the same result that you're getting. Here's the output I'm getting:

Error at /xsl:stylesheet/xsl:template[1] XPST0017 XSLT Pattern syntax error at char 0 on line -1 in {invalidFunctionCal l()}: Unknown system function invalidFunctionCall() The RecordingTextWriter object recorded the following string: I'm writing on the default Console. Next, I want to see what Saxon writes on th e default Console. Oh no! There was an error! javax.xml.transform.TransformerConfigurationException: Failed to compile stylesh eet. 1 error detected.

As you can see, part of the output describing what went wrong came at the very beginning, indicating that it did not get written to the "RecordingTextWriter" object.

I am putting together a system for a client where my client will be able to modify the XSLT he's using, as well as a number of XML files to be read using the document() function. Whenever Saxon encounters any kind of error, it is critical that all the details of the error be available to my client so he can fix the problem once I move on to another project.

That being said, this Saxon library has some really great functionality. In my experience so far, even the open-source version is superior to some commercial XSLT processors I've tried.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Is it possible for you to move forward to the latest Saxon release, i.e. Saxon 9.5.1.3. It is highly possible your problem has been fixed in that release. I just do not have a bug issue to reference, but I will see if I can trace it back.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

O'Nell,

I just now downloaded and installed Saxon 9.5.1.3 and linked my program to it. I get the exact same output I previously posted here.

I may owe you an apology. I was wondering why you weren't getting the same result. I tried copying and pasting the code that I originally pasted here into Visual Studio and I see that it HTML-encoded some of the code, causing it to throw a different exception from the one I originally got.

I have inserted the code into this post with a box around it, and I'm attaching the code file, as well. You should now be able to reproduce the issue.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace SaxonOutputBug
{
    class Program
    {
        static void Main(string[] args)
        {
            // Cache the default Console TextWriter so we can use it again later.
            TextWriter defaultWriter = Console.Out;

            // Create a RecordingTextWriter and set it to be used for Console.Out and Console.Error
            RecordingTextWriter writer = new RecordingTextWriter();
            Console.SetOut(writer);
            Console.SetError(writer);

            // Perform some operations that will write to the Console, which has been set to the RecordingTextWriter.
            performOperation();

            // Now that everything is done, set Console.Out back to the default value and display the result:
            Console.SetOut(defaultWriter);
            Console.WriteLine("The RecordingTextWriter object recorded the following string:\n" + writer.Record.ToString());
        }

        private static void performOperation()
        {
            // Will record to the RecordingTextWriter class
            Console.WriteLine("I'm writing on the default Console.  Next, I want to see what Saxon writes on the default Console.");

            // Next, create some Saxon classes and make them dump errors on the Console.
            try
            {
                Saxon.Api.Processor processor = new Saxon.Api.Processor();
                XmlDocument invalidXsltDocument = new XmlDocument();
                invalidXsltDocument.LoadXml(@"");
                Saxon.Api.XdmNode xsltInput = processor.NewDocumentBuilder().Build(invalidXsltDocument);
                Saxon.Api.XsltCompiler xsltCompiler = processor.NewXsltCompiler();
                Saxon.Api.XsltExecutable xsltExecutable = xsltCompiler.Compile(xsltInput);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("Oh no!  There was an error!");
                Console.Error.WriteLine(ex.ToString());
            }
        }
    }

    public class RecordingTextWriter : TextWriter
    {
        public StringBuilder Record { get; private set; }

        public RecordingTextWriter()
        {
            Record = new StringBuilder();
        }

        // Override Write and WriteLine so that the values recorded go to the Record StringBuilder.
        public override void Write(string value)
        {
            Record.Append(value);
        }
        public override void WriteLine(string value)
        {
            Record.AppendLine(value);
        }
        public override void WriteLine()
        {
            Record.AppendLine();
        }

        public override Encoding Encoding
        {
            get { throw new NotImplementedException(); }
        }
    }
}

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Thanks Daniel for attaching the c# code. I have now been able to reproduce the output you mentioned and I am currently investigating the issue. As Mike suggested on Stackoverflow as a workaround you should be able to attach a ErrorListener of your own to redirect exceptions thrown from Saxon.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

O'Nell, thanks for taking the time to look into this.

Unfortunately, I don't believe that the fix is as simple as using the ErrorListener as Michael suggested. When he gave me this suggestion, I did implement it and it did give me access to most of the error data. (For some reason, the line numbers didn't show up in the debugger, though).

However, I've discovered that when errors originate from other parts of the process, I still have the original problem that they are output directly to the text Console and not captured by my code. For example, when the XSLT document function is used to load data from an external XML document and the document contains an error, my users will not have access to this data. I suppose that I could probably eventually find a way to capture these errors as well, but then I'd be left wondering if errors could potentially originate somewhere else in the program and not be captured.

That's why I thought that the simplest solution to the problem would be to figure out why I can't control what happens with the error messages that come out of Saxon.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

I just found this other post that seems to be referring to the same issue: https://saxonica.plan.io/boards/3/topics/5685

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Michael Kay over 10 years ago

Compile-time error messages on .NET can be captured by setting the ErrorList property on the XsltCompiler. Using this is probably easier than writing an ErrorListener.

The reason there are no line numbers in the error messages is that you have supplied the stylesheet not in the form of source XML, but in the form of a DOM. The DOM does not hold any line numbers. I assume there is some reason you chose to do it this way; if not, then it's best avoided.

Run-time failures will generally result in the transform() method throwing an exception. The only time you need an ErrorListener for run-time failures is for "recoverable errors" such as ambiguous template matches, or failures from the document() function. My recommendation here would be to set the recovery policy so these are either fatal, or ignored entirely. With XSLT 2.0 the need for recoverable errors has largely disappeared, for example in the case of the document() function it's better to check that the document is available using doc-available(), and then treat a failure from document() as fatal.

We're still working on the question of why redirecting Console.Out and Console.Err doesn't seem to be working.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

I created a simple Java program which calls to the System.out.println with a simple text. I confirm again that under C# it does not write the output to the the TextWriter, but instead writes to the console display window.

I have raised this issue of the redirecting Console.Out and Console.Err problem on the IKVM developers mailing list, as I believe they might be able to help get to the bottom of this issue. I will update this forum with any relevant feedback.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Reply from Jeroen Frijters, IKVM:

For maximum compatibility System.out/err/in use the underlying streams and the Java encodings, so if you want to redirect you have to either do it at the stdout/stderr/stdin level or use Java's System.setOut(), System.setErr() or System.setIn().

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Michael Kay over 10 years ago

I guess it would be a good idea if we supplied a method in the Saxon.Api.Processor equivalent to Configuration.setStandardErrorOutput(), but accepting a .NET print stream rather than a Java print stream. This redirects all System.err output at the Saxon level. Saxon doesn't use System.out/Console.Out, except as the default for query/transformation output if you don't supply anything else.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

Yes I think that would work.

In the meanwhile, as Mike suggested earlier as a workaround ErrorList does stop error messages printing to the console. The following code snippet does the job:


...
   List errorList = new List();
   xsltCompiler.ErrorList = errorList;
...

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

            Saxon.Api.Processor processor = new Saxon.Api.Processor();
            XmlDocument invalidXsltDocument = new XmlDocument();
            invalidXsltDocument.LoadXml(@"<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""><xsl:template match=""/""><xsl:value-of select='document(""abc"")' /></xsl:template></xsl:stylesheet>");
            xsltInput = processor.NewDocumentBuilder().Build(invalidXsltDocument);
            xsltCompiler = processor.NewXsltCompiler();
            xsltCompiler.ErrorList = errorList;
            xsltExecutable = xsltCompiler.Compile(xsltInput);
            xsltTransformer = xsltExecutable.Load();

            XmlDocument document = new XmlDocument();
            document.LoadXml("<Hello><world/></Hello>");
            Stream transformedStream = new MemoryStream();
            xsltTransformer.InitialContextNode = processor.NewDocumentBuilder().Build(document);
            dataSerializer = processor.NewSerializer(transformedStream);                                        
            xsltTransformer.Run(dataSerializer);

I've modified my testing code as shown above and I've made the following two observations:

(1) If I modify the XSLT so that the XSLT is invalid, the errors get saved to the specified errorList object.

(2) If I run this code as it is, it throws a run-time error because it cannot load "abc". It seems that this error is dumped directly to the console regardless of whether or not xsltCompiler.ErrorList is assigned.

How can I capture the errors in this latter case?

RE: Trouble When I use Console.SetOut and Console.SetError - Added by O'Neil Delpratt over 10 years ago

For dynamic errors which occur in the execution of the stylesheet the ErrorList will not work.

You can directly set Java's System.Out and System.Err in your C# code. This is shown in the following code snippet. It must be inserted at the top of your @performOperation@ method for it to redirect from its default:


java.io.ByteArrayOutputStream os = new java.io.ByteArrayOutputStream();
java.io.PrintStream ps = new java.io.PrintStream(os);
java.lang.System.setErr(ps);
java.lang.System.setOut(ps);
...

At the end of the method you can then write to your own TextWriter, for example:


Console.Error.WriteLine(os.toString());

In the next major Saxon release (i.e. 9.6?) we hope to add this redirection of setting the SetOut and SetErr in the Saxon.Api for C#.

RE: Trouble When I use Console.SetOut and Console.SetError - Added by Daniel Langdon over 10 years ago

OK, I did something a little bit different.

I wrote my own implementation of the ByteArrayOutputStream class and overrode the "write" methods to write to the Console. That way, I get all the messages in order (because some messages come from my .net code, after all).

I've attached a copy of the Program.cs file with the behavior that I want.

Thank you very much for your assistance with this matter.

Your Saxon-HE library has proven very useful to my client and me.

    (1-16/16)

    Please register to reply