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
10 changes: 0 additions & 10 deletions .idea/.gitignore

This file was deleted.

10 changes: 0 additions & 10 deletions .idea/go.imports.xml

This file was deleted.

9 changes: 0 additions & 9 deletions .idea/loom.iml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

90 changes: 90 additions & 0 deletions cmd/query/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/Aneeshie/loom/internal/agent"
"github.com/Aneeshie/loom/internal/nlq"

pb "github.com/Aneeshie/loom/proto"
)
Expand All @@ -23,6 +24,7 @@ var (
search = flag.String("search", "", "search logs by message")
since = flag.Duration("since", 0, "show logs since duration (e.g. 1h, 24h, 7m)")
similar = flag.String("similar", "", "semantic search query")
ask = flag.String("ask", "", "ask a natural language question about your logs")
)

func main() {
Expand Down Expand Up @@ -77,6 +79,11 @@ func main() {
return
}

if *ask != "" {
runNLQ(client, *ask, *limit)
return
}

resp, err := client.GetLogs(context.Background(), &pb.GetLogsRequest{
Limit: *limit,
Filter: filter,
Expand Down Expand Up @@ -144,3 +151,86 @@ func runSemanticSearch(client pb.LogServiceClient, query string, limit int64, fi
)
}
}

func runNLQ(
client pb.LogServiceClient,
query string,
limit int64,
) {

parser := nlq.NewOllamaParser("llama3.2:3b")

intent, err := parser.Parse(
context.Background(),
query,
)

if err != nil {
log.Fatal(err)
}

filter := &pb.LogFilter{}

if intent.Level != "" {
filter.Level = &intent.Level
}

if intent.Service != "" {
filter.ServiceName = &intent.Service
}

if intent.Host != "" {
filter.Host = &intent.Host
}

if intent.Since != "" {

duration, err := time.ParseDuration(
intent.Since,
)

if err == nil {

start := time.Now().
Add(-duration).
Unix()

filter.StartTime = &start
}
}
Comment on lines +186 to +200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Go time.ParseDuration support day units like 7d?

💡 Result:

No, Go's time.ParseDuration function does not support "d" (day) or any units larger than hours [1][2][3]. The valid unit suffixes supported by time.ParseDuration are [1][2]: - ns (nanoseconds) - us or µs (microseconds) - ms (milliseconds) - s (seconds) - m (minutes) - h (hours) The Go language designers intentionally omitted units for days or larger intervals to avoid the ambiguity caused by daylight savings time transitions and varying day lengths [4][5][6]. If you need to represent a duration in days, you should convert it to hours (e.g., use "72h" instead of "3d" or "7d") [3][5].

Citations:


🏁 Script executed:

# First, check if the file exists and inspect the relevant lines
cd /
find . -name "main.go" -path "*/cmd/query/*" 2>/dev/null | head -5

Repository: Aneeshie/Loom

Length of output: 95


🏁 Script executed:

# Count lines in the file to understand its size
wc -l cmd/query/main.go

Repository: Aneeshie/Loom

Length of output: 79


🏁 Script executed:

# Read the specific section mentioned (lines 186-200)
sed -n '186,200p' cmd/query/main.go

Repository: Aneeshie/Loom

Length of output: 255


🏁 Script executed:

# Check the broader context around runNLQ function and the LLM prompt
sed -n '155,236p' cmd/query/main.go

Repository: Aneeshie/Loom

Length of output: 1166


🏁 Script executed:

# Check the imports to see what's available
sed -n '1,30p' cmd/query/main.go

Repository: Aneeshie/Loom

Length of output: 931


time.ParseDuration does not support day units ("7d").

The LLM parser likely outputs durations like "7d" for natural language queries such as "last week", but Go's time.ParseDuration only supports ns, us, ms, s, m, h. When parsing fails, the error is silently ignored (lines 192–193), causing the StartTime filter to remain unset.

Either normalize day units (e.g., convert "7d" to "168h") before parsing or warn the user when parsing fails.

Proposed fix — convert days to hours before parsing
 	if intent.Since != "" {
+		// Normalize day units to hours (time.ParseDuration doesn't support "d")
+		since := intent.Since
+		if strings.HasSuffix(since, "d") {
+			daysStr := strings.TrimSuffix(since, "d")
+			if days, err := strconv.Atoi(daysStr); err == nil {
+				since = fmt.Sprintf("%dh", days*24)
+			}
+		}
 
 		duration, err := time.ParseDuration(
-			intent.Since,
+			since,
 		)
 
-		if err == nil {
-
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "warning: could not parse duration %q: %v\n", intent.Since, err)
+		} else {
 			start := time.Now().
 				Add(-duration).
 				Unix()
-
 			filter.StartTime = &start
 		}
 	}

Add imports for strconv, strings, and os.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/query/main.go` around lines 186 - 200, The time.ParseDuration function
does not support day units like "7d" that the LLM parser may output, causing
silent failures where the StartTime filter remains unset. Before calling
time.ParseDuration on intent.Since, add preprocessing logic to normalize day
units by converting patterns like "7d" to their equivalent hour format (e.g.,
"168h") using string manipulation. This requires parsing the duration string to
identify day unit suffixes and converting them to supported units before passing
to time.ParseDuration. Consider using strings and strconv packages to handle
this conversion, and optionally warn the user if the parsing still fails after
normalization.


// semantic search path
if intent.Query != "" {

runSemanticSearch(
client,
intent.Query,
limit,
filter,
)

return
}

// filter-only path
resp, err := client.GetLogs(
context.Background(),
&pb.GetLogsRequest{
Limit: limit,
Filter: filter,
},
)

if err != nil {
log.Fatal(err)
}

for _, logEntry := range resp.Logs {
fmt.Printf(
"[%s] %s - %s\n",
logEntry.Level,
logEntry.ServiceName,
logEntry.Message,
)
}
}
18 changes: 18 additions & 0 deletions internal/nlq/intent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nlq

import "context"

type QueryIntent struct {
Query string `json:"query"`
Level string `json:"level"`
Service string `json:"service"`
Host string `json:"host"`
Since string `json:"since"`
}

type IntentParser interface {
Parse(
ctx context.Context,
query string,
) (*QueryIntent, error)
}
Loading
Loading