@@ -3,11 +3,48 @@ use crate::handlers::Context;
3
3
use anyhow:: Context as _;
4
4
use hyper:: header:: { CONTENT_SECURITY_POLICY , CONTENT_TYPE } ;
5
5
use hyper:: { Body , Response , StatusCode } ;
6
+ use std:: collections:: VecDeque ;
6
7
use std:: str:: FromStr ;
7
8
use std:: sync:: Arc ;
8
9
use uuid:: Uuid ;
9
10
10
11
const ANSI_UP_URL : & str =
"https://cdn.jsdelivr.net/npm/[email protected] /+esm" ;
12
+ const MAX_CACHE_CAPACITY_BYTES : u64 = 50 * 1024 * 1024 ; // 50 Mb
13
+
14
+ #[ derive( Default ) ]
15
+ pub struct GitHubActionLogsCache {
16
+ capacity : u64 ,
17
+ entries : VecDeque < ( String , Arc < String > ) > ,
18
+ }
19
+
20
+ impl GitHubActionLogsCache {
21
+ pub fn get ( & mut self , key : & String ) -> Option < & Arc < String > > {
22
+ if let Some ( pos) = self . entries . iter ( ) . position ( |( k, _) | k == key) {
23
+ let entry = self . entries . remove ( pos) . unwrap ( ) ;
24
+ self . entries . push_front ( entry) ;
25
+ Some ( & self . entries . front ( ) . unwrap ( ) . 1 )
26
+ } else {
27
+ None
28
+ }
29
+ }
30
+
31
+ pub fn put ( & mut self , key : String , value : Arc < String > ) -> & Arc < String > {
32
+ let removed = if let Some ( pos) = self . entries . iter ( ) . position ( |( k, _) | k == & key) {
33
+ self . entries . remove ( pos)
34
+ } else if self . capacity + value. len ( ) as u64 >= MAX_CACHE_CAPACITY_BYTES {
35
+ self . entries . pop_back ( )
36
+ } else {
37
+ None
38
+ } ;
39
+ if let Some ( removed) = removed {
40
+ self . capacity -= removed. 1 . len ( ) as u64 ;
41
+ }
42
+ // Insert at the front (most recently used)
43
+ self . capacity += value. len ( ) as u64 ;
44
+ self . entries . push_front ( ( key, value) ) ;
45
+ & self . entries . front ( ) . unwrap ( ) . 1
46
+ }
47
+ }
11
48
12
49
pub async fn gha_logs (
13
50
ctx : Arc < Context > ,
@@ -52,27 +89,43 @@ async fn process_logs(
52
89
anyhow:: bail!( "Repository `{repo}` is not part of team repos" ) ;
53
90
}
54
91
55
- let logs = ctx
56
- . github
57
- . raw_job_logs (
58
- & github:: IssueRepository {
59
- organization : owner. to_string ( ) ,
60
- repository : repo. to_string ( ) ,
61
- } ,
62
- log_id,
63
- )
64
- . await
65
- . context ( "unable to get the raw logs" ) ?;
92
+ let log_uuid = format ! ( "{owner}/{repo}${log_id}" ) ;
93
+
94
+ let logs = ' logs: {
95
+ if let Some ( logs) = ctx. gha_logs . write ( ) . await . get ( & log_uuid) {
96
+ tracing:: info!( "Cache found for {log_uuid}" ) ;
97
+ break ' logs logs. clone ( ) ;
98
+ }
99
+
100
+ tracing:: info!( "Cache NOT found for {log_uuid}" ) ;
101
+ let logs = ctx
102
+ . github
103
+ . raw_job_logs (
104
+ & github:: IssueRepository {
105
+ organization : owner. to_string ( ) ,
106
+ repository : repo. to_string ( ) ,
107
+ } ,
108
+ log_id,
109
+ )
110
+ . await
111
+ . context ( "unable to get the raw logs" ) ?;
112
+
113
+ ctx. gha_logs
114
+ . write ( )
115
+ . await
116
+ . put ( log_uuid. clone ( ) , logs. into ( ) )
117
+ . clone ( )
118
+ } ;
66
119
67
- let json_logs = serde_json:: to_string ( & logs) . context ( "unable to JSON-ify the raw logs" ) ?;
120
+ let json_logs = serde_json:: to_string ( & * logs) . context ( "unable to JSON-ify the raw logs" ) ?;
68
121
69
122
let nonce = Uuid :: new_v4 ( ) . to_hyphenated ( ) . to_string ( ) ;
70
123
71
124
let html = format ! (
72
125
r#"<!DOCTYPE html>
73
126
<html>
74
127
<head>
75
- <title>{owner}/{repo}${log_id } - triagebot</title>
128
+ <title>{log_uuid } - triagebot</title>
76
129
<meta charset="UTF-8">
77
130
<meta name="viewport" content="width=device-width, initial-scale=1.0">
78
131
<link rel="icon" sizes="32x32" type="image/png" href="https://rust-lang.org/static/images/favicon-32x32.png">
@@ -101,7 +154,7 @@ async fn process_logs(
101
154
</html>"# ,
102
155
) ;
103
156
104
- tracing:: info!( "Serving GHA Logs for {owner}/{repo}#{log_id }" ) ;
157
+ tracing:: info!( "Serving GHA Logs for {log_uuid }" ) ;
105
158
106
159
return Ok ( Response :: builder ( )
107
160
. status ( StatusCode :: OK )
0 commit comments