1111// SPDX-License-Identifier: Apache-2.0
1212//
1313//===----------------------------------------------------------------------===//
14+ import * as fs from "fs" ;
1415import * as vscode from "vscode" ;
1516import * as winston from "winston" ;
1617
1718import configuration from "../configuration" ;
18- import { IS_RUNNING_UNDER_TEST } from "../utilities/utilities" ;
19+ import { IS_RUNNING_IN_DEVELOPMENT_MODE , IS_RUNNING_UNDER_TEST } from "../utilities/utilities" ;
1920import { OutputChannelTransport } from "./OutputChannelTransport" ;
2021import { RollingLog } from "./RollingLog" ;
2122import { RollingLogTransport } from "./RollingLogTransport" ;
@@ -31,6 +32,8 @@ export class SwiftLogger implements vscode.Disposable {
3132 private logger : winston . Logger ;
3233 protected rollingLog : RollingLog ;
3334 protected outputChannel : vscode . OutputChannel ;
35+ private fileTransportReady = false ;
36+ private pendingLogs : Array < { level : string ; message : string ; meta ?: LoggerMeta } > = [ ] ;
3437
3538 constructor (
3639 public readonly name : string ,
@@ -42,16 +45,19 @@ export class SwiftLogger implements vscode.Disposable {
4245 const ouptutChannelTransport = new OutputChannelTransport ( this . outputChannel ) ;
4346 ouptutChannelTransport . level = this . outputChannelLevel ;
4447 const rollingLogTransport = new RollingLogTransport ( this . rollingLog ) ;
48+
49+ // Create logger with minimal transports initially for faster startup
50+ const initialTransports = [
51+ ouptutChannelTransport ,
52+ // Only want to capture the rolling log in memory when testing
53+ ...( IS_RUNNING_UNDER_TEST ? [ rollingLogTransport ] : [ ] ) ,
54+ ...( IS_RUNNING_IN_DEVELOPMENT_MODE
55+ ? [ new winston . transports . Console ( { level : "debug" } ) ]
56+ : [ ] ) ,
57+ ] ;
58+
4559 this . logger = winston . createLogger ( {
46- transports : [
47- new winston . transports . File ( {
48- filename : this . logFilePath ,
49- level : "debug" , // File logging at the 'debug' level always
50- } ) ,
51- ouptutChannelTransport ,
52- // Only want to capture the rolling log in memory when testing
53- ...( IS_RUNNING_UNDER_TEST ? [ rollingLogTransport ] : [ ] ) ,
54- ] ,
60+ transports : initialTransports ,
5561 format : winston . format . combine (
5662 winston . format . errors ( { stack : true } ) ,
5763 winston . format . timestamp ( { format : "YYYY-MM-DD HH:mm:ss.SSS" } ) , // This is the format of `vscode.LogOutputChannel`
@@ -61,6 +67,43 @@ export class SwiftLogger implements vscode.Disposable {
6167 winston . format . colorize ( )
6268 ) ,
6369 } ) ;
70+
71+ // Add file transport asynchronously to avoid blocking startup
72+ setTimeout ( ( ) => {
73+ try {
74+ const fileTransport = new winston . transports . File ( {
75+ filename : this . logFilePath ,
76+ level : "debug" , // File logging at the 'debug' level always
77+ } ) ;
78+
79+ // Add the file transport to the main logger
80+ this . logger . add ( fileTransport ) ;
81+ this . fileTransportReady = true ;
82+
83+ // Write buffered logs directly to the file using Node.js fs
84+ if ( this . pendingLogs . length > 0 ) {
85+ const logEntries =
86+ this . pendingLogs
87+ . map ( ( { level, message } ) => {
88+ const timestamp = new Date ( )
89+ . toISOString ( )
90+ . replace ( "T" , " " )
91+ . replace ( "Z" , "" ) ;
92+ return `${ timestamp } [${ level } ] ${ message } ` ;
93+ } )
94+ . join ( "\n" ) + "\n" ;
95+
96+ fs . appendFileSync ( this . logFilePath , logEntries ) ;
97+ }
98+
99+ this . pendingLogs = [ ] ; // Clear the buffer
100+ } catch ( error ) {
101+ // If file transport fails, continue with output channel only
102+ this . logger . warn ( `Failed to initialize file logging: ${ error } ` ) ;
103+ this . fileTransportReady = true ; // Mark as ready even if failed to stop buffering
104+ this . pendingLogs = [ ] ; // Clear the buffer
105+ }
106+ } , 0 ) ;
64107 this . disposables . push (
65108 {
66109 dispose : ( ) => {
@@ -78,30 +121,50 @@ export class SwiftLogger implements vscode.Disposable {
78121 e . affectsConfiguration ( "swift.outputChannelLogLevel" ) ||
79122 e . affectsConfiguration ( "swift.diagnostics" )
80123 ) {
124+ // Clear cache when configuration changes
125+ this . _cachedOutputChannelLevel = undefined ;
81126 ouptutChannelTransport . level = this . outputChannelLevel ;
82127 }
83128 } )
84129 ) ;
85130 }
86131
87132 debug ( message : LoggerMeta , label ?: string , options ?: LogMessageOptions ) {
88- this . logger . debug ( this . normalizeMessage ( message , label ) , options ) ;
133+ const normalizedMessage = this . normalizeMessage ( message , label ) ;
134+ this . logWithBuffer ( "debug" , normalizedMessage , options ) ;
89135 }
90136
91137 info ( message : LoggerMeta , label ?: string , options ?: LogMessageOptions ) {
92- this . logger . info ( this . normalizeMessage ( message , label ) , options ) ;
138+ const normalizedMessage = this . normalizeMessage ( message , label ) ;
139+ this . logWithBuffer ( "info" , normalizedMessage , options ) ;
93140 }
94141
95142 warn ( message : LoggerMeta , label ?: string , options ?: LogMessageOptions ) {
96- this . logger . warn ( this . normalizeMessage ( message , label ) , options ) ;
143+ const normalizedMessage = this . normalizeMessage ( message , label ) ;
144+ this . logWithBuffer ( "warn" , normalizedMessage , options ) ;
97145 }
98146
99147 error ( message : LoggerMeta , label ?: string , options ?: LogMessageOptions ) {
100148 if ( message instanceof Error ) {
101- this . logger . error ( message ) ;
149+ this . logWithBuffer ( "error" , message ) ;
102150 return ;
103151 }
104- this . logger . error ( this . normalizeMessage ( message , label ) , options ) ;
152+ const normalizedMessage = this . normalizeMessage ( message , label ) ;
153+ this . logWithBuffer ( "error" , normalizedMessage , options ) ;
154+ }
155+
156+ private logWithBuffer ( level : string , message : string | Error , meta ?: LoggerMeta ) {
157+ // Always log to current transports (output channel, console, etc.)
158+ if ( message instanceof Error ) {
159+ this . logger . log ( level , message ) ;
160+ } else {
161+ this . logger . log ( level , message , meta ) ;
162+ }
163+
164+ // If file transport isn't ready yet, buffer the log for replay
165+ if ( ! this . fileTransportReady ) {
166+ this . pendingLogs . push ( { level, message : message . toString ( ) , meta } ) ;
167+ }
105168 }
106169
107170 get logs ( ) : string [ ] {
@@ -130,17 +193,25 @@ export class SwiftLogger implements vscode.Disposable {
130193 return fullMessage ;
131194 }
132195
196+ private _cachedOutputChannelLevel : string | undefined ;
197+
133198 private get outputChannelLevel ( ) : string {
134- const info = vscode . workspace . getConfiguration ( "swift" ) . inspect ( "outputChannelLogLevel" ) ;
135- // If the user has explicitly set `outputChannelLogLevel` then use it, otherwise
136- // check the deprecated `diagnostics` property
137- if ( info ?. globalValue || info ?. workspaceValue || info ?. workspaceFolderValue ) {
138- return configuration . outputChannelLogLevel ;
139- } else if ( configuration . diagnostics ) {
140- return "debug" ;
141- } else {
142- return configuration . outputChannelLogLevel ;
199+ // Cache the configuration lookup to avoid repeated expensive calls during initialization
200+ if ( this . _cachedOutputChannelLevel === undefined ) {
201+ const info = vscode . workspace
202+ . getConfiguration ( "swift" )
203+ . inspect ( "outputChannelLogLevel" ) ;
204+ // If the user has explicitly set `outputChannelLogLevel` then use it, otherwise
205+ // check the deprecated `diagnostics` property
206+ if ( info ?. globalValue || info ?. workspaceValue || info ?. workspaceFolderValue ) {
207+ this . _cachedOutputChannelLevel = configuration . outputChannelLogLevel ;
208+ } else if ( configuration . diagnostics ) {
209+ this . _cachedOutputChannelLevel = "debug" ;
210+ } else {
211+ this . _cachedOutputChannelLevel = configuration . outputChannelLogLevel ;
212+ }
143213 }
214+ return this . _cachedOutputChannelLevel ;
144215 }
145216
146217 dispose ( ) {
0 commit comments