Skip to content

Commit ba38043

Browse files
v1.0.0: Handle Logs with Overlapping Threads (#14)
* v1.0.0: Handle Logs with Overlapping Threads * Bump Pester. Fix tests. * Tweak test verbs
1 parent a1f3f09 commit ba38043

File tree

14 files changed

+395
-106
lines changed

14 files changed

+395
-106
lines changed

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"-Task Test",
1111
"-Verbose"
1212
],
13-
"cwd": "${workspaceFolder}"
13+
"cwd": "${workspaceFolder}",
14+
"createTemporaryIntegratedConsole": true
1415
}
1516
]
1617
}

.vscode/tasks.json

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,42 @@
22
// See https://go.microsoft.com/fwlink/?LinkId=733558
33
// for the documentation about the tasks.json format
44
"version": "2.0.0",
5-
65
// Start PowerShell (pwsh on *nix)
76
"windows": {
87
"options": {
98
"shell": {
10-
"executable": "powershell.exe",
11-
"args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command" ]
9+
"executable": "pwsh.exe",
10+
"args": [
11+
"-NoProfile",
12+
"-ExecutionPolicy",
13+
"Bypass",
14+
"-Command"
15+
]
1216
}
1317
}
1418
},
1519
"linux": {
1620
"options": {
1721
"shell": {
1822
"executable": "/usr/bin/pwsh",
19-
"args": [ "-NoProfile", "-Command" ]
23+
"args": [
24+
"-NoProfile",
25+
"-Command"
26+
]
2027
}
2128
}
2229
},
2330
"osx": {
2431
"options": {
2532
"shell": {
2633
"executable": "/usr/local/bin/pwsh",
27-
"args": [ "-NoProfile", "-Command" ]
34+
"args": [
35+
"-NoProfile",
36+
"-Command"
37+
]
2838
}
2939
}
3040
},
31-
3241
"tasks": [
3342
{
3443
"label": "Clean",

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [1.0.0] Handle Logs with Out of Order Threads
9+
10+
### Added
11+
12+
- Bump Log4NetParse dependency to 1.1.1 which handles threads better.
13+
- Add TypeAccelerator for ChocoLog
14+
15+
## Removed
16+
17+
- Logs no longer returned for Log4NetLog format. It was suggested that
18+
this was noisy.
19+
20+
### Fixes
21+
22+
- Handle when logs that have thread lines that overlap.
23+
- Fix tests to handle logs dates and ordering.
24+
825
## [0.5.0] Add Get-ChocoLogEntry
926

1027
- Add a new cmdlet that grabs the latest log.

ChocoLogParse/ChocoLog.format.ps1xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
<TableColumnHeader>
2424
<Label>Command Line</Label>
2525
</TableColumnHeader>
26-
<TableColumnHeader>
27-
<Label>Log Lines</Label>
28-
</TableColumnHeader>
2926
</TableHeaders>
3027
<TableRowEntries>
3128
<TableRowEntry>
@@ -52,9 +49,6 @@
5249
[String]::Format("{0}", ($_.cli -replace '"C:\\ProgramData\\chocolatey\\choco.exe"', 'choco'))
5350
</ScriptBlock>
5451
</TableColumnItem>
55-
<TableColumnItem>
56-
<PropertyName>logs</PropertyName>
57-
</TableColumnItem>
5852
</TableColumnItems>
5953
<Wrap/>
6054
</TableRowEntry>

ChocoLogParse/ChocoLogParse.psd1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'ChocoLogParse.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.5.0'
15+
ModuleVersion = '1.0.0'
1616

1717
# Supported PSEditions
1818
# CompatiblePSEditions = @()
@@ -24,7 +24,7 @@
2424
Author = 'Gilbert Sanchez'
2525

2626
# Company or vendor of this module
27-
CompanyName = 'Unknown'
27+
CompanyName = 'Gilbert Sanchez'
2828

2929
# Copyright statement for this module
3030
Copyright = '(c) Gilbert Sanchez. All rights reserved.'
@@ -51,7 +51,7 @@
5151
# ProcessorArchitecture = ''
5252

5353
# Modules that must be imported into the global environment prior to importing this module
54-
RequiredModules = @(@{ModuleName = "Log4NetParse"; ModuleVersion = "0.3.0"; })
54+
RequiredModules = @(@{ModuleName = "Log4NetParse"; ModuleVersion = "1.1.1"; })
5555

5656
# Assemblies that must be loaded prior to importing this module
5757
# RequiredAssemblies = @()

ChocoLogParse/ChocoLogParse.psm1

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,42 @@ $format = Join-Path -Path $PSScriptRoot -ChildPath 'ChocoLog.format.ps1xml'
1515
Update-FormatData -PrependPath $format
1616

1717
Export-ModuleMember -Function $public.Basename
18+
19+
# This module is combined. Any code in this file as added to the very end.
20+
21+
# Define the types to export with type accelerators.
22+
$ExportableTypes = @(
23+
[ChocoLog]
24+
)
25+
# Get the internal TypeAccelerators class to use its static methods.
26+
$TypeAcceleratorsClass = [psobject].Assembly.GetType(
27+
'System.Management.Automation.TypeAccelerators'
28+
)
29+
# Ensure none of the types would clobber an existing type accelerator.
30+
# If a type accelerator with the same name exists, throw an exception.
31+
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get
32+
foreach ($Type in $ExportableTypes) {
33+
if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
34+
$Message = @(
35+
"Unable to register type accelerator '$($Type.FullName)'"
36+
'Accelerator already exists.'
37+
) -join ' - '
38+
39+
throw [System.Management.Automation.ErrorRecord]::new(
40+
[System.InvalidOperationException]::new($Message),
41+
'TypeAcceleratorAlreadyExists',
42+
[System.Management.Automation.ErrorCategory]::InvalidOperation,
43+
$Type.FullName
44+
)
45+
}
46+
}
47+
# Add type accelerators for every exportable type.
48+
foreach ($Type in $ExportableTypes) {
49+
[void]$TypeAcceleratorsClass::Add($Type.FullName, $Type)
50+
}
51+
# Remove type accelerators when the module is removed.
52+
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
53+
foreach ($Type in $ExportableTypes) {
54+
[void]$TypeAcceleratorsClass::Remove($Type.FullName)
55+
}
56+
}.GetNewClosure()

