lean4-htt/src/Lean/Server/Logging.lean
Marc Huisinga 19533ab1d4
feat: revamp server logging (#10787)
This PR revamps the server logging mechanism to allow filtering the log
output by LSP method.
2025-10-28 16:26:59 +00:00

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