将控制台输出镜像到文件

在 C # 控制台应用中,是否有一种聪明的方法可以将控制台输出镜像到一个文本文件?

目前,我只是在 log 方法中将同一个字符串传递给 Console.WriteLineInstanceOfStreamWriter.WriteLine

71420 次浏览

Check out log4net. With log4net you can set up console and file appenders that will can output log messages to both places with a single log statement.

Log4net can do this for you. You would only write something like this:

logger.info("Message");

A configuration will determine whether the print out will go to console, file or both.

You could subclass the TextWriter class, and then assign its instance to the Console.Out using the Console.SetOut method - which in particular does the same thing as passing the same string to both methods in the log method.

Another way might declaring your own Console class and use the using statement to distinguish between the classes:

using Console = My.Very.Own.Little.Console;

To access the standard console you'd then need:

global::Console.Whatever

This may be some kind of more work, but I would go the other way round.

Instantiate a TraceListener for the console and one for the log file; thereafter use Trace.Write statements in your code instead of Console.Write. It becomes easier afterwards to remove the log, or the console output, or to attach another logging mechanism.

static void Main(string[] args)
{
Trace.Listeners.Clear();


TextWriterTraceListener twtl = new TextWriterTraceListener(Path.Combine(Path.GetTempPath(), AppDomain.CurrentDomain.FriendlyName));
twtl.Name = "TextLogger";
twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;


ConsoleTraceListener ctl = new ConsoleTraceListener(false);
ctl.TraceOutputOptions = TraceOptions.DateTime;


Trace.Listeners.Add(twtl);
Trace.Listeners.Add(ctl);
Trace.AutoFlush = true;


Trace.WriteLine("The first line to be in the logfile and on the console.");
}

As far as I can recall, you can define the listeners in the application configuration making it possible to activate or deactivate the logging without touching the build.

Can't you just redirect the output to a file, using the > command?

c:\>Console.exe > c:/temp/output.txt

If you need to mirror, you can try find a win32 version of tee that splits the output to a file.

See https://superuser.com/questions/74127/tee-for-windows to run tee from PowerShell

This is a simple class which subclasses TextWriter to allow redirection of the input to both a file and the console.

Use it like this

  using (var cc = new ConsoleCopy("mylogfile.txt"))
{
Console.WriteLine("testing 1-2-3");
Console.WriteLine("testing 4-5-6");
Console.ReadKey();
}

Here is the class:

class ConsoleCopy : IDisposable
{


FileStream fileStream;
StreamWriter fileWriter;
TextWriter doubleWriter;
TextWriter oldOut;


class DoubleWriter : TextWriter
{


TextWriter one;
TextWriter two;


public DoubleWriter(TextWriter one, TextWriter two)
{
this.one = one;
this.two = two;
}


public override Encoding Encoding
{
get { return one.Encoding; }
}


public override void Flush()
{
one.Flush();
two.Flush();
}


public override void Write(char value)
{
one.Write(value);
two.Write(value);
}


}


public ConsoleCopy(string path)
{
oldOut = Console.Out;


try
{
fileStream = File.Create(path);


fileWriter = new StreamWriter(fileStream);
fileWriter.AutoFlush = true;


doubleWriter = new DoubleWriter(fileWriter, oldOut);
}
catch (Exception e)
{
Console.WriteLine("Cannot open file for writing");
Console.WriteLine(e.Message);
return;
}
Console.SetOut(doubleWriter);
}


public void Dispose()
{
Console.SetOut(oldOut);
if (fileWriter != null)
{
fileWriter.Flush();
fileWriter.Close();
fileWriter = null;
}
if (fileStream != null)
{
fileStream.Close();
fileStream = null;
}
}


}

As suggested by Arul, using Console.SetOut can be used to redirect output to a text file:

Console.SetOut(new StreamWriter("Output.txt"));

EDIT: This method provide the possibility to redirect the console information come from third party package. override the WriteLine method is good for my situation, but you may need to override other Write methods depends on the third party package.

First we need to create new class inherent from StreamWriter, say CombinedWriter;

Then init a new instant of CombinedWriter with Console.Out;

Finally we can redirect console output to the instant of the new class by Console.SetOut;

Following code is the new class works for me.

