Skip to content

Commit

Permalink
Improve logging (haskell#2558)
Browse files Browse the repository at this point in the history
* convert to contravariant logging style part 1, uses additional hardcoded log file to see it side by side with original logging

* convert Session to contravariant logging style

* convert Plugin/HLS and FireStore to contravariant logging style

* convert Rules (and most of the universe) to contravariant logging style

* fix tests, allow old style logging and contravariant logging to write to same log file

* fix import inside wrong CPP

* add CPP for LogTactic constructor

* remove redundant import

* fix ghcide tests

* remove unused import

* fix plugin tests

* LSP_TEST_STDERR should apply to contra logger as well

* fix tactic plugin test

* use CPP for Log datatype plugin constructors, remove unused imports

* add a few Pretty instances, add prettyprinter to haskell-language-sever and hls-plugin-api dependencies

* add Pretty Log instances for Session, FileStore, Notifications

* add remaining Pretty Log instances

* add logToPriorities

* fix slight interleaving issue with hslogger and logger both logging, have default logger be mutex stderr or file handle, use stderr if failing to open log file

* forgot to add .cabal files with hslogger dep

* dont use UnliftIO file IO helpers because they are too new

* remove log helper comments, use Doc instead of Text as final console/file logger input, renaming, export Log constructors

* remove accidentally added useless file, removed prettyprinter dep from hls-plugin-api because stack ghc8.6.5 doesnt have it?

* use deprecated prettyprint modules import for the sake of circleci ghc-8.6.5

* use dummy stderr logger for plugin cli commands, use priorityToHsLoggerPriority function instead of manual mapping

* remove old plugin detritus that somehow got committed

* fix prettyprinter imports for 8.6.5

* try enforcing prettyprinter bounds?

* enforcing bound makes no sense

* maybe changing stack yamls does trick

* filter out warnings when their diags are empty to more closely match original

* add ability to select wanted logging columns, match prev ghcide exe logging behaviour

* dont log anything when diags are empty in some defineEarlyCutoff versions

* use non-deprecated prettyprinter imports

* fix ghcide test module

* change logWith to accept priority at call site, remove all logToPriority functions, add cmapWithPrio that contramaps through WithPriority

* remove useless hiding import list, add comments to default recorder makers

* make cradleToOptsAndLibDir take concrete cradle to remove existential type var in Log constructor

* Types.Logger now re-exports prettyprinter, remove unused dependencies on prettyprinter and hslogger

* existential type var to remove boilerplate in Plugins.hs, remove a few Show instances

* add SourceLoc logging column, inline logToDoc functions, add comment explaining hslogger setup existence

* qualify a name to match original source

* fix -WError
  • Loading branch information
eddiemundo authored and drsooch committed Feb 23, 2022
1 parent 2e8c95b commit 30750a7
Show file tree
Hide file tree
Showing 46 changed files with 1,572 additions and 645 deletions.
51 changes: 40 additions & 11 deletions exe/Main.hs
@@ -1,21 +1,50 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Main(main) where

import Ide.Arguments (Arguments (..), GhcideArguments (..),
getArguments)
import Ide.Main (defaultMain)
import Plugins
import Data.Function ((&))
import Development.IDE.Types.Logger (Priority (Debug, Info),
WithPriority (WithPriority, priority),
cfilter, cmapWithPrio,
makeDefaultStderrRecorder,
withDefaultRecorder)
import Ide.Arguments (Arguments (..),
GhcideArguments (..),
getArguments)
import Ide.Main (defaultMain)
import qualified Ide.Main as IdeMain
import qualified Plugins
import Prettyprinter (Pretty (pretty))

data Log
= LogIdeMain IdeMain.Log
| LogPlugins Plugins.Log

instance Pretty Log where
pretty log = case log of
LogIdeMain ideMainLog -> pretty ideMainLog
LogPlugins pluginsLog -> pretty pluginsLog

main :: IO ()
main = do
args <- getArguments "haskell-language-server" (idePlugins False)
-- plugin cli commands use stderr logger for now unless we change the args
-- parser to get logging arguments first or do more complicated things
pluginCliRecorder <- cmapWithPrio pretty <$> makeDefaultStderrRecorder Nothing Info
args <- getArguments "haskell-language-server" (Plugins.idePlugins (cmapWithPrio LogPlugins pluginCliRecorder) False)

let (minPriority, logFilePath, includeExamplePlugins) =
case args of
Ghcide GhcideArguments{ argsTesting, argsDebugOn, argsLogFile, argsExamplePlugin } ->
let minPriority = if argsDebugOn || argsTesting then Debug else Info
in (minPriority, argsLogFile, argsExamplePlugin)
_ -> (Info, Nothing, False)

let withExamples =
case args of
Ghcide GhcideArguments{..} -> argsExamplePlugin
_ -> False
withDefaultRecorder logFilePath Nothing minPriority $ \textWithPriorityRecorder -> do
let recorder =
textWithPriorityRecorder
& cfilter (\WithPriority{ priority } -> priority >= minPriority)
& cmapWithPrio pretty

defaultMain args (idePlugins withExamples)
defaultMain (cmapWithPrio LogIdeMain recorder) args (Plugins.idePlugins (cmapWithPrio LogPlugins recorder) includeExamplePlugins)
82 changes: 46 additions & 36 deletions exe/Plugins.hs
@@ -1,75 +1,78 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
module Plugins where

import Development.IDE.Types.Logger (Pretty (pretty), Recorder,
WithPriority, cmapWithPrio)
import Ide.PluginUtils (pluginDescToIdePlugins)
import Ide.Types (IdePlugins)

-- fixed plugins
import Development.IDE (IdeState)
import Development.IDE.Plugin.HLS.GhcIde as GhcIde
import Ide.Plugin.Example as Example
import Ide.Plugin.Example2 as Example2
import qualified Development.IDE.Plugin.HLS.GhcIde as GhcIde
import qualified Ide.Plugin.Example as Example
import qualified Ide.Plugin.Example2 as Example2

-- haskell-language-server optional plugins
#if qualifyImportedNames
import Ide.Plugin.QualifyImportedNames as QualifyImportedNames
import qualified Ide.Plugin.QualifyImportedNames as QualifyImportedNames
#endif

#if callHierarchy
import Ide.Plugin.CallHierarchy as CallHierarchy
import qualified Ide.Plugin.CallHierarchy as CallHierarchy
#endif

#if class
import Ide.Plugin.Class as Class
import qualified Ide.Plugin.Class as Class
#endif

#if haddockComments
import Ide.Plugin.HaddockComments as HaddockComments
import qualified Ide.Plugin.HaddockComments as HaddockComments
#endif

#if eval
import Ide.Plugin.Eval as Eval
import qualified Ide.Plugin.Eval as Eval
#endif

#if importLens
import Ide.Plugin.ExplicitImports as ExplicitImports
import qualified Ide.Plugin.ExplicitImports as ExplicitImports
#endif

#if refineImports
import Ide.Plugin.RefineImports as RefineImports
import qualified Ide.Plugin.RefineImports as RefineImports
#endif

#if rename
import Ide.Plugin.Rename as Rename
import qualified Ide.Plugin.Rename as Rename
#endif

#if retrie
import Ide.Plugin.Retrie as Retrie
import qualified Ide.Plugin.Retrie as Retrie
#endif

#if tactic
import Ide.Plugin.Tactic as Tactic
import qualified Ide.Plugin.Tactic as Tactic
#endif

#if hlint
import Ide.Plugin.Hlint as Hlint
import qualified Ide.Plugin.Hlint as Hlint
#endif

#if moduleName
import Ide.Plugin.ModuleName as ModuleName
import qualified Ide.Plugin.ModuleName as ModuleName
#endif

#if pragmas
import Ide.Plugin.Pragmas as Pragmas
import qualified Ide.Plugin.Pragmas as Pragmas
#endif

#if splice
import Ide.Plugin.Splice as Splice
import qualified Ide.Plugin.Splice as Splice
#endif

#if alternateNumberFormat
import Ide.Plugin.AlternateNumberFormat as AlternateNumberFormat
import qualified Ide.Plugin.AlternateNumberFormat as AlternateNumberFormat
#endif

#if selectionRange
Expand All @@ -82,35 +85,42 @@ import Ide.Plugin.ChangeTypeSignature as ChangeTypeSignature
-- formatters

#if floskell
import Ide.Plugin.Floskell as Floskell
import qualified Ide.Plugin.Floskell as Floskell
#endif

#if fourmolu
import Ide.Plugin.Fourmolu as Fourmolu
import qualified Ide.Plugin.Fourmolu as Fourmolu
#endif

#if ormolu
import Ide.Plugin.Ormolu as Ormolu
import qualified Ide.Plugin.Ormolu as Ormolu
#endif

#if stylishHaskell
import Ide.Plugin.StylishHaskell as StylishHaskell
import qualified Ide.Plugin.StylishHaskell as StylishHaskell
#endif

#if brittany
import Ide.Plugin.Brittany as Brittany
import qualified Ide.Plugin.Brittany as Brittany
#endif

data Log = forall a. (Pretty a) => Log a

instance Pretty Log where
pretty (Log a) = pretty a

-- ---------------------------------------------------------------------

-- | The plugins configured for use in this instance of the language
-- server.
-- These can be freely added or removed to tailor the available
-- features of the server.

idePlugins :: Bool -> IdePlugins IdeState
idePlugins includeExamples = pluginDescToIdePlugins allPlugins
idePlugins :: Recorder (WithPriority Log) -> Bool -> IdePlugins IdeState
idePlugins recorder includeExamples = pluginDescToIdePlugins allPlugins
where
pluginRecorder :: forall log. (Pretty log) => Recorder (WithPriority log)
pluginRecorder = cmapWithPrio Log recorder
allPlugins = if includeExamples
then basePlugins ++ examplePlugins
else basePlugins
Expand All @@ -125,7 +135,7 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins
Fourmolu.descriptor "fourmolu" :
#endif
#if tactic
Tactic.descriptor "tactics" :
Tactic.descriptor pluginRecorder "tactics" :
#endif
#if ormolu
Ormolu.descriptor "ormolu" :
Expand All @@ -152,28 +162,28 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins
HaddockComments.descriptor "haddockComments" :
#endif
#if eval
Eval.descriptor "eval" :
Eval.descriptor pluginRecorder "eval" :
#endif
#if importLens
ExplicitImports.descriptor "importLens" :
ExplicitImports.descriptor pluginRecorder "importLens" :
#endif
#if qualifyImportedNames
QualifyImportedNames.descriptor "qualifyImportedNames" :
#endif
#if refineImports
RefineImports.descriptor "refineImports" :
RefineImports.descriptor pluginRecorder "refineImports" :
#endif
#if moduleName
ModuleName.descriptor "moduleName" :
#endif
#if hlint
Hlint.descriptor "hlint" :
Hlint.descriptor pluginRecorder "hlint" :
#endif
#if splice
Splice.descriptor "splice" :
#endif
#if alternateNumberFormat
AlternateNumberFormat.descriptor "alternateNumberFormat" :
AlternateNumberFormat.descriptor pluginRecorder "alternateNumberFormat" :
#endif
#if selectionRange
SelectionRange.descriptor "selectionRange" :
Expand All @@ -183,8 +193,8 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins
#endif
-- The ghcide descriptors should come last so that the notification handlers
-- (which restart the Shake build) run after everything else
GhcIde.descriptors
GhcIde.descriptors pluginRecorder
examplePlugins =
[Example.descriptor "eg"
,Example2.descriptor "eg2"
[Example.descriptor pluginRecorder "eg"
,Example2.descriptor pluginRecorder "eg2"
]
73 changes: 58 additions & 15 deletions ghcide/exe/Main.hs
Expand Up @@ -9,17 +9,28 @@ import Arguments (Arguments (..),
getArguments)
import Control.Monad.Extra (unless)
import Data.Default (def)
import Data.Function ((&))
import Data.Version (showVersion)
import Development.GitRev (gitHash)
import Development.IDE (Priority (Debug, Info),
action)
import Development.IDE (action)
import Development.IDE.Core.OfInterest (kick)
import Development.IDE.Core.Rules (mainRule)
import qualified Development.IDE.Core.Rules as Rules
import Development.IDE.Core.Tracing (withTelemetryLogger)
import Development.IDE.Graph (ShakeOptions (shakeThreads))
import qualified Development.IDE.Main as Main
import qualified Development.IDE.Main as IDEMain
import qualified Development.IDE.Plugin.HLS.GhcIde as GhcIde
import Development.IDE.Types.Logger (Logger (Logger),
LoggingColumn (DataColumn, PriorityColumn),
Pretty (pretty),
Priority (Debug, Info),
Recorder (Recorder),
WithPriority (WithPriority, priority),
cfilter, cmapWithPrio,
makeDefaultStderrRecorder)
import qualified Development.IDE.Types.Logger as Logger
import Development.IDE.Types.Options
import GHC.Stack (emptyCallStack)
import Ide.Plugin.Config (Config (checkParents, checkProject))
import Ide.PluginUtils (pluginDescToIdePlugins)
import Paths_ghcide (version)
Expand All @@ -29,6 +40,17 @@ import System.Exit (exitSuccess)
import System.IO (hPutStrLn, stderr)
import System.Info (compilerVersion)

data Log
= LogIDEMain IDEMain.Log
| LogRules Rules.Log
| LogGhcIde GhcIde.Log

instance Pretty Log where
pretty = \case
LogIDEMain log -> pretty log
LogRules log -> pretty log
LogGhcIde log -> pretty log

ghcideVersion :: IO String
ghcideVersion = do
path <- getExecutablePath
Expand All @@ -42,7 +64,12 @@ ghcideVersion = do

main :: IO ()
main = withTelemetryLogger $ \telemetryLogger -> do
let hlsPlugins = pluginDescToIdePlugins GhcIde.descriptors
-- stderr recorder just for plugin cli commands
pluginCliRecorder <-
cmapWithPrio pretty
<$> makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn]) Info

