Skip to content
ConsoleMonster

Event log summary

Turn a day of System and Application events into one screen: the hourly trend, who is noisy, which IDs repeat and what was critical.

Demos Reports Showcase

Use it when the morning check has to answer what happened overnight before anyone opens Event Viewer.

  • Static
  • 100×28
  • PowerShell 7.4+
  • .NET 8.0+
Event log summary 100×2824-bitFull

How it's built

The whole program in one file, with its sample data at the top. It drew the recording above; all three styles produce the same frame bytes.

PowerShellevent-log-summary.ps1View on GitHub
# Summarise a day of Windows event logs: trend, noisy sources, top IDs and the critical events.
Import-Module -Name ConsoleMonster

# Data: fictional counts already grouped from the System and Application logs of six servers.
$errorsPerHour = @(4, 3, 5, 2, 3, 4, 6, 9, 14, 11, 8, 7, 6, 9, 31, 44, 27, 12, 8, 6, 5, 4, 6, 5)
$warningsPerHour = @(12, 10, 11, 9, 10, 12, 15, 22, 30, 26, 21, 19, 18, 20, 41, 52, 38, 24, 19, 16,
    14, 13, 15, 14)
$sources = @(
    [pscustomobject] @{ Source = 'Service Control Manager'; Errors = 74 }
    [pscustomobject] @{ Source = 'Microsoft-Windows-DNS-Client'; Errors = 52 }
    [pscustomobject] @{ Source = 'MSSQLSERVER'; Errors = 41 }
    [pscustomobject] @{ Source = 'Schannel'; Errors = 23 }
    [pscustomobject] @{ Source = 'Disk'; Errors = 17 }
    [pscustomobject] @{ Source = 'Kernel-Power'; Errors = 4 }
)
$eventIds = @(
    [pscustomobject] @{ Id = 7031; Count = 48; Meaning = 'Service terminated unexpectedly' }
    [pscustomobject] @{ Id = 1014; Count = 52; Meaning = 'Name resolution timed out' }
    [pscustomobject] @{ Id = 17806; Count = 37; Meaning = 'SSPI handshake failed' }
    [pscustomobject] @{ Id = 36887; Count = 23; Meaning = 'Fatal alert received' }
    [pscustomobject] @{ Id = 153; Count = 17; Meaning = 'IO operation retried' }
)
$critical = @(
    [pscustomobject] @{ Time = '14:52'; Host = 'sql-01'; Source = 'Kernel-Power'; Id = 41
        Message = 'Rebooted without a clean shutdown' }
    [pscustomobject] @{ Time = '15:07'; Host = 'sql-01'; Source = 'MSSQLSERVER'; Id = 824
        Message = 'Logical consistency I/O error on tempdb' }
    [pscustomobject] @{ Time = '15:31'; Host = 'fs-02'; Source = 'Disk'; Id = 7
        Message = 'Bad block on \Device\Harddisk1\DR1' }
    [pscustomobject] @{ Time = '16:10'; Host = 'web-03'; Source = 'Kernel-Power'; Id = 41
        Message = 'Rebooted without a clean shutdown' }
)

# Behaviour: hour 0 is midnight; the busiest hour and the totals come from the same counts the charts use.
$errorTotal = ($errorsPerHour | Measure-Object -Sum).Sum
$warningTotal = ($warningsPerHour | Measure-Object -Sum).Sum
$peak = [array]::IndexOf($errorsPerHour, [int] ($errorsPerHour | Measure-Object -Maximum).Maximum)
$topIds = @($eventIds | Sort-Object -Property Count -Descending)