public class CombinedWriter : StreamWriter
{
TextWriter console;
public CombinedWriter(string path, bool append, Encoding encoding, int bufferSize, TextWriter console)
:base(path, append, encoding, bufferSize)
{
this.console = console;
base.AutoFlush = true; // thanks for @konoplinovich reminding
}
public override void WriteLine(string value)
{
console.Write(value);
base.WriteLine(value);
}
}

The decision to use a class, inherited from the StreamWriter, suggestions by user Keep Thinking, works. But I had to to add into constructor base.AutoFlush = true:

{
this.console = console;
base.AutoFlush = true;
}

аnd an explicit call to the destructor:

public new void Dispose ()
{
base.Dispose ();
}

Otherwise, the file is closed earlier than he recorded all the data.

I am using it as:

CombinedWriter cw = new CombinedWriter ( "out.txt", true, Encoding.Unicode, 512, Console.Out );
Console.SetOut (cw);

Thank you to Keep Thinking for the excellent solution! I added some further overrides to avoid logging certain console write events that (for my purposes) are only expected for console display.

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


namespace RedirectOutput
{
public class CombinedWriter  : StreamWriter
{
TextWriter console;
public CombinedWriter(string path, bool append, TextWriter consoleout)
: base(path, append)
{
this.console = consoleout;
base.AutoFlush = true;
}
public override void Write(string value)
{
console.Write(value);
//base.Write(value);//do not log writes without line ends as these are only for console display
}
public override void WriteLine()
{
console.WriteLine();
//base.WriteLine();//do not log empty writes as these are only for advancing console display
}
public override void WriteLine(string value)
{
console.WriteLine(value);
if (value != "")
{
base.WriteLine(value);
}
}
public new void Dispose()
{
base.Dispose();
}
}
class Program
{
static void Main(string[] args)
{
CombinedWriter cw = new CombinedWriter("combined.log", false, Console.Out);
Console.SetOut(cw);
Console.WriteLine("Line 1");
Console.WriteLine();
Console.WriteLine("Line 2");
Console.WriteLine("");
for (int i = 0; i < 10; i++)
{
Console.Write("Waiting " + i.ToString());
Console.CursorLeft = 0;
}
Console.WriteLine();
for (int i = 0; i < 10; i++)
{
Console.Write("Waiting " + i.ToString());
}
Console.WriteLine();
Console.WriteLine("Line 3");
cw.Dispose();
}
}
}

I think what you already are using is kind of the best approach. A simple method to essentially mirror your output.

First declare a global TextWriter at the beginning:

private TextWriter txtMirror = new StreamWriter("mirror.txt");

Then make a method for writing:

// Write empty line
private void Log()
{
Console.WriteLine();
txtMirror.WriteLine();
}


// Write text
private void Log(string strText)
{
Console.WriteLine(strText);
txtMirror.WriteLine(strText);
}

Now, instead of using Console.WriteLine("...");, use Log("...");. Simple as that. It's even shorter!


There could be some trouble if you shift the cursorposition (Console.SetCursorPosition(x, y);), but otherwise works well, I use it myself too!

EDIT

Ofcourse you can make a method for Console.Write(); the same way if you're not using only WriteLines

You can actually create a transparent mirroring of Console.Out to Trace by implementing your own class inherited from TextWriter and overriding WriteLine method.

In WriteLine you can write it to Trace which can then be configured to write to file.

I found this answer very helpful: https://stackoverflow.com/a/10918320/379132

It actually worked for me!

If you duplicate console output from a code you do not control, for example 3rd party library, all members of TextWriter should be overwritten. The code uses ideas from this thread.

Usage:

using (StreamWriter writer = new StreamWriter(filePath))
{
using (new ConsoleMirroring(writer))
{
// code using console output
}
}

ConsoleMirroring class

