Dublowanie danych wyjściowych konsoli do pliku


88

Czy w aplikacji konsolowej C # istnieje sprytny sposób dublowania danych wyjściowych konsoli do pliku tekstowego?

Obecnie po prostu przekazuję ten sam ciąg do obu Console.WriteLinei InstanceOfStreamWriter.WriteLinew metodzie dziennika.

Odpowiedzi:


117

To może być trochę więcej pracy, ale zrobiłbym odwrotnie.

Utwórz wystąpienie a TraceListenerdla konsoli i jednego dla pliku dziennika; następnie użyj Trace.Writeinstrukcji w swoim kodzie zamiast Console.Write. Później łatwiej będzie usunąć dziennik, dane wyjściowe konsoli lub dołączyć inny mechanizm rejestrowania.

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

O ile dobrze pamiętam, słuchaczy można zdefiniować w konfiguracji aplikacji, dzięki czemu możliwe będzie włączenie lub wyłączenie logowania bez dotykania builda.


4
Doskonale - dzięki. Byłem świadomy istnienia Log4Net, ale wydaje mi się, że to niewłaściwe, aby pobrać bibliotekę w celu wykonania czegoś takiego.
xyz,

3
Nie wiem, dlaczego nie robią więcej z Trace - wydaje mi się, że powinno to dobrze działać przy rejestrowaniu na skalę produkcyjną, ale każdy chce dodać dodatkową bibliotekę (taką jak log4net), aby to zrobić.
Coderer

To jest jednokierunkowe dublowanie. Miałem na myśli, że jeśli masz konsolę interaktywną i pobierasz jakieś dane od użytkownika i chcesz logować wszystko do pliku to rozwiązanie nie działa. Pomimo tego prostego faktu moje pytanie jest zamknięte. Tutaj: stackoverflow.com/questions/3886895/ ...
Xaqron

6
Naprawdę podobało mi się to rozwiązanie, więc zrobiłem o nim krótkiego bloga z drobnymi porządkami i omówieniem kilku przeszkód po drodze. mcrook.com/2014/11/quick-and-easy-console-logging-trace.html Dzięki za świetne rozwiązanie :)
Michael Crook

51

Jest to prosta klasa, która podklasa TextWriter umożliwia przekierowanie danych wejściowych zarówno do pliku, jak i do konsoli.

Użyj tego w ten sposób

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

Oto klasa:

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

}

5
Myślę, że jest to najbardziej kompletne rozwiązanie. Nie ma potrzeby zastępowania wszystkich przeciążeń metod Write / WriteLine i jest przezroczysty dla innego kodu. Zatem cała aktywność konsoli zostanie zduplikowana w pliku, bez dokonywania jakichkolwiek zmian w innym kodzie.
papadi

3
Dzięki stary! To jest zajebiste! Właśnie zastąpiłem File.Create przez File.Open (path, FileMode.Append, FileAccess.Write, FileShare.Read); ponieważ nie chcę wymazywać starych dzienników podczas uruchamiania i chcę mieć możliwość otwarcia pliku dziennika, gdy program jest nadal uruchomiony.
John

1
Nie wymagało to ode mnie zastępowania wszystkich moich dotychczasowych połączeń Console.WriteLine(), co było dokładnie tym, czego chciałem.
x6herbius,

Dla każdego, kto się waha, jest zdezorientowany, jak to robi. Szukaj Console.SetOut(doubleWriter);. Co modyfikuje globalną konsolę, zajęło mi to trochę, ponieważ jestem przyzwyczajony do pracy w aplikacjach, w których praktycznie nic nie jest globalne. Dobry towar!
Douglas Gaskell

13

Sprawdź log4net . Dzięki log4net możesz skonfigurować konsolę i programy dołączające pliki, które będą wysyłały komunikaty dziennika do obu miejsc za pomocą jednej instrukcji dziennika.


6
Cóż, myślę, że należy unikać dodatkowych bibliotek, jeśli można to zrobić z tym, co już jest.
Oliver Friedrich

Polecam również log4net, ale wygląda na to, że NLog zajmuje jego miejsce w społeczności.
Mark Richman

10

Czy nie możesz po prostu przekierować wyjścia do pliku za pomocą >polecenia?

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

Jeśli potrzebujesz teekopii lustrzanej, możesz spróbować znaleźć wersję win32, która dzieli dane wyjściowe na plik.

Zobacz /superuser/74127/tee-for-windows, aby uruchomić tee z PowerShell


8
Muszę zrobić lustro. Dlatego jest to wspomniane w temacie i treści. Dzięki za cynk :)
xyz

8

Możesz utworzyć podklasę klasy TextWriter, a następnie przypisać jej instancję do klasy Console.Out przy użyciu metody Console.SetOut - która w szczególności robi to samo, co przekazanie tego samego ciągu do obu metod w metodzie dziennika.

