11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
3+ #nullable enable
34
5+ using System ;
6+ using System . Collections . Generic ;
7+ using System . Linq ;
8+ using System . Reactive . Disposables ;
49using Microsoft . Extensions . DependencyInjection ;
510using Microsoft . Extensions . Logging ;
611using Microsoft . Extensions . Options ;
12+ using Newtonsoft . Json ;
13+ using OmniSharp . Extensions . LanguageServer . Protocol . Client ;
14+ using OmniSharp . Extensions . LanguageServer . Protocol . Models ;
15+ using OmniSharp . Extensions . LanguageServer . Protocol . Server ;
16+ using OmniSharp . Extensions . LanguageServer . Protocol . Window ;
717
818namespace Microsoft . PowerShell . EditorServices . Logging ;
9- internal class DynamicLogLevelOptions (
10- LogLevel initialLevel ,
11- IOptionsMonitor < LoggerFilterOptions > optionsMonitor ) : IConfigureOptions < LoggerFilterOptions >
19+
20+ internal class LanguageServerLogger ( ILanguageServerFacade responseRouter , string categoryName ) : ILogger
1221{
13- private LogLevel _currentLevel = initialLevel ;
14- private readonly IOptionsMonitor < LoggerFilterOptions > _optionsMonitor = optionsMonitor ;
22+ public IDisposable ? BeginScope < TState > ( TState state ) where TState : notnull => Disposable . Empty ;
23+ public bool IsEnabled ( LogLevel logLevel ) => true ;
1524
16- public void Configure ( LoggerFilterOptions options ) => options . MinLevel = _currentLevel ;
25+ public void Log < TState > (
26+ LogLevel logLevel , EventId eventId , TState state , Exception ? exception ,
27+ Func < TState , Exception ? , string > formatter
28+ )
29+ {
30+ // Any Omnisharp or trace logs are directly LSP protocol related and we send them to the trace channel
31+ // TODO: Dynamically adjust if SetTrace is reported
32+ if ( categoryName . StartsWith ( "OmniSharp" ) || logLevel == LogLevel . Trace )
33+ {
34+ // Everything with omnisharp goes directly to trace
35+ string eventMessage = string . Empty ;
36+ string exceptionName = exception ? . GetType ( ) . Name ?? string . Empty ;
37+ if ( eventId . Name is not null )
38+ {
39+ eventMessage = eventId . Id == 0 ? eventId . Name : $ "{ eventId . Name } [{ eventId . Id } ] ";
40+ }
1741
18- public void SetLogLevel ( LogLevel level )
42+ LogTraceParams trace = new ( )
43+ {
44+ Message = categoryName + ": " + eventMessage + exceptionName ,
45+ Verbose = formatter ( state , exception )
46+ } ;
47+ responseRouter . Client . LogTrace ( trace ) ;
48+ }
49+ else if ( TryGetMessageType ( logLevel , out MessageType messageType ) )
50+ {
51+ LogMessageParams logMessage = new ( )
52+ {
53+ Type = messageType ,
54+ // TODO: Add Critical and Debug delineations
55+ Message = categoryName + ": " + formatter ( state , exception ) +
56+ ( exception != null ? " - " + exception : "" ) + " | " +
57+ //Hopefully this isn't too expensive in the long run
58+ FormatState ( state , exception )
59+ } ;
60+ responseRouter . Window . Log ( logMessage ) ;
61+ }
62+ }
63+
64+
65+ private static string FormatState < TState > ( TState state , Exception ? exception )
1966 {
20- _currentLevel = level ;
21- // Trigger reload of options to apply new log level
22- _optionsMonitor . CurrentValue . MinLevel = level ;
67+ return state switch
68+ {
69+ IEnumerable < KeyValuePair < string , object > > dict => string . Join ( " " , dict . Where ( z => z . Key != "{OriginalFormat}" ) . Select ( z => $ "{ z . Key } ='{ z . Value } '") ) ,
70+ _ => JsonConvert . SerializeObject ( state ) . Replace ( "\" " , "'" )
71+ } ;
2372 }
73+
74+ private static bool TryGetMessageType ( LogLevel logLevel , out MessageType messageType )
75+ {
76+ switch ( logLevel )
77+ {
78+ case LogLevel . Critical :
79+ case LogLevel . Error :
80+ messageType = MessageType . Error ;
81+ return true ;
82+ case LogLevel . Warning :
83+ messageType = MessageType . Warning ;
84+ return true ;
85+ case LogLevel . Information :
86+ messageType = MessageType . Info ;
87+ return true ;
88+ case LogLevel . Debug :
89+ case LogLevel . Trace :
90+ messageType = MessageType . Log ;
91+ return true ;
92+ }
93+
94+ messageType = MessageType . Log ;
95+ return false ;
96+ }
97+ }
98+
99+ internal class LanguageServerLoggerProvider ( ILanguageServerFacade languageServer ) : ILoggerProvider
100+ {
101+ public ILogger CreateLogger ( string categoryName ) => new LanguageServerLogger ( languageServer , categoryName ) ;
102+
103+ public void Dispose ( ) { }
24104}
25105
26- public static class LoggingBuilderExtensions
106+
107+ public static class LanguageServerLoggerExtensions
27108{
109+ /// <summary>
110+ /// Adds a custom logger provider for PSES LSP, that provides more granular categorization than the default Omnisharp logger, such as separating Omnisharp and PSES messages to different channels.
111+ /// </summary>
112+ public static ILoggingBuilder AddPsesLanguageServerLogging ( this ILoggingBuilder builder )
113+ {
114+ builder . Services . AddSingleton < ILoggerProvider , LanguageServerLoggerProvider > ( ) ;
115+ return builder ;
116+ }
117+
28118 public static ILoggingBuilder AddLspClientConfigurableMinimumLevel (
29119 this ILoggingBuilder builder ,
30120 LogLevel initialLevel = LogLevel . Trace
@@ -38,7 +128,24 @@ public static ILoggingBuilder AddLspClientConfigurableMinimumLevel(
38128 } ) ;
39129 builder . Services . AddSingleton < IConfigureOptions < LoggerFilterOptions > > ( sp =>
40130 sp . GetRequiredService < DynamicLogLevelOptions > ( ) ) ;
131+
41132 return builder ;
42133 }
43134}
44135
136+ internal class DynamicLogLevelOptions (
137+ LogLevel initialLevel ,
138+ IOptionsMonitor < LoggerFilterOptions > optionsMonitor ) : IConfigureOptions < LoggerFilterOptions >
139+ {
140+ private LogLevel _currentLevel = initialLevel ;
141+ private readonly IOptionsMonitor < LoggerFilterOptions > _optionsMonitor = optionsMonitor ;
142+
143+ public void Configure ( LoggerFilterOptions options ) => options . MinLevel = _currentLevel ;
144+
145+ public void SetLogLevel ( LogLevel level )
146+ {
147+ _currentLevel = level ;
148+ // Trigger reload of options to apply new log level
149+ _optionsMonitor . CurrentValue . MinLevel = level ;
150+ }
151+ }
0 commit comments