-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: restructure CLI — analyze includes ingest pipeline, move ingest under debug #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5bd60cf
1302c30
3ba13ad
8af29fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,9 @@ | ||
| OPENROUTER_API_KEY=your_openrouter_api_key_here | ||
|
|
||
| # Langfuse tracing (optional, for LLM observability) | ||
| LANGFUSE_HOST=http://localhost:3000 | ||
| LANGFUSE_PUBLIC_KEY=pk-lf-lapp-dev | ||
| LANGFUSE_SECRET_KEY=sk-lf-lapp-dev | ||
|
|
||
| # OpenTelemetry tracing (optional, for distributed tracing with Jaeger) | ||
| OTEL_TRACING_ENABLED=true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,12 @@ import ( | |
| "github.com/spf13/cobra" | ||
| "github.com/strrl/lapp/pkg/analyzer" | ||
| "github.com/strrl/lapp/pkg/multiline" | ||
| "github.com/strrl/lapp/pkg/pattern" | ||
| "github.com/strrl/lapp/pkg/semantic" | ||
| "github.com/strrl/lapp/pkg/store" | ||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/attribute" | ||
| "go.opentelemetry.io/otel/codes" | ||
| ) | ||
|
|
||
| var analyzeModel string | ||
|
|
@@ -18,8 +24,9 @@ func analyzeCmd() *cobra.Command { | |
| cmd := &cobra.Command{ | ||
| Use: "analyze <logfile> [question]", | ||
| Short: "Analyze a log file using an AI agent to find root causes", | ||
| Long: `Read a log file, parse it through the template pipeline, then use an AI agent | ||
| to autonomously explore the processed logs and provide analysis. | ||
| Long: `Read a log file, run the full ingest pipeline (Drain clustering, semantic labeling, | ||
| DuckDB storage), then use an AI agent to autonomously explore the processed logs | ||
| and provide analysis. | ||
|
|
||
| Requires OPENROUTER_API_KEY environment variable to be set. | ||
|
|
||
|
|
@@ -46,6 +53,11 @@ func runAnalyze(cmd *cobra.Command, args []string) error { | |
| question = args[1] | ||
| } | ||
|
|
||
| ctx, span := otel.Tracer("lapp/cmd").Start(cmd.Context(), "cmd.Analyze") | ||
| defer span.End() | ||
|
|
||
| span.SetAttributes(attribute.String("log.file", logFile)) | ||
|
|
||
| // Read all lines | ||
| slog.Info("Reading logs...") | ||
| lines, err := readLines(logFile) | ||
|
|
@@ -56,20 +68,67 @@ func runAnalyze(cmd *cobra.Command, args []string) error { | |
| if err != nil { | ||
| return errors.Errorf("multiline detector: %w", err) | ||
| } | ||
| merged := multiline.MergeSlice(lines, detector) | ||
| merged := multiline.MergeSlice(ctx, lines, detector) | ||
| mergedLines := make([]string, len(merged)) | ||
| for i, m := range merged { | ||
| mergedLines[i] = m.Content | ||
| } | ||
| slog.Info("Read lines", "lines", len(lines), "merged_entries", len(mergedLines)) | ||
|
|
||
| // Refuse to reuse an existing database to avoid silently mixing datasets | ||
| if _, err := os.Stat(dbPath); err == nil { | ||
| return errors.Errorf("database %q already exists; remove it first or choose a different --db path", dbPath) | ||
| } | ||
|
|
||
| // Ingest pipeline: Drain clustering + semantic labeling + DuckDB storage | ||
| drainParser, err := pattern.NewDrainParser() | ||
| if err != nil { | ||
| return errors.Errorf("drain parser: %w", err) | ||
| } | ||
|
|
||
| s, err := store.NewDuckDBStore(dbPath) | ||
STRRL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if err != nil { | ||
| return errors.Errorf("store: %w", err) | ||
| } | ||
| defer func() { _ = s.Close() }() | ||
|
|
||
| if err := s.Init(ctx); err != nil { | ||
| return errors.Errorf("store init: %w", err) | ||
| } | ||
|
|
||
| semanticIDMap, patternCount, templateCount, err := discoverAndSavePatterns(ctx, s, drainParser, mergedLines, semantic.Config{ | ||
|
Comment on lines
+95
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| APIKey: apiKey, | ||
| Model: analyzeModel, | ||
| }) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| templates, err := drainParser.Templates(ctx) | ||
| if err != nil { | ||
| return errors.Errorf("drain templates: %w", err) | ||
| } | ||
| if err := storeLogsWithLabels(ctx, s, merged, templates, semanticIDMap); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| slog.Info("Ingestion complete", | ||
| "lines", len(mergedLines), | ||
| "templates", templateCount, | ||
| "patterns_with_2+_matches", patternCount, | ||
| ) | ||
| slog.Info("Database stored", "path", dbPath) | ||
|
|
||
| // Run AI agent analysis | ||
| config := analyzer.Config{ | ||
| APIKey: apiKey, | ||
| Model: analyzeModel, | ||
| } | ||
|
|
||
| result, err := analyzer.Analyze(cmd.Context(), config, mergedLines, question) | ||
| result, err := analyzer.AnalyzeWithTemplates(ctx, config, mergedLines, templates, question) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return err | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This existence guard makes retries fail after transient errors:
runAnalyzecreates and initializesdbPathbefore the LLM labeling/storage steps, but ifdiscoverAndSavePatternsor later ingest work returns an error (for example, temporary OpenRouter/API failures), the command exits without deleting the new file; the next invocation with the same--dbimmediately aborts here with "database already exists." That turns a recoverable failure into manual cleanup work for every retry.Useful? React with 👍 / 👎.