@@ -29,9 +29,14 @@ export class LoggingService {
2929 public async getServiceLogs (
3030 service : string ,
3131 stream : Writable ,
32+ tailLines : number ,
3233 previous = false ,
3334 download = false ,
3435 isJob = false ,
36+ since ?: {
37+ start : string ;
38+ until : string ;
39+ } ,
3540 ) : Promise < void > {
3641 let archive : archiver . Archiver ;
3742
@@ -74,6 +79,7 @@ export class LoggingService {
7479 }
7580
7681 const podLogs : Promise < void > [ ] = [ ] ;
82+
7783 for ( const pod of pods ) {
7884 podLogs . push (
7985 this . getLogsForPod (
@@ -82,7 +88,8 @@ export class LoggingService {
8288 download ,
8389 previous ,
8490 archive ,
85- download ? undefined : 250 ,
91+ download ? undefined : tailLines ,
92+ since ,
8693 ) ,
8794 ) ;
8895 }
@@ -111,6 +118,40 @@ export class LoggingService {
111118 return podList . items ;
112119 }
113120
121+ private async getFirstLogTimestamp (
122+ logApi : Log ,
123+ namespace : string ,
124+ pod : V1Pod ,
125+ containerName : string ,
126+ previous : boolean ,
127+ ) {
128+ const logStream = new PassThrough ( ) ;
129+ await logApi . log ( namespace , pod . metadata . name , containerName , logStream , {
130+ previous,
131+ timestamps : true ,
132+ limitBytes : 8 * 1024 ,
133+ } ) ;
134+
135+ return await new Promise ( ( resolve ) => {
136+ logStream . on ( "data" , ( chunk ) => {
137+ for ( const line of chunk . toString ( ) . split ( "\n" ) ) {
138+ const data = line . trim ( ) ;
139+ if ( data . length === 0 ) {
140+ return ;
141+ }
142+ const log = this . parseLog ( data ) ;
143+ if ( log . timestamp ) {
144+ resolve ( new Date ( log . timestamp ) ) ;
145+ }
146+ }
147+ } ) ;
148+
149+ logStream . on ( "end" , ( ) => {
150+ resolve ( null ) ;
151+ } ) ;
152+ } ) ;
153+ }
154+
114155 private async tryGetPodLogs (
115156 logApi : Log ,
116157 namespace : string ,
@@ -120,7 +161,8 @@ export class LoggingService {
120161 stream : Writable ,
121162 previous : boolean ,
122163 download : boolean ,
123- tailLines : number ,
164+ tailLines : number = 250 ,
165+ since ?: string ,
124166 ) : Promise < void > {
125167 try {
126168 let podLogs : Awaited < ReturnType < typeof logApi . log > > ;
@@ -147,8 +189,14 @@ export class LoggingService {
147189 previous,
148190 pretty : false ,
149191 timestamps : true ,
150- follow : download === false ,
151- tailLines,
192+ tailLines : since || download ? undefined : tailLines || 250 ,
193+ ...( since
194+ ? {
195+ sinceTime : since ,
196+ }
197+ : {
198+ follow : download === false ,
199+ } ) ,
152200 } ,
153201 ) ;
154202 } catch ( error ) {
@@ -175,23 +223,67 @@ export class LoggingService {
175223 previous = false ,
176224 archive ?: archiver . Archiver ,
177225 tailLines ?: number ,
226+ since ?: {
227+ start : string ;
228+ until : string ;
229+ } ,
178230 ) {
179231 let totalAdded = 0 ;
180232 let streamEnded = false ;
181233
234+ let oldestTimestamp : Date ;
235+ const until = since ? new Date ( since . until ) : undefined ;
236+
182237 const endStream = ( ) => {
183238 if ( ! streamEnded ) {
184239 streamEnded = true ;
185240 stream . end ( ) ;
186241 }
187242 } ;
188243
244+ let totalLines = 0 ;
189245 for ( const container of pod . spec . containers ) {
190246 const logStream = new PassThrough ( ) ;
191247
192- logStream . on ( "end" , ( ) => {
193- this . logger . log ( "log stream ended" ) ;
248+ logStream . on ( "end" , async ( ) => {
194249 ++ totalAdded ;
250+
251+ if ( totalLines < tailLines ) {
252+ this . logger . log (
253+ `loading more logs from service ${ pod . metadata . name } ...` ,
254+ ) ;
255+
256+ const firstLogTimestamp = await this . getFirstLogTimestamp (
257+ logApi ,
258+ this . namespace ,
259+ pod ,
260+ container . name ,
261+ previous ,
262+ ) ;
263+
264+ const until = new Date ( oldestTimestamp . toISOString ( ) ) ;
265+ oldestTimestamp . setMinutes ( oldestTimestamp . getMinutes ( ) - 60 ) ;
266+
267+ if ( oldestTimestamp < firstLogTimestamp ) {
268+ endStream ( ) ;
269+ return ;
270+ }
271+
272+ await this . getLogsForPod (
273+ pod ,
274+ stream ,
275+ download ,
276+ previous ,
277+ archive ,
278+ tailLines - totalLines ,
279+ {
280+ start : oldestTimestamp . toISOString ( ) ,
281+ until : until . toISOString ( ) ,
282+ } ,
283+ ) ;
284+ return ;
285+ }
286+
195287 if ( archive && totalAdded == pod . spec . containers . length ) {
196288 void archive . finalize ( ) ;
197289 }
@@ -209,15 +301,24 @@ export class LoggingService {
209301 return ;
210302 }
211303
212- for ( let log of text . split ( / \n / ) ) {
213- const timestampMatch = log . match (
214- / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } \. \d + Z / ,
215- ) ;
216- const timestamp = timestampMatch ? timestampMatch [ 0 ] : "" ;
217- log = log . replace (
218- / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } \. \d + Z \s * / ,
219- "" ,
220- ) ;
304+ for ( let data of text . split ( / \n / ) ) {
305+ const { timestamp, log } = this . parseLog ( data ) ;
306+
307+ if ( since ) {
308+ if ( new Date ( timestamp ) ) {
309+ const latestTimestamp = new Date ( timestamp ) ;
310+
311+ if ( ! oldestTimestamp || oldestTimestamp > latestTimestamp ) {
312+ oldestTimestamp = latestTimestamp ;
313+ }
314+
315+ if ( latestTimestamp && latestTimestamp >= until ) {
316+ continue ;
317+ }
318+ }
319+ }
320+
321+ totalLines ++ ;
221322
222323 stream . write (
223324 JSON . stringify ( {
@@ -236,7 +337,6 @@ export class LoggingService {
236337 } ) ;
237338
238339 logStream . on ( "close" , ( ) => {
239- this . logger . log ( "log stream closed" ) ;
240340 endStream ( ) ;
241341 } ) ;
242342
@@ -258,10 +358,23 @@ export class LoggingService {
258358 previous ,
259359 download ,
260360 tailLines ,
361+ since ?. start ,
261362 ) ;
262363 }
263364 }
264365
366+ private parseLog ( log : string ) {
367+ const timestampMatch = log . match (
368+ / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } \. \d + Z / ,
369+ ) ;
370+ const timestamp = timestampMatch ? timestampMatch [ 0 ] : "" ;
371+ log = log . replace ( / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } \. \d + Z \s * / , "" ) ;
372+ return {
373+ log,
374+ timestamp,
375+ } ;
376+ }
377+
265378 public async getJobStatus ( jobName : string ) {
266379 try {
267380 const job = await this . batchApi . readNamespacedJob ( {
0 commit comments