Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add logger, use execution timeout
  • Loading branch information
Theodore Li committed Apr 3, 2026
commit 4e75c4760c3ff863cd34cbc8bf405e883a3e1073
4 changes: 4 additions & 0 deletions apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
DescribeAlarmsCommand,
type StateValue,
} from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchDescribeAlarms')

const DescribeAlarmsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
Expand Down Expand Up @@ -87,6 +90,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch alarms'
logger.error('DescribeAlarms failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
10 changes: 1 addition & 9 deletions apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ export async function POST(request: NextRequest) {
}

const body = await request.json()
logger.info('Received request', {
hasRegion: Boolean(body.region),
hasAccessKeyId: Boolean(body.accessKeyId),
hasSecretAccessKey: Boolean(body.secretAccessKey),
region: body.region,
accessKeyIdPrefix: body.accessKeyId?.slice(0, 8),
})
const validatedData = DescribeLogGroupsSchema.parse(body)

Comment thread
TheodoreSpeaks marked this conversation as resolved.
const client = new CloudWatchLogsClient({
Expand All @@ -42,14 +35,12 @@ export async function POST(request: NextRequest) {
},
})
Comment thread
TheodoreSpeaks marked this conversation as resolved.

logger.info('Sending DescribeLogGroupsCommand')
const command = new DescribeLogGroupsCommand({
...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }),
...(validatedData.limit !== undefined && { limit: validatedData.limit }),
})

const response = await client.send(command)
logger.info('DescribeLogGroupsCommand returned', { count: response.logGroups?.length ?? 0 })

const logGroups = (response.logGroups ?? []).map((lg) => ({
logGroupName: lg.logGroupName ?? '',
Expand All @@ -66,6 +57,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log groups'
logger.error('DescribeLogGroups failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchDescribeLogStreams')

import { createCloudWatchLogsClient, describeLogStreams } from '@/app/api/tools/cloudwatch/utils'

const DescribeLogStreamsSchema = z.object({
Expand Down Expand Up @@ -43,6 +47,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log streams'
logger.error('DescribeLogStreams failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
5 changes: 5 additions & 0 deletions apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchGetLogEvents')

import { createCloudWatchLogsClient, getLogEvents } from '@/app/api/tools/cloudwatch/utils'

const GetLogEventsSchema = z.object({
Expand Down Expand Up @@ -51,6 +55,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch log events'
logger.error('GetLogEvents failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { CloudWatchClient, GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchGetMetricStatistics')

const GetMetricStatisticsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
Expand Down Expand Up @@ -88,6 +91,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch metric statistics'
logger.error('GetMetricStatistics failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
4 changes: 4 additions & 0 deletions apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { CloudWatchClient, ListMetricsCommand } from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchListMetrics')

const ListMetricsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
Expand Down Expand Up @@ -58,6 +61,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to list CloudWatch metrics'
logger.error('ListMetrics failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
5 changes: 5 additions & 0 deletions apps/sim/app/api/tools/cloudwatch/query-logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { StartQueryCommand } from '@aws-sdk/client-cloudwatch-logs'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'

const logger = createLogger('CloudWatchQueryLogs')

import { createCloudWatchLogsClient, pollQueryResults } from '@/app/api/tools/cloudwatch/utils'

const QueryLogsSchema = z.object({
Expand Down Expand Up @@ -62,6 +66,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'CloudWatch Log Insights query failed'
logger.error('QueryLogs failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}
10 changes: 6 additions & 4 deletions apps/sim/app/api/tools/cloudwatch/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GetQueryResultsCommand,
type ResultField,
} from '@aws-sdk/client-cloudwatch-logs'
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits'

interface AwsCredentials {
region: string
Expand Down Expand Up @@ -53,7 +54,7 @@ export async function pollQueryResults(
queryId: string,
options: PollOptions = {}
): Promise<PollResult> {
const { maxWaitMs = 60_000, pollIntervalMs = 1_000 } = options
const { maxWaitMs = DEFAULT_EXECUTION_TIMEOUT_MS, pollIntervalMs = 1_000 } = options
const startTime = Date.now()

while (Date.now() - startTime < maxWaitMs) {
Expand Down Expand Up @@ -107,11 +108,12 @@ export async function describeLogStreams(
storedBytes: number
}[]
}> {
const hasPrefix = Boolean(options?.prefix)
const command = new DescribeLogStreamsCommand({
logGroupName,
orderBy: 'LastEventTime',
descending: true,
...(options?.prefix && { logStreamNamePrefix: options.prefix }),
...(hasPrefix
? { orderBy: 'LogStreamName', logStreamNamePrefix: options!.prefix }
: { orderBy: 'LastEventTime', descending: true }),
...(options?.limit !== undefined && { limit: options.limit }),
})
Comment thread
TheodoreSpeaks marked this conversation as resolved.

Expand Down
Loading