This PR revamps the server logging mechanism to allow filtering the log output by LSP method.
121 lines
3.6 KiB
Text
121 lines
3.6 KiB
Text
/-
|
|
Copyright (c) 2025 Lean FRO. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
|
|
Author: Marc Huisinga
|
|
-/
|
|
module
|
|
|
|
prelude
|
|
public import Lean.Data.JsonRpc
|
|
import Std.Time
|
|
import Lean.Data.Lsp.Extra
|
|
public import Lean.Data.Lsp.InitShutdown
|
|
|
|
namespace Lean.Server.Logging
|
|
|
|
public structure LogConfig where
|
|
isEnabled : Bool
|
|
logFile : System.FilePath
|
|
allowedMethods? : Option (Std.HashSet String)
|
|
disallowedMethods? : Option (Std.HashSet String)
|
|
|
|
public def LogConfig.ofLspLogConfig (lspCfg? : Option Lsp.LogConfig) : IO LogConfig := do
|
|
let time ← Std.Time.ZonedDateTime.now
|
|
let time := time.format "yyyy-MM-dd-HH-mm-ss-SSSSXX"
|
|
let logFileName := s!"LSP_{time}.log"
|
|
let defaultLogFile : System.FilePath := System.FilePath.join "." logFileName
|
|
let some lspCfg := lspCfg?
|
|
| return {
|
|
isEnabled := false
|
|
logFile := defaultLogFile
|
|
allowedMethods? := none
|
|
disallowedMethods? := none
|
|
}
|
|
return {
|
|
isEnabled := true
|
|
logFile := lspCfg.logDir?.map (System.FilePath.join · logFileName) |>.getD defaultLogFile
|
|
allowedMethods? := lspCfg.allowedMethods?
|
|
disallowedMethods? := lspCfg.disallowedMethods?
|
|
}
|
|
|
|
open Lean
|
|
|
|
public inductive MessageMethod where
|
|
| request (method : String)
|
|
| rpcRequest (method : String)
|
|
| notification (method : String)
|
|
deriving Inhabited
|
|
|
|
def MessageMethod.all : MessageMethod → Array String
|
|
| .request method
|
|
| .notification method =>
|
|
#[method]
|
|
| .rpcRequest method =>
|
|
#["$/lean/rpc/call", method]
|
|
|
|
public def messageMethod? : JsonRpc.Message → Option MessageMethod
|
|
| .request _ method params? => do
|
|
if method == "$/lean/rpc/call" then
|
|
let params := toJson params?
|
|
if let .ok (callParams : Lsp.RpcCallParams) := fromJson? params then
|
|
return .rpcRequest callParams.method.toString
|
|
return .request method
|
|
| .notification method _ =>
|
|
some <| .notification method
|
|
| _ =>
|
|
none
|
|
|
|
def messageId? : JsonRpc.Message → Option JsonRpc.RequestID
|
|
| .request id .. => some id
|
|
| .response id .. => some id
|
|
| .responseError id .. => some id
|
|
| _ => none
|
|
|
|
def isMsgAllowed (cfg : LogConfig)
|
|
(pending : Std.HashMap JsonRpc.RequestID MessageMethod)
|
|
(msg : JsonRpc.Message) : Bool := Id.run do
|
|
if ! cfg.isEnabled then
|
|
return false
|
|
let some method := method?
|
|
| return false
|
|
let allMethods := method.all
|
|
if let some allowedMethods := cfg.allowedMethods? then
|
|
if allMethods.any (! allowedMethods.contains ·) then
|
|
return false
|
|
if let some disallowedMethods := cfg.disallowedMethods? then
|
|
if allMethods.any (disallowedMethods.contains ·) then
|
|
return false
|
|
return true
|
|
where
|
|
method? : Option MessageMethod :=
|
|
messageMethod? msg <|> (do pending.get? (← messageId? msg))
|
|
|
|
local instance : ToJson Std.Time.ZonedDateTime where
|
|
toJson dt := dt.toISO8601String
|
|
|
|
local instance : FromJson Std.Time.ZonedDateTime where
|
|
fromJson?
|
|
| .str s => Std.Time.ZonedDateTime.fromISO8601String s
|
|
| _ => throw "Expected string when converting JSON to Std.Time.ZonedDateTime"
|
|
|
|
structure LogEntry where
|
|
time : Std.Time.ZonedDateTime
|
|
direction : JsonRpc.MessageDirection
|
|
kind : JsonRpc.MessageKind
|
|
msg : JsonRpc.Message
|
|
deriving FromJson, ToJson
|
|
|
|
public def writeLogEntry (cfg : LogConfig) (pending : Std.HashMap JsonRpc.RequestID MessageMethod)
|
|
(log : IO.FS.Handle) (direction : JsonRpc.MessageDirection) (msg : JsonRpc.Message) : IO Unit := do
|
|
if ! isMsgAllowed cfg pending msg then
|
|
return
|
|
let entry : LogEntry := {
|
|
time := ← Std.Time.ZonedDateTime.now
|
|
direction
|
|
kind := .ofMessage msg
|
|
msg
|
|
}
|
|
let entry := toJson entry |>.compress
|
|
log.putStrLn entry
|
|
log.flush
|