Skip to content

Commit

Permalink
Grok Parser (#3)
Browse files Browse the repository at this point in the history
- Parse log entries using Grok parser.
- Use only CLI arguments for configuring applications.
  • Loading branch information
milandjurdjevic authored Jan 4, 2025
1 parent 6763b41 commit bab7546
Show file tree
Hide file tree
Showing 20 changed files with 481 additions and 725 deletions.
8 changes: 3 additions & 5 deletions src/Analog.Tests/Analog.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="Snapshot.fs" />
<Compile Include="FilterTest.fs" />
<Compile Include="ExtractTest.fs" />
<Compile Include="TemplateTest.fs" />
<Compile Include="EntryParserTest.fs" />
<Compile Include="FilterParserTest.fs" />
<Compile Include="FilterEvaluatorTest.fs" />
<Compile Include="Program.fs"/>
</ItemGroup>

Expand All @@ -23,7 +22,6 @@
</PackageReference>
<PackageReference Include="FsUnit.xUnit" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Verify.Xunit" Version="25.2.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
Expand Down
42 changes: 42 additions & 0 deletions src/Analog.Tests/EntryParserTest.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Analog.Tests.EntryParserTest

open System
open Analog
open FsUnit.Xunit
open Xunit

let parse txt =
EntryParser.value |> EntryParser.parse txt

let stringOf = Literal.String
let timestampOf = DateTimeOffset.Parse >> Literal.Timestamp

[<Fact>]
let ``parse single log line`` () =
let expected =
[ "loglevel", stringOf "INFO"
"message", stringOf "User logged in successfully"
"timestamp", timestampOf "2024-11-17 14:30:55" ]
|> Map.ofList
|> List.singleton

"[2024-11-17 14:30:55] [INFO] User logged in successfully"
|> parse
|> should equal expected

[<Fact>]
let ``parse two log lines`` () =
let expected =
[ [ "loglevel", stringOf "INFO"
"message", stringOf "User logged in successfully"
"timestamp", timestampOf "2024-11-17 14:30:55" ]
|> Map.ofList
[ "loglevel", stringOf "ERROR"
"message", stringOf "Failed to authenticate user"
"timestamp", timestampOf "2024-11-17 14:31:00" ]
|> Map.ofList ]
|> List.ofSeq

"[2024-11-17 14:30:55] [INFO] User logged in successfully\n[2024-11-17 14:31:00] [ERROR] Failed to authenticate user"
|> parse
|> should equal expected
85 changes: 0 additions & 85 deletions src/Analog.Tests/ExtractTest.fs

This file was deleted.

111 changes: 111 additions & 0 deletions src/Analog.Tests/FilterEvaluatorTest.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
module Analog.Tests.FilterEvaluatorTest

open System
open Xunit
open FsUnit.Xunit
open Analog

[<Fact>]
let ``Evaluate Const filter with Boolean literal`` () =
let entry = Map.empty<string, Literal>
let filter = Filter.Const (Literal.Boolean true)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Field filter with matching field in entry`` () =
let entry = Map.ofList [ "key", Literal.Boolean true ]
let filter = Filter.Field "key"
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Field filter with non-matching field in entry`` () =
let entry = Map.ofList [ "key", Literal.Boolean true ]
let filter = Filter.Field "missingKey"
let result = FilterEvaluator.evaluate entry filter
result |> should equal false

[<Fact>]
let ``Evaluate Binary Equal filter with matching String literals`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.String "test"),
Operator.Equal,
Filter.Const (Literal.String "test")
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Binary NotEqual filter with non-matching Number literals`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.Number 1.0),
Operator.NotEqual,
Filter.Const (Literal.Number 2.0)
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Binary GreaterThan filter with Timestamp literals`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.Timestamp (DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))),
Operator.GreaterThan,
Filter.Const (Literal.Timestamp (DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero)))
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Binary And filter with Boolean literals`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.Boolean true),
Operator.And,
Filter.Const (Literal.Boolean true)
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Binary Or filter with one Boolean literal true`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.Boolean false),
Operator.Or,
Filter.Const (Literal.Boolean true)
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal true

[<Fact>]
let ``Evaluate Binary Equal filter with mismatched Literal types`` () =
let entry = Map.empty<string, Literal>
let filter =
Filter.Binary(
Filter.Const (Literal.String "test"),
Operator.Equal,
Filter.Const (Literal.Number 42.0)
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal false

[<Fact>]
let ``Evaluate Binary GreaterThan filter with invalid field in entry`` () =
let entry = Map.ofList [ "key", Literal.Number 10.0 ]
let filter =
Filter.Binary(
Filter.Field "key",
Operator.GreaterThan,
Filter.Const (Literal.Number 20.0)
)
let result = FilterEvaluator.evaluate entry filter
result |> should equal false
60 changes: 60 additions & 0 deletions src/Analog.Tests/FilterParserTest.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module Analog.Tests.FilterParserTest

open System

open FsUnitTyped
open Xunit

open Analog

let parse = FilterParser.expression |> ParserRunner.run

[<Fact>]
let ``parse should correctly parse a constant string`` () =
parse "'hello'" |> shouldEqual (Result.Ok(Const(String "hello")))

[<Fact>]
let ``parse should correctly parse a constant number`` () =
parse "42.5" |> shouldEqual (Result.Ok(Const(Literal.Number 42.5)))

[<Fact>]
let ``parse should correctly parse a constant boolean (true)`` () =
parse "true" |> shouldEqual (Result.Ok(Const(Boolean true)))

[<Fact>]
let ``parse should correctly parse a constant boolean (false)`` () =
parse "false" |> shouldEqual (Result.Ok(Const(Boolean false)))

[<Fact>]
let ``parse should correctly parse a field identifier`` () =
parse "fieldName" |> shouldEqual (Result.Ok(Field "fieldName"))

[<Fact>]
let ``parse should correctly parse a simple binary expression`` () =
parse "'value' = fieldName"
|> shouldEqual (Result.Ok(Binary(Const(String "value"), Equal, Field "fieldName")))

[<Fact>]
let ``parse should correctly parse a complex binary expression`` () =
parse "'value' = fieldName & 42 > 10"
|> shouldEqual (
Result.Ok(
Binary(
Binary(Const(String "value"), Equal, Field "fieldName"),
And,
Binary(Const(Literal.Number 42.0), GreaterThan, Const(Literal.Number 10.0))
)
)
)

[<Fact>]
let ``parse should correctly parse a timestamp`` () =
let input = "2024-01-03T12:34:56+00:00"
let result = parse input
result |> shouldEqual (Result.Ok(Const(Timestamp(DateTimeOffset.Parse input))))

[<Fact>]
let ``parse should return an error for invalid input`` () =
match parse "'unterminated string" with
| Ok _ -> failwith "parse should return an error for invalid input"
| Error _ -> ()
25 changes: 0 additions & 25 deletions src/Analog.Tests/FilterTest.fs

This file was deleted.

Loading

0 comments on commit bab7546

Please sign in to comment.