@@ -8,6 +8,7 @@ let appendLogEntry;
88let getLogEntries ;
99let clearLogEntries ;
1010let clearLogSettingsCache ;
11+ let prependConsoleTimestamp ;
1112let registerLogRoutes ;
1213let registerSettingsRoutes ;
1314let originalRead ;
@@ -28,10 +29,7 @@ ${'\u2505'.repeat(44)}
2829const DEFAULT_IGNORED_NOISE_LOGS = [
2930 '[sub-store] INFO: Surge AnyTLS Parser is activated' ,
3031 '[sub-store] ERROR: Fallback Base64 Pre-processor error: decoded line does not start with protocol' ,
31- ] ;
32- const DEFAULT_VISIBLE_PREPROCESSOR_LOGS = [
33- '[sub-store] INFO: Pre-processor [Fallback Base64 Pre-processor] activated' ,
34- '[sub-store] INFO: Pre-processor [Clash Pre-processor] activated' ,
32+ '[sub-store] INFO: [CORS] allowed origins: https://sub-store.vercel.app,http://127.0.0.1:8888 (env:SUB_STORE_CORS_ALLOWED_ORIGINS)' ,
3533] ;
3634
3735function createRouteApp ( ) {
@@ -88,6 +86,12 @@ function createResponse(routePath) {
8886 } ;
8987}
9088
89+ function loadDebugLogsModuleFresh ( ) {
90+ const modulePath = require . resolve ( '../../utils/debug-logs' ) ;
91+ delete require . cache [ modulePath ] ;
92+ return require ( '../../utils/debug-logs' ) ;
93+ }
94+
9195describe ( 'logs routes' , function ( ) {
9296 before ( async function ( ) {
9397 ( { default : $ } = require ( '@/core/app' ) ) ;
@@ -96,6 +100,7 @@ describe('logs routes', function () {
96100 getLogEntries,
97101 clearLogEntries,
98102 clearLogSettingsCache,
103+ prependConsoleTimestamp,
99104 } = require ( '@/utils/debug-logs' ) ) ;
100105 ( { default : registerLogRoutes } = require ( '@/restful/logs' ) ) ;
101106 ( { default : registerSettingsRoutes } = require ( '@/restful/settings' ) ) ;
@@ -138,6 +143,157 @@ describe('logs routes', function () {
138143 expect ( storedLogs [ 1 ] . message ) . to . equal ( '[unknown] ERROR: third' ) ;
139144 } ) ;
140145
146+ it ( 'prefixes locale timestamp in string console output arguments' , function ( ) {
147+ const args = [ 'route log' , { ok : true } ] ;
148+ const originalToLocaleString = Date . prototype . toLocaleString ;
149+
150+ try {
151+ Date . prototype . toLocaleString = ( ) => 'TEST_LOCALE_TIME' ;
152+
153+ const timestampedArgs = prependConsoleTimestamp ( args , {
154+ isNode : true ,
155+ } ) ;
156+
157+ expect ( timestampedArgs ) . to . deep . equal ( [
158+ 'TEST_LOCALE_TIME route log' ,
159+ { ok : true } ,
160+ ] ) ;
161+ expect ( args ) . to . deep . equal ( [ 'route log' , { ok : true } ] ) ;
162+ } finally {
163+ Date . prototype . toLocaleString = originalToLocaleString ;
164+ }
165+ } ) ;
166+
167+ it ( 'preserves node console format placeholders when timestamping' , function ( ) {
168+ const { format } = require ( 'util' ) ;
169+ const originalToLocaleString = Date . prototype . toLocaleString ;
170+
171+ try {
172+ Date . prototype . toLocaleString = ( ) => 'TEST_LOCALE_TIME' ;
173+
174+ const timestampedArgs = prependConsoleTimestamp (
175+ [ 'route log %s %d' , 'ok' , 1 ] ,
176+ { isNode : true } ,
177+ ) ;
178+
179+ expect ( timestampedArgs ) . to . deep . equal ( [
180+ 'TEST_LOCALE_TIME route log %s %d' ,
181+ 'ok' ,
182+ 1 ,
183+ ] ) ;
184+ expect ( format ( ...timestampedArgs ) ) . to . equal (
185+ 'TEST_LOCALE_TIME route log ok 1' ,
186+ ) ;
187+ } finally {
188+ Date . prototype . toLocaleString = originalToLocaleString ;
189+ }
190+ } ) ;
191+
192+ it ( 'does not treat timestamp percent signs as console format placeholders' , function ( ) {
193+ const { format } = require ( 'util' ) ;
194+ const originalToLocaleString = Date . prototype . toLocaleString ;
195+
196+ try {
197+ Date . prototype . toLocaleString = ( ) => 'TEST_%s_TIME' ;
198+
199+ const stringArgs = prependConsoleTimestamp ( [ 'route log %s' , 'ok' ] , {
200+ isNode : true ,
201+ } ) ;
202+ const objectArgs = prependConsoleTimestamp ( [ { ok : true } ] , {
203+ isNode : true ,
204+ } ) ;
205+
206+ expect ( format ( ...stringArgs ) ) . to . equal ( 'TEST_%s_TIME route log ok' ) ;
207+ expect ( format ( ...objectArgs ) ) . to . equal ( 'TEST_%s_TIME { ok: true }' ) ;
208+ } finally {
209+ Date . prototype . toLocaleString = originalToLocaleString ;
210+ }
211+ } ) ;
212+
213+ it ( 'keeps locale timestamp separate for non-string console output arguments' , function ( ) {
214+ const args = [ { ok : true } , 'route log' ] ;
215+ const originalToLocaleString = Date . prototype . toLocaleString ;
216+
217+ try {
218+ Date . prototype . toLocaleString = ( ) => 'TEST_LOCALE_TIME' ;
219+
220+ const timestampedArgs = prependConsoleTimestamp ( args , {
221+ isNode : true ,
222+ } ) ;
223+
224+ expect ( timestampedArgs ) . to . deep . equal ( [
225+ 'TEST_LOCALE_TIME' ,
226+ { ok : true } ,
227+ 'route log' ,
228+ ] ) ;
229+ expect ( args ) . to . deep . equal ( [ { ok : true } , 'route log' ] ) ;
230+ } finally {
231+ Date . prototype . toLocaleString = originalToLocaleString ;
232+ }
233+ } ) ;
234+
235+ it ( 'prints locale timestamp for empty node console output arguments' , function ( ) {
236+ const originalToLocaleString = Date . prototype . toLocaleString ;
237+
238+ try {
239+ Date . prototype . toLocaleString = ( ) => 'TEST_LOCALE_TIME' ;
240+
241+ expect ( prependConsoleTimestamp ( [ ] , { isNode : true } ) ) . to . deep . equal (
242+ [ 'TEST_LOCALE_TIME' ] ,
243+ ) ;
244+ } finally {
245+ Date . prototype . toLocaleString = originalToLocaleString ;
246+ }
247+ } ) ;
248+
249+ it ( 'does not prepend locale timestamp outside node runtime' , function ( ) {
250+ const args = [ 'route log' , { ok : true } ] ;
251+
252+ expect ( prependConsoleTimestamp ( args , { isNode : false } ) ) . to . deep . equal (
253+ args ,
254+ ) ;
255+ } ) ;
256+
257+ it ( 'captures console logs outside node runtime' , function ( ) {
258+ const consoleMethods = [ 'log' , 'info' , 'warn' , 'error' , 'debug' ] ;
259+ const originalConsole = Object . fromEntries (
260+ consoleMethods . map ( ( method ) => [ method , console [ method ] ] ) ,
261+ ) ;
262+ const testState = {
263+ [ SETTINGS_KEY ] : { logsMaxCount : 500 } ,
264+ [ LOGS_KEY ] : '[]' ,
265+ } ;
266+ const testApp = {
267+ env : { isNode : false } ,
268+ read ( key ) {
269+ return testState [ key ] ;
270+ } ,
271+ write ( data , key ) {
272+ testState [ key ] = data ;
273+ return true ;
274+ } ,
275+ } ;
276+ const { installConsoleLogCapture : installConsoleLogCaptureFresh } =
277+ loadDebugLogsModuleFresh ( ) ;
278+
279+ try {
280+ consoleMethods . forEach ( ( method ) => {
281+ console [ method ] = ( ) => { } ;
282+ } ) ;
283+
284+ installConsoleLogCaptureFresh ( testApp ) ;
285+ console . log ( 'route log' ) ;
286+
287+ const storedLogs = JSON . parse ( testState [ LOGS_KEY ] ) ;
288+ expect ( storedLogs ) . to . have . length ( 1 ) ;
289+ expect ( storedLogs [ 0 ] . message ) . to . equal ( '[unknown] LOG: route log' ) ;
290+ } finally {
291+ consoleMethods . forEach ( ( method ) => {
292+ console [ method ] = originalConsole [ method ] ;
293+ } ) ;
294+ }
295+ } ) ;
296+
141297 it ( 'disables persistent log cache IO when logsMaxCount is zero' , function ( ) {
142298 state [ SETTINGS_KEY ] = { logsMaxCount : 0 } ;
143299 state [ LOGS_KEY ] = JSON . stringify ( [
@@ -488,13 +644,15 @@ describe('logs routes', function () {
488644 appendLogEntry ( $ , 'info' , [ 'before change 2' ] ) ;
489645 appendLogEntry ( $ , 'info' , [ 'before change 3' ] ) ;
490646
491- await patchHandler ( { body : { logsMaxCount : 1 } } , createResponse ( '/api/settings' ) ) ;
647+ await patchHandler (
648+ { body : { logsMaxCount : 1 } } ,
649+ createResponse ( '/api/settings' ) ,
650+ ) ;
492651
493652 appendLogEntry ( $ , 'info' , [ 'after change' ] ) ;
494653
495654 const storedLogs = JSON . parse ( state [ LOGS_KEY ] ) ;
496655 expect ( storedLogs ) . to . have . length ( 1 ) ;
497656 expect ( storedLogs [ 0 ] . message ) . to . equal ( '[unknown] INFO: after change' ) ;
498657 } ) ;
499-
500658} ) ;
0 commit comments