let hlsPlugins = pluginDescToIdePlugins (GhcIde.descriptors (cmapWithPrio LogGhcIde pluginCliRecorder))
-- WARNING: If you write to stdout before runLanguageServer
-- then the language server will not work
Arguments{..} <- getArguments hlsPlugins
Expand All @@ -55,26 +82,42 @@ main = withTelemetryLogger $ \telemetryLogger -> do
Nothing -> IO.getCurrentDirectory
Just root -> IO.setCurrentDirectory root >> IO.getCurrentDirectory

let logPriority = if argsVerbose then Debug else Info
arguments = if argsTesting then Main.testing else Main.defaultArguments logPriority
let minPriority = if argsVerbose then Debug else Info

docWithPriorityRecorder <- makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn]) minPriority

let docWithFilteredPriorityRecorder@Recorder{ logger_ } =
docWithPriorityRecorder
& cfilter (\WithPriority{ priority } -> priority >= minPriority)

-- exists so old-style logging works. intended to be phased out
let logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m))

let recorder = docWithFilteredPriorityRecorder
& cmapWithPrio pretty

let arguments =
if argsTesting
then IDEMain.testing (cmapWithPrio LogIDEMain recorder) logger
else IDEMain.defaultArguments (cmapWithPrio LogIDEMain recorder) logger

