forked from
slices.network/slices
Highly ambitious ATProtocol AppView service and sdks
1import { parseArgs } from "@std/cli/parse-args";
2import { red, yellow, cyan, gray } from "@std/fmt/colors";
3import { createAuthenticatedClient } from "../utils/client.ts";
4import { logger } from "../utils/logger.ts";
5import { SlicesConfigLoader, mergeConfig } from "../utils/config.ts";
6
7function showLogsHelp() {
8 console.log(`
9slices logs - View Jetstream logs for a slice
10
11USAGE:
12 slices logs [OPTIONS]
13
14OPTIONS:
15 --slice <SLICE_URI> Target slice URI (optional, or from slices.json)
16 --limit <NUMBER> Maximum number of log entries to return (default: 100, max: 1000)
17 --api-url <URL> Slices API base URL (default: https://api.slices.network or from slices.json)
18 -h, --help Show this help message
19
20EXAMPLES:
21 slices logs --slice at://did:plc:example/slice
22 slices logs --limit 50
23 slices logs # Uses config from slices.json
24`);
25}
26
27export async function logsCommand(commandArgs: unknown[], _globalArgs: Record<string, unknown>): Promise<void> {
28 const args = parseArgs(commandArgs as string[], {
29 boolean: ["help"],
30 string: ["slice", "api-url", "limit"],
31 alias: {
32 h: "help",
33 },
34 });
35
36 if (args.help) {
37 showLogsHelp();
38 return;
39 }
40
41 const configLoader = new SlicesConfigLoader();
42 const slicesConfig = await configLoader.load();
43 const mergedConfig = mergeConfig(slicesConfig, args);
44
45 const sliceUri = mergedConfig.slice;
46 const apiUrl = mergedConfig.apiUrl || "https://api.slices.network";
47 const limit = args.limit ? parseInt(args.limit as string, 10) : 100;
48
49 if (isNaN(limit) || limit < 1 || limit > 1000) {
50 logger.error("--limit must be a number between 1 and 1000");
51 Deno.exit(1);
52 }
53
54 try {
55 const client = await createAuthenticatedClient(sliceUri || "", apiUrl);
56
57 const response = await client.network.slices.slice.getJetstreamLogs({
58 slice: sliceUri,
59 limit,
60 });
61
62 if (response.logs.length === 0) {
63 logger.info("No logs found");
64 return;
65 }
66
67 logger.section(`Jetstream Logs (${response.logs.length} entries)`);
68
69 for (const log of response.logs) {
70 const timestamp = new Date(log.createdAt).toLocaleString();
71 const level = log.level.toUpperCase().padEnd(5);
72 const coloredLevel = colorizeLevel(level, log.level);
73
74 console.log(`${timestamp} ${coloredLevel} ${log.message}`);
75
76 if (log.metadata && Object.keys(log.metadata).length > 0) {
77 console.log(` ${gray(JSON.stringify(log.metadata))}`);
78 }
79 }
80
81 } catch (error) {
82 const err = error as Error;
83 logger.error(`Failed to fetch logs: ${err.message}`);
84 Deno.exit(1);
85 }
86}
87
88function colorizeLevel(levelText: string, level: string): string {
89 switch (level.toLowerCase()) {
90 case "error":
91 return red(levelText);
92 case "warn":
93 return yellow(levelText);
94 case "info":
95 return cyan(levelText);
96 case "debug":
97 return gray(levelText);
98 default:
99 return levelText;
100 }
101}