-
-
Notifications
You must be signed in to change notification settings - Fork 22
Adds basic logging example #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| guard (not infoExists && not errorExists) <|> do | ||
| error $ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that I approve of the way I'm using guard and error here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never been a huge fan of guard, personally I never remember exactly what it does, and for most of our audience I'd rather present practice with basic case expressions than use the combinator. I think this is a place where you want to use fail instead of error?
| } | ||
|
|
||
|
|
||
| -- A log that writes to the console. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these comments are necessary, since you'll be adding your own I think.
|
I've been rolling this around mostly splitting things up into smaller functions and trying to get the log output to look simple. I think the I really don't think it's too convoluted, but I think to present it more palatably I want to split it up into two parts -
{-# LANGUAGE LambdaCase #-}
import Control.Exception.Safe (displayException, tryAny)
import Data.Foldable (fold)
import System.Directory (getPermissions, writable)
import System.Environment (getEnv)
import System.IO (hPutStr, stdout, stderr)
data Level = Info | Error
data Event = Event Level String
data Log = Log { record :: Event -> IO () }
standardStream = \case Info -> stdout; Error -> stderr
consoleLog = Log $ \(Event level message) ->
hPutStr (standardStream level) (message <> "\n")
fileLog :: (Level -> FilePath) -> Log
fileLog path = Log $ \(Event level message) ->
appendFile (path level) (message <> "\n")
multiLog log1 log2 = Log $ \event ->
do
record log1 event
record log2 event
nullLog = Log (\_ -> return ())
formatEvent topic (Event level msg) = Event level msg'
where
msg' = paren (topic ! levelString level) ! msg
paren x = "(" <> x <> ")"
x ! y = x <> " " <> y
levelString = \case Info -> "info"; Error -> "error"
formattedLog topic log = Log $ \event ->
record log (formatEvent topic event)
instance Semigroup Log where (<>) = multiLog
instance Monoid Log where mempty = nullLog
logFunc log functionName f x =
do
record log (Event Info msg)
return (f x)
where
msg = functionName ! show x ! "=" ! show (f x)
exceptionEvent ex = Event Error (displayException ex)
logAction log taskDescription action =
do
record log (event Info "Starting")
result <- tryAny action >>=
\case
Left e ->
do
record log (exceptionEvent e)
return Nothing
Right x ->
return (Just x)
record log (event Info "Done")
return result
where
event level message = Event level (taskDescription ! "-" ! message)
envLogPath varName =
do
path <- getEnv varName
assertWritable path
return path
assertWritable path =
do
permissions <- getPermissions path
case writable permissions of
True -> return ()
False -> fail ("Log path" ! path ! "is not writable")
initFileLog :: IO Log
initFileLog =
do
infoPath <- envLogPath "INFO_LOG"
errorPath <- envLogPath "ERROR_LOG"
return (fileLog (\case Info -> infoPath; Error -> errorPath))
main =
do
let bootLog = formattedLog "Boot" consoleLog
record bootLog (Event Info "Starting ...")
fileLog <- logAction bootLog "initFileLog" initFileLog
let appLog = formattedLog "App" consoleLog <> fold fileLog
record appLog (Event Info "Application started")
let double = logFunc appLog "double" (* 2)
x <- double (5 :: Int)
y <- double (6 :: Int)
z <- double (7 :: Int)
record appLog (Event Info ("Results: " <> show [x, y, z])) |
|
I just glanced back at #15 and remembered that I had wanted to address using a queue to safely combine logs from multiple threads - but that puts us way over the complexity budget for this example... so I think that original example I wrote in the issue actually just needs to be turned into a page about queues. |
|
This looks great! I like how your dilemma (checking for write permissions) is a lot less contrived than my dilemma (warning about modifying an existing files). I’ll try to find a logical way to split it, like you suggested. |
|
Been doing some writing, should be ready to merge soon. Oh, how should we list you in the contributor list, and for the record is the CC BY-NC 4.0 license agreeable? |
|
Jeez, I'm sorry I missed this comment. The license is agreeable. |
|
How is this (https://gist.github.com/friedbrice/70666e9fe4c053a40936a47c5d77cafc) for queues? |
|
Cool, I subsequently missed your comment as well. Will get this out soon! Made a note on #14 to get back around to the queue example. |
Addresses issue #15
I want the logging to be somewhat realistic (even though I'm completely ignoring time), and I want to have nice things like log combinators, so I have stuff like formatting and combining logs. But now that I'm looking at it, I can't help but think that this is way too convoluted.
What are the essential features you'd like to see?