11import {
22 CloudWatchLogsClient ,
3+ DescribeLogGroupsCommand ,
34 DescribeLogStreamsCommand ,
45 FilterLogEventsCommand ,
56 GetLogEventsCommand ,
@@ -10,12 +11,22 @@ import { createLogger } from '@sim/logger'
1011import { sleep } from '@sim/utils/helpers'
1112import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits'
1213
14+ const logger = createLogger ( 'CloudWatchUtils' )
15+
1316interface AwsCredentials {
1417 region : string
1518 accessKeyId : string
1619 secretAccessKey : string
1720}
1821
22+ interface DescribedLogGroup {
23+ logGroupName : string
24+ arn : string
25+ storedBytes : number
26+ retentionInDays : number | undefined
27+ creationTime : number | undefined
28+ }
29+
1930export function createCloudWatchLogsClient ( config : AwsCredentials ) : CloudWatchLogsClient {
2031 return new CloudWatchLogsClient ( {
2132 region : config . region ,
@@ -26,6 +37,60 @@ export function createCloudWatchLogsClient(config: AwsCredentials): CloudWatchLo
2637 } )
2738}
2839
40+ /** AWS DescribeLogGroups caps `limit` at 50 items per page. */
41+ const LOG_GROUPS_PAGE_SIZE = 50
42+
43+ /** Upper bound on pages drained to avoid unbounded loops on very large accounts. */
44+ const MAX_LOG_GROUPS_PAGES = 20
45+
46+ /** Lists CloudWatch log groups with the same bounded pagination used by tool and selector routes. */
47+ export async function describeLogGroups (
48+ client : CloudWatchLogsClient ,
49+ options ?: { prefix ?: string ; limit ?: number }
50+ ) : Promise < { logGroups : DescribedLogGroup [ ] } > {
51+ const totalLimit = options ?. limit
52+ const logGroups : DescribedLogGroup [ ] = [ ]
53+ let nextToken : string | undefined
54+
55+ for ( let page = 0 ; page < MAX_LOG_GROUPS_PAGES ; page ++ ) {
56+ const pageLimit =
57+ totalLimit !== undefined
58+ ? Math . min ( LOG_GROUPS_PAGE_SIZE , totalLimit - logGroups . length )
59+ : LOG_GROUPS_PAGE_SIZE
60+ const response = await client . send (
61+ new DescribeLogGroupsCommand ( {
62+ ...( options ?. prefix && { logGroupNamePrefix : options . prefix } ) ,
63+ limit : pageLimit ,
64+ ...( nextToken && { nextToken } ) ,
65+ } )
66+ )
67+
68+ for ( const logGroup of response . logGroups ?? [ ] ) {
69+ logGroups . push ( {
70+ logGroupName : logGroup . logGroupName ?? '' ,
71+ arn : logGroup . arn ?? '' ,
72+ storedBytes : logGroup . storedBytes ?? 0 ,
73+ retentionInDays : logGroup . retentionInDays ,
74+ creationTime : logGroup . creationTime ,
75+ } )
76+ }
77+
78+ nextToken = response . nextToken
79+ if ( ! nextToken ) break
80+ if ( totalLimit !== undefined && logGroups . length >= totalLimit ) break
81+
82+ if ( page === MAX_LOG_GROUPS_PAGES - 1 ) {
83+ logger . warn (
84+ `DescribeLogGroups hit pagination cap of ${ MAX_LOG_GROUPS_PAGES } pages; log group list may be incomplete`
85+ )
86+ }
87+ }
88+
89+ return {
90+ logGroups : totalLimit !== undefined ? logGroups . slice ( 0 , totalLimit ) : logGroups ,
91+ }
92+ }
93+
2994interface PollOptions {
3095 maxWaitMs ?: number
3196 pollIntervalMs ?: number
@@ -104,8 +169,6 @@ const LOG_STREAMS_PAGE_SIZE = 50
104169/** Upper bound on pages drained to avoid unbounded loops on log groups with many streams. */
105170const MAX_LOG_STREAMS_PAGES = 20
106171
107- const logger = createLogger ( 'CloudWatchUtils' )
108-
109172interface DescribedLogStream {
110173 logStreamName : string
111174 lastEventTimestamp : number | undefined
0 commit comments