The Haskell library Database.Persist.Sqlite includes functions that run within a LoggingT context, to control debugging output. So I expected to be able to limit the debugging output they produce, thus:
runStdoutLoggingT . filterLogger (\_ _ -> False) (runSqlPool (insertBy myData) myPool)
(condensed and simplified from my actual code) However, it doesn't suppress logging. The evalation of insertBy produces a line on stdout of the form
[Debug#SQL] SELECT "id","key","data_source_row_id","loaded" FROM "data_row" WHERE "key"=? AND "data_source_row_id"=?; [PersistText blahblahblah]
So why isn't the output suppressed by the filterLogger call ?
Since the question has received two downvotes, I'll add that the pattern shown above (i.e., runStdoutLoggingT . filterLogger) is used in many GitHub projects and I can't see how my application is any different. It is somewhat frustrating to be downvoted without explanation or means of recourse.
The architecture of Persistent is a little circuitous and underdocumented:
withSqlPool
takes a builder. The builder is able to build a SqlBackend
out of any "logging function" (basically the internal type that MonadLogger uses). The function then creates a resource pool of SqlBackend
s, for you to acquire and release and use. This is the continuation argument Pool SqlBackend -> m a
you pass in. In return, withSqlPool
promises to give you back a bunch of side effects, typed as (MonadIO m, MonadBaseControl IO m, MonadLogger m) => m a
.
runSqlPool
, on the other hand, takes a MonadBaseControl IO m => ReaderT SqlBackend m a
and a Pool SqlBackend
and returns m a
. We can infer from this that it basically acquires a SqlBackend
from the resource pool, uses it to construct and run a SQL query, and then returns MonadBaseControl IO => m a
. Indeed, its documentation is "Get a connection from the pool, run the given action, and then return the connection to the pool."
Though named similarly, they do two very different things. The first function constructs the resource pool and the second function uses it. Most Persistent SQL code will have this shape:
withSqlPool (\logFunc -> do
conn <- makeConnection connectionString
return SqlBackend { ... , connLogFunc = logFunc })
numberOfOpenConnections
(\pool -> do
runSqlPool (insertBy myData) pool
runSqlPool (anotherTransaction moreData) pool)
In fact, if you're using persistent-postgresql
, the above is simply the expanded form of
withPostgresqlPool connectionString
numberOfOpenConnections
(\pool -> do
runSqlPool (insertBy myData) pool
runSqlPool (anotherTransaction moreData) pool)
But wait! We can't quite execute this as an IO
action yet. MonadIO m, MonadBaseControl IO m, MonadLogger m
are our constraints and it's that third one that we have to discharge:
main :: IO ()
main =
runStdoutLoggingT $
withPostgresqlPool connectionString
numberOfOpenConnections
(\pool -> do
runSqlPool (insertBy myData) pool
runSqlPool (anotherTransaction moreData) pool
return ())
When the third constraints disappears, we're able to unify IO ()
with (MonadIO m, MonadBaseControl IO m) => m ()
by realizing m ~ IO
.
It's now, at this stage, that we're able to insert our filterLogger
– right before the constraint is discharged with runStdoutLoggingT
:
main :: IO ()
main =
runStdoutLoggingT . filterLogger (\_ _ -> False) $
withPostgresqlPool connectionString
numberOfOpenConnections
(\pool -> do
runSqlPool (insertBy myData) pool
runSqlPool (anotherTransaction moreData) pool
return ())
Overall, a mess created by bad naming and the underwhelmingly documented Database.Persist.Sql
module.
Let's underline the point: runSqlPool
simply inherits the logging behavior from the MonadLogger
constraint generated by withSqlPool
. It is only at the withSqlPool
level that we're able to insert the desired filterLogger
call.