haskell / haskell-language-server

Official haskell ide support via language server (LSP). Successor of ghcide & haskell-ide-engine.
Apache License 2.0
2.71k stars 368 forks source link

Fix and improve server logging #2492

Open jneira opened 2 years ago

jneira commented 2 years ago

As @alanz suggested in this issue about harmonising logging :

  • how and what are we going to log? Tapping into the LSP expectations makes sense, which seems to be to use stderr, which can then be managed per process and per client.
  • Which logging framework will we use? In my mind this should be common across haskell-lsp, ghcide, and hls

I would add:

pepeiborra commented 2 years ago

Sigma IDE uses a contra variant logger with support for tracing. It has the usual backends (console, event log) as well as an LSP backend.

Contra variant has these USPs:

  1. error level is decided by caller (e.g. main function)
  2. in-process tests can attach event handlers to log messages and use them to prove invariants
    
    data LogMessage
    = LogRule LogRuleMessage
    | LogCore LogCoreMessage
    | LogCompile LogCompileMessage
    | LogOther LogOtherMessage

data LogRuleMessage = LogReindexingFile FilePath | LogRegeneratingInterface FilePath | ...

-- | A contravariant tracing abstraction data Tracer msg = Tracer { logMsg :: forall m . (HasCallStack, MonadCatch m) => msg -> m () -- ^ Log a message , traceMsg :: forall a m . (HasCallStack, MonadCatch m) => msg -> m a -> m a -- ^ Trace the begin and end of a computation } instance Contravariant Tracer instance Monoid Tracer instance Semigroup Tracer

jneira commented 2 years ago

Wow that sounds cool, any chance to use it in hls? i mean the source code is available to be copied or used via a public library

pepeiborra commented 2 years ago

This idea is implemented by logging libraries like co-log but Sigma IDE just rolls its own solution with the abstractions pasted above. There really isn't that much code.

-- Explicit record accessors to preserve call stacks

logMsg :: (HasCallStack, MonadTrace m) => Tracer msg -> msg -> m ()
logMsg logger msg = withFrozenCallStack $ logMsg_ logger msg

traceMsg :: (HasCallStack, MonadTrace m) => Tracer msg -> msg -> m a -> m a
traceMsg logger msg act = withFrozenCallStack $ traceMsg_ logger msg act

The only slightly tricky bit is the LSP backend, which involves registering a plugin to capture the LSPEnv in order to send SCustomMethod notifications in the logger.

Proving invariants in tests can be done by either parsing the LSP notifications sent by the LSP logger backend, or by creating new logger backends on the fly that do more complicated things

michaelpj commented 2 years ago

Why do you need a SCustomMethod? There's WindowLogMessage, right?

michaelpj commented 2 years ago

Also I'm very pro this, it would be very helpful.

The other component is just logging more. For example, we don't give users any logging about why we pick certain cradles, which is one of the things that we often end up helping people debug.

jneira commented 2 years ago

The other component is just logging more. For example, we don't give users any logging about why we pick certain cradles, which is one of the things that we often end up helping people debug.

yep, this is a implicit-hie thing, which has not log output at all afaik :-(

michaelpj commented 2 years ago

I might make a gentle attempt to use co-log-core or something in HLS, but that shouldn't stop anyone else from doing something on this.

jneira commented 2 years ago

One of the aspects of server logging which could be improved is the log of lsp messages, see #1904

michaelpj commented 2 years ago

Adding some TODOS for after https://github.com/haskell/haskell-language-server/pull/2558 lands (cc @eddiemundo )

eddiemundo commented 2 years ago

More TODOs that #2558 didn't complete

michaelpj commented 2 years ago

considering putting the "recorder" inside ShakeExtras like before

I'm not sure how I feel about this. It depends on whether we want to do much in the way of "local" logging control. For example, if we wanted to have the ability to turn off plugin logs or something,, we'd want to make sure they were being passed the logger that we had modified, not taking one from the global environment.

eddiemundo commented 2 years ago

Nice that's another disadvantage. It's just an idea that Pepe asked if I thought about that I reflexively didn't like, but couldn't really articulate why beyond it being uglier. I'll edit the comment.

pepeiborra commented 2 years ago

The recorder should be an input to defaultMain and therefore under your full control, regardless of whether it is stored in ShakeExtras or not.

My suggestion was to add a field argsRecorder to the Arguments value taken by defaultMain, iirc, not to add it to ShakeExtras. Or maybe I suggested both?

eddiemundo commented 2 years ago

You're right my brain failed and now I see my reply makes no sense. My train of thought was previous logger in Arguments goes into ShakeExtras, therefore adding recorder to Arguments is for going into ShakeExtras.

michaelpj commented 2 years ago

A few more:

eddiemundo commented 2 years ago

With #2750 we can send messages to window by setting priority of message to Error. If that goes through the places that still use SWindowMessages directly instead of the recorder afaik are: splice plugin tactics plugin fourmolu plugin retrie plugin extendImportHandler plugin in Completions.hs displayTHWarning in Rules.hs

Maybe setting message to Error isn't appropriate for all like displayTHWarning since that will also come with a report this to issue tracker when using the hls exe which we probably don't want.

xsebek commented 2 years ago

As a side question, is there a place where the new loggers get their format string?

There seem to be four formats*:

2022-05-30 23:01:33.8870000 [client] INFO Finding haskell-language-server
2022-05-30T23:01:34.751639Z | Info | Starting (haskell-language-server) LSP server...
[Info  - 1:01:36 AM] Starting (haskell-language-server) LSP server...
2022-05-31 01:01:38.501386828 [ThreadId 155] INFO hie-bios: Build profile: -w ghc-8.10.7 -O1

I was thinking about adding the thread id to debug one issue but could not find where the new structured logs are formatted with time, level and stuff. :confused:

*) Funny that each time format is different too, with the first two ignoring time zones I guess?

drsooch commented 2 years ago

As a side question, is there a place where the new loggers get their format string?

There seem to be four formats*:

2022-05-30 23:01:33.8870000 [client] INFO Finding haskell-language-server
2022-05-30T23:01:34.751639Z | Info | Starting (haskell-language-server) LSP server...
[Info  - 1:01:36 AM] Starting (haskell-language-server) LSP server...
2022-05-31 01:01:38.501386828 [ThreadId 155] INFO hie-bios:   Build profile: -w ghc-8.10.7 -O1

I was thinking about adding the thread id to debug one issue but could not find where the new structured logs are formatted with time, level and stuff. confused

*) Funny that each time format is different too, with the first two ignoring time zones I guess?

I believe the first one is the old legacy logging format. Otherwise, checkout ghcide/src/Development/IDE/Types/Logger.hs search for defaultLoggingColumns and add ThreadIdColumn you should get the Thread IDs

drsooch commented 2 years ago

Here's another thought:

michaelpj commented 2 years ago

There seem to be four formats

Yeah, there's still a mess here:

  1. I don't know what this is
  2. This is the "new" log format that stuff should mostly be using
  3. This is the same log as the previous one, but sent to the client, so vscode displays it. It's not ideal that it displays the logs sent to the client and stdout interleaved, maybe we shouldn't send all our logs to vscode if it's going to do that?
  4. This is hslogger, which is on its way out. I recently purged it from hie-bios, so once we get a new hie-bios that should be gone.

Context aware logging.

I think this shouldn't be too bad? It could be another field in our message type, of type [Context], and then you could write withContext :: Context -> Recorder ... -> Recorder ... fairly easily I think?