Reflejar la salida de la consola en un archivo

88

En una aplicación de consola de C #, ¿hay alguna forma inteligente de reflejar la salida de la consola en un archivo de texto?

Actualmente solo estoy pasando la misma cadena a ambos Console.WriteLiney InstanceOfStreamWriter.WriteLineen un método de registro.

xyz
fuente

Respuestas:

117

Esto puede ser algo más trabajo, pero yo iría al revés.

Cree una instancia TraceListenerpara la consola y otra para el archivo de registro; a partir de entonces, use Trace.Writedeclaraciones en su código en lugar de Console.Write. Posteriormente, resulta más fácil eliminar el registro o la salida de la consola, o adjuntar otro mecanismo de registro.

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.");
}

Por lo que puedo recordar, puede definir los oyentes en la configuración de la aplicación, lo que permite activar o desactivar el registro sin tocar la compilación.

Oliver Friedrich
fuente
4
Eso es perfecto, gracias. Estaba al tanto de Log4Net, pero parece incorrecto tener que acceder a una biblioteca para algo como esto.
xyz
3
No sé por qué no le dan mayor importancia a Trace; me parece que debería funcionar bien para el registro a escala de producción, pero todos quieren agregar una biblioteca adicional (como log4net) para hacerlo.
Coderer
Este es un reflejo unidireccional. Me refiero a que si tiene una consola interactiva y obtiene algunos datos del usuario y desea registrar todo en un archivo, esta solución no funciona. A pesar de este simple hecho, mi pregunta está cerrada. Aquí: stackoverflow.com/questions/3886895/…
Xaqron
6
Realmente me gustó esta solución, así que hice un blog rápido al respecto con una limpieza menor y una guía de algunos inconvenientes en el camino. mcrook.com/2014/11/quick-and-easy-console-logging-trace.html Gracias por la gran solución :)
Michael Crook
51

Esta es una clase simple que subclasifica TextWriter para permitir la redirección de la entrada tanto a un archivo como a la consola.

Úselo así

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

Aquí está la clase:

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;
    }
  }

}
cristiano
fuente
5
Creo que esta es la solución más completa. No es necesario anular todas las sobrecargas de los métodos Write / WriteLine y es transparente para el otro código. Por lo tanto, toda la actividad de la consola se duplicará en el archivo, sin realizar cambios en otro código.
papadi
3
¡Gracias hombre! ¡Es impresionante! Acabo de reemplazar File.Create por File.Open (ruta, FileMode.Append, FileAccess.Write, FileShare.Read); porque no quiero borrar los registros antiguos al inicio y quiero poder abrir el archivo de registro mientras el programa aún se está ejecutando.
Juan
1
Esto no requirió que reemplazara todas mis llamadas existentes a Console.WriteLine(), que era exactamente lo que quería.
x6herbius
Para cualquiera que pase por ahí, está confundido cómo hace esto. Busque Console.SetOut(doubleWriter);. Lo que es modificar un global para Console, me tomó un poco, ya que estoy tan acostumbrado a trabajar en aplicaciones donde prácticamente nada es global. ¡Buen material!
Douglas Gaskell
13

Consulte log4net . Con log4net puede configurar la consola y los anexos de archivos que pueden enviar mensajes de registro a ambos lugares con una sola declaración de registro.

tvanfosson
fuente
6
Bueno, creo que se deben evitar las bibliotecas adicionales si se puede hacer con lo que ya está allí.
Oliver Friedrich
También recomiendo log4net, pero parece que NLog está tomando su lugar en la comunidad.
Mark Richman
10

¿No puede simplemente redirigir la salida a un archivo, usando el >comando?

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

Si necesita duplicar, puede intentar encontrar una versión win32 teeque divida la salida en un archivo.

Consulte /superuser/74127/tee-for-windows para ejecutar tee desde PowerShell

xtofl
fuente
8
Necesito reflejar. Por eso se menciona en el tema y en el cuerpo. Sin embargo, gracias por el consejo :)
xyz
8

Puede subclasificar la clase TextWriter y luego asignar su instancia a Console.Out usando el método Console.SetOut , que en particular hace lo mismo que pasar la misma cadena a ambos métodos en el método de registro.