public class ConsoleMirroring : TextWriter
{
private TextWriter _consoleOutput;
private TextWriter _consoleError;


private StreamWriter _streamWriter;


public ConsoleMirroring(StreamWriter streamWriter)
{
this._streamWriter = streamWriter;
_consoleOutput = Console.Out;
_consoleError = Console.Error;


Console.SetOut(this);
Console.SetError(this);
}


public override Encoding Encoding { get { return _consoleOutput.Encoding; } }
public override IFormatProvider FormatProvider { get { return _consoleOutput.FormatProvider; } }
public override string NewLine { get { return _consoleOutput.NewLine; } set { _consoleOutput.NewLine = value; } }


public override void Close()
{
_consoleOutput.Close();
_streamWriter.Close();
}


public override void Flush()
{
_consoleOutput.Flush();
_streamWriter.Flush();
}


public override void Write(double value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}
public override void Write(string value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(object value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(decimal value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(float value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(bool value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(int value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(uint value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(ulong value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(long value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(char[] buffer)
{
_consoleOutput.Write(buffer);
_streamWriter.Write(buffer);


}


public override void Write(char value)
{
_consoleOutput.Write(value);
_streamWriter.Write(value);


}


public override void Write(string format, params object[] arg)
{
_consoleOutput.Write(format, arg);
_streamWriter.Write(format, arg);


}


public override void Write(string format, object arg0)
{
_consoleOutput.Write(format, arg0);
_streamWriter.Write(format, arg0);


}


public override void Write(string format, object arg0, object arg1)
{
_consoleOutput.Write(format, arg0, arg1);
_streamWriter.Write(format, arg0, arg1);


}


public override void Write(char[] buffer, int index, int count)
{
_consoleOutput.Write(buffer, index, count);
_streamWriter.Write(buffer, index, count);


}


public override void Write(string format, object arg0, object arg1, object arg2)
{
_consoleOutput.Write(format, arg0, arg1, arg2);
_streamWriter.Write(format, arg0, arg1, arg2);


}


public override void WriteLine()
{
_consoleOutput.WriteLine();
_streamWriter.WriteLine();


}


public override void WriteLine(double value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(decimal value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(string value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(object value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(float value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(bool value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(uint value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(long value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(ulong value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(int value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(char[] buffer)
{
_consoleOutput.WriteLine(buffer);
_streamWriter.WriteLine(buffer);


}
public override void WriteLine(char value)
{
_consoleOutput.WriteLine(value);
_streamWriter.WriteLine(value);


}
public override void WriteLine(string format, params object[] arg)
{
_consoleOutput.WriteLine(format, arg);
_streamWriter.WriteLine(format, arg);


}
public override void WriteLine(string format, object arg0)
{
_consoleOutput.WriteLine(format, arg0);
_streamWriter.WriteLine(format, arg0);


}
public override void WriteLine(string format, object arg0, object arg1)
{
_consoleOutput.WriteLine(format, arg0, arg1);
_streamWriter.WriteLine(format, arg0, arg1);


}
public override void WriteLine(char[] buffer, int index, int count)
{
_consoleOutput.WriteLine(buffer, index, count);
_streamWriter.WriteLine(buffer, index, count);


}
public override void WriteLine(string format, object arg0, object arg1, object arg2)
{
_consoleOutput.WriteLine(format, arg0, arg1, arg2);
_streamWriter.WriteLine(format, arg0, arg1, arg2);


}


protected override void Dispose(bool disposing)
{
if (disposing)
{
Console.SetOut(_consoleOutput);
Console.SetError(_consoleError);
}
}
}

My answer is based on the most-voted non-accepted answer, and also the least-voted answer which I think is the most elegant solution so far. It's a little more generic in terms of the stream type you can use (you may use a MemoryStream for instance), but I've omitted all the extended functionality included in the latter answer for brevity.

class ConsoleMirrorWriter : TextWriter
{
private readonly StreamWriter _writer;
private readonly TextWriter _consoleOut;


public ConsoleMirrorWriter(Stream stream)
{
_writer = new StreamWriter(stream);
_consoleOut = Console.Out;
Console.SetOut(this);
}


public override Encoding Encoding => _writer.Encoding;


public override void Flush()
{
_writer.Flush();
_consoleOut.Flush();
}


public override void Write(char value)
{
_writer.Write(value);
_consoleOut.Write(value);
}


protected override void Dispose(bool disposing)
{
if (!disposing) return;
_writer.Dispose();
Console.SetOut(_consoleOut);
}
}

Usage:

using (var stream = File.Create(Path.Combine(Path.GetTempPath(), AppDomain.CurrentDomain.FriendlyName)))
using (var writer = new ConsoleMirrorWriter(stream))
{
// Code using console output.
}