1919import * as process from 'process' ;
2020import * as vscode from 'vscode' ;
2121
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' ;
2326import { ALSClientFeatures } from './alsClientFeatures' ;
2427import { alsCommandExecutor } from './alsExecuteCommand' ;
2528import { ContextClients } from './clients' ;
@@ -28,39 +31,116 @@ import { initializeDebugging } from './debugConfigProvider';
2831import { initializeTestView } from './gnattest' ;
2932import {
3033 assertSupportedEnvironments ,
34+ getCustomEnvSettingName ,
3135 getEvaluatedCustomEnv ,
3236 setCustomEnvironment ,
37+ startedInDebugMode ,
3338} from './helpers' ;
34- import { ExecuteCommandRequest } from 'vscode-languageclient/node' ;
3539
3640const ADA_CONTEXT = 'ADA_PROJECT_CONTEXT' ;
3741export 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+ }
39106
40107export 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 ) ;
46124
47125 // Log the environment that the extension (and all VS Code) will be using
48126 const customEnv = getEvaluatedCustomEnv ( ) ;
49127
50128 if ( customEnv && Object . keys ( customEnv ) . length > 0 ) {
51- mainLogChannel . appendLine ( 'Setting environment variables:' ) ;
129+ logger . info ( 'Setting environment variables:' ) ;
52130 for ( const varName in customEnv ) {
53131 const varValue : string = customEnv [ varName ] ;
54- mainLogChannel . appendLine ( `${ varName } =${ varValue } ` ) ;
132+ logger . info ( `${ varName } =${ varValue } ` ) ;
55133 }
134+ } else {
135+ logger . debug ( 'No custom environment variables set in %s' , getCustomEnvSettingName ( ) ) ;
56136 }
57137
58138 // Set the custom environment into the current node process. This must be
59139 // done before calling assertSupportedEnvironments in order to set the ALS
60140 // environment variable if provided.
61141 setCustomEnvironment ( ) ;
62142
63- assertSupportedEnvironments ( mainLogChannel ) ;
143+ assertSupportedEnvironments ( logger ) ;
64144
65145 // Create the Ada and GPR clients.
66146 contextClients = new ContextClients ( context ) ;
@@ -72,6 +152,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
72152 contextClients . adaClient . registerFeature ( new ALSClientFeatures ( ) ) ;
73153
74154 contextClients . start ( ) ;
155+
75156 context . subscriptions . push ( contextClients ) ;
76157
77158 context . subscriptions . push (
@@ -91,8 +172,56 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
91172 initializeDebugging ( context ) ;
92173
93174 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' ) ;
94182
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+ }
96225}
97226
98227export async function deactivate ( ) {
0 commit comments