forked from aws/agentcore-cli
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcloudwatch.ts
More file actions
130 lines (107 loc) · 3.4 KB
/
Copy pathcloudwatch.ts
File metadata and controls
130 lines (107 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { getCredentialProvider } from './account';
import { CloudWatchLogsClient, FilterLogEventsCommand, StartLiveTailCommand } from '@aws-sdk/client-cloudwatch-logs';
export interface LogEvent {
timestamp: number;
message: string;
}
export interface StreamLogsOptions {
logGroupName: string;
region: string;
accountId: string;
filterPattern?: string;
abortSignal?: AbortSignal;
}
export interface SearchLogsOptions {
logGroupName: string;
region: string;
startTimeMs: number;
endTimeMs: number;
filterPattern?: string;
limit?: number;
}
/**
* Stream logs in real-time using StartLiveTail.
* Auto-reconnects on 3-hour session timeout.
*/
export async function* streamLogs(options: StreamLogsOptions): AsyncGenerator<LogEvent> {
const { logGroupName, region, accountId, filterPattern, abortSignal } = options;
// StartLiveTail requires ARN format for logGroupIdentifiers
const logGroupArn = `arn:aws:logs:${region}:${accountId}:log-group:${logGroupName}`;
while (!abortSignal?.aborted) {
const client = new CloudWatchLogsClient({
region,
credentials: getCredentialProvider(),
});
const command = new StartLiveTailCommand({
logGroupIdentifiers: [logGroupArn],
...(filterPattern ? { logEventFilterPattern: filterPattern } : {}),
});
const response = await client.send(command, {
abortSignal,
});
if (!response.responseStream) {
return;
}
let sessionTimedOut = false;
try {
for await (const event of response.responseStream) {
if (abortSignal?.aborted) break;
if ('sessionUpdate' in event && event.sessionUpdate) {
const logEvents = event.sessionUpdate.sessionResults ?? [];
for (const logEvent of logEvents) {
yield {
timestamp: logEvent.timestamp ?? Date.now(),
message: logEvent.message ?? '',
};
}
}
if ('SessionTimeoutException' in event) {
sessionTimedOut = true;
break;
}
}
} catch (err: unknown) {
if (abortSignal?.aborted) return;
const errorName = (err as { name?: string })?.name;
if (errorName === 'SessionTimeoutException') {
sessionTimedOut = true;
} else {
throw err;
}
}
// Auto-reconnect on session timeout
if (!sessionTimedOut) return;
}
}
/**
* Search logs using FilterLogEvents with pagination.
*/
export async function* searchLogs(options: SearchLogsOptions): AsyncGenerator<LogEvent> {
const { logGroupName, region, startTimeMs, endTimeMs, filterPattern, limit } = options;
const client = new CloudWatchLogsClient({
region,
credentials: getCredentialProvider(),
});
let nextToken: string | undefined;
let yielded = 0;
do {
const command = new FilterLogEventsCommand({
logGroupName,
startTime: startTimeMs,
endTime: endTimeMs,
...(filterPattern ? { filterPattern } : {}),
...(nextToken ? { nextToken } : {}),
...(limit ? { limit: Math.min(limit - yielded, 10000) } : {}),
});
const response = await client.send(command);
for (const event of response.events ?? []) {
if (limit && yielded >= limit) return;
yield {
timestamp: event.timestamp ?? Date.now(),
message: event.message ?? '',
};
yielded++;
}
nextToken = response.nextToken;
} while (nextToken && (!limit || yielded < limit));
}