Main.defaultMain arguments
{ Main.argsProjectRoot = Just argsCwd
, Main.argCommand = argsCommand
,Main.argsLogger = Main.argsLogger arguments <> pure telemetryLogger
IDEMain.defaultMain (cmapWithPrio LogIDEMain recorder) arguments
{ IDEMain.argsProjectRoot = Just argsCwd
, IDEMain.argCommand = argsCommand
, IDEMain.argsLogger = IDEMain.argsLogger arguments <> pure telemetryLogger

,Main.argsRules = do
, IDEMain.argsRules = do
-- install the main and ghcide-plugin rules
mainRule def
mainRule (cmapWithPrio LogRules recorder) def
-- install the kick action, which triggers a typecheck on every
-- Shake database restart, i.e. on every user edit.
unless argsDisableKick $
action kick

,Main.argsThreads = case argsThreads of 0 -> Nothing ; i -> Just (fromIntegral i)
, IDEMain.argsThreads = case argsThreads of 0 -> Nothing ; i -> Just (fromIntegral i)

,Main.argsIdeOptions = \config sessionLoader ->
let defOptions = Main.argsIdeOptions arguments config sessionLoader
, IDEMain.argsIdeOptions = \config sessionLoader ->
let defOptions = IDEMain.argsIdeOptions arguments config sessionLoader
in defOptions
{ optShakeProfiling = argsShakeProfiling
, optOTMemoryProfiling = IdeOTMemoryProfiling argsOTMemoryProfiling
Expand Down

0 comments on commit 30750a7

Please sign in to comment.