@@ -21,38 +21,50 @@ module Development.IDE.Types.Logger
21
21
, priorityToHsLoggerPriority
22
22
, LoggingColumn (.. )
23
23
, cmapWithPrio
24
+ , withBacklog
25
+ , lspClientMessageRecorder
26
+ , lspClientLogRecorder
24
27
, module PrettyPrinterModule
25
28
, renderStrict
26
29
) where
27
30
28
- import Control.Concurrent (myThreadId )
29
- import Control.Concurrent.Extra (Lock , newLock , withLock )
30
- import Control.Exception (IOException )
31
- import Control.Monad (forM_ , when , (>=>) )
32
- import Control.Monad.IO.Class (MonadIO (liftIO ))
33
- import Data.Functor.Contravariant (Contravariant (contramap ))
34
- import Data.Maybe (fromMaybe )
35
- import Data.Text (Text )
36
- import qualified Data.Text as T
37
- import qualified Data.Text as Text
38
- import qualified Data.Text.IO as Text
39
- import Data.Time (defaultTimeLocale , formatTime ,
40
- getCurrentTime )
41
- import GHC.Stack (CallStack , HasCallStack ,
42
- SrcLoc (SrcLoc , srcLocModule , srcLocStartCol , srcLocStartLine ),
43
- callStack , getCallStack ,
44
- withFrozenCallStack )
45
- import Prettyprinter as PrettyPrinterModule
46
- import Prettyprinter.Render.Text (renderStrict )
47
- import System.IO (Handle , IOMode (AppendMode ),
48
- hClose , hFlush , hSetEncoding ,
49
- openFile , stderr , utf8 )
50
- import qualified System.Log.Formatter as HSL
51
- import qualified System.Log.Handler as HSL
52
- import qualified System.Log.Handler.Simple as HSL
53
- import qualified System.Log.Logger as HsLogger
54
- import UnliftIO (MonadUnliftIO , displayException ,
55
- finally , try )
31
+ import Control.Concurrent (myThreadId )
32
+ import Control.Concurrent.Extra (Lock , newLock , withLock )
33
+ import Control.Concurrent.STM (atomically ,
34
+ newTVarIO , writeTVar , readTVarIO , newTBQueueIO , flushTBQueue , writeTBQueue , isFullTBQueue )
35
+ import Control.Exception (IOException )
36
+ import Control.Monad (forM_ , when , (>=>) , unless )
37
+ import Control.Monad.IO.Class (MonadIO (liftIO ))
38
+ import Data.Foldable (for_ )
39
+ import Data.Functor.Contravariant (Contravariant (contramap ))
40
+ import Data.Maybe (fromMaybe )
41
+ import Data.Text (Text )
42
+ import qualified Data.Text as T
43
+ import qualified Data.Text as Text
44
+ import qualified Data.Text.IO as Text
45
+ import Data.Time (defaultTimeLocale , formatTime ,
46
+ getCurrentTime )
47
+ import GHC.Stack (CallStack , HasCallStack ,
48
+ SrcLoc (SrcLoc , srcLocModule , srcLocStartCol , srcLocStartLine ),
49
+ callStack , getCallStack ,
50
+ withFrozenCallStack )
51
+ import Language.LSP.Server
52
+ import qualified Language.LSP.Server as LSP
53
+ import Language.LSP.Types (LogMessageParams (.. ),
54
+ MessageType (.. ),
55
+ SMethod (SWindowLogMessage , SWindowShowMessage ),
56
+ ShowMessageParams (.. ))
57
+ import Prettyprinter as PrettyPrinterModule
58
+ import Prettyprinter.Render.Text (renderStrict )
59
+ import System.IO (Handle , IOMode (AppendMode ),
60
+ hClose , hFlush , hSetEncoding ,
61
+ openFile , stderr , utf8 )
62
+ import qualified System.Log.Formatter as HSL
63
+ import qualified System.Log.Handler as HSL
64
+ import qualified System.Log.Handler.Simple as HSL
65
+ import qualified System.Log.Logger as HsLogger
66
+ import UnliftIO (MonadUnliftIO , displayException ,
67
+ finally , try )
56
68
57
69
data Priority
58
70
-- Don't change the ordering of this type or you will mess up the Ord
@@ -204,10 +216,10 @@ makeDefaultHandleRecorder columns minPriority lock handle = do
204
216
205
217
priorityToHsLoggerPriority :: Priority -> HsLogger. Priority
206
218
priorityToHsLoggerPriority = \ case
207
- Debug -> HsLogger. DEBUG
208
- Info -> HsLogger. INFO
209
- Warning -> HsLogger. WARNING
210
- Error -> HsLogger. ERROR
219
+ Debug -> HsLogger. DEBUG
220
+ Info -> HsLogger. INFO
221
+ Warning -> HsLogger. WARNING
222
+ Error -> HsLogger. ERROR
211
223
212
224
-- | The purpose of setting up `hslogger` at all is that `hie-bios` uses
213
225
-- `hslogger` to output compilation logs. The easiest way to merge these logs
@@ -290,3 +302,61 @@ textWithPriorityToText columns WithPriority{ priority, callStack_, payload } = d
290
302
pure (threadIdToText threadId)
291
303
PriorityColumn -> pure (priorityToText priority)
292
304
DataColumn -> pure payload
305
+
306
+ -- | Given a 'Recorder' that requires an argument, produces a 'Recorder'
307
+ -- that queues up messages until the argument is provided using the callback, at which
308
+ -- point it sends the backlog and begins functioning normally.
309
+ withBacklog :: (v -> Recorder a ) -> IO (Recorder a , v -> IO () )
310
+ withBacklog recFun = do
311
+ -- Arbitrary backlog capacity
312
+ backlog <- newTBQueueIO 100
313
+ let backlogRecorder = Recorder $ \ it -> liftIO $ atomically $ do
314
+ -- If the queue is full just drop the message on the floor. This is most likely
315
+ -- to happen if the callback is just never going to be called; in which case
316
+ -- we want neither to build up an unbounded backlog in memory, nor block waiting
317
+ -- for space!
318
+ full <- isFullTBQueue backlog
319
+ unless full $ writeTBQueue backlog it
320
+
321
+ -- The variable holding the recorder starts out holding the recorder that writes
322
+ -- to the backlog.
323
+ recVar <- newTVarIO backlogRecorder
324
+ -- The callback atomically swaps out the recorder for the final one, and flushes
325
+ -- the backlog to it.
326
+ let cb arg = do
327
+ let recorder = recFun arg
328
+ toRecord <- atomically $ writeTVar recVar recorder >> flushTBQueue backlog
329
+ for_ toRecord (logger_ recorder)
330
+
331
+ -- The recorder we actually return looks in the variable and uses whatever is there.
332
+ let varRecorder = Recorder $ \ it -> do
333
+ r <- liftIO $ readTVarIO recVar
334
+ logger_ r it
335
+
336
+ pure (varRecorder, cb)
337
+
338
+ -- | Creates a recorder that sends logs to the LSP client via @window/showMessage@ notifications.
339
+ lspClientMessageRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text )
340
+ lspClientMessageRecorder env = Recorder $ \ WithPriority {.. } ->
341
+ liftIO $ LSP. runLspT env $ LSP. sendNotification SWindowShowMessage
342
+ ShowMessageParams
343
+ { _xtype = priorityToLsp priority,
344
+ _message = payload
345
+ }
346
+
347
+ -- | Creates a recorder that sends logs to the LSP client via @window/logMessage@ notifications.
348
+ lspClientLogRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text )
349
+ lspClientLogRecorder env = Recorder $ \ WithPriority {.. } ->
350
+ liftIO $ LSP. runLspT env $ LSP. sendNotification SWindowLogMessage
351
+ LogMessageParams
352
+ { _xtype = priorityToLsp priority,
353
+ _message = payload
354
+ }
355
+
356
+ priorityToLsp :: Priority -> MessageType
357
+ priorityToLsp =
358
+ \ case
359
+ Debug -> MtLog
360
+ Info -> MtInfo
361
+ Warning -> MtWarning
362
+ Error -> MtError
0 commit comments