1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
+ #nullable enable
3
4
5
+ using System ;
6
+ using System . Collections . Generic ;
7
+ using System . Linq ;
8
+ using System . Reactive . Disposables ;
4
9
using Microsoft . Extensions . DependencyInjection ;
5
10
using Microsoft . Extensions . Logging ;
6
11
using 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 ;
7
17
8
18
namespace 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
12
21
{
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 ;
15
24
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
+ }
17
41
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 )
19
66
{
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
+ } ;
23
72
}
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 ( ) { }
24
104
}
25
105
26
- public static class LoggingBuilderExtensions
106
+
107
+ public static class LanguageServerLoggerExtensions
27
108
{
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
+
28
118
public static ILoggingBuilder AddLspClientConfigurableMinimumLevel (
29
119
this ILoggingBuilder builder ,
30
120
LogLevel initialLevel = LogLevel . Trace
@@ -38,7 +128,24 @@ public static ILoggingBuilder AddLspClientConfigurableMinimumLevel(
38
128
} ) ;
39
129
builder . Services . AddSingleton < IConfigureOptions < LoggerFilterOptions > > ( sp =>
40
130
sp . GetRequiredService < DynamicLogLevelOptions > ( ) ) ;
131
+
41
132
return builder ;
42
133
}
43
134
}
44
135
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