Innym sposobem może być zadeklarowanie własnej klasy Console i użycie instrukcji using do rozróżnienia klas:

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

Aby uzyskać dostęp do standardowej konsoli, której potrzebujesz:

global::Console.Whatever

8

EDYCJA: Ta metoda daje możliwość przekierowania informacji konsoli pochodzących z pakietu innej firmy. zastąpienie metody WriteLine jest dobre w mojej sytuacji, ale może być konieczne zastąpienie innych metod Write, które zależą od pakietu innej firmy.

Najpierw musimy utworzyć nową klasę nieodłączną od StreamWriter, powiedzmy CombinedWriter;

Następnie zainicjuj nową chwilę CombinedWriter z Console.Out;

Wreszcie możemy przekierować dane wyjściowe konsoli do momentu nowej klasy przez Console.SetOut;

Poniższy kod to nowa klasa działa dla mnie.

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

W ten sposób nie przegapimy niczego wyświetlanego w konsoli.
Myśl dalej

1
Należy zastąpić następującymi metodami public override void Write(char value);, public override void Write(char[] buffer);, public override void Write(string value);i public override void Write(char[] buffer, int index, int count);. W przeciwnym razie nie drukuje do konsoli, jeśli używasz WriteLine(format, ...)metody.
Dmytro Ovdiienko

6

Log4net może to zrobić za Ciebie. Napisałbyś tylko coś takiego:

logger.info("Message");

Konfiguracja określi, czy wydruk trafi do konsoli, do pliku czy do obu.


4

Myślę, że to, czego już używasz, jest najlepszym podejściem. Prosta metoda odzwierciedlania wyników.

Najpierw zadeklaruj globalny TextWriter na początku:

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

Następnie utwórz metodę pisania:

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

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

Teraz zamiast Console.WriteLine("...");używać Log("...");. Proste. Jest jeszcze krótszy!


Przesunięcie pozycji kursora ( Console.SetCursorPosition(x, y);) może spowodować pewne problemy , ale poza tym działa dobrze, sam też z niej korzystam!

EDYTOWAĆ

Oczywiście możesz znaleźć metodę Console.Write(); ten sam sposób, jeśli nie używasz tylko WriteLines


1
To najprostsze rozwiązanie. Nie zapomnij dodać tego na końcu programu: <br/> txtMirror.Flush (); txtMirror.Close ();
Dominic Isaia

3

Jak sugeruje Arul, użycie Console.SetOutmoże być użyte do przekierowania wyjścia do pliku tekstowego:

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

2

Decyzja o użyciu klasy, odziedziczona po StreamWriter, sugestie użytkownika Keep Thinking, działa. Ale musiałem dodać do bazy konstruktora.AutoFlush = true:

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

i wyraźne wezwanie do destruktora:

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

W przeciwnym razie plik zostanie zamknięty wcześniej niż zarejestrował wszystkie dane.

Używam go jako:

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

2

Dziękuję za doskonałe rozwiązanie! Dodałem kilka dalszych nadpisań, aby uniknąć rejestrowania niektórych zdarzeń zapisu konsoli, które (dla moich celów) są oczekiwane tylko do wyświetlania konsoli.

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

Jest jakiś szczególny powód, dla którego zastępujesz metodę Dispose, a następnie wywołujesz metodę base.Dispose ()?
Adam Plocher

1

Jeśli zduplikujesz dane wyjściowe konsoli z kodu, nad którym nie kontrolujesz, na przykład biblioteki innej firmy, wszyscy członkowie TextWriter powinni zostać nadpisani. Kod wykorzystuje pomysły z tego wątku.

Stosowanie:

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

ConsoleMirroring, klasa

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

0

W rzeczywistości możesz stworzyć przezroczyste dublowanie Console.Out to Trace, implementując własną klasę odziedziczoną po TextWriter i nadpisując metodę WriteLine.

W WriteLine możesz zapisać go do Trace, który można następnie skonfigurować do zapisu do pliku.

Ta odpowiedź okazała się bardzo pomocna: https://stackoverflow.com/a/10918320/379132

To faktycznie zadziałało dla mnie!


0

Moja odpowiedź opiera się na największej liczbie głosów odrzuconych, które nie zostały zaakceptowane , a także na najmniejszej liczbie głosów, co moim zdaniem jest jak dotąd najbardziej eleganckim rozwiązaniem. Jest trochę bardziej ogólny pod względem typu strumienia, którego możesz użyć (możesz użyć MemoryStreamna przykład), ale dla zwięzłości pominąłem wszystkie rozszerzone funkcje zawarte w drugiej odpowiedzi .

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

Stosowanie:

using (var stream = File.Create(Path.Combine(Path.GetTempPath(), AppDomain.CurrentDomain.FriendlyName)))
using (var writer = new ConsoleMirrorWriter(stream))
{
    // Code using console output.
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.