Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ In case multi-cluster support is enabled (default) and you have access to multip
- `resource_type` (`string`) **(required)** - Type of resource to get details for (service, workload)
- `step` (`string`) - Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds

- **workload_logs** - Get logs for a specific workload's pods in a namespace. Only requires namespace and workload name - automatically discovers pods and containers. Optionally filter by container name, time range, and other parameters. Container is auto-detected if not specified.
- **kiali_workload_logs** - Get logs for a specific workload's pods in a namespace. Only requires namespace and workload name - automatically discovers pods and containers. Optionally filter by container name, time range, and other parameters. Container is auto-detected if not specified.
- `container` (`string`) - Optional container name to filter logs. If not provided, automatically detects and uses the main application container (excludes istio-proxy and istio-init)
- `namespace` (`string`) **(required)** - Namespace containing the workload
- `since` (`string`) - Time duration to fetch logs from (e.g., '5m', '1h', '30s'). If not provided, returns recent logs
Expand Down
21 changes: 3 additions & 18 deletions pkg/kiali/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ import (
// - namespace: the namespace containing the workload
// - workload: the name of the workload
// - container: container name (optional, will be auto-detected if not provided)
// - service: service name (optional)
// - duration: time duration (e.g., "5m", "1h") - optional
// - logType: type of logs (app, proxy, ztunnel, waypoint) - optional
// - sinceTime: Unix timestamp for start time - optional
// - maxLines: maximum number of lines to return - optional
func (k *Kiali) WorkloadLogs(ctx context.Context, namespace string, workload string, container string, service string, duration string, logType string, sinceTime string, maxLines string) (string, error) {
func (k *Kiali) WorkloadLogs(ctx context.Context, namespace string, workload string, container string, duration string, maxLines string) (string, error) {
if namespace == "" {
return "", fmt.Errorf("namespace is required")
}
Expand Down Expand Up @@ -77,7 +74,7 @@ func (k *Kiali) WorkloadLogs(ctx context.Context, namespace string, workload str
continue
}

podLogs, err := k.PodLogs(ctx, namespace, pod.Name, podContainer, workload, service, duration, logType, sinceTime, maxLines)
podLogs, err := k.PodLogs(ctx, namespace, pod.Name, podContainer, workload, duration, maxLines)
if err != nil {
// Log the error but continue with other pods
allLogs = append(allLogs, fmt.Sprintf("Error getting logs for pod %s: %v", pod.Name, err))
Expand All @@ -101,12 +98,9 @@ func (k *Kiali) WorkloadLogs(ctx context.Context, namespace string, workload str
// - podName: the name of the pod
// - container: container name (optional, will be auto-detected if not provided)
// - workload: workload name (optional)
// - service: service name (optional)
// - duration: time duration (e.g., "5m", "1h") - optional
// - logType: type of logs (app, proxy, ztunnel, waypoint) - optional
// - sinceTime: Unix timestamp for start time - optional
// - maxLines: maximum number of lines to return - optional
func (k *Kiali) PodLogs(ctx context.Context, namespace string, podName string, container string, workload string, service string, duration string, logType string, sinceTime string, maxLines string) (string, error) {
func (k *Kiali) PodLogs(ctx context.Context, namespace string, podName string, container string, workload string, duration string, maxLines string) (string, error) {
if namespace == "" {
return "", fmt.Errorf("namespace is required")
}
Expand Down Expand Up @@ -167,18 +161,9 @@ func (k *Kiali) PodLogs(ctx context.Context, namespace string, podName string, c
if workload != "" {
q.Set("workload", workload)
}
if service != "" {
q.Set("service", service)
}
if duration != "" {
q.Set("duration", duration)
}
if logType != "" {
q.Set("logType", logType)
}
if sinceTime != "" {
q.Set("sinceTime", sinceTime)
}
if maxLines != "" {
q.Set("maxLines", maxLines)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/mcp/testdata/toolsets-kiali-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,6 @@
"workload"
]
},
"name": "workload_logs"
"name": "kiali_workload_logs"
}
]
61 changes: 6 additions & 55 deletions pkg/toolsets/kiali/logs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package kiali

import (
"encoding/json"
"fmt"

"github.com/google/jsonschema-go/jsonschema"
Expand All @@ -16,7 +15,7 @@ func initLogs() []api.ServerTool {
// Workload logs tool
ret = append(ret, api.ServerTool{
Tool: api.Tool{
Name: "workload_logs",
Name: "kiali_workload_logs",
Description: "Get logs for a specific workload's pods in a namespace. Only requires namespace and workload name - automatically discovers pods and containers. Optionally filter by container name, time range, and other parameters. Container is auto-detected if not specified.",
InputSchema: &jsonschema.Schema{
Type: "object",
Expand Down Expand Up @@ -62,7 +61,6 @@ func workloadLogsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, err
// Extract required parameters
namespace, _ := params.GetArguments()["namespace"].(string)
workload, _ := params.GetArguments()["workload"].(string)
k := params.NewKiali()
if namespace == "" {
return api.NewToolCallResult("", fmt.Errorf("namespace parameter is required")), nil
}
Expand All @@ -76,8 +74,7 @@ func workloadLogsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, err
tail := params.GetArguments()["tail"]

// Convert parameters to Kiali API format
var duration, logType, sinceTime, maxLines string
var service string // We don't have service parameter in our schema, but Kiali API supports it
var duration, maxLines string

// Convert since to duration (Kiali expects duration format like "5m", "1h")
if since != "" {
Expand All @@ -96,56 +93,10 @@ func workloadLogsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, err
}
}

// If no container specified, we need to get workload details first to find the main app container
if container == "" {
workloadDetails, err := k.WorkloadDetails(params.Context, namespace, workload)
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get workload details: %v", err)), nil
}

// Parse the workload details JSON to extract container names
var workloadData struct {
Pods []struct {
Name string `json:"name"`
Containers []struct {
Name string `json:"name"`
} `json:"containers"`
} `json:"pods"`
}

if err := json.Unmarshal([]byte(workloadDetails), &workloadData); err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to parse workload details: %v", err)), nil
}

if len(workloadData.Pods) == 0 {
return api.NewToolCallResult("", fmt.Errorf("no pods found for workload %s in namespace %s", workload, namespace)), nil
}

// Find the main application container (not istio-proxy or istio-init)
for _, pod := range workloadData.Pods {
for _, c := range pod.Containers {
if c.Name != "istio-proxy" && c.Name != "istio-init" {
container = c.Name
break
}
}
if container != "" {
break
}
}

// If no app container found, use the first container
if container == "" && len(workloadData.Pods) > 0 && len(workloadData.Pods[0].Containers) > 0 {
container = workloadData.Pods[0].Containers[0].Name
}
}

if container == "" {
return api.NewToolCallResult("", fmt.Errorf("no container found for workload %s in namespace %s", workload, namespace)), nil
}

// Use the WorkloadLogs method with the correct parameters
logs, err := k.WorkloadLogs(params.Context, namespace, workload, container, service, duration, logType, sinceTime, maxLines)
// WorkloadLogs handles container auto-detection internally, so we can pass empty string
// if container is not specified
k := params.NewKiali()
logs, err := k.WorkloadLogs(params.Context, namespace, workload, container, duration, maxLines)
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get workload logs: %v", err)), nil
}
Expand Down