Otra forma podría declarar su propia clase de consola y usar la declaración using para distinguir entre las clases:

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

Para acceder a la consola estándar, necesitaría:

global::Console.Whatever
arul
fuente
8

EDITAR: Este método brinda la posibilidad de redirigir la información de la consola proveniente de un paquete de terceros. anular el método WriteLine es bueno para mi situación, pero es posible que deba anular otros métodos de escritura dependiendo del paquete de terceros.

Primero necesitamos crear una nueva clase inherente a StreamWriter, digamos CombinedWriter;

Luego inicie un nuevo instante de CombinedWriter con Console.Out;

Finalmente, podemos redirigir la salida de la consola al instante de la nueva clase mediante Console.SetOut;

El siguiente código es la nueva clase que funciona para mí.

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);
    }
}
Sigue pensando
fuente
De esta forma no nos perderemos nada mostrado en consola.
Sigue pensando
1
Usted debe reemplazar los métodos siguientes public override void Write(char value);, public override void Write(char[] buffer);, public override void Write(string value);y public override void Write(char[] buffer, int index, int count);. De lo contrario, no se imprime en la consola si usa el WriteLine(format, ...)método.
Dmytro Ovdiienko
6

Log4net puede hacer esto por usted. Solo escribirías algo como esto:

logger.info("Message");

Una configuración determinará si la impresión se enviará a la consola, al archivo oa ambos.

kgiannakakis
fuente
4

Creo que lo que ya está utilizando es el mejor enfoque. Un método simple para reflejar esencialmente su salida.

Primero declare un TextWriter global al principio:

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

Luego crea un método para escribir:

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

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

Ahora, en lugar de usar Console.WriteLine("...");, use Log("...");. Simple como eso. ¡Es aún más corto!


Podría haber algún problema si cambia la posición del cursor ( Console.SetCursorPosition(x, y);), pero por lo demás funciona bien, ¡yo también lo uso!

EDITAR

Por supuesto, puede crear un método de Console.Write();la misma manera si no está usando solo WriteLines

Richard de Wit
fuente
1
Ésta es la solución más sencilla. No olvide agregar esto al final de su programa: <br/> txtMirror.Flush (); txtMirror.Close ();
Dominic Isaia
3

Como sugirió Arul, el uso Console.SetOutse puede usar para redirigir la salida a un archivo de texto:

Console.SetOut(new StreamWriter("Output.txt"));
hgirish
fuente
2

La decisión de usar una clase, heredada de StreamWriter, sugerencias del usuario Keep Thinking, funciona. Pero tuve que agregar a la base del constructor.AutoFlush = true:

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

y una llamada explícita al destructor:

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

De lo contrario, el archivo se cierra antes de que haya registrado todos los datos.

Lo estoy usando como:

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

¡Gracias a Keep Thinking por la excelente solución! Agregué algunas anulaciones adicionales para evitar registrar ciertos eventos de escritura de la consola que (para mis propósitos) solo se esperan para la visualización de la consola.

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();
        }
    }
}
usuario2789183
fuente
¿Alguna razón en particular está reemplazando el método Dispose y luego llamando a base.Dispose ()?
Adam Plocher
1

Si duplica la salida de la consola de un código que no controla, por ejemplo, una biblioteca de terceros, todos los miembros de TextWriter deben sobrescribirse. El código usa ideas de este hilo.

Uso:

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

ConsoleMirroring clase

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);
        }
    }
}
Cangrejo
fuente
0

En realidad, puede crear un reflejo transparente de Console.Out to Trace implementando su propia clase heredada de TextWriter y anulando el método WriteLine.

En WriteLine puede escribirlo en Trace, que luego se puede configurar para escribir en un archivo.

Encontré esta respuesta muy útil: https://stackoverflow.com/a/10918320/379132

¡Realmente funcionó para mí!

Sushil
fuente
0

Mi respuesta se basa en la respuesta no aceptada más votada , y también en la respuesta menos votada, que creo que es la solución más elegante hasta ahora. Es un poco más genérico en términos del tipo de flujo que puede usar (puede usar, MemoryStreampor ejemplo), pero he omitido toda la funcionalidad extendida incluida en la última respuesta por brevedad.

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);
    }
}

Uso:

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