# Layout: headline, the 24-hour trend, the noisiest sources beside the top IDs, then criticals.
$report = New-ConsoleRows -Gap 1 -Content {
    New-ConsoleRule -Title '[bold]Event log summary[/] [muted]· 6 servers · Sunday 2026-10-25[/]'
    ("[fail]$($critical.Count) critical[/]   [warn]$errorTotal errors[/]   " +
        "[muted]$warningTotal warnings · busiest hour $('{0:00}:00' -f $peak)[/]")
    New-ConsoleColumns -Content {
        New-ConsolePanel -Title 'Errors per hour' -Content {
            $chartParameters = @{
                Type = 'Sparkline'
                Title = 'errors'
                Height = 3
                Role = 'Fail'
                Values = $errorsPerHour
            }
            New-ConsoleChart @chartParameters
        }
        New-ConsolePanel -Title 'Warnings per hour' -Content {
            $chartParameters = @{
                Type = 'Sparkline'
                Title = 'warnings'
                Height = 3
                Role = 'Warn'
                Values = $warningsPerHour
            }
            New-ConsoleChart @chartParameters
        }
    }
    New-ConsoleColumns -Content {
        New-ConsolePanel -Title 'Noisiest sources' -Content {
            $sources |
                ForEach-Object -Process {
                    [pscustomobject] @{ Label = $_.Source; Value = $_.Errors }
                } |
                    New-ConsoleChart -Type Bar
        }
        New-ConsolePanel -Title 'Top event IDs' -Content {
            New-ConsoleTable -InputObject $topIds -Property Id, Count, Meaning -Border None -Expand
        }
    }
    New-ConsolePanel -Title 'Critical events' -Content {
        New-ConsoleTable -InputObject $critical -Property Time, Host, Source, Id,
            Message -Border None -Expand
    }
}

# Behaviour: print once; feed it Get-WinEvent results grouped the same way for a real day.
$report | Show-Console
C#Program.csView on GitHub
// Summarise a day of Windows event logs: trend, noisy sources, top IDs and the critical events.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: fictional counts already grouped from the System and Application logs of six servers.
double[] errorsPerHour = { 4, 3, 5, 2, 3, 4, 6, 9, 14, 11, 8, 7, 6, 9, 31, 44, 27, 12, 8, 6, 5, 4,
    6, 5 };
double[] warningsPerHour = { 12, 10, 11, 9, 10, 12, 15, 22, 30, 26, 21, 19, 18, 20, 41, 52, 38, 24,
    19, 16, 14, 13, 15, 14 };
var sources = new SourceCount[]
{
    new("Service Control Manager", 74),
    new("Microsoft-Windows-DNS-Client", 52),
    new("MSSQLSERVER", 41),
    new("Schannel", 23),
    new("Disk", 17),
    new("Kernel-Power", 4)
};
var eventIds = new EventIdCount[]
{
    new(7031, 48, "Service terminated unexpectedly"),
    new(1014, 52, "Name resolution timed out"),
    new(17806, 37, "SSPI handshake failed"),
    new(36887, 23, "Fatal alert received"),
    new(153, 17, "IO operation retried")
};
var critical = new CriticalEvent[]
{
    new("14:52", "sql-01", "Kernel-Power", 41, "Rebooted without a clean shutdown"),
    new("15:07", "sql-01", "MSSQLSERVER", 824, "Logical consistency I/O error on tempdb"),
    new("15:31", "fs-02", "Disk", 7, @"Bad block on \Device\Harddisk1\DR1"),
    new("16:10", "web-03", "Kernel-Power", 41, "Rebooted without a clean shutdown")
};

// Behaviour: hour 0 is midnight; the busiest hour and the totals come from the same counts the charts use.
double errorTotal = errorsPerHour.Sum();
double warningTotal = warningsPerHour.Sum();
int peak = Array.IndexOf(errorsPerHour, errorsPerHour.Max());
var topIds = eventIds.OrderByDescending(row => row.Count).ToArray();

// Layout: headline, the 24-hour trend, the noisiest sources beside the top IDs, then criticals.
var errors = new Sparkline { Title = "errors", Height = 3, Role = ThemeToken.Fail };
errors.AddRange(errorsPerHour);
var warnings = new Sparkline { Title = "warnings", Height = 3, Role = ThemeToken.Warn };
warnings.AddRange(warningsPerHour);
var noisiest = new BarChart();
foreach (var source in sources)
    noisiest.Add(source.Source, source.Errors);