ChocoLogParse/Classes/ChocoLog.ps1

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
using module Log4NetParse
22

33
class ChocoLog : Log4NetLog {
4-
[string]$cli
5-
[string]$exitCode
6-
[LogType]$logType
4+
[string]$CLI
5+
[string]$ExitCode
6+
[LogType]$LogType
77
[hashtable]$Configuration
88

99
ChocoLog(
1010
[int]$Thread,
11-
[datetime]$startTime,
1211
[string]$filePath
1312
) : base (
1413
$Thread,
15-
$startTime,
1614
$filePath
1715
) {
18-
$this.thread = $Thread
19-
$this.startTime = $startTime
20-
$this.logs = [System.Collections.Generic.List[Log4NetLogLine]]::new()
21-
$this.filePath = $filePath
16+
$this.Thread = $Thread
17+
$this.FilePath = $filePath
2218
$this.Configuration = @{}
2319
if ($filePath -like "*summary*") {
2420
$this.logType = [LogType]::Summary
@@ -30,10 +26,12 @@ class ChocoLog : Log4NetLog {
3026
# This parses all the logs for entries that are part of the class
3127
[void] ParseSpecialLogs() {
3228
# Set the end time here since we are done parsing
33-
$this.endTime = $this.logs[-1].time
29+
$this._logs.Sort()
30+
$this.StartTime = $this._logs[0].time
31+
$this.EndTime = $this._logs[-1].time
3432

3533
# Detect known patterns
36-
$this.logs | ForEach-Object {
34+
$this._logs | ForEach-Object {
3735
switch ($_.message) {
3836
{ $_.StartsWith('Command line: ') } { $this.SetCli($_) }
3937
{ $_.StartsWith('Exiting with ') } { $this.SetExitCode($_) }
@@ -42,7 +40,6 @@ class ChocoLog : Log4NetLog {
4240
}
4341
Default {}
4442
}
45-
4643
}
4744
}
4845

ChocoLogParse/Public/Read-ChocoLog.ps1

Lines changed: 33 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,61 +43,56 @@ function Read-ChocoLog {
4343
[string]
4444
$PatternLayout = '%date %thread [%-5level] - %message'
4545
)
46-
4746
$files = Get-Item -Path $Path
4847
if ($files.PSIsContainer) {
49-
$files = Get-ChildItem -Path $Path -Filter $Filter | Sort-Object -Property LastWriteTime | Select-Object -Last $FileLimit
48+
$files = Get-ChildItem -Path $Path -Filter $Filter |
49+
Sort-Object -Property LastWriteTime | Select-Object -Last $FileLimit
5050
}
51+
Write-Verbose "Found files: $($files -join ',')"
5152

52-
[System.Collections.Generic.List[ChocoLog]]$parsed = @()
53-
$detected = [System.Collections.Generic.List[int]]::new()
54-
55-
$RegularExpression = Convert-PatternLayout $PatternLayout
53+
$parsed = @{}
5654

55+
# Get the regex for the Log4Net PatternLayout
56+
$RegularExpression = Convert-PatternLayout -PatternLayout $PatternLayout
5757
$files | ForEach-Object -Process {
5858
$file = $_
59+
Write-Verbose "Reading over file: $file"
5960
$raw = [System.IO.File]::ReadAllLines($file.FullName)
60-
61+
Write-Verbose "Lines read: $($raw.Count)"
6162
# Iterate over each line
6263
foreach ($line in $raw) {
63-
# Write-Debug $line
64+
Write-Debug $line
6465
$m = $RegularExpression.match($line)
6566
if ($m.Success) {
66-
$threadMatch = $m.Groups['thread'].Value
67-
# If it matches the regex, tag it
68-
if ($threadMatch -ne $currentSession.thread) {
69-
# This is a different thread
70-
71-
# Save the current session to the parsed list
72-
if ($currentSession) {
73-
$parsed.Add($currentSession) > $null
74-
}
75-
76-
# Look up if current thread exists in Parsed and append to that if so
77-
if ($detected.Contains($threadMatch)) {
78-
$currentSession = $parsed | Where-Object { $_.Thread -eq $threadMatch }
79-
} else {
80-
# We haven't seen this thread before, let's make a new object
81-
$detected.Add($threadMatch) > $null
82-
$currentSession = [ChocoLog]::new(
67+
[int]$threadMatch = $m.Groups['thread'].Value
68+
# Replace comma with period to make it a valid datetime
69+
[datetime]$currentDateTime = $m.Groups['date'].Value -replace ',', '.'
70+
# Check if thread exists, if not make it.
71+
if (-not ($parsed.ContainsKey($threadMatch))) {
72+
Write-Verbose "New thread detected: $threadMatch"
73+
$null = $parsed.Add(
74+
$threadMatch,
75+
[ChocoLog]::new(
8376
$threadMatch,
84-
($m.Groups['date'].Value -replace ',', '.'),
8577
$file
8678
)
87-
}
79+
)
8880
}
89-
90-
$currentSession.logs.Add(
81+
Write-Verbose "Adding new log line to thread $threadMatch"
82+
$parsed.Item($threadMatch).AddLogLine(
9183
[Log4NetLogLine]::new(
92-
[Datetime]($m.Groups['date'].Value -replace ',', '.'),
84+
$currentDateTime,
9385
$threadMatch,
9486
$m.Groups['level'].Value,
9587
$m.Groups['message'].Value
96-
)) > $null
88+
))
9789
} else {
90+
Write-Verbose "Line did not match regex"
91+
Write-Debug $line
9892
# if it doesn't match regex, append to the previous
99-
if ($currentSession) {
100-
$currentSession.logs[-1].AppendMessage($line)
93+
if ($threadMatch) {
94+
Write-Verbose "Appending to existing thread: $threadMatch"
95+
$parsed.Item($threadMatch).AppendLastLogLine($line)
10196
} else {
10297
# This might happen if the log starts on what should have been a
10398
# multiline entry... Not very likely
@@ -106,17 +101,15 @@ function Read-ChocoLog {
106101
}
107102
}
108103
}
109-
# Write out the last log line!
110-
if (-Not $parsed.Contains($currentSession)) {
111-
$parsed.Add($currentSession) > $null
112-
}
113104

114105
# Doing this at the end since threads can get mixed
115-
$parsed | ForEach-Object {
106+
$parsed.Keys | ForEach-Object {
107+
Write-Verbose "Parsing special logs for: $_"
116108
# This updates fields like: cli, environment, and configuration
117-
$_.ParseSpecialLogs()
109+
$parsed.Item($_).ParseSpecialLogs()
118110
}
119111

120112
# Return the whole parsed object
121-
$parsed
113+
Write-Verbose "Returning results in desceding order. Count: $($parsed.Count)"
114+
$parsed.Values | Sort-Object -Descending Time
122115
}

0 commit comments

Comments
 (0)