From cf63e69bfcd48298dfc50ce6cc683d820dfce364 Mon Sep 17 00:00:00 2001 From: Noel Rivas Date: Sun, 14 Apr 2024 19:36:11 -0600 Subject: [PATCH 1/4] Chapter outline, first draft. First 2 iterations, first drafts. --- logger/index.md | 92 +++++++++++++++++++++ logger/iterations/0-minimal-logger/main.roc | 11 +++ logger/iterations/1-append-to-file/main.roc | 36 ++++++++ 3 files changed, 139 insertions(+) create mode 100644 logger/index.md create mode 100644 logger/iterations/0-minimal-logger/main.roc create mode 100644 logger/iterations/1-append-to-file/main.roc diff --git a/logger/index.md b/logger/index.md new file mode 100644 index 0000000..3a08db3 --- /dev/null +++ b/logger/index.md @@ -0,0 +1,92 @@ +# Logging Library + +This chapter goes through the building of a simple logging library with the following capabilities: + +- Log level filtering +- Multiple channels (stderr, append to file) +- JSON encoding +- Ability to use multiple logging configurations in the same program +- Logger configuration through environment vars + +## Chapter structure + +The chapter follows a _challenge and response_ pattern. The first iteration builds the simplest logger imaginable (just write to stdout) and each iteration improves on the previous one. After each implementation, its downsides are commented, and the next set of improvements are outlined. + +After the final implementation, a recap of the iterations, and the concepts that were explained in each of them, is offered. + +_NOTE: On each of the iterations below, I'll list some concepts that could be explained using the iteration code._ + +_The list is offered as a suggestion: whether we actually flesh out those concepts in this chapter or not will depend on the editorial decisions on chapter ordering and interdependence._ + +### 0. Minimum viable logger + +An extremely simple, single-function "logger" that takes simple arguments (A message and a value) and outputs to Stdout. + +This iteration is only pertinent if the chapter introduces the concept of platform. It may be overkill in terms of paring down the first iteration. + +``` roc +main = + log "This is a value:" 42 + +log = \msg, val -> + Stdout.line "$(msg): $(Inspect.toStr val)" +``` + +_Concepts_: + +- Capabilities defined by platform +- Importing from platform + +### 1. Append to file + +At the end of this iteration, the logger: + +- Is composed by just a couple of functions in the main file (no library) +- Appends to a file +- Includes timestamps +- Has a hardcoded output path +- Needs the log file to be created manually + +The idea behind its simplicity is to have the least amount of moving parts possible, so we can explore tasks, error handling, and the syntax around them in detail, without the burden of a larger program. + +I would like to present the code for this iteration with / without syntax sugar, and take steps to guide the reader to make the connection between type annotations in the documentation, the function calls without syntactic sugar, and the code with sugar. + + +_Concepts_: + +- Task +- Error handling +- Using Inspect +- Backpassing syntax +- Pipe syntax (first pass, simplest case, `f |> a`) +- Reading type annotations + +### 2. A usable library + +This iteration is a small step from the previous one in terms of complexity of the logger. It mostly expands on the previous one as a reinforcement. + +An important step is that the logger becomes a module, and a more realistic use case is presented. + +At the end of this iteration, the logger: + +- Becomes a module +- Handles log file creation +- Handles permission errors +- Takes a configuration record as argument (which makes it possible to have multiple channels) +- Reads configuration overrides from environment variables + +_Concepts_: + +- Modules +- Types (configuration record) + +### 3. Log level + +This iteration adds JSON encoding and log level configuration. + +_NOTE_: JSON encoding would depend on [roc-json](https://github.com/lukewilliamboswell/roc-json). If the library is too much, we can do something like rudimentary CSV without escaping or headers: just join a list with commas. + +_Concepts_: + +- Tags and payloads +- Pattern matching \ No newline at end of file diff --git a/logger/iterations/0-minimal-logger/main.roc b/logger/iterations/0-minimal-logger/main.roc new file mode 100644 index 0000000..8afee37 --- /dev/null +++ b/logger/iterations/0-minimal-logger/main.roc @@ -0,0 +1,11 @@ +app "task-usage" packages { + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" + } + imports [cli.Stdout] + provides [ main ] to cli + +main = + log "This is a value:" 42 + +log = \msg, val -> + Stdout.line "$(msg): $(Inspect.toStr val)" \ No newline at end of file diff --git a/logger/iterations/1-append-to-file/main.roc b/logger/iterations/1-append-to-file/main.roc new file mode 100644 index 0000000..6efed15 --- /dev/null +++ b/logger/iterations/1-append-to-file/main.roc @@ -0,0 +1,36 @@ +app "task-usage" packages { + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" + } + imports [ + cli.Stdout, + cli.File, + cli.Task, + cli.Path, + cli.Utc, + ] + provides [ main ] to cli + +# TODO: write sugary version + +log = \msg, val -> + path = Path.fromStr "logFile" + Task.await Utc.now \now -> + millis = Utc.toMillisSinceEpoch now + seconds = Num.round (Num.toFrac millis / Num.toFrac 1000) + time = Num.toStr seconds + appendToFile path "$(time) $(msg): $(Inspect.toStr val)\n" + +# TODO: figure out type annotation, and its pertinence for this iteration +# appendToFile : Path, Str -> Task {} [FileReadErr Path.Path InternalFile.ReadErr, FileWriteErr Path.Path InternalFile.WriteErr] +appendToFile = \path, msg -> + newBytes = Str.toUtf8 msg + Task.await (File.readBytes path) \existingBytes -> + File.writeBytes path (List.concat newBytes existingBytes) + +main = + Task.onErr + (log "This is a value:" 42) + handleErr + +handleErr = \err -> + Stdout.line "We found an error: $(Inspect.toStr err)" \ No newline at end of file From 783d54b72938436b93c6c176384ee6f97468b518 Mon Sep 17 00:00:00 2001 From: Noel Rivas Date: Sun, 14 Apr 2024 22:48:30 -0600 Subject: [PATCH 2/4] Added 1-append with syntactic sugar. --- logger/iterations/1-append-to-file/main.roc | 2 -- .../1-append-to-file/main.sweet.roc | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 logger/iterations/1-append-to-file/main.sweet.roc diff --git a/logger/iterations/1-append-to-file/main.roc b/logger/iterations/1-append-to-file/main.roc index 6efed15..df0f564 100644 --- a/logger/iterations/1-append-to-file/main.roc +++ b/logger/iterations/1-append-to-file/main.roc @@ -10,8 +10,6 @@ app "task-usage" packages { ] provides [ main ] to cli -# TODO: write sugary version - log = \msg, val -> path = Path.fromStr "logFile" Task.await Utc.now \now -> diff --git a/logger/iterations/1-append-to-file/main.sweet.roc b/logger/iterations/1-append-to-file/main.sweet.roc new file mode 100644 index 0000000..7e89003 --- /dev/null +++ b/logger/iterations/1-append-to-file/main.sweet.roc @@ -0,0 +1,33 @@ +app "task-usage" packages { + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" + } + imports [ + cli.Stdout, + cli.File, + cli.Task, + cli.Path, + cli.Utc, + ] + provides [ main ] to cli + +log = \msg, val -> + path = Path.fromStr "logFile" + now <- Utc.now |> Task.await + millis = Utc.toMillisSinceEpoch now + seconds = Num.round (Num.toFrac millis / Num.toFrac 1000) + time = Num.toStr seconds + appendToFile path "$(time) $(msg): $(Inspect.toStr val)\n" + +# TODO: figure out type annotation, and its pertinence for this iteration +# appendToFile : Path, Str -> Task {} [FileReadErr Path.Path InternalFile.ReadErr, FileWriteErr Path.Path InternalFile.WriteErr] +appendToFile = \path, msg -> + newBytes = Str.toUtf8 msg + existingBytes <- File.readBytes path |> Task.await + File.writeBytes path (List.concat newBytes existingBytes) + +main = + log "This is a value:" 42 + |> Task.onErr handleErr + +handleErr = \err -> + Stdout.line "We found an error: $(Inspect.toStr err)" From ae6d8e3ba756982c4fefce72a7ea7fc31a4b0f67 Mon Sep 17 00:00:00 2001 From: Noel Rivas Date: Sun, 14 Apr 2024 22:50:27 -0600 Subject: [PATCH 3/4] Change app name for 1-append --- logger/iterations/1-append-to-file/main.roc | 2 +- logger/iterations/1-append-to-file/main.sweet.roc | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/logger/iterations/1-append-to-file/main.roc b/logger/iterations/1-append-to-file/main.roc index df0f564..6e9a810 100644 --- a/logger/iterations/1-append-to-file/main.roc +++ b/logger/iterations/1-append-to-file/main.roc @@ -1,4 +1,4 @@ -app "task-usage" packages { +app "log-append" packages { cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" } imports [ diff --git a/logger/iterations/1-append-to-file/main.sweet.roc b/logger/iterations/1-append-to-file/main.sweet.roc index 7e89003..bb64038 100644 --- a/logger/iterations/1-append-to-file/main.sweet.roc +++ b/logger/iterations/1-append-to-file/main.sweet.roc @@ -1,4 +1,4 @@ -app "task-usage" packages { +app "log-append" packages { cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" } imports [ @@ -26,8 +26,10 @@ appendToFile = \path, msg -> File.writeBytes path (List.concat newBytes existingBytes) main = - log "This is a value:" 42 - |> Task.onErr handleErr + log "This is a value:" + Task.onErr + (log "This is a value:" 42) + handleErr handleErr = \err -> Stdout.line "We found an error: $(Inspect.toStr err)" From ad1ba3f277cddf44e91c2b9bb2e143d273890795 Mon Sep 17 00:00:00 2001 From: Noel Rivas Date: Sun, 14 Apr 2024 22:52:07 -0600 Subject: [PATCH 4/4] Changed app name for 0-minimal --- logger/iterations/0-minimal-logger/main.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger/iterations/0-minimal-logger/main.roc b/logger/iterations/0-minimal-logger/main.roc index 8afee37..c55493e 100644 --- a/logger/iterations/0-minimal-logger/main.roc +++ b/logger/iterations/0-minimal-logger/main.roc @@ -1,4 +1,4 @@ -app "task-usage" packages { +app "log-minimal" packages { cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" } imports [cli.Stdout]