@sentio/cli 3.4.2-rc.1 → 3.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +502 -36
- package/package.json +1 -1
- package/src/commands/dashboard.ts +472 -0
- package/src/commands/processor.ts +178 -3
- package/src/index.ts +2 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ProcessorExtService, ProcessorService } from '@sentio/api'
|
|
2
2
|
import { Command, InvalidArgumentError } from '@commander-js/extra-typings'
|
|
3
|
+
import chalk from 'chalk'
|
|
3
4
|
import process from 'process'
|
|
4
5
|
import yaml from 'yaml'
|
|
5
6
|
import {
|
|
@@ -34,6 +35,14 @@ interface ProcessorSourceOptions extends ProcessorOptions {
|
|
|
34
35
|
path?: string
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
interface ProcessorLogsOptions extends ProcessorOptions {
|
|
39
|
+
limit?: number
|
|
40
|
+
follow?: boolean
|
|
41
|
+
logType?: string
|
|
42
|
+
level?: string
|
|
43
|
+
query?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
export function createProcessorCommand() {
|
|
38
47
|
const processorCommand = new Command('processor').description('Manage Sentio processor versions')
|
|
39
48
|
processorCommand.addCommand(createProcessorStatusCommand())
|
|
@@ -42,6 +51,7 @@ export function createProcessorCommand() {
|
|
|
42
51
|
processorCommand.addCommand(createProcessorPauseCommand())
|
|
43
52
|
processorCommand.addCommand(createProcessorResumeCommand())
|
|
44
53
|
processorCommand.addCommand(createProcessorStopCommand())
|
|
54
|
+
processorCommand.addCommand(createProcessorLogsCommand())
|
|
45
55
|
return processorCommand
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -142,6 +152,26 @@ function createProcessorStopCommand() {
|
|
|
142
152
|
})
|
|
143
153
|
}
|
|
144
154
|
|
|
155
|
+
function createProcessorLogsCommand() {
|
|
156
|
+
return withOutputOptions(
|
|
157
|
+
withSharedProjectOptions(withAuthOptions(new Command('logs').description('View processor logs')))
|
|
158
|
+
)
|
|
159
|
+
.showHelpAfterError()
|
|
160
|
+
.argument('[processorId]', 'ID of the processor (defaults to active processor)')
|
|
161
|
+
.option('--limit <count>', 'Maximum number of log entries to fetch', parseInteger, 100)
|
|
162
|
+
.option('-f, --follow', 'Poll for new log entries continuously')
|
|
163
|
+
.option('--log-type <type>', 'Filter by log type (e.g. execution, system)')
|
|
164
|
+
.option('--level <level>', 'Filter by log level: DEBUG, INFO, WARNING, ERROR')
|
|
165
|
+
.option('--query <query>', 'Free-text filter query')
|
|
166
|
+
.action(async (processorId, options, command) => {
|
|
167
|
+
try {
|
|
168
|
+
await runProcessorLogs(processorId, options)
|
|
169
|
+
} catch (error) {
|
|
170
|
+
handleProcessorCommandError(error, command)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
145
175
|
async function runProcessorStatus(options: ProcessorStatusOptions) {
|
|
146
176
|
const context = createApiContext(options)
|
|
147
177
|
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
@@ -319,6 +349,134 @@ async function runProcessorStop(processorId: string | undefined, options: Proces
|
|
|
319
349
|
})
|
|
320
350
|
}
|
|
321
351
|
|
|
352
|
+
async function resolveProcessorId(processorId: string | undefined, options: ProcessorOptions): Promise<string> {
|
|
353
|
+
if (processorId) return processorId
|
|
354
|
+
const context = createApiContext(options)
|
|
355
|
+
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
356
|
+
const statusResponse = await ProcessorService.getProcessorStatusV2({
|
|
357
|
+
path: { owner: project.owner, slug: project.slug },
|
|
358
|
+
query: { version: 'ACTIVE' },
|
|
359
|
+
headers: context.headers
|
|
360
|
+
})
|
|
361
|
+
const data = unwrapApiResult(statusResponse)
|
|
362
|
+
const processors = Array.isArray(data.processors) ? data.processors : []
|
|
363
|
+
const activeProcessor = processors.find((p) => asString(p.versionState) === 'ACTIVE')
|
|
364
|
+
if (!activeProcessor || !activeProcessor.processorId) {
|
|
365
|
+
throw new CliError(
|
|
366
|
+
`No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
return asString(activeProcessor.processorId)!
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function runProcessorLogs(processorId: string | undefined, options: ProcessorLogsOptions) {
|
|
373
|
+
const resolvedProcessorId = await resolveProcessorId(processorId, options)
|
|
374
|
+
const context = createApiContext(options)
|
|
375
|
+
|
|
376
|
+
if (options.follow && !options.json && !options.yaml) {
|
|
377
|
+
await followProcessorLogs(resolvedProcessorId, context, options)
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const response = await postApiJson<ProcessorLogsResponse>(
|
|
382
|
+
`/api/v1/processors/${resolvedProcessorId}/logs`,
|
|
383
|
+
context,
|
|
384
|
+
buildLogsRequestBody(resolvedProcessorId, options)
|
|
385
|
+
)
|
|
386
|
+
printOutput(options, response)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function followProcessorLogs(
|
|
390
|
+
processorId: string,
|
|
391
|
+
context: ReturnType<typeof createApiContext>,
|
|
392
|
+
options: ProcessorLogsOptions
|
|
393
|
+
) {
|
|
394
|
+
let until: unknown[] | undefined
|
|
395
|
+
let running = true
|
|
396
|
+
const seenIds = new Set<string>()
|
|
397
|
+
|
|
398
|
+
process.on('SIGINT', () => {
|
|
399
|
+
running = false
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
while (running) {
|
|
403
|
+
try {
|
|
404
|
+
const body = { ...buildLogsRequestBody(processorId, options), until }
|
|
405
|
+
const response = await postApiJson<ProcessorLogsResponse>(`/api/v1/processors/${processorId}/logs`, context, body)
|
|
406
|
+
const entries = Array.isArray(response.logs) ? response.logs : []
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
const e = entry as ProcessorLog
|
|
409
|
+
const id = e.id ?? ''
|
|
410
|
+
if (id && seenIds.has(id)) continue
|
|
411
|
+
if (id) seenIds.add(id)
|
|
412
|
+
process.stdout.write(formatLogEntry(e) + '\n')
|
|
413
|
+
}
|
|
414
|
+
if (response.until) {
|
|
415
|
+
until = response.until
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
// Ignore transient fetch errors during follow mode
|
|
419
|
+
}
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function buildLogsRequestBody(processorId: string, options: ProcessorLogsOptions): Record<string, unknown> {
|
|
425
|
+
const body: Record<string, unknown> = { processorId, limit: options.limit }
|
|
426
|
+
if (options.logType) {
|
|
427
|
+
body.logTypeFilters = [options.logType]
|
|
428
|
+
}
|
|
429
|
+
if (options.level || options.query) {
|
|
430
|
+
const parts: string[] = []
|
|
431
|
+
if (options.level) parts.push(options.level.toUpperCase())
|
|
432
|
+
if (options.query) parts.push(options.query)
|
|
433
|
+
body.query = parts.join(' ')
|
|
434
|
+
}
|
|
435
|
+
return body
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
interface ProcessorLog {
|
|
439
|
+
id?: string
|
|
440
|
+
message?: string
|
|
441
|
+
timestamp?: string
|
|
442
|
+
attributes?: Record<string, unknown>
|
|
443
|
+
logType?: string
|
|
444
|
+
level?: string
|
|
445
|
+
highlightedMessage?: string
|
|
446
|
+
chainId?: string
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
interface ProcessorLogsResponse {
|
|
450
|
+
logs?: ProcessorLog[]
|
|
451
|
+
until?: unknown[]
|
|
452
|
+
total?: string
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function formatLogEntry(entry: ProcessorLog): string {
|
|
456
|
+
const formattedTime = entry.timestamp ? chalk.gray(entry.timestamp.replace('T', ' ').replace('Z', '')) : ''
|
|
457
|
+
const level = (entry.level ?? 'INFO').toUpperCase()
|
|
458
|
+
const logType = entry.logType ? chalk.gray(`[${entry.logType}]`) : ''
|
|
459
|
+
const coloredLevel = colorSeverity(level)
|
|
460
|
+
const message = entry.message ?? ''
|
|
461
|
+
const chain = entry.chainId ? chalk.gray(`(chain=${entry.chainId})`) : ''
|
|
462
|
+
|
|
463
|
+
return [formattedTime, coloredLevel, logType, message, chain].filter(Boolean).join(' ')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function colorSeverity(severity: string): string {
|
|
467
|
+
switch (severity) {
|
|
468
|
+
case 'ERROR':
|
|
469
|
+
return chalk.red(`[${severity}]`)
|
|
470
|
+
case 'WARNING':
|
|
471
|
+
case 'WARN':
|
|
472
|
+
return chalk.yellow(`[${severity}]`)
|
|
473
|
+
case 'DEBUG':
|
|
474
|
+
return chalk.gray(`[${severity}]`)
|
|
475
|
+
default:
|
|
476
|
+
return chalk.cyan(`[${severity}]`)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
322
480
|
function withAuthOptions<T extends Command<any, any, any>>(command: T) {
|
|
323
481
|
return command
|
|
324
482
|
.option('--host <host>', 'Override Sentio host')
|
|
@@ -380,8 +538,8 @@ function formatOutput(data: unknown) {
|
|
|
380
538
|
lines.push(`${group.versionState} (${group.processors.length})`)
|
|
381
539
|
for (const processor of group.processors) {
|
|
382
540
|
const version = asNumber(processor.version)
|
|
383
|
-
const
|
|
384
|
-
|
|
541
|
+
const processorStatus = processor.processorStatus as Record<string, unknown> | undefined
|
|
542
|
+
const statusState = asString(processorStatus?.state) ?? 'UNKNOWN'
|
|
385
543
|
const uploadedAt = asString(processor.uploadedAt)
|
|
386
544
|
lines.push(`- v${version ?? '?'} status=${statusState}${uploadedAt ? ` uploaded=${uploadedAt}` : ''}`)
|
|
387
545
|
if (asString(processor.processorId)) {
|
|
@@ -391,9 +549,17 @@ function formatOutput(data: unknown) {
|
|
|
391
549
|
for (const stateEntry of states.slice(0, 5)) {
|
|
392
550
|
const state = stateEntry as Record<string, unknown>
|
|
393
551
|
const chainId = asString(state.chainId) ?? '?'
|
|
394
|
-
const
|
|
552
|
+
const stateStatus = state.status as Record<string, unknown> | undefined
|
|
553
|
+
const chainState = asString(stateStatus?.state) ?? 'UNKNOWN'
|
|
395
554
|
const block = asString(state.processedBlockNumber) ?? '?'
|
|
396
555
|
lines.push(` chain ${chainId}: ${chainState} block=${block}`)
|
|
556
|
+
const errorRecord = stateStatus?.errorRecord as Record<string, unknown> | undefined
|
|
557
|
+
const chainError = asString(errorRecord?.message)
|
|
558
|
+
if (chainError) {
|
|
559
|
+
const createdAt = asString(errorRecord?.createdAt)
|
|
560
|
+
const prefix = createdAt ? `[${createdAt}] ` : ''
|
|
561
|
+
lines.push(` error: ${prefix}${chainError}`)
|
|
562
|
+
}
|
|
397
563
|
}
|
|
398
564
|
if (states.length > 5) {
|
|
399
565
|
lines.push(` ... ${states.length - 5} more chains`)
|
|
@@ -432,6 +598,15 @@ function formatOutput(data: unknown) {
|
|
|
432
598
|
return `Processor ${asString(objectData.processorId)} successfully ${asString(objectData.action)}.`
|
|
433
599
|
}
|
|
434
600
|
|
|
601
|
+
if (data && typeof data === 'object' && 'logs' in (data as Record<string, unknown>)) {
|
|
602
|
+
const logsData = data as ProcessorLogsResponse
|
|
603
|
+
const entries = Array.isArray(logsData.logs) ? logsData.logs : []
|
|
604
|
+
if (entries.length === 0) {
|
|
605
|
+
return 'No logs found.'
|
|
606
|
+
}
|
|
607
|
+
return entries.map((entry) => formatLogEntry(entry)).join('\n')
|
|
608
|
+
}
|
|
609
|
+
|
|
435
610
|
return JSON.stringify(data, null, 2)
|
|
436
611
|
}
|
|
437
612
|
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { createAlertCommand } from './commands/alert.js'
|
|
|
18
18
|
import { createPriceCommand } from './commands/price.js'
|
|
19
19
|
import { createSimulationCommand } from './commands/simulation.js'
|
|
20
20
|
import { createEndpointCommand } from './commands/endpoint.js'
|
|
21
|
+
import { createDashboardCommand } from './commands/dashboard.js'
|
|
21
22
|
import { enableApiDebug } from './api.js'
|
|
22
23
|
import { printVersions } from './utils.js'
|
|
23
24
|
|
|
@@ -50,5 +51,6 @@ program.addCommand(createAlertCommand())
|
|
|
50
51
|
program.addCommand(createPriceCommand())
|
|
51
52
|
program.addCommand(createSimulationCommand())
|
|
52
53
|
program.addCommand(createEndpointCommand())
|
|
54
|
+
program.addCommand(createDashboardCommand())
|
|
53
55
|
|
|
54
56
|
program.parse()
|