@@ -37,6 +37,21 @@ const OUTPUT_CAPTURE_CHARS = readPositiveInt(
3737) ;
3838const DEFAULT_PORT = 19000 + Math . floor ( Math . random ( ) * 1000 ) ;
3939const LOG_SCAN_CHUNK_BYTES = 64 * 1024 ;
40+ const LOG_SCAN_MAX_LINE_CHARS = 16 * 1024 ;
41+ const LOG_TAIL_BYTES = 256 * 1024 ;
42+ const ERROR_LOG_DENY_PATTERNS = [
43+ / \b u n c a u g h t e x c e p t i o n \b / iu,
44+ / \b u n h a n d l e d r e j e c t i o n \b / iu,
45+ / \b f a t a l \b / iu,
46+ / \b p a n i c \b / iu,
47+ / \b l e v e l [ " ' ] ? \s * : \s * [ " ' ] e r r o r [ " ' ] / iu,
48+ / \[ (?: e r r o r | E R R O R ) \] / u,
49+ ] ;
50+ const ERROR_LOG_ALLOW_PATTERNS = [
51+ / 0 e r r o r s ? / iu,
52+ / e x p e c t e d n o d i a g n o s t i c s e r r o r s ? / iu,
53+ / d i a g n o s t i c s e r r o r s ? : \s * $ / iu,
54+ ] ;
4055
4156let callGatewayModulePromise ;
4257
@@ -1138,37 +1153,109 @@ export function assertResourceCeiling(sample) {
11381153 }
11391154}
11401155
1156+ export function findErrorLogFindings ( logPath ) {
1157+ if ( ! fs . existsSync ( logPath ) ) {
1158+ return [ ] ;
1159+ }
1160+ const scanBytes = fs . statSync ( logPath ) . size ;
1161+
1162+ const findings = [ ] ;
1163+ let currentLine = "" ;
1164+ let currentLineNumber = 1 ;
1165+ let currentLineHasFinding = false ;
1166+ let currentLineTruncated = false ;
1167+ const recordLine = ( lineNumber , line ) => {
1168+ if ( currentLineHasFinding ) {
1169+ return ;
1170+ }
1171+ if (
1172+ ERROR_LOG_ALLOW_PATTERNS . some ( ( pattern ) => pattern . test ( line ) ) ||
1173+ ! ERROR_LOG_DENY_PATTERNS . some ( ( pattern ) => pattern . test ( line ) )
1174+ ) {
1175+ return ;
1176+ }
1177+ currentLineHasFinding = true ;
1178+ findings . push ( { line, lineNumber } ) ;
1179+ if ( findings . length > 20 ) {
1180+ findings . shift ( ) ;
1181+ }
1182+ } ;
1183+ const inspectCurrentLine = ( ) => {
1184+ const normalizedLine = currentLine . replace ( / \r $ / u, "" ) ;
1185+ const line = currentLineTruncated ? `[truncated] ${ normalizedLine } ` : normalizedLine ;
1186+ recordLine ( currentLineNumber , line ) ;
1187+ } ;
1188+ const appendLineFragment = ( fragment ) => {
1189+ currentLine += fragment ;
1190+ if ( currentLine . length <= LOG_SCAN_MAX_LINE_CHARS ) {
1191+ return ;
1192+ }
1193+ inspectCurrentLine ( ) ;
1194+ currentLine = currentLine . slice ( - LOG_SCAN_MAX_LINE_CHARS ) ;
1195+ currentLineTruncated = true ;
1196+ } ;
1197+ const finishLine = ( ) => {
1198+ inspectCurrentLine ( ) ;
1199+ currentLine = "" ;
1200+ currentLineNumber += 1 ;
1201+ currentLineHasFinding = false ;
1202+ currentLineTruncated = false ;
1203+ } ;
1204+
1205+ const fd = fs . openSync ( logPath , "r" ) ;
1206+ try {
1207+ const buffer = Buffer . allocUnsafe ( LOG_SCAN_CHUNK_BYTES ) ;
1208+ let offset = 0 ;
1209+ while ( offset < scanBytes ) {
1210+ const bytesToRead = Math . min ( buffer . length , scanBytes - offset ) ;
1211+ const bytesRead = fs . readSync ( fd , buffer , 0 , bytesToRead , offset ) ;
1212+ if ( bytesRead <= 0 ) {
1213+ break ;
1214+ }
1215+ offset += bytesRead ;
1216+ const lines = buffer . subarray ( 0 , bytesRead ) . toString ( "utf8" ) . split ( / \n / u) ;
1217+ for ( const [ index , line ] of lines . entries ( ) ) {
1218+ appendLineFragment ( line ) ;
1219+ if ( index < lines . length - 1 ) {
1220+ finishLine ( ) ;
1221+ }
1222+ }
1223+ }
1224+ } finally {
1225+ fs . closeSync ( fd ) ;
1226+ }
1227+ if ( currentLine ) {
1228+ inspectCurrentLine ( ) ;
1229+ }
1230+ return findings ;
1231+ }
1232+
11411233function assertNoErrorLogs ( logPath ) {
1142- const log = fs . existsSync ( logPath ) ? fs . readFileSync ( logPath , "utf8" ) : "" ;
1143- const deny = [
1144- / \b u n c a u g h t e x c e p t i o n \b / iu,
1145- / \b u n h a n d l e d r e j e c t i o n \b / iu,
1146- / \b f a t a l \b / iu,
1147- / \b p a n i c \b / iu,
1148- / \b l e v e l [ " ' ] ? \s * : \s * [ " ' ] e r r o r [ " ' ] / iu,
1149- / \[ (?: e r r o r | E R R O R ) \] / u,
1150- ] ;
1151- const allow = [ / 0 e r r o r s ? / iu, / e x p e c t e d n o d i a g n o s t i c s e r r o r s ? / iu, / d i a g n o s t i c s e r r o r s ? : \s * $ / iu] ;
1152- const findings = log
1153- . split ( / \r ? \n / u)
1154- . map ( ( line , index ) => ( { line, lineNumber : index + 1 } ) )
1155- . filter ( ( { line } ) => ! allow . some ( ( pattern ) => pattern . test ( line ) ) )
1156- . filter ( ( { line } ) => deny . some ( ( pattern ) => pattern . test ( line ) ) ) ;
1234+ const findings = findErrorLogFindings ( logPath ) ;
11571235 if ( findings . length > 0 ) {
11581236 throw new Error (
11591237 `unexpected error-like gateway logs:\n${ findings
1160- . slice ( - 20 )
11611238 . map ( ( { line, lineNumber } ) => `${ logPath } :${ lineNumber } : ${ line } ` )
11621239 . join ( "\n" ) } `,
11631240 ) ;
11641241 }
11651242}
11661243
1167- function tailFile ( file ) {
1244+ export function tailFile ( file , maxBytes = LOG_TAIL_BYTES ) {
11681245 if ( ! fs . existsSync ( file ) ) {
11691246 return "" ;
11701247 }
1171- return tailText ( fs . readFileSync ( file , "utf8" ) ) ;
1248+ const stat = fs . statSync ( file ) ;
1249+ const start = Math . max ( 0 , stat . size - Math . max ( 1 , maxBytes ) ) ;
1250+ const length = stat . size - start ;
1251+ const fd = fs . openSync ( file , "r" ) ;
1252+ try {
1253+ const buffer = Buffer . allocUnsafe ( length ) ;
1254+ fs . readSync ( fd , buffer , 0 , length , start ) ;
1255+ return tailText ( buffer . toString ( "utf8" ) ) ;
1256+ } finally {
1257+ fs . closeSync ( fd ) ;
1258+ }
11721259}
11731260
11741261function tailText ( text ) {
0 commit comments