19
19
import * as process from 'process' ;
20
20
import * as vscode from 'vscode' ;
21
21
22
- import { LanguageClient , Middleware } from 'vscode-languageclient/node' ;
22
+ import { MESSAGE } from 'triple-beam' ;
23
+ import { ExecuteCommandRequest , LanguageClient , Middleware } from 'vscode-languageclient/node' ;
24
+ import winston , { format , transports } from 'winston' ;
25
+ import Transport from 'winston-transport' ;
23
26
import { ALSClientFeatures } from './alsClientFeatures' ;
24
27
import { alsCommandExecutor } from './alsExecuteCommand' ;
25
28
import { ContextClients } from './clients' ;
@@ -28,39 +31,116 @@ import { initializeDebugging } from './debugConfigProvider';
28
31
import { initializeTestView } from './gnattest' ;
29
32
import {
30
33
assertSupportedEnvironments ,
34
+ getCustomEnvSettingName ,
31
35
getEvaluatedCustomEnv ,
32
36
setCustomEnvironment ,
37
+ startedInDebugMode ,
33
38
} from './helpers' ;
34
- import { ExecuteCommandRequest } from 'vscode-languageclient/node' ;
35
39
36
40
const ADA_CONTEXT = 'ADA_PROJECT_CONTEXT' ;
37
41
export let contextClients : ContextClients ;
38
- export let mainLogChannel : vscode . OutputChannel ;
42
+
43
+ /**
44
+ * The `vscode.OutputChannel` that hosts messages from the extension. This is
45
+ * different from the channels associated with the language servers.
46
+ */
47
+ export let mainOutputChannel : vscode . OutputChannel ;
48
+
49
+ /**
50
+ * A Winston logger object associated with the main output channel that allows
51
+ * logging messages in a uniform format.
52
+ */
53
+ export const logger : winston . Logger = winston . createLogger ( {
54
+ format : format . combine (
55
+ // Include a timestamp
56
+ format . timestamp ( {
57
+ format : 'YYYY-MM-DD HH:mm:ss.SSS' ,
58
+ } ) ,
59
+ // Annotate log messages with a label
60
+ format . label ( { label : 'Ada Extension' } ) ,
61
+ // Pad message levels for alignment
62
+ format . padLevels ( ) ,
63
+ // Include a stack trace for logged Error objects
64
+ format . errors ( { stack : true } ) ,
65
+ // Perform printf-style %s,%d replacements
66
+ format . splat ( )
67
+ ) ,
68
+ } ) ;
69
+
70
+ /**
71
+ * This is a custom Winston transport that forwards logged messages onto a given
72
+ * `vscode.OutputChannel`.
73
+ */
74
+ class VSCodeOutputChannelTransport extends Transport {
75
+ outputChannel : vscode . OutputChannel ;
76
+
77
+ constructor ( channel : vscode . OutputChannel , opts ?: Transport . TransportStreamOptions ) {
78
+ super ( opts ) ;
79
+ this . outputChannel = channel ;
80
+ }
81
+
82
+ /**
83
+ * This implementation is based on the Winston documentation for custom transports.
84
+ *
85
+ * @param info - the log entry info object
86
+ * @param next - a callback
87
+ */
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ public log ( info : any , next : ( ) => void ) {
90
+ setImmediate ( ( ) => {
91
+ this . emit ( 'logged' , info ) ;
92
+ } ) ;
93
+
94
+ /*
95
+ * The formatted message is stored under the 'message' symbol in the info object.
96
+ */
97
+ // eslint-disable-next-line max-len
98
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
99
+ this . outputChannel . appendLine ( info [ MESSAGE ] ) ;
100
+
101
+ if ( next ) {
102
+ next ( ) ;
103
+ }
104
+ }
105
+ }
39
106
40
107
export async function activate ( context : vscode . ExtensionContext ) : Promise < void > {
41
- // Create an output channel for the extension. There are dedicated channels
42
- // for the Ada and Gpr language servers, and this one is a general channel
43
- // for non-LSP features of the extension.
44
- mainLogChannel = vscode . window . createOutputChannel ( 'Ada Extension' ) ;
45
- mainLogChannel . appendLine ( 'Starting Ada extension' ) ;
108
+ setUpLogging ( ) ;
109
+
110
+ logger . info ( 'Starting Ada extension' ) ;
111
+
112
+ try {
113
+ await activateExtension ( context ) ;
114
+ } catch ( error ) {
115
+ logger . error ( 'Error while starting Ada extension. ' , error ) ;
116
+ throw error ;
117
+ }
118
+
119
+ logger . info ( 'Finished starting Ada extension' ) ;
120
+ }
121
+
122
+ async function activateExtension ( context : vscode . ExtensionContext ) {
123
+ assertSupportedEnvironments ( logger ) ;
46
124
47
125
// Log the environment that the extension (and all VS Code) will be using
48
126
const customEnv = getEvaluatedCustomEnv ( ) ;
49
127
50
128
if ( customEnv && Object . keys ( customEnv ) . length > 0 ) {
51
- mainLogChannel . appendLine ( 'Setting environment variables:' ) ;
129
+ logger . info ( 'Setting environment variables:' ) ;
52
130
for ( const varName in customEnv ) {
53
131
const varValue : string = customEnv [ varName ] ;
54
- mainLogChannel . appendLine ( `${ varName } =${ varValue } ` ) ;
132
+ logger . info ( `${ varName } =${ varValue } ` ) ;
55
133
}
134
+ } else {
135
+ logger . debug ( 'No custom environment variables set in %s' , getCustomEnvSettingName ( ) ) ;
56
136
}
57
137
58
138
// Set the custom environment into the current node process. This must be
59
139
// done before calling assertSupportedEnvironments in order to set the ALS
60
140
// environment variable if provided.
61
141
setCustomEnvironment ( ) ;
62
142
63
- assertSupportedEnvironments ( mainLogChannel ) ;
143
+ assertSupportedEnvironments ( logger ) ;
64
144
65
145
// Create the Ada and GPR clients.
66
146
contextClients = new ContextClients ( context ) ;
@@ -72,6 +152,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
72
152
contextClients . adaClient . registerFeature ( new ALSClientFeatures ( ) ) ;
73
153
74
154
contextClients . start ( ) ;
155
+
75
156
context . subscriptions . push ( contextClients ) ;
76
157
77
158
context . subscriptions . push (
@@ -91,8 +172,56 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
91
172
initializeDebugging ( context ) ;
92
173
93
174
registerCommands ( context , contextClients ) ;
175
+ }
176
+
177
+ function setUpLogging ( ) {
178
+ // Create an output channel for the extension. There are dedicated channels
179
+ // for the Ada and Gpr language servers, and this one is a general channel
180
+ // for non-LSP features of the extension.
181
+ mainOutputChannel = vscode . window . createOutputChannel ( 'Ada Extension' ) ;
94
182
95
- mainLogChannel . appendLine ( 'Started Ada extension' ) ;
183
+ /*
184
+ * This is a printing formatter that converts log entries to a string. It
185
+ * used both for logging to the output channel and to the console.
186
+ */
187
+ const printfFormatter = format . printf ( ( info ) => {
188
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
189
+ return `${ info . timestamp } [${ info . label } ] ${ info . level . toUpperCase ( ) } ${ info . message } ${
190
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
191
+ info . stack ?? ''
192
+ } `;
193
+ } ) ;
194
+
195
+ /*
196
+ * Add a transport to direct log messages to the main
197
+ * `vscode.OutputChannel`. Set the log level to 'info' to include 'error',
198
+ * 'warn' and 'info'. See winston documentation for log levels:
199
+ * https://github.com/winstonjs/winston#logging-levels
200
+ *
201
+ * TODO consider making the log level configurable through a verbosity
202
+ * extension setting
203
+ */
204
+ logger . add (
205
+ new VSCodeOutputChannelTransport ( mainOutputChannel , {
206
+ format : printfFormatter ,
207
+ level : 'info' ,
208
+ } )
209
+ ) ;
210
+
211
+ if ( startedInDebugMode ( ) ) {
212
+ // In debug mode, print log messages to the console with colors. Use
213
+ // level 'debug' for more verbosity.
214
+ logger . add (
215
+ new transports . Console ( {
216
+ format : format . combine (
217
+ printfFormatter ,
218
+ // Colorization must be applied after the finalizing printf formatter
219
+ format . colorize ( { all : true } )
220
+ ) ,
221
+ level : 'debug' ,
222
+ } )
223
+ ) ;
224
+ }
96
225
}
97
226
98
227
export async function deactivate ( ) {
0 commit comments