var idTable = Table.For(topIds)
    .Column(row => row.Id, column => column.Presenter = CellPresenters.Identifier)
    .Column(row => row.Count)
    .Column(row => row.Meaning);
idTable.Border = TableBorder.None;
idTable.Expand = true;
var criticalTable = Table.For(critical)
    .Column(row => row.Time)
    .Column(row => row.Host)
    .Column(row => row.Source)
    .Column(row => row.Id, column => column.Presenter = CellPresenters.Identifier)
    .Column(row => row.Message);
criticalTable.Border = TableBorder.None;
criticalTable.Expand = true;

var report = new Rows(
    new Rule("[bold]Event log summary[/] [muted]· 6 servers · Sunday 2026-10-25[/]"),
    new TextBlock(
        ($"[fail]{critical.Length} critical[/]   [warn]{errorTotal} errors[/]   [muted]"
            + $"{warningTotal} warnings · busiest hour {peak:00}:00[/]")),
    new Columns(new Panel(errors, "Errors per hour"), new Panel(warnings, "Warnings per hour")),
    new Columns(new Panel(noisiest, "Noisiest sources"), new Panel(idTable, "Top event IDs")),
    new Panel(criticalTable, "Critical events"))
{
    Gap = 1
};

// Behaviour: print once; feed it Get-WinEvent results grouped the same way for a real day.
Terminal.Write(report);

record SourceCount(string Source, int Errors);

record EventIdCount(int Id, int Count, string Meaning);

record CriticalEvent(string Time, string Host, string Source, int Id, string Message);
C# fluentProgram.Fluent.csView on GitHub
// Summarise a day of Windows event logs: trend, noisy sources, top IDs and the critical events.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: fictional counts already grouped from the System and Application logs of six servers.
double[] errorsPerHour =
{
    4, 3, 5, 2, 3, 4, 6, 9, 14, 11, 8, 7, 6, 9, 31, 44, 27, 12, 8, 6, 5, 4, 6, 5
};
double[] warningsPerHour =
{
    12, 10, 11, 9, 10, 12, 15, 22, 30, 26, 21, 19, 18, 20, 41, 52, 38, 24, 19, 16, 14, 13, 15, 14
};
var sources = new SourceCount[]
{
    new("Service Control Manager", 74),
    new("Microsoft-Windows-DNS-Client", 52),
    new("MSSQLSERVER", 41),
    new("Schannel", 23),
    new("Disk", 17),
    new("Kernel-Power", 4)
};
var eventIds = new EventIdCount[]
{
    new(7031, 48, "Service terminated unexpectedly"),
    new(1014, 52, "Name resolution timed out"),
    new(17806, 37, "SSPI handshake failed"),
    new(36887, 23, "Fatal alert received"),
    new(153, 17, "IO operation retried")
};
var critical = new CriticalEvent[]
{
    new("14:52", "sql-01", "Kernel-Power", 41, "Rebooted without a clean shutdown"),
    new("15:07", "sql-01", "MSSQLSERVER", 824, "Logical consistency I/O error on tempdb"),
    new("15:31", "fs-02", "Disk", 7, @"Bad block on \Device\Harddisk1\DR1"),
    new("16:10", "web-03", "Kernel-Power", 41, "Rebooted without a clean shutdown")
};

// Behaviour: hour 0 is midnight; the busiest hour and the totals come from the same counts the charts use.
double errorTotal = errorsPerHour.Sum();
double warningTotal = warningsPerHour.Sum();
int peak = Array.IndexOf(errorsPerHour, errorsPerHour.Max());
var topIds = eventIds.OrderByDescending(row => row.Count).ToArray();

// Layout: headline, the 24-hour trend, the noisiest sources beside the top IDs, then criticals.
var errors = (Sparkline)Ui.Chart()
    .Type(ChartType.Sparkline)
    .Title("errors")
    .Height(3)
    .Role(ThemeToken.Fail)
    .Build();
errors.AddRange(errorsPerHour);
var warnings = (Sparkline)Ui.Chart()
    .Type(ChartType.Sparkline)
    .Title("warnings")
    .Height(3)
    .Role(ThemeToken.Warn)
    .Build();
warnings.AddRange(warningsPerHour);
var noisiest = (BarChart)Ui.Chart().Type(ChartType.Bar).Build();
foreach (var source in sources)
    noisiest.Add(source.Source, source.Errors);
var idTable = Ui.Table(topIds)
    .Column(row => row.Id, column => column.Presenter(CellPresenters.Identifier))
    .Column(row => row.Count)
    .Column(row => row.Meaning)
    .Border(TableBorder.None)
    .Expand();
var criticalTable = Ui.Table(critical)
    .Column(row => row.Time)
    .Column(row => row.Host)
    .Column(row => row.Source)
    .Column(row => row.Id, column => column.Presenter(CellPresenters.Identifier))
    .Column(row => row.Message)
    .Border(TableBorder.None)
    .Expand();
var report = Ui.Rows().Gap(1).Content(c3 =>
{
    c3.Rule("[bold]Event log summary[/] [muted]· 6 servers · Sunday 2026-10-25[/]");
    c3.Markup(($"[fail]{critical.Length} critical[/]   [warn]{errorTotal} errors[/]   [muted]"
        + $"{warningTotal} warnings · busiest hour {peak:00}:00[/]"));
    c3.Columns(columns => columns.Content(c2 =>
    {
        c2.Panel(p => p.Title("Errors per hour").Content(c =>
        {
            c.Add(errors);
        }));
        c2.Panel(p => p.Title("Warnings per hour").Content(c =>
        {
            c.Add(warnings);
        }));
    }));
    c3.Columns(columns => columns.Content(c2 =>
    {
        c2.Panel(p => p.Title("Noisiest sources").Content(c =>
        {
            c.Add(noisiest);
        }));
        c2.Panel(p => p.Title("Top event IDs").Content(c =>
        {
            c.Add(idTable);
        }));
    }));
    c3.Panel(p => p.Title("Critical events").Content(c =>
    {
        c.Add(criticalTable);
    }));
});

// Behaviour: print once; feed it Get-WinEvent results grouped the same way for a real day.
report.Show();

record SourceCount(string Source, int Errors);

record EventIdCount(int Id, int Count, string Meaning);

record CriticalEvent(string Time, string Host, string Source, int Id, string Message);

Run it yourself

Three steps in PowerShell, four in C#. Every command copies with one click.

  1. Install the module

    Once. Windows PowerShell 5.1 or PowerShell 7 on Windows, macOS or Linux.

    Install-Module ConsoleMonster -Scope CurrentUser
  2. Save the program

    Download it, or copy it from above into event-log-summary.ps1.

  3. Run it

    From the folder you saved it in.

    ./event-log-summary.ps1
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n EventLogSummary; cd EventLogSummary
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n EventLogSummary; cd EventLogSummary
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run --property:DemoStyle=Fluent

Or skip the copying. Every demo ships with the module and the project template.

Show-ConsoleDemo event-log-summary
Export-ConsoleDemo event-log-summary -Path ./event-log-summary.ps1
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo event-log-summary -n EventLogSummary
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo event-log-summary --DemoStyle Fluent -n EventLogSummary

Make it yours

Start with these, then read the rest of the file.

  1. Fill the counts from Get-WinEvent -FilterHashtable with Level 1 to 3 and StartTime set to 24 hours ago, grouped by hour, ProviderName and Id.

  2. Add your own meaning for the event IDs you see every week, so the table explains them.

  3. Run it per server or per role and keep the busiest hour beside your change calendar.

More reports

All demos