@blogic-cz/agent-tools 0.11.0 → 0.12.0
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/README.md +60 -60
- package/package.json +8 -8
- package/schemas/agent-tools.schema.json +12 -12
- package/src/config/index.ts +2 -2
- package/src/config/loader.ts +6 -6
- package/src/config/types.ts +7 -7
- package/src/index.ts +1 -1
- package/src/observability-tool/errors.ts +8 -0
- package/src/{grafana-tool → observability-tool}/index.ts +7 -16
- package/src/observability-tool/metrics.ts +129 -0
- package/src/{grafana-tool → observability-tool}/shared.ts +90 -34
- package/src/observability-tool/trace.ts +379 -0
- package/src/observability-tool/types.ts +119 -0
- package/src/grafana-tool/alerts.ts +0 -159
- package/src/grafana-tool/dashboards.ts +0 -151
- package/src/grafana-tool/datasources.ts +0 -72
- package/src/grafana-tool/errors.ts +0 -8
- package/src/grafana-tool/health.ts +0 -57
- package/src/grafana-tool/logs.ts +0 -124
- package/src/grafana-tool/metrics.ts +0 -217
- package/src/grafana-tool/types.ts +0 -29
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
2
|
-
import { Command, Flag } from "effect/unstable/cli";
|
|
3
|
-
|
|
4
|
-
import { formatOption, formatOutput } from "#shared";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
envOption,
|
|
8
|
-
formatGrafanaError,
|
|
9
|
-
grafanaFetch,
|
|
10
|
-
profileOption,
|
|
11
|
-
resolveConfig,
|
|
12
|
-
} from "./shared";
|
|
13
|
-
|
|
14
|
-
type AlertRulesResponse = Record<
|
|
15
|
-
string,
|
|
16
|
-
Array<{
|
|
17
|
-
name: string;
|
|
18
|
-
rules: Array<{
|
|
19
|
-
name: string;
|
|
20
|
-
state: string;
|
|
21
|
-
health: string;
|
|
22
|
-
lastEvaluation?: string;
|
|
23
|
-
evaluationTime?: number;
|
|
24
|
-
}>;
|
|
25
|
-
}>
|
|
26
|
-
>;
|
|
27
|
-
|
|
28
|
-
type AlertInstance = {
|
|
29
|
-
labels: Record<string, string>;
|
|
30
|
-
annotations: Record<string, string>;
|
|
31
|
-
state: string;
|
|
32
|
-
activeAt?: string;
|
|
33
|
-
value?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const listCommand = Command.make(
|
|
37
|
-
"list",
|
|
38
|
-
{ format: formatOption, env: envOption, profile: profileOption },
|
|
39
|
-
({ format, env, profile }) => {
|
|
40
|
-
const start = Date.now();
|
|
41
|
-
|
|
42
|
-
return Effect.gen(function* () {
|
|
43
|
-
const config = yield* resolveConfig(env, profile);
|
|
44
|
-
const data = yield* grafanaFetch<AlertRulesResponse>(
|
|
45
|
-
config,
|
|
46
|
-
"/api/ruler/grafana/api/v1/rules",
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const rules = Object.entries(data).flatMap(([namespace, groups]) =>
|
|
50
|
-
groups.flatMap((group) =>
|
|
51
|
-
group.rules.map((rule) => ({
|
|
52
|
-
name: rule.name,
|
|
53
|
-
state: rule.state,
|
|
54
|
-
health: rule.health,
|
|
55
|
-
namespace,
|
|
56
|
-
group: group.name,
|
|
57
|
-
lastEvaluation: rule.lastEvaluation,
|
|
58
|
-
evaluationTime: rule.evaluationTime,
|
|
59
|
-
})),
|
|
60
|
-
),
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const result = {
|
|
64
|
-
success: true,
|
|
65
|
-
message: `Found ${rules.length} alert rule(s)`,
|
|
66
|
-
data: { rules, count: rules.length },
|
|
67
|
-
executionTimeMs: Date.now() - start,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
yield* Console.log(formatOutput(result, format));
|
|
71
|
-
}).pipe(
|
|
72
|
-
Effect.catch((error) =>
|
|
73
|
-
Effect.gen(function* () {
|
|
74
|
-
const result = {
|
|
75
|
-
success: false,
|
|
76
|
-
message: "Failed to list alert rules",
|
|
77
|
-
error: formatGrafanaError(error),
|
|
78
|
-
hint: "Check Grafana is running and accessible",
|
|
79
|
-
executionTimeMs: Date.now() - start,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
yield* Console.log(formatOutput(result, format));
|
|
83
|
-
}),
|
|
84
|
-
),
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
).pipe(Command.withDescription("List all alert rules"));
|
|
88
|
-
|
|
89
|
-
const statusCommand = Command.make(
|
|
90
|
-
"status",
|
|
91
|
-
{
|
|
92
|
-
format: formatOption,
|
|
93
|
-
env: envOption,
|
|
94
|
-
profile: profileOption,
|
|
95
|
-
all: Flag.boolean("all").pipe(
|
|
96
|
-
Flag.withDescription("Show all alerts including normal state"),
|
|
97
|
-
Flag.withDefault(false),
|
|
98
|
-
),
|
|
99
|
-
},
|
|
100
|
-
({ format, env, profile, all }) => {
|
|
101
|
-
const start = Date.now();
|
|
102
|
-
|
|
103
|
-
return Effect.gen(function* () {
|
|
104
|
-
const config = yield* resolveConfig(env, profile);
|
|
105
|
-
const alerts = yield* grafanaFetch<AlertInstance[]>(
|
|
106
|
-
config,
|
|
107
|
-
"/api/alertmanager/grafana/api/v2/alerts",
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const filtered = all
|
|
111
|
-
? alerts
|
|
112
|
-
: alerts.filter((alert) => alert.state === "firing" || alert.state === "pending");
|
|
113
|
-
const firingCount = filtered.filter((alert) => alert.state === "firing").length;
|
|
114
|
-
const pendingCount = filtered.filter((alert) => alert.state === "pending").length;
|
|
115
|
-
|
|
116
|
-
const result = {
|
|
117
|
-
success: true,
|
|
118
|
-
message:
|
|
119
|
-
firingCount === 0 && pendingCount === 0
|
|
120
|
-
? "No active alerts"
|
|
121
|
-
: `${firingCount} firing, ${pendingCount} pending alert(s)`,
|
|
122
|
-
data: {
|
|
123
|
-
alerts: filtered.map((alert) => ({
|
|
124
|
-
state: alert.state,
|
|
125
|
-
labels: alert.labels,
|
|
126
|
-
annotations: alert.annotations,
|
|
127
|
-
activeAt: alert.activeAt,
|
|
128
|
-
value: alert.value,
|
|
129
|
-
})),
|
|
130
|
-
firingCount,
|
|
131
|
-
pendingCount,
|
|
132
|
-
totalCount: filtered.length,
|
|
133
|
-
},
|
|
134
|
-
executionTimeMs: Date.now() - start,
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
yield* Console.log(formatOutput(result, format));
|
|
138
|
-
}).pipe(
|
|
139
|
-
Effect.catch((error) =>
|
|
140
|
-
Effect.gen(function* () {
|
|
141
|
-
const result = {
|
|
142
|
-
success: false,
|
|
143
|
-
message: "Failed to get alert status",
|
|
144
|
-
error: formatGrafanaError(error),
|
|
145
|
-
hint: "Check Grafana is running and accessible",
|
|
146
|
-
executionTimeMs: Date.now() - start,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
yield* Console.log(formatOutput(result, format));
|
|
150
|
-
}),
|
|
151
|
-
),
|
|
152
|
-
);
|
|
153
|
-
},
|
|
154
|
-
).pipe(Command.withDescription("Show firing and pending alerts"));
|
|
155
|
-
|
|
156
|
-
export const alertsCommand = Command.make("alerts", {}).pipe(
|
|
157
|
-
Command.withDescription("Alert operations"),
|
|
158
|
-
Command.withSubcommands([listCommand, statusCommand]),
|
|
159
|
-
);
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { Console, Effect, Option } from "effect";
|
|
2
|
-
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
-
|
|
4
|
-
import { formatOption, formatOutput } from "#shared";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
envOption,
|
|
8
|
-
formatGrafanaError,
|
|
9
|
-
grafanaFetch,
|
|
10
|
-
profileOption,
|
|
11
|
-
resolveConfig,
|
|
12
|
-
} from "./shared";
|
|
13
|
-
|
|
14
|
-
type DashboardSearchItem = {
|
|
15
|
-
id: number;
|
|
16
|
-
uid: string;
|
|
17
|
-
title: string;
|
|
18
|
-
url: string;
|
|
19
|
-
type: string;
|
|
20
|
-
tags: string[];
|
|
21
|
-
folderTitle?: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type DashboardDetail = {
|
|
25
|
-
dashboard: {
|
|
26
|
-
id: number;
|
|
27
|
-
uid: string;
|
|
28
|
-
title: string;
|
|
29
|
-
tags: string[];
|
|
30
|
-
version: number;
|
|
31
|
-
panels?: Array<{ id: number; title: string; type: string }>;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const listCommand = Command.make(
|
|
36
|
-
"list",
|
|
37
|
-
{
|
|
38
|
-
format: formatOption,
|
|
39
|
-
env: envOption,
|
|
40
|
-
profile: profileOption,
|
|
41
|
-
folder: Flag.optional(
|
|
42
|
-
Flag.string("folder").pipe(Flag.withDescription("Filter by folder title (case-insensitive)")),
|
|
43
|
-
),
|
|
44
|
-
},
|
|
45
|
-
({ format, env, profile, folder }) => {
|
|
46
|
-
const start = Date.now();
|
|
47
|
-
|
|
48
|
-
return Effect.gen(function* () {
|
|
49
|
-
const config = yield* resolveConfig(env, profile);
|
|
50
|
-
|
|
51
|
-
const items = yield* grafanaFetch<DashboardSearchItem[]>(
|
|
52
|
-
config,
|
|
53
|
-
"/api/search?type=dash-db&limit=1000",
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const folderFilter = Option.getOrUndefined(folder)?.toLowerCase();
|
|
57
|
-
const dashboards = items
|
|
58
|
-
.map((item) => ({
|
|
59
|
-
uid: item.uid,
|
|
60
|
-
title: item.title,
|
|
61
|
-
url: item.url,
|
|
62
|
-
tags: item.tags,
|
|
63
|
-
folderTitle: item.folderTitle ?? "General",
|
|
64
|
-
}))
|
|
65
|
-
.filter((item) =>
|
|
66
|
-
folderFilter ? item.folderTitle.toLowerCase().includes(folderFilter) : true,
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const result = {
|
|
70
|
-
success: true,
|
|
71
|
-
message: folderFilter
|
|
72
|
-
? `Found ${dashboards.length} dashboard(s) in folder '${folderFilter}'`
|
|
73
|
-
: `Found ${dashboards.length} dashboard(s)`,
|
|
74
|
-
data: { dashboards, count: dashboards.length },
|
|
75
|
-
executionTimeMs: Date.now() - start,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
yield* Console.log(formatOutput(result, format));
|
|
79
|
-
}).pipe(
|
|
80
|
-
Effect.catch((error) =>
|
|
81
|
-
Effect.gen(function* () {
|
|
82
|
-
const result = {
|
|
83
|
-
success: false,
|
|
84
|
-
message: "Failed to list dashboards",
|
|
85
|
-
error: formatGrafanaError(error),
|
|
86
|
-
hint: "Check Grafana is running and accessible",
|
|
87
|
-
executionTimeMs: Date.now() - start,
|
|
88
|
-
};
|
|
89
|
-
yield* Console.log(formatOutput(result, format));
|
|
90
|
-
}),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
},
|
|
94
|
-
).pipe(Command.withDescription("List all dashboards"));
|
|
95
|
-
|
|
96
|
-
const getCommand = Command.make(
|
|
97
|
-
"get",
|
|
98
|
-
{ uid: Argument.string("uid"), format: formatOption, env: envOption, profile: profileOption },
|
|
99
|
-
({ uid, format, env, profile }) => {
|
|
100
|
-
const start = Date.now();
|
|
101
|
-
|
|
102
|
-
return Effect.gen(function* () {
|
|
103
|
-
const config = yield* resolveConfig(env, profile);
|
|
104
|
-
|
|
105
|
-
const detail = yield* grafanaFetch<DashboardDetail>(
|
|
106
|
-
config,
|
|
107
|
-
`/api/dashboards/uid/${encodeURIComponent(uid)}`,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const panels = (detail.dashboard.panels ?? []).map((panel) => ({
|
|
111
|
-
id: panel.id,
|
|
112
|
-
title: panel.title,
|
|
113
|
-
type: panel.type,
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
const result = {
|
|
117
|
-
success: true,
|
|
118
|
-
message: `Dashboard: ${detail.dashboard.title}`,
|
|
119
|
-
data: {
|
|
120
|
-
uid: detail.dashboard.uid,
|
|
121
|
-
title: detail.dashboard.title,
|
|
122
|
-
tags: detail.dashboard.tags,
|
|
123
|
-
version: detail.dashboard.version,
|
|
124
|
-
panels,
|
|
125
|
-
panelCount: panels.length,
|
|
126
|
-
},
|
|
127
|
-
executionTimeMs: Date.now() - start,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
yield* Console.log(formatOutput(result, format));
|
|
131
|
-
}).pipe(
|
|
132
|
-
Effect.catch((error) =>
|
|
133
|
-
Effect.gen(function* () {
|
|
134
|
-
const result = {
|
|
135
|
-
success: false,
|
|
136
|
-
message: `Failed to get dashboard '${uid}'`,
|
|
137
|
-
error: formatGrafanaError(error),
|
|
138
|
-
hint: "Check the dashboard UID is correct",
|
|
139
|
-
executionTimeMs: Date.now() - start,
|
|
140
|
-
};
|
|
141
|
-
yield* Console.log(formatOutput(result, format));
|
|
142
|
-
}),
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
},
|
|
146
|
-
).pipe(Command.withDescription("Get dashboard details by UID"));
|
|
147
|
-
|
|
148
|
-
export const dashboardsCommand = Command.make("dashboards", {}).pipe(
|
|
149
|
-
Command.withDescription("Dashboard operations"),
|
|
150
|
-
Command.withSubcommands([listCommand, getCommand]),
|
|
151
|
-
);
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
2
|
-
import { Command } from "effect/unstable/cli";
|
|
3
|
-
|
|
4
|
-
import { formatOption, formatOutput } from "#shared";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
envOption,
|
|
8
|
-
formatGrafanaError,
|
|
9
|
-
grafanaFetch,
|
|
10
|
-
profileOption,
|
|
11
|
-
resolveConfig,
|
|
12
|
-
} from "./shared";
|
|
13
|
-
|
|
14
|
-
type Datasource = {
|
|
15
|
-
id: number;
|
|
16
|
-
uid: string;
|
|
17
|
-
name: string;
|
|
18
|
-
type: string;
|
|
19
|
-
url: string;
|
|
20
|
-
isDefault: boolean;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const listCommand = Command.make(
|
|
24
|
-
"list",
|
|
25
|
-
{ format: formatOption, env: envOption, profile: profileOption },
|
|
26
|
-
({ format, env, profile }) => {
|
|
27
|
-
const start = Date.now();
|
|
28
|
-
|
|
29
|
-
return Effect.gen(function* () {
|
|
30
|
-
const config = yield* resolveConfig(env, profile);
|
|
31
|
-
|
|
32
|
-
const items = yield* grafanaFetch<Datasource[]>(config, "/api/datasources");
|
|
33
|
-
|
|
34
|
-
const result = {
|
|
35
|
-
success: true,
|
|
36
|
-
message: `Found ${items.length} datasource(s)`,
|
|
37
|
-
data: {
|
|
38
|
-
datasources: items.map((item) => ({
|
|
39
|
-
uid: item.uid,
|
|
40
|
-
name: item.name,
|
|
41
|
-
type: item.type,
|
|
42
|
-
url: item.url,
|
|
43
|
-
isDefault: item.isDefault,
|
|
44
|
-
})),
|
|
45
|
-
count: items.length,
|
|
46
|
-
},
|
|
47
|
-
executionTimeMs: Date.now() - start,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
yield* Console.log(formatOutput(result, format));
|
|
51
|
-
}).pipe(
|
|
52
|
-
Effect.catch((error) =>
|
|
53
|
-
Effect.gen(function* () {
|
|
54
|
-
const result = {
|
|
55
|
-
success: false,
|
|
56
|
-
message: "Failed to list datasources",
|
|
57
|
-
error: formatGrafanaError(error),
|
|
58
|
-
hint: "Check Grafana is running and accessible",
|
|
59
|
-
executionTimeMs: Date.now() - start,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
yield* Console.log(formatOutput(result, format));
|
|
63
|
-
}),
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
},
|
|
67
|
-
).pipe(Command.withDescription("List configured datasources"));
|
|
68
|
-
|
|
69
|
-
export const datasourcesCommand = Command.make("datasources", {}).pipe(
|
|
70
|
-
Command.withDescription("Datasource operations"),
|
|
71
|
-
Command.withSubcommands([listCommand]),
|
|
72
|
-
);
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
2
|
-
import { Command } from "effect/unstable/cli";
|
|
3
|
-
|
|
4
|
-
import { formatOption, formatOutput } from "#shared";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
envOption,
|
|
8
|
-
formatGrafanaError,
|
|
9
|
-
grafanaFetch,
|
|
10
|
-
profileOption,
|
|
11
|
-
resolveConfig,
|
|
12
|
-
} from "./shared";
|
|
13
|
-
|
|
14
|
-
export const healthCommand = Command.make(
|
|
15
|
-
"health",
|
|
16
|
-
{ format: formatOption, env: envOption, profile: profileOption },
|
|
17
|
-
({ format, env, profile }) => {
|
|
18
|
-
const start = Date.now();
|
|
19
|
-
|
|
20
|
-
return Effect.gen(function* () {
|
|
21
|
-
const config = yield* resolveConfig(env, profile);
|
|
22
|
-
|
|
23
|
-
const body = yield* grafanaFetch<{ database?: string; version?: string; commit?: string }>(
|
|
24
|
-
config,
|
|
25
|
-
"/api/health",
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const result = {
|
|
29
|
-
success: true,
|
|
30
|
-
message: `Grafana is healthy (${config.url})`,
|
|
31
|
-
data: {
|
|
32
|
-
url: config.url,
|
|
33
|
-
httpStatus: 200,
|
|
34
|
-
response: body,
|
|
35
|
-
env,
|
|
36
|
-
},
|
|
37
|
-
executionTimeMs: Date.now() - start,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
yield* Console.log(formatOutput(result, format));
|
|
41
|
-
}).pipe(
|
|
42
|
-
Effect.catch((error) =>
|
|
43
|
-
Effect.gen(function* () {
|
|
44
|
-
const result = {
|
|
45
|
-
success: false,
|
|
46
|
-
message: "Grafana is unreachable",
|
|
47
|
-
error: formatGrafanaError(error),
|
|
48
|
-
hint: "Check Grafana is running and accessible",
|
|
49
|
-
executionTimeMs: Date.now() - start,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
yield* Console.log(formatOutput(result, format));
|
|
53
|
-
}),
|
|
54
|
-
),
|
|
55
|
-
);
|
|
56
|
-
},
|
|
57
|
-
).pipe(Command.withDescription("Check Grafana health and connectivity"));
|
package/src/grafana-tool/logs.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
2
|
-
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
-
|
|
4
|
-
import { formatOption, formatOutput } from "#shared";
|
|
5
|
-
|
|
6
|
-
import { GrafanaToolError } from "./errors";
|
|
7
|
-
import {
|
|
8
|
-
envOption,
|
|
9
|
-
formatGrafanaError,
|
|
10
|
-
grafanaDsQuery,
|
|
11
|
-
profileOption,
|
|
12
|
-
resolveConfig,
|
|
13
|
-
} from "./shared";
|
|
14
|
-
|
|
15
|
-
function parseLabel(value: string | Record<string, string>): Record<string, string> {
|
|
16
|
-
if (typeof value === "object" && value !== null) {
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
return JSON.parse(value) as Record<string, string>;
|
|
22
|
-
} catch {
|
|
23
|
-
return {};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const queryCommand = Command.make(
|
|
28
|
-
"query",
|
|
29
|
-
{
|
|
30
|
-
logql: Argument.string("logql"),
|
|
31
|
-
format: formatOption,
|
|
32
|
-
env: envOption,
|
|
33
|
-
profile: profileOption,
|
|
34
|
-
limit: Flag.integer("limit").pipe(
|
|
35
|
-
Flag.withDescription("Max log lines (default: 100)"),
|
|
36
|
-
Flag.withDefault(100),
|
|
37
|
-
),
|
|
38
|
-
start: Flag.string("start").pipe(
|
|
39
|
-
Flag.withDescription("Start time (default: now-1h)"),
|
|
40
|
-
Flag.withDefault("now-1h"),
|
|
41
|
-
),
|
|
42
|
-
end: Flag.string("end").pipe(
|
|
43
|
-
Flag.withDescription("End time (default: now)"),
|
|
44
|
-
Flag.withDefault("now"),
|
|
45
|
-
),
|
|
46
|
-
},
|
|
47
|
-
({ logql, format, env, profile, limit, start, end }) => {
|
|
48
|
-
const startedAt = Date.now();
|
|
49
|
-
|
|
50
|
-
return Effect.gen(function* () {
|
|
51
|
-
const config = yield* resolveConfig(env, profile);
|
|
52
|
-
const response = yield* grafanaDsQuery(config, config.lokiUid, "loki", logql, {
|
|
53
|
-
from: start,
|
|
54
|
-
to: end,
|
|
55
|
-
maxLines: limit,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (response.results.A.error) {
|
|
59
|
-
return yield* new GrafanaToolError({
|
|
60
|
-
cause: new Error(response.results.A.error),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const logs: Array<{ timestamp: string; line: string; labels: Record<string, string> }> = [];
|
|
65
|
-
for (const frame of response.results.A.frames ?? []) {
|
|
66
|
-
const fields = frame.schema.fields;
|
|
67
|
-
const values = frame.data.values;
|
|
68
|
-
const timeIndex = fields.findIndex(
|
|
69
|
-
(field) => field.name === "timestamp" || field.type === "time",
|
|
70
|
-
);
|
|
71
|
-
const lineIndex = fields.findIndex(
|
|
72
|
-
(field) => field.name === "body" || field.name === "Line" || field.name === "line",
|
|
73
|
-
);
|
|
74
|
-
const labelsIndex = fields.findIndex(
|
|
75
|
-
(field) => field.name === "labels" || field.name === "labelTypes",
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const timestamps = (timeIndex >= 0 ? values[timeIndex] : []) as Array<string | number>;
|
|
79
|
-
const lines = (lineIndex >= 0 ? values[lineIndex] : []) as string[];
|
|
80
|
-
const labelValues = (labelsIndex >= 0 ? values[labelsIndex] : []) as Array<
|
|
81
|
-
string | Record<string, string>
|
|
82
|
-
>;
|
|
83
|
-
|
|
84
|
-
for (const [index, line] of lines.entries()) {
|
|
85
|
-
logs.push({
|
|
86
|
-
timestamp: String(timestamps[index] ?? ""),
|
|
87
|
-
line,
|
|
88
|
-
labels: labelValues[index] ? parseLabel(labelValues[index]) : {},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
logs.sort((left, right) => (left.timestamp > right.timestamp ? -1 : 1));
|
|
94
|
-
|
|
95
|
-
const result = {
|
|
96
|
-
success: true,
|
|
97
|
-
message: `LogQL query returned ${logs.length} log line(s)`,
|
|
98
|
-
data: { logs, query: logql, logCount: logs.length },
|
|
99
|
-
executionTimeMs: Date.now() - startedAt,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
yield* Console.log(formatOutput(result, format));
|
|
103
|
-
}).pipe(
|
|
104
|
-
Effect.catch((error) =>
|
|
105
|
-
Effect.gen(function* () {
|
|
106
|
-
const result = {
|
|
107
|
-
success: false,
|
|
108
|
-
message: "Failed to execute LogQL query",
|
|
109
|
-
error: formatGrafanaError(error),
|
|
110
|
-
hint: "Check LogQL syntax and Grafana/Loki connectivity",
|
|
111
|
-
executionTimeMs: Date.now() - startedAt,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
yield* Console.log(formatOutput(result, format));
|
|
115
|
-
}),
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
},
|
|
119
|
-
).pipe(Command.withDescription("Query logs via Grafana/Loki"));
|
|
120
|
-
|
|
121
|
-
export const logsCommand = Command.make("logs", {}).pipe(
|
|
122
|
-
Command.withDescription("Loki log operations"),
|
|
123
|
-
Command.withSubcommands([queryCommand]),
|
|
124
|
-
);
|