如何在C#中同时将控制台输出到多个流?

3
我有一个程序可以将控制台输出写入日志文件,但是它不再显示在控制台窗口中。有没有办法让它既能在窗口中显示,又能写入日志文件? 更新:
appLogStream = new FileStream(logFile, FileMode.Append, FileAccess.Write, FileShare.Read);
TextWriter logtxtWriter = Console.Out;
logstrmWriter = new StreamWriter(appLogStream);
if(!console) Console.SetOut(logstrmWriter);
logstrmWriter.AutoFlush = true;
Console.WriteLine("Started at " + DateTime.Now);

控制台是类中的一个常量集。它基本上告诉它是否正在使用控制台窗口(如果不在控制台中,不调用readline等)。

那么有没有办法同时写入控制台和文件?


你能详细说明一下你是如何将控制台输出发送到文件的吗?可以给个小的代码示例吗? - undefined
顺便说一句,如果你用它来记录日志,请考虑使用内置的日志基础设施(或其他类库,如Log4net)。可以阅读一下TraceListener和相关类的资料,了解如何实现和配置这种一对多输出。 - undefined
你所工作的环境中是否有某些因素使得你必须采用当前的方法,而不是使用更好的解决方案,比如一个中央日志记录器类或者.NET跟踪基础设施? - undefined
4个回答

3
你可以简单地读取该流,记录并打印它。
这有点取决于你的代码,如果你将输出流分配给输出文件的输入流,那么如果你将内容读入缓冲区,那么这可能会更容易些。
关于你的更新,我建议你将所有的Console都替换为自定义日志函数,例如MyLogger的一个实例(以下是代码)。它将你的输出写入控制台和日志文件。
class MyLogger {
    private FileStream appLogStream;

    public MyLogger() {
        appLogStream = new FileStream(logFile, FileMode.Append, FileAccess.Write,
                                      FileShare.Read);
        appLogStream.WriteLine("Started at " + DateTime.Now);
    }

    public Write(string msg) {
        Console.Write(msg);
        appLogStream.Write(msg);
    }

    public WriteLine(string msg) {
        Console.WriteLine(msg);
        appLogStream.WriteLine(msg);
    }
}

我觉得问题是,一旦你调用了Console.SetOut,你写入Console的所有内容都会被发送到那个编写器,而不是标准输出。你上面的代码很可能只是复制了发送到appLogStream的输出。 - undefined
1
+1。本质上,它是一种一对多的流写入器。对于实际使用,可能更容易拥有输出写入器列表,并将输出克隆到所有写入器中。 - undefined
@rekire - 我同意使用一个日志类是理想的。但他的问题可能是他正在处理一个已经遍布了Console.Write...的现有大型代码库,并且他正在寻找一种不显眼的方式来控制输出位置。 - undefined
幸运的是,我没有使用大型代码库,所以我只需实现这个功能。我有一个问题。如果类被标记为静态,我可以使用构造函数吗?如果可以,它会在什么时候被调用? - undefined

2

我认为你可以做类似这样的事情:

 public class ConsoleDecorator : TextWriter
{
    private TextWriter m_OriginalConsoleStream;

    public ConsoleDecorator(TextWriter consoleTextWriter)
    {
        m_OriginalConsoleStream = consoleTextWriter;
    }

    public override void WriteLine(string value)
    {
        m_OriginalConsoleStream.WriteLine(value);

        // Fire event here with value
    }


    public static void SetToConsole()
    {
        Console.SetOut(new ConsoleDecorator(Console.Out));
    }
}

你需要使用 ConsoleDecorator.SetToConsole() 来“注册”包装器; 之后,每个 Console.WriteLine 调用都会到达自定义方法,在那里你可以触发一个事件并在其他地方获取写入的文本(例如日志记录)。
如果你想要使用这种方式,你需要将该类设置为单例,然后你就可以从其他类中访问事件注册(当事件被触发时,这些类应该写入日志文件)。

0
当你调用Console.SetOut时,你可以指定Console应该写入的位置。如果你不这样做(即通常使用的方式),并且你调用Console.Write,它会检查是否有输出写入器,如果没有,则设置为默认值。
   stream = OpenStandardOutput(256);

然后

        Encoding encoding = Encoding.GetEncoding((int) Win32Native.GetConsoleOutputCP());
        writer = TextWriter.Synchronized(new StreamWriter(stream, encoding, 256, false) { HaveWrittenPreamble = true, AutoFlush = true });

所以你现在应该能够做你正在做的事情,如果你还想像没有重定向控制台一样将所有内容回显到标准输出,你可以使用Console.OpenStandardOutput方法创建自己的写入器,使用你自己打开的流。那段代码中使用的Win32Native是内部的,所以你无法访问它,但是你可以使用Console.OutputEncoding来获取它所使用的编码。
另外你可以尝试使用Console.Out属性,在调用SetOut之前获取并保存标准输出的TextWriter。然后你就可以使用它来回显到标准输出了。

这是内部的(从kernal32中的GetConsoleOutputCP()函数获取)。我想展示控制台的功能。只需尝试使用标准输出流创建最简单的文本写入器,而无需直接复制控制台的内部实现。 - undefined
@ArlenBeiler - 我已经编辑了我的答案,添加了一个更简单的方法供你尝试。 - undefined
好的,是的,谢谢。明天我会告诉大家这一切如何运作。我想我可能会将其中一些答案结合起来 :) - undefined
@ArlenBeiler - 如果Console.Out不起作用,请查看我的编辑答案,了解如何获取Console使用的编码。 - undefined

0
最后我就这样离开了课堂。
public class ConsoleDecorator : TextWriter
{
    private TextWriter m_OriginalConsoleStream;

    public override Encoding Encoding => m_OriginalConsoleStream.Encoding;

    public ConsoleDecorator(TextWriter consoleTextWriter)
    {
        m_OriginalConsoleStream = consoleTextWriter;
    }

    public override void WriteLine(string value)
    {
        string cHora = DateTime.Now.ToString("yyyyMMddTHHmm:ss") + "." + DateTime.Now.Millisecond.ToString().PadLeft(3, '0');
        string msg = $"{cHora}: {value}";
        m_OriginalConsoleStream.WriteLine(msg);

        // Fire event here with value
        if (Properties.Settings.Default.GenerateLog)
        {
            // Ruta y nombre del archivo de texto
            string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "APP_" + DateTime.Now.ToString("yyyyMMdd").Substring(2, 6) + ".Log");
            try
            {
                using (StreamWriter writer = new StreamWriter(filePath, true))
                {
                    writer.Write('\n' + msg);
                }
            }
            catch { }
        }
    }

    public static void SetToConsole()
    {
        Console.SetOut(new ConsoleDecorator(Console.Out));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接