Skip to content
ConsoleMonster

Live log tails

Follow a file or Windows event log with level colours, source timestamps, literal highlighting and a scrollable history.

Watch-ConsoleLog follows completed log lines with level colours and a source timestamp column. Up, PgUp and Home pause following so you can read older entries. End returns to the newest entries. Accent keys and muted descriptions keep q quit · PgUp history · End follow readable. The warning-colored paused state counts new lines as they are applied; End resets that counter. Discarded history appears only when nonzero.

Source timestamps use HH:mm:ss at both 60 and 120 columns, adding yyyy-MM-dd when retained history spans source calendar days. Use -TimestampFormat 'yyyy-MM-dd HH:mm:ss zzz' for an explicit invariant .NET format. The C# equivalents are LogTailOptions.TimestampFormat and LogTailView.TimestampFormat. Unknown source timestamps stay blank, and plain output keeps the original lines.

Run the synthetic example

  1. Install and import the module.

    Install-Module ConsoleMonster -Scope CurrentUser
    Import-Module ConsoleMonster
    
  2. Save the synthetic log and file.ps1 in the same folder. The log contains 16 invented entries and no real service data.

  3. Run the script. It ends after one source poll, which processes all 16 completed lines. To follow later appends, run the command below without -Count.

    ./file.ps1
    Watch-ConsoleLog -Path ./synthetic.log -Tail 16 -Highlight database
    

The finite script and its complete C# equivalent produce these frames. PowerShell 5.1 and 7 are captured from the same native script and checked against the C# result.

Following 120 columns24-bitFull
Following 60 columns24-bitFull
PowerShellfile.ps1
# One poll returns the last 16 completed lines. Omit -Count to keep following.
Watch-ConsoleLog -Path (Join-Path $PSScriptRoot 'synthetic.log') -Tail 16 -Highlight database -Count 1
Full program
Import-Module ConsoleMonster

# One poll returns the last 16 completed lines. Omit -Count to keep following.
Watch-ConsoleLog -Path (Join-Path $PSScriptRoot 'synthetic.log') -Tail 16 -Highlight database -Count 1
C#Program.cs
// Run beside synthetic.log. Omit Count to keep following until q or Escape.
using var source = new FileLogTail("synthetic.log", initialLineCount: 16);
ConsoleLogTail.Run(source.ReadAvailable, new LogTailOptions {
    Highlight = "database",
    Count = 1
});
Full program
using ConsoleMonster.Live.Tails;

// Run beside synthetic.log. Omit Count to keep following until q or Escape.
using var source = new FileLogTail("synthetic.log", initialLineCount: 16);
ConsoleLogTail.Run(source.ReadAvailable, new LogTailOptions {
    Highlight = "database",
    Count = 1
});
The same synthetic file at 120 and 60 columns. Long messages are clipped to the terminal width.

For C#, create an application, save file.cs as Program.cs, and put synthetic.log in the working directory. Then run it:

dotnet new console -n LogTail
cd LogTail
dotnet add package ConsoleMonster
dotnet run

The shown C# program uses Count = 1, so it is finite in a headless process too. Omit Count to follow until quit or cancellation. Redirected output uses plain original lines.

Pause, inspect and resume

KeyAction
Up / PgUp / HomeMove toward older history and pause following.
Down / PgDnMove toward newer history; reaching the newest resumes following.
EndResume following at the newest entries.
q / Escape / Ctrl+CQuit and restore the terminal.

The complete program below embeds LogTailView in a LiveSession. It polls the file and passes input through Navigate; Post queues entries for the render thread. The capture tool supplies PgUp, then End, then q. Normal use reads your keyboard.

PgUp · paused 120 columns24-bitFull
PgUp · paused 60 columns24-bitFull
C#Program.cs
// Run beside synthetic.log. This application owns its reader and input lease.
using var source = new FileLogTail("synthetic.log", initialLineCount: 16);
var view = new LogTailView(capacity: 1000, highlight: "database");
using var session = new LiveSession(view, new LiveSessionOptions {
    CaptureConsole = false,
    PrintFinalState = false,
    TaskbarProgress = false
});
session.Start();
try {
    using var input = ConsoleInput.Acquire();
    while (!session.Cancellation.IsCancellationRequested) {
        foreach (var entry in source.ReadAvailable()) {
            view.Post(session, entry);
        }
        var key = input.Source.Read(TimeSpan.FromMilliseconds(250), session.Cancellation);
        if (key == null) { continue; }
        if (key.IsChar('q') || key.Is(ConsoleKey.Escape)) { break; }
        view.Navigate(session, key);
    }
} catch (OperationCanceledException) when (session.Cancellation.IsCancellationRequested) {
    // Ctrl+C cancels the session; finally restores the terminal.
} finally {
    session.Stop();
}
Full program
using ConsoleMonster.Input;
using ConsoleMonster.Live;
using ConsoleMonster.Live.Tails;

// Run beside synthetic.log. This application owns its reader and input lease.
using var source = new FileLogTail("synthetic.log", initialLineCount: 16);
var view = new LogTailView(capacity: 1000, highlight: "database");
using var session = new LiveSession(view, new LiveSessionOptions {
    CaptureConsole = false,
    PrintFinalState = false,
    TaskbarProgress = false
});
session.Start();
try {
    using var input = ConsoleInput.Acquire();
    while (!session.Cancellation.IsCancellationRequested) {
        foreach (var entry in source.ReadAvailable()) {
            view.Post(session, entry);
        }
        var key = input.Source.Read(TimeSpan.FromMilliseconds(250), session.Cancellation);
        if (key == null) { continue; }
        if (key.IsChar('q') || key.Is(ConsoleKey.Escape)) { break; }
        view.Navigate(session, key);
    }
} catch (OperationCanceledException) when (session.Cancellation.IsCancellationRequested) {
    // Ctrl+C cancels the session; finally restores the terminal.
} finally {
    session.Stop();
}
PgUp moves to older rows and changes the footer to Paused. New entries continue to be retained.
End · following 120 columns24-bitFull
End · following 60 columns24-bitFull
End restores the newest rows and the Following state.

Save navigate.cs as Program.cs in the same application to try the embedded view. The public examples need no recording harness.

Select and highlight literal text

-Filter selects lines containing a literal, ordinal case-insensitive substring. -Highlight emphasizes a literal substring in the live view. Neither accepts a regular expression. Highlighting never changes plain output.

Watch-ConsoleLog -Path ./synthetic.log -Tail 16 -Filter ERROR -Count 1 -Plain
Watch-ConsoleLog -Path ./synthetic.log -Tail 16 -Highlight DATABASE

The live renderer shows timestamp and level columns once, followed by the message. It omits recognized leading timestamp and level fields from the display message, while filtering and plain output retain the complete original line. At compact widths the source time uses HH:mm:ss; wider views include the date, milliseconds and offset. Unknown source timestamps leave the timestamp column blank; no current-time timestamp is invented.

Follow a Windows event log

On Windows, select a local log your account can read:

Watch-ConsoleLog -LogName Application -Tail 20 -Highlight database

This sample uses the platform's Get-WinEvent command. It is documentation only: the generated evidence on this page reads the synthetic file, without querying Windows logs.

Polling, retention and plain output

-Tail defaults to 10 initial completed lines or events. -Tail 0 starts with subsequent entries. -Count defaults to 0, following until quit or cancellation; it counts source polls, including empty polls. -Count 1 is a snapshot, not a one-line limit. -Interval defaults to 0.25 seconds.

-Capacity defaults to 1000 retained history entries. Updates use the existing live session's unbounded queue. Accepted updates are applied before history eviction; posts after shutdown follow the session's normal dropped-update rule. Capacity limits history after application, rather than the entire memory footprint. Scrolling pauses the viewport, not ingestion; eviction can remove the oldest retained history.

-Plain, piping, redirection and CI use raw line output and never acquire input. Timestamp and level prefixes and markup-like characters remain unchanged:

Watch-ConsoleLog -Path ./synthetic.log -Tail 16 -Count 1 | Set-Content ./snapshot.txt

The core ConsoleLogTail.Run invokes its reader synchronously on the caller's thread. The caller owns the reader; each read must return promptly. LogTailOptions exposes Profile, Theme, borrowed Output and Input, and a WriteLine callback for raw text in plain mode. LogTailView owns its LogRegion, which handles ShowTimestamp, HighlightText and ScrollOffset.

File tails recognize completed CR/LF/CRLF lines and UTF-8/UTF-16/UTF-32 byte-order marks. Without a BOM, -Encoding chooses the file fallback, defaulting to utf8; a recognized BOM takes precedence. The accepted names match Write-ConsoleText: unknown, string, unicode, bigendianunicode, utf8, utf8BOM, utf8NoBOM, utf32, ascii, default, and oem. This parameter applies only to -Path.

Watch-ConsoleLog -Path ./legacy.log -Encoding unicode -Count 1 -Plain

Partial lines wait for their terminator and can use unbounded memory. Observed rotation or truncation restarts reading at the path; unread data in the old renamed file is not drained. An unobserved truncate/regrow with identical checkpoint bytes can look like an append. File identity supports Windows and x64/arm64 Linux/macOS.

Initialization scans the current file to find the requested completed history and retain any partial line, including with -Tail 0. Initial work grows with file size. Reads run synchronously, so cancellation is observed after an in-progress source read returns.

Next, put a tail beside other widgets in a live session, or use logging integration to send application events directly to a dashboard.

Edit this page on GitHub