log_store.rs

  1use std::{collections::VecDeque, sync::Arc};
  2
  3use collections::HashMap;
  4use futures::{StreamExt, channel::mpsc};
  5use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, Subscription, WeakEntity};
  6use lsp::{
  7    IoKind, LanguageServer, LanguageServerId, LanguageServerName, LanguageServerSelector,
  8    MessageType, TraceValue,
  9};
 10use rpc::proto;
 11use settings::WorktreeId;
 12
 13use crate::{LanguageServerLogType, LspStore, Project, ProjectItem as _};
 14
 15const SEND_LINE: &str = "\n// Send:";
 16const RECEIVE_LINE: &str = "\n// Receive:";
 17const MAX_STORED_LOG_ENTRIES: usize = 2000;
 18
 19pub fn init(on_headless_host: bool, cx: &mut App) -> Entity<LogStore> {
 20    let log_store = cx.new(|cx| LogStore::new(on_headless_host, cx));
 21    cx.set_global(GlobalLogStore(log_store.clone()));
 22    log_store
 23}
 24
 25pub struct GlobalLogStore(pub Entity<LogStore>);
 26
 27impl Global for GlobalLogStore {}
 28
 29#[derive(Debug)]
 30pub enum Event {
 31    NewServerLogEntry {
 32        id: LanguageServerId,
 33        kind: LanguageServerLogType,
 34        text: String,
 35    },
 36}
 37
 38impl EventEmitter<Event> for LogStore {}
 39
 40pub struct LogStore {
 41    on_headless_host: bool,
 42    projects: HashMap<WeakEntity<Project>, ProjectState>,
 43    pub copilot_log_subscription: Option<lsp::Subscription>,
 44    pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
 45    io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String)>,
 46}
 47
 48struct ProjectState {
 49    _subscriptions: [Subscription; 2],
 50}
 51
 52pub trait Message: AsRef<str> {
 53    type Level: Copy + std::fmt::Debug;
 54    fn should_include(&self, _: Self::Level) -> bool {
 55        true
 56    }
 57}
 58
 59#[derive(Debug)]
 60pub struct LogMessage {
 61    message: String,
 62    typ: MessageType,
 63}
 64
 65impl AsRef<str> for LogMessage {
 66    fn as_ref(&self) -> &str {
 67        &self.message
 68    }
 69}
 70
 71impl Message for LogMessage {
 72    type Level = MessageType;
 73
 74    fn should_include(&self, level: Self::Level) -> bool {
 75        match (self.typ, level) {
 76            (MessageType::ERROR, _) => true,
 77            (_, MessageType::ERROR) => false,
 78            (MessageType::WARNING, _) => true,
 79            (_, MessageType::WARNING) => false,
 80            (MessageType::INFO, _) => true,
 81            (_, MessageType::INFO) => false,
 82            _ => true,
 83        }
 84    }
 85}
 86
 87#[derive(Debug)]
 88pub struct TraceMessage {
 89    message: String,
 90    is_verbose: bool,
 91}
 92
 93impl AsRef<str> for TraceMessage {
 94    fn as_ref(&self) -> &str {
 95        &self.message
 96    }
 97}
 98
 99impl Message for TraceMessage {
100    type Level = TraceValue;
101
102    fn should_include(&self, level: Self::Level) -> bool {
103        match level {
104            TraceValue::Off => false,
105            TraceValue::Messages => !self.is_verbose,
106            TraceValue::Verbose => true,
107        }
108    }
109}
110
111#[derive(Debug)]
112pub struct RpcMessage {
113    message: String,
114}
115
116impl AsRef<str> for RpcMessage {
117    fn as_ref(&self) -> &str {
118        &self.message
119    }
120}
121
122impl Message for RpcMessage {
123    type Level = ();
124}
125
126pub struct LanguageServerState {
127    pub name: Option<LanguageServerName>,
128    pub worktree_id: Option<WorktreeId>,
129    pub kind: LanguageServerKind,
130    log_messages: VecDeque<LogMessage>,
131    trace_messages: VecDeque<TraceMessage>,
132    pub rpc_state: Option<LanguageServerRpcState>,
133    pub trace_level: TraceValue,
134    pub log_level: MessageType,
135    io_logs_subscription: Option<lsp::Subscription>,
136    pub toggled_log_kind: Option<LogKind>,
137}
138
139impl std::fmt::Debug for LanguageServerState {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        f.debug_struct("LanguageServerState")
142            .field("name", &self.name)
143            .field("worktree_id", &self.worktree_id)
144            .field("kind", &self.kind)
145            .field("log_messages", &self.log_messages)
146            .field("trace_messages", &self.trace_messages)
147            .field("rpc_state", &self.rpc_state)
148            .field("trace_level", &self.trace_level)
149            .field("log_level", &self.log_level)
150            .field("toggled_log_kind", &self.toggled_log_kind)
151            .finish_non_exhaustive()
152    }
153}
154
155#[derive(PartialEq, Clone)]
156pub enum LanguageServerKind {
157    Local { project: WeakEntity<Project> },
158    Remote { project: WeakEntity<Project> },
159    LocalSsh { lsp_store: WeakEntity<LspStore> },
160    Global,
161}
162
163impl std::fmt::Debug for LanguageServerKind {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
167            LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
168            LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"),
169            LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
170        }
171    }
172}
173
174impl LanguageServerKind {
175    pub const fn project(&self) -> Option<&WeakEntity<Project>> {
176        match self {
177            Self::Local { project } => Some(project),
178            Self::Remote { project } => Some(project),
179            Self::LocalSsh { .. } => None,
180            Self::Global { .. } => None,
181        }
182    }
183}
184
185#[derive(Debug)]
186pub struct LanguageServerRpcState {
187    pub rpc_messages: VecDeque<RpcMessage>,
188    last_message_kind: Option<MessageKind>,
189}
190
191#[derive(Debug, Copy, Clone, PartialEq, Eq)]
192enum MessageKind {
193    Send,
194    Receive,
195}
196
197#[derive(Clone, Copy, Debug, Default, PartialEq)]
198pub enum LogKind {
199    Rpc,
200    Trace,
201    #[default]
202    Logs,
203    ServerInfo,
204}
205
206impl LogKind {
207    pub const fn from_server_log_type(log_type: &LanguageServerLogType) -> Self {
208        match log_type {
209            LanguageServerLogType::Log(_) => Self::Logs,
210            LanguageServerLogType::Trace { .. } => Self::Trace,
211            LanguageServerLogType::Rpc { .. } => Self::Rpc,
212        }
213    }
214}
215
216impl LogStore {
217    pub fn new(on_headless_host: bool, cx: &mut Context<Self>) -> Self {
218        let (io_tx, mut io_rx) = mpsc::unbounded();
219
220        let log_store = Self {
221            projects: HashMap::default(),
222            language_servers: HashMap::default(),
223            copilot_log_subscription: None,
224            on_headless_host,
225            io_tx,
226        };
227        cx.spawn(async move |log_store, cx| {
228            while let Some((server_id, io_kind, message)) = io_rx.next().await {
229                if let Some(log_store) = log_store.upgrade() {
230                    log_store.update(cx, |log_store, cx| {
231                        log_store.on_io(server_id, io_kind, &message, cx);
232                    })?;
233                }
234            }
235            anyhow::Ok(())
236        })
237        .detach_and_log_err(cx);
238
239        log_store
240    }
241
242    pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
243        let weak_project = project.downgrade();
244        self.projects.insert(
245            project.downgrade(),
246            ProjectState {
247                _subscriptions: [
248                    cx.observe_release(project, move |this, _, _| {
249                        this.projects.remove(&weak_project);
250                        this.language_servers
251                            .retain(|_, state| state.kind.project() != Some(&weak_project));
252                    }),
253                    cx.subscribe(project, move |log_store, project, event, cx| {
254                        let server_kind = if project.read(cx).is_local() {
255                            LanguageServerKind::Local {
256                                project: project.downgrade(),
257                            }
258                        } else {
259                            LanguageServerKind::Remote {
260                                project: project.downgrade(),
261                            }
262                        };
263                        match event {
264                            crate::Event::LanguageServerAdded(id, name, worktree_id) => {
265                                log_store.add_language_server(
266                                    server_kind,
267                                    *id,
268                                    Some(name.clone()),
269                                    *worktree_id,
270                                    project
271                                        .read(cx)
272                                        .lsp_store()
273                                        .read(cx)
274                                        .language_server_for_id(*id),
275                                    cx,
276                                );
277                            }
278                            crate::Event::LanguageServerBufferRegistered {
279                                server_id,
280                                buffer_id,
281                                name,
282                                ..
283                            } => {
284                                let worktree_id = project
285                                    .read(cx)
286                                    .buffer_for_id(*buffer_id, cx)
287                                    .and_then(|buffer| {
288                                        Some(buffer.read(cx).project_path(cx)?.worktree_id)
289                                    });
290                                let name = name.clone().or_else(|| {
291                                    project
292                                        .read(cx)
293                                        .lsp_store()
294                                        .read(cx)
295                                        .language_server_statuses
296                                        .get(server_id)
297                                        .map(|status| status.name.clone())
298                                });
299                                log_store.add_language_server(
300                                    server_kind,
301                                    *server_id,
302                                    name,
303                                    worktree_id,
304                                    None,
305                                    cx,
306                                );
307                            }
308                            crate::Event::LanguageServerRemoved(id) => {
309                                log_store.remove_language_server(*id, cx);
310                            }
311                            crate::Event::LanguageServerLog(id, typ, message) => {
312                                log_store.add_language_server(
313                                    server_kind,
314                                    *id,
315                                    None,
316                                    None,
317                                    None,
318                                    cx,
319                                );
320                                match typ {
321                                    crate::LanguageServerLogType::Log(typ) => {
322                                        log_store.add_language_server_log(*id, *typ, message, cx);
323                                    }
324                                    crate::LanguageServerLogType::Trace { verbose_info } => {
325                                        log_store.add_language_server_trace(
326                                            *id,
327                                            message,
328                                            verbose_info.clone(),
329                                            cx,
330                                        );
331                                    }
332                                    crate::LanguageServerLogType::Rpc { received } => {
333                                        let kind = if *received {
334                                            MessageKind::Receive
335                                        } else {
336                                            MessageKind::Send
337                                        };
338                                        log_store.add_language_server_rpc(*id, kind, message, cx);
339                                    }
340                                }
341                            }
342                            crate::Event::ToggleLspLogs {
343                                server_id,
344                                enabled,
345                                toggled_log_kind,
346                            } => {
347                                if let Some(server_state) =
348                                    log_store.get_language_server_state(*server_id)
349                                {
350                                    if *enabled {
351                                        server_state.toggled_log_kind = Some(*toggled_log_kind);
352                                    } else {
353                                        server_state.toggled_log_kind = None;
354                                    }
355                                }
356                                if LogKind::Rpc == *toggled_log_kind {
357                                    if *enabled {
358                                        log_store.enable_rpc_trace_for_language_server(*server_id);
359                                    } else {
360                                        log_store.disable_rpc_trace_for_language_server(*server_id);
361                                    }
362                                }
363                            }
364                            _ => {}
365                        }
366                    }),
367                ],
368            },
369        );
370    }
371
372    pub fn get_language_server_state(
373        &mut self,
374        id: LanguageServerId,
375    ) -> Option<&mut LanguageServerState> {
376        self.language_servers.get_mut(&id)
377    }
378
379    pub fn add_language_server(
380        &mut self,
381        kind: LanguageServerKind,
382        server_id: LanguageServerId,
383        name: Option<LanguageServerName>,
384        worktree_id: Option<WorktreeId>,
385        server: Option<Arc<LanguageServer>>,
386        cx: &mut Context<Self>,
387    ) -> Option<&mut LanguageServerState> {
388        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
389            cx.notify();
390            LanguageServerState {
391                name: None,
392                worktree_id: None,
393                kind,
394                rpc_state: None,
395                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
396                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
397                trace_level: TraceValue::Off,
398                log_level: MessageType::LOG,
399                io_logs_subscription: None,
400                toggled_log_kind: None,
401            }
402        });
403
404        if let Some(name) = name {
405            server_state.name = Some(name);
406        }
407        if let Some(worktree_id) = worktree_id {
408            server_state.worktree_id = Some(worktree_id);
409        }
410
411        if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
412            let io_tx = self.io_tx.clone();
413            let server_id = server.server_id();
414            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
415                io_tx
416                    .unbounded_send((server_id, io_kind, message.to_string()))
417                    .ok();
418            }));
419        }
420
421        Some(server_state)
422    }
423
424    pub fn add_language_server_log(
425        &mut self,
426        id: LanguageServerId,
427        typ: MessageType,
428        message: &str,
429        cx: &mut Context<Self>,
430    ) -> Option<()> {
431        let store_logs = !self.on_headless_host;
432        let language_server_state = self.get_language_server_state(id)?;
433
434        let log_lines = &mut language_server_state.log_messages;
435        let message = message.trim_end().to_string();
436        if !store_logs {
437            // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
438            self.emit_event(
439                Event::NewServerLogEntry {
440                    id,
441                    kind: LanguageServerLogType::Log(typ),
442                    text: message,
443                },
444                cx,
445            );
446        } else if let Some(new_message) = Self::push_new_message(
447            log_lines,
448            LogMessage { message, typ },
449            language_server_state.log_level,
450        ) {
451            self.emit_event(
452                Event::NewServerLogEntry {
453                    id,
454                    kind: LanguageServerLogType::Log(typ),
455                    text: new_message,
456                },
457                cx,
458            );
459        }
460        Some(())
461    }
462
463    fn add_language_server_trace(
464        &mut self,
465        id: LanguageServerId,
466        message: &str,
467        verbose_info: Option<String>,
468        cx: &mut Context<Self>,
469    ) -> Option<()> {
470        let store_logs = !self.on_headless_host;
471        let language_server_state = self.get_language_server_state(id)?;
472
473        let log_lines = &mut language_server_state.trace_messages;
474        if !store_logs {
475            // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
476            self.emit_event(
477                Event::NewServerLogEntry {
478                    id,
479                    kind: LanguageServerLogType::Trace { verbose_info },
480                    text: message.trim().to_string(),
481                },
482                cx,
483            );
484        } else if let Some(new_message) = Self::push_new_message(
485            log_lines,
486            TraceMessage {
487                message: message.trim().to_string(),
488                is_verbose: false,
489            },
490            TraceValue::Messages,
491        ) {
492            if let Some(verbose_message) = verbose_info.as_ref() {
493                Self::push_new_message(
494                    log_lines,
495                    TraceMessage {
496                        message: verbose_message.clone(),
497                        is_verbose: true,
498                    },
499                    TraceValue::Verbose,
500                );
501            }
502            self.emit_event(
503                Event::NewServerLogEntry {
504                    id,
505                    kind: LanguageServerLogType::Trace { verbose_info },
506                    text: new_message,
507                },
508                cx,
509            );
510        }
511        Some(())
512    }
513
514    fn push_new_message<T: Message>(
515        log_lines: &mut VecDeque<T>,
516        message: T,
517        current_severity: <T as Message>::Level,
518    ) -> Option<String> {
519        while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
520            log_lines.pop_front();
521        }
522        let visible = message.should_include(current_severity);
523
524        let visible_message = visible.then(|| message.as_ref().to_string());
525        log_lines.push_back(message);
526        visible_message
527    }
528
529    fn add_language_server_rpc(
530        &mut self,
531        language_server_id: LanguageServerId,
532        kind: MessageKind,
533        message: &str,
534        cx: &mut Context<'_, Self>,
535    ) {
536        let store_logs = !self.on_headless_host;
537        let Some(state) = self
538            .get_language_server_state(language_server_id)
539            .and_then(|state| state.rpc_state.as_mut())
540        else {
541            return;
542        };
543
544        let received = kind == MessageKind::Receive;
545        let rpc_log_lines = &mut state.rpc_messages;
546        if state.last_message_kind != Some(kind) {
547            while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
548                rpc_log_lines.pop_front();
549            }
550            let line_before_message = match kind {
551                MessageKind::Send => SEND_LINE,
552                MessageKind::Receive => RECEIVE_LINE,
553            };
554            if store_logs {
555                rpc_log_lines.push_back(RpcMessage {
556                    message: line_before_message.to_string(),
557                });
558            }
559            // Do not send a synthetic message over the wire, it will be derived from the actual RPC message
560            cx.emit(Event::NewServerLogEntry {
561                id: language_server_id,
562                kind: LanguageServerLogType::Rpc { received },
563                text: line_before_message.to_string(),
564            });
565        }
566
567        while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
568            rpc_log_lines.pop_front();
569        }
570
571        if store_logs {
572            rpc_log_lines.push_back(RpcMessage {
573                message: message.trim().to_owned(),
574            });
575        }
576
577        self.emit_event(
578            Event::NewServerLogEntry {
579                id: language_server_id,
580                kind: LanguageServerLogType::Rpc { received },
581                text: message.to_owned(),
582            },
583            cx,
584        );
585    }
586
587    pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
588        self.language_servers.remove(&id);
589        cx.notify();
590    }
591
592    pub fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
593        Some(&self.language_servers.get(&server_id)?.log_messages)
594    }
595
596    pub fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
597        Some(&self.language_servers.get(&server_id)?.trace_messages)
598    }
599
600    pub fn server_ids_for_project<'a>(
601        &'a self,
602        lookup_project: &'a WeakEntity<Project>,
603    ) -> impl Iterator<Item = LanguageServerId> + 'a {
604        self.language_servers
605            .iter()
606            .filter_map(move |(id, state)| match &state.kind {
607                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
608                    if project == lookup_project {
609                        Some(*id)
610                    } else {
611                        None
612                    }
613                }
614                LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id),
615            })
616    }
617
618    pub fn enable_rpc_trace_for_language_server(
619        &mut self,
620        server_id: LanguageServerId,
621    ) -> Option<&mut LanguageServerRpcState> {
622        let rpc_state = self
623            .language_servers
624            .get_mut(&server_id)?
625            .rpc_state
626            .get_or_insert_with(|| LanguageServerRpcState {
627                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
628                last_message_kind: None,
629            });
630        Some(rpc_state)
631    }
632
633    pub fn disable_rpc_trace_for_language_server(
634        &mut self,
635        server_id: LanguageServerId,
636    ) -> Option<()> {
637        self.language_servers.get_mut(&server_id)?.rpc_state.take();
638        Some(())
639    }
640
641    pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
642        match server {
643            LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
644            LanguageServerSelector::Name(name) => self
645                .language_servers
646                .iter()
647                .any(|(_, state)| state.name.as_ref() == Some(name)),
648        }
649    }
650
651    fn on_io(
652        &mut self,
653        language_server_id: LanguageServerId,
654        io_kind: IoKind,
655        message: &str,
656        cx: &mut Context<Self>,
657    ) -> Option<()> {
658        let is_received = match io_kind {
659            IoKind::StdOut => true,
660            IoKind::StdIn => false,
661            IoKind::StdErr => {
662                self.add_language_server_log(language_server_id, MessageType::LOG, message, cx);
663                return Some(());
664            }
665        };
666
667        let kind = if is_received {
668            MessageKind::Receive
669        } else {
670            MessageKind::Send
671        };
672
673        self.add_language_server_rpc(language_server_id, kind, message, cx);
674        cx.notify();
675        Some(())
676    }
677
678    fn emit_event(&mut self, e: Event, cx: &mut Context<Self>) {
679        let on_headless_host = self.on_headless_host;
680        match &e {
681            Event::NewServerLogEntry { id, kind, text } => {
682                if let Some(state) = self.get_language_server_state(*id) {
683                    let downstream_client = match &state.kind {
684                        LanguageServerKind::Remote { project }
685                        | LanguageServerKind::Local { project } => project
686                            .upgrade()
687                            .map(|project| project.read(cx).lsp_store()),
688                        LanguageServerKind::LocalSsh { lsp_store } => lsp_store.upgrade(),
689                        LanguageServerKind::Global => None,
690                    }
691                    .and_then(|lsp_store| lsp_store.read(cx).downstream_client());
692                    if let Some((client, project_id)) = downstream_client {
693                        if on_headless_host
694                            || Some(LogKind::from_server_log_type(kind)) == state.toggled_log_kind
695                        {
696                            client
697                                .send(proto::LanguageServerLog {
698                                    project_id,
699                                    language_server_id: id.to_proto(),
700                                    message: text.clone(),
701                                    log_type: Some(kind.to_proto()),
702                                })
703                                .ok();
704                        }
705                    }
706                }
707            }
708        }
709
710        cx.emit(e);
711    }
712}