SshLspAdapterDelegate (#17965)

Conrad Irwin created

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs      |  20 
crates/languages/src/rust.rs                 |   2 
crates/project/src/lsp_store.rs              | 337 ++++++++++++++-------
crates/proto/proto/zed.proto                 |  28 +
crates/proto/src/proto.rs                    |  11 
crates/remote/src/ssh_session.rs             |  19 
crates/remote_server/src/headless_project.rs |   2 
crates/remote_server/src/main.rs             |   1 
8 files changed, 270 insertions(+), 150 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -54,7 +54,7 @@ use language_model::{
 use language_model::{LanguageModelImage, LanguageModelToolUse};
 use multi_buffer::MultiBufferRow;
 use picker::{Picker, PickerDelegate};
-use project::lsp_store::ProjectLspAdapterDelegate;
+use project::lsp_store::LocalLspAdapterDelegate;
 use project::{Project, Worktree};
 use search::{buffer_search::DivRegistrar, BufferSearchBar};
 use serde::{Deserialize, Serialize};
@@ -5384,18 +5384,16 @@ fn make_lsp_adapter_delegate(
         let worktree = project
             .worktrees(cx)
             .next()
-            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
-        let fs = if project.is_local() {
-            Some(project.fs().clone())
-        } else {
-            None
-        };
+            .ok_or_else(|| anyhow!("no worktrees when constructing LocalLspAdapterDelegate"))?;
         let http_client = project.client().http_client().clone();
         project.lsp_store().update(cx, |lsp_store, cx| {
-            Ok(
-                ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
-                    as Arc<dyn LspAdapterDelegate>,
-            )
+            Ok(LocalLspAdapterDelegate::new(
+                lsp_store,
+                &worktree,
+                http_client,
+                project.fs().clone(),
+                cx,
+            ) as Arc<dyn LspAdapterDelegate>)
         })
     })
 }

crates/languages/src/rust.rs 🔗

@@ -77,7 +77,7 @@ impl LspAdapter for RustLspAdapter {
                     {
                         Ok(()) => (Some(path), Some(env), None),
                         Err(err) => {
-                            log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {:?}", path, err);
+                            log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err);
                             (None, None, None)
                         }
                     }

crates/project/src/lsp_store.rs 🔗

@@ -2305,8 +2305,7 @@ impl LspStore {
                                 .read(cx)
                                 .worktree_for_id(*worktree_id, cx)?;
                             let state = this.as_local()?.language_servers.get(server_id)?;
-                            let delegate =
-                                ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+                            let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
                             match state {
                                 LanguageServerState::Starting(_) => None,
                                 LanguageServerState::Running {
@@ -4368,7 +4367,7 @@ impl LspStore {
         let response = this
             .update(&mut cx, |this, cx| {
                 let worktree = this.worktree_for_id(worktree_id, cx)?;
-                let delegate = ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
                 anyhow::Ok(
                     cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
                 )
@@ -4389,7 +4388,7 @@ impl LspStore {
         let response = this
             .update(&mut cx, |this, cx| {
                 let worktree = this.worktree_for_id(worktree_id, cx)?;
-                let delegate = ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
                 anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
             })??
             .await;
@@ -4398,6 +4397,52 @@ impl LspStore {
             env: response.into_iter().collect(),
         })
     }
+    pub async fn handle_try_exec(
+        this: Model<Self>,
+        envelope: TypedEnvelope<proto::TryExec>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::Ack> {
+        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
+        let binary = envelope
+            .payload
+            .binary
+            .ok_or_else(|| anyhow!("missing binary"))?;
+        let binary = LanguageServerBinary {
+            path: PathBuf::from(binary.path),
+            env: None,
+            arguments: binary.arguments.into_iter().map(Into::into).collect(),
+        };
+        this.update(&mut cx, |this, cx| {
+            let worktree = this.worktree_for_id(worktree_id, cx)?;
+            let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
+            anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
+        })??
+        .await?;
+
+        Ok(proto::Ack {})
+    }
+
+    pub async fn handle_read_text_file(
+        this: Model<Self>,
+        envelope: TypedEnvelope<proto::ReadTextFile>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::ReadTextFileResponse> {
+        let path = envelope
+            .payload
+            .path
+            .ok_or_else(|| anyhow!("missing path"))?;
+        let worktree_id = WorktreeId::from_proto(path.worktree_id);
+        let path = PathBuf::from(path.path);
+        let response = this
+            .update(&mut cx, |this, cx| {
+                let worktree = this.worktree_for_id(worktree_id, cx)?;
+                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
+                anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
+            })??
+            .await?;
+
+        Ok(proto::ReadTextFileResponse { text: response })
+    }
 
     async fn handle_apply_additional_edits_for_completion(
         this: Model<Self>,
@@ -4535,9 +4580,12 @@ impl LspStore {
     ) {
         let ssh = self.as_ssh().unwrap();
 
-        let delegate =
-            ProjectLspAdapterDelegate::for_ssh(self, worktree, ssh.upstream_client.clone(), cx)
-                as Arc<dyn LspAdapterDelegate>;
+        let delegate = Arc::new(SshLspAdapterDelegate {
+            lsp_store: cx.handle().downgrade(),
+            worktree: worktree.read(cx).snapshot(),
+            upstream_client: ssh.upstream_client.clone(),
+            language_registry: self.languages.clone(),
+        }) as Arc<dyn LspAdapterDelegate>;
 
         // TODO: We should use `adapter` here instead of reaching through the `CachedLspAdapter`.
         let lsp_adapter = adapter.adapter.clone();
@@ -4645,7 +4693,7 @@ impl LspStore {
         let local = self.as_local().unwrap();
 
         let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
-        let lsp_adapter_delegate = ProjectLspAdapterDelegate::for_local(self, worktree_handle, cx);
+        let lsp_adapter_delegate = LocalLspAdapterDelegate::for_local(self, worktree_handle, cx);
         let project_environment = local.environment.update(cx, |environment, cx| {
             environment.get_environment(Some(worktree_id), Some(worktree_path.clone()), cx)
         });
@@ -6938,18 +6986,32 @@ impl LspAdapter for SshLspAdapter {
         None
     }
 }
+pub fn language_server_settings<'a, 'b: 'a>(
+    delegate: &'a dyn LspAdapterDelegate,
+    language: &str,
+    cx: &'b AppContext,
+) -> Option<&'a LspSettings> {
+    ProjectSettings::get(
+        Some(SettingsLocation {
+            worktree_id: delegate.worktree_id(),
+            path: delegate.worktree_root_path(),
+        }),
+        cx,
+    )
+    .lsp
+    .get(language)
+}
 
-pub struct ProjectLspAdapterDelegate {
+pub struct LocalLspAdapterDelegate {
     lsp_store: WeakModel<LspStore>,
     worktree: worktree::Snapshot,
-    fs: Option<Arc<dyn Fs>>,
+    fs: Arc<dyn Fs>,
     http_client: Arc<dyn HttpClient>,
     language_registry: Arc<LanguageRegistry>,
     load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
-    upstream_client: Option<AnyProtoClient>,
 }
 
-impl ProjectLspAdapterDelegate {
+impl LocalLspAdapterDelegate {
     fn for_local(
         lsp_store: &LspStore,
         worktree: &Model<Worktree>,
@@ -6957,45 +7019,37 @@ impl ProjectLspAdapterDelegate {
     ) -> Arc<Self> {
         let local = lsp_store
             .as_local()
-            .expect("ProjectLspAdapterDelegate cannot be constructed on a remote");
+            .expect("LocalLspAdapterDelegate cannot be constructed on a remote");
 
         let http_client = local
             .http_client
             .clone()
             .unwrap_or_else(|| Arc::new(BlockedHttpClient));
 
-        Self::new(
-            lsp_store,
-            worktree,
-            http_client,
-            Some(local.fs.clone()),
-            None,
-            cx,
-        )
-    }
-
-    fn for_ssh(
-        lsp_store: &LspStore,
-        worktree: &Model<Worktree>,
-        upstream_client: AnyProtoClient,
-        cx: &mut ModelContext<LspStore>,
-    ) -> Arc<Self> {
-        Self::new(
-            lsp_store,
-            worktree,
-            Arc::new(BlockedHttpClient),
-            None,
-            Some(upstream_client),
-            cx,
-        )
-    }
+        Self::new(lsp_store, worktree, http_client, local.fs.clone(), cx)
+    }
+
+    // fn for_ssh(
+    //     lsp_store: &LspStore,
+    //     worktree: &Model<Worktree>,
+    //     upstream_client: AnyProtoClient,
+    //     cx: &mut ModelContext<LspStore>,
+    // ) -> Arc<Self> {
+    //     Self::new(
+    //         lsp_store,
+    //         worktree,
+    //         Arc::new(BlockedHttpClient),
+    //         None,
+    //         Some(upstream_client),
+    //         cx,
+    //     )
+    // }
 
     pub fn new(
         lsp_store: &LspStore,
         worktree: &Model<Worktree>,
         http_client: Arc<dyn HttpClient>,
-        fs: Option<Arc<dyn Fs>>,
-        upstream_client: Option<AnyProtoClient>,
+        fs: Arc<dyn Fs>,
         cx: &mut ModelContext<LspStore>,
     ) -> Arc<Self> {
         let worktree_id = worktree.read(cx).id();
@@ -7015,52 +7069,14 @@ impl ProjectLspAdapterDelegate {
             worktree: worktree.read(cx).snapshot(),
             fs,
             http_client,
-            upstream_client,
             language_registry: lsp_store.languages.clone(),
             load_shell_env_task,
         })
     }
 }
 
-struct BlockedHttpClient;
-
-impl HttpClient for BlockedHttpClient {
-    fn send(
-        &self,
-        _req: Request<AsyncBody>,
-    ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
-        Box::pin(async {
-            Err(std::io::Error::new(
-                std::io::ErrorKind::PermissionDenied,
-                "ssh host blocked http connection",
-            )
-            .into())
-        })
-    }
-
-    fn proxy(&self) -> Option<&Uri> {
-        None
-    }
-}
-
-pub fn language_server_settings<'a, 'b: 'a>(
-    delegate: &'a dyn LspAdapterDelegate,
-    language: &str,
-    cx: &'b AppContext,
-) -> Option<&'a LspSettings> {
-    ProjectSettings::get(
-        Some(SettingsLocation {
-            worktree_id: delegate.worktree_id(),
-            path: delegate.worktree_root_path(),
-        }),
-        cx,
-    )
-    .lsp
-    .get(language)
-}
-
 #[async_trait]
-impl LspAdapterDelegate for ProjectLspAdapterDelegate {
+impl LspAdapterDelegate for LocalLspAdapterDelegate {
     fn show_notification(&self, message: &str, cx: &mut AppContext) {
         self.lsp_store
             .update(cx, |_, cx| {
@@ -7082,42 +7098,12 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
     }
 
     async fn shell_env(&self) -> HashMap<String, String> {
-        if let Some(upstream_client) = &self.upstream_client {
-            use rpc::proto::SSH_PROJECT_ID;
-
-            return upstream_client
-                .request(proto::ShellEnv {
-                    project_id: SSH_PROJECT_ID,
-                    worktree_id: self.worktree_id().to_proto(),
-                })
-                .await
-                .map(|response| response.env.into_iter().collect())
-                .unwrap_or_default();
-        }
-
         let task = self.load_shell_env_task.clone();
         task.await.unwrap_or_default()
     }
 
     #[cfg(not(target_os = "windows"))]
     async fn which(&self, command: &OsStr) -> Option<PathBuf> {
-        if let Some(upstream_client) = &self.upstream_client {
-            use rpc::proto::SSH_PROJECT_ID;
-
-            return upstream_client
-                .request(proto::WhichCommand {
-                    project_id: SSH_PROJECT_ID,
-                    worktree_id: self.worktree_id().to_proto(),
-                    command: command.to_string_lossy().to_string(),
-                })
-                .await
-                .log_err()
-                .and_then(|response| response.path)
-                .map(PathBuf::from);
-        }
-
-        self.fs.as_ref()?;
-
         let worktree_abs_path = self.worktree.abs_path();
         let shell_path = self.shell_env().await.get("PATH").cloned();
         which::which_in(command, shell_path.as_ref(), worktree_abs_path).ok()
@@ -7125,8 +7111,6 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
 
     #[cfg(target_os = "windows")]
     async fn which(&self, command: &OsStr) -> Option<PathBuf> {
-        self.fs.as_ref()?;
-
         // todo(windows) Getting the shell env variables in a current directory on Windows is more complicated than other platforms
         //               there isn't a 'default shell' necessarily. The closest would be the default profile on the windows terminal
         //               SEE: https://learn.microsoft.com/en-us/windows/terminal/customize-settings/startup
@@ -7134,10 +7118,6 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
     }
 
     async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
-        if self.fs.is_none() {
-            return Ok(());
-        }
-
         let working_dir = self.worktree_root_path();
         let output = smol::process::Command::new(&command.path)
             .args(command.arguments)
@@ -7170,12 +7150,127 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
         if self.worktree.entry_for_path(&path).is_none() {
             return Err(anyhow!("no such path {path:?}"));
         };
-        if let Some(fs) = &self.fs {
-            let content = fs.load(&path).await?;
-            Ok(content)
-        } else {
-            return Err(anyhow!("cannot open {path:?} on ssh host (yet!)"));
-        }
+        self.fs.load(&path).await
+    }
+}
+
+struct BlockedHttpClient;
+
+impl HttpClient for BlockedHttpClient {
+    fn send(
+        &self,
+        _req: Request<AsyncBody>,
+    ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
+        Box::pin(async {
+            Err(std::io::Error::new(
+                std::io::ErrorKind::PermissionDenied,
+                "ssh host blocked http connection",
+            )
+            .into())
+        })
+    }
+
+    fn proxy(&self) -> Option<&Uri> {
+        None
+    }
+}
+
+struct SshLspAdapterDelegate {
+    lsp_store: WeakModel<LspStore>,
+    worktree: worktree::Snapshot,
+    upstream_client: AnyProtoClient,
+    language_registry: Arc<LanguageRegistry>,
+}
+
+#[async_trait]
+impl LspAdapterDelegate for SshLspAdapterDelegate {
+    fn show_notification(&self, message: &str, cx: &mut AppContext) {
+        self.lsp_store
+            .update(cx, |_, cx| {
+                cx.emit(LspStoreEvent::Notification(message.to_owned()))
+            })
+            .ok();
+    }
+
+    fn http_client(&self) -> Arc<dyn HttpClient> {
+        Arc::new(BlockedHttpClient)
+    }
+
+    fn worktree_id(&self) -> WorktreeId {
+        self.worktree.id()
+    }
+
+    fn worktree_root_path(&self) -> &Path {
+        self.worktree.abs_path().as_ref()
+    }
+
+    async fn shell_env(&self) -> HashMap<String, String> {
+        use rpc::proto::SSH_PROJECT_ID;
+
+        self.upstream_client
+            .request(proto::ShellEnv {
+                project_id: SSH_PROJECT_ID,
+                worktree_id: self.worktree_id().to_proto(),
+            })
+            .await
+            .map(|response| response.env.into_iter().collect())
+            .unwrap_or_default()
+    }
+
+    async fn which(&self, command: &OsStr) -> Option<PathBuf> {
+        use rpc::proto::SSH_PROJECT_ID;
+
+        self.upstream_client
+            .request(proto::WhichCommand {
+                project_id: SSH_PROJECT_ID,
+                worktree_id: self.worktree_id().to_proto(),
+                command: command.to_string_lossy().to_string(),
+            })
+            .await
+            .log_err()
+            .and_then(|response| response.path)
+            .map(PathBuf::from)
+    }
+
+    async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
+        self.upstream_client
+            .request(proto::TryExec {
+                project_id: rpc::proto::SSH_PROJECT_ID,
+                worktree_id: self.worktree.id().to_proto(),
+                binary: Some(proto::LanguageServerCommand {
+                    path: command.path.to_string_lossy().to_string(),
+                    arguments: command
+                        .arguments
+                        .into_iter()
+                        .map(|s| s.to_string_lossy().to_string())
+                        .collect(),
+                    env: command.env.unwrap_or_default().into_iter().collect(),
+                }),
+            })
+            .await?;
+        Ok(())
+    }
+
+    fn update_status(
+        &self,
+        server_name: LanguageServerName,
+        status: language::LanguageServerBinaryStatus,
+    ) {
+        self.language_registry
+            .update_lsp_status(server_name, status);
+    }
+
+    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
+        self.upstream_client
+            .request(proto::ReadTextFile {
+                project_id: rpc::proto::SSH_PROJECT_ID,
+                path: Some(proto::ProjectPath {
+                    worktree_id: self.worktree.id().to_proto(),
+                    path: path.to_string_lossy().to_string(),
+                }),
+            })
+            .await
+            .map(|r| r.text)
     }
 }
 

crates/proto/proto/zed.proto 🔗

@@ -289,7 +289,11 @@ message Envelope {
         WhichCommandResponse which_command_response = 249;
 
         ShellEnv shell_env = 250;
-        ShellEnvResponse shell_env_response = 251; // current max
+        ShellEnvResponse shell_env_response = 251;
+
+        TryExec try_exec = 252;
+        ReadTextFile read_text_file = 253;
+        ReadTextFileResponse read_text_file_response = 254; // current max
     }
 
     reserved 158 to 161;
@@ -2551,13 +2555,21 @@ message ShellEnvResponse {
     map<string, string> env = 1;
 }
 
-// message RestartLanguageServer {
-
-// }
-// message DestroyLanguageServer {
+message ReadTextFile {
+    uint64 project_id = 1;
+    ProjectPath path = 2;
+}
 
-// }
+message ReadTextFileResponse {
+    string text = 1;
+}
 
-// message LspWorkspaceConfiguration {
+message TryExec {
+    uint64 project_id = 1;
+    uint64 worktree_id = 2;
+    LanguageServerCommand binary = 3;
+}
 
-// }
+message TryExecResponse {
+    string text = 1;
+}

crates/proto/src/proto.rs 🔗

@@ -370,6 +370,9 @@ messages!(
     (WhichCommandResponse, Foreground),
     (ShellEnv, Foreground),
     (ShellEnvResponse, Foreground),
+    (TryExec, Foreground),
+    (ReadTextFile, Foreground),
+    (ReadTextFileResponse, Foreground)
 );
 
 request_messages!(
@@ -495,7 +498,9 @@ request_messages!(
     (AddWorktree, AddWorktreeResponse),
     (CreateLanguageServer, Ack),
     (WhichCommand, WhichCommandResponse),
-    (ShellEnv, ShellEnvResponse)
+    (ShellEnv, ShellEnvResponse),
+    (ReadTextFile, ReadTextFileResponse),
+    (TryExec, Ack),
 );
 
 entity_messages!(
@@ -571,7 +576,9 @@ entity_messages!(
     UpdateUserSettings,
     CreateLanguageServer,
     WhichCommand,
-    ShellEnv
+    ShellEnv,
+    TryExec,
+    ReadTextFile
 );
 
 entity_messages!(

crates/remote/src/ssh_session.rs 🔗

@@ -15,7 +15,7 @@ use gpui::{AppContext, AsyncAppContext, Model, SemanticVersion};
 use parking_lot::Mutex;
 use rpc::{
     proto::{self, build_typed_envelope, Envelope, EnvelopedMessage, PeerId, RequestMessage},
-    EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet,
+    EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet, RpcError,
 };
 use smol::{
     fs,
@@ -157,8 +157,9 @@ impl SshSession {
 
         let mut remote_server_child = socket
             .ssh_command(format!(
-                "RUST_LOG={} {:?} run",
+                "RUST_LOG={} RUST_BACKTRACE={} {:?} run",
                 std::env::var("RUST_LOG").unwrap_or_default(),
+                std::env::var("RUST_BACKTRACE").unwrap_or_default(),
                 remote_binary_path,
             ))
             .spawn()
@@ -349,7 +350,7 @@ impl SshSession {
                                 }
                                 Err(error) => {
                                     log::error!(
-                                        "error handling message. type:{type_name}, error:{error:?}",
+                                        "error handling message. type:{type_name}, error:{error}",
                                     );
                                 }
                             }
@@ -371,7 +372,7 @@ impl SshSession {
         payload: T,
     ) -> impl 'static + Future<Output = Result<T::Response>> {
         log::debug!("ssh request start. name:{}", T::NAME);
-        let response = self.request_dynamic(payload.into_envelope(0, None, None), "");
+        let response = self.request_dynamic(payload.into_envelope(0, None, None), T::NAME);
         async move {
             let response = response.await?;
             log::debug!("ssh request finish. name:{}", T::NAME);
@@ -388,7 +389,7 @@ impl SshSession {
     pub fn request_dynamic(
         &self,
         mut envelope: proto::Envelope,
-        _request_type: &'static str,
+        type_name: &'static str,
     ) -> impl 'static + Future<Output = Result<proto::Envelope>> {
         envelope.id = self.next_message_id.fetch_add(1, SeqCst);
         let (tx, rx) = oneshot::channel();
@@ -396,7 +397,13 @@ impl SshSession {
         response_channels_lock.insert(MessageId(envelope.id), tx);
         drop(response_channels_lock);
         self.outgoing_tx.unbounded_send(envelope).ok();
-        async move { Ok(rx.await.context("connection lost")?.0) }
+        async move {
+            let response = rx.await.context("connection lost")?.0;
+            if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
+                return Err(RpcError::from_proto(error, type_name));
+            }
+            Ok(response)
+        }
     }
 
     pub fn send_dynamic(&self, mut envelope: proto::Envelope) -> Result<()> {

crates/remote_server/src/headless_project.rs 🔗

@@ -107,6 +107,8 @@ impl HeadlessProject {
         client.add_model_request_handler(LspStore::handle_create_language_server);
         client.add_model_request_handler(LspStore::handle_which_command);
         client.add_model_request_handler(LspStore::handle_shell_env);
+        client.add_model_request_handler(LspStore::handle_try_exec);
+        client.add_model_request_handler(LspStore::handle_read_text_file);
 
         BufferStore::init(&client);
         WorktreeStore::init(&client);

crates/remote_server/src/main.rs 🔗

@@ -24,7 +24,6 @@ fn main() {
 
 #[cfg(not(windows))]
 fn main() {
-    env::set_var("RUST_BACKTRACE", "1");
     env_logger::builder()
         .format(|buf, record| {
             serde_json::to_writer(&mut *buf, &LogRecord::new(record))?;