Skip to content

Conversation

@aqsaaqeel
Copy link

@aqsaaqeel aqsaaqeel commented Oct 27, 2025

Description

Added comprehensive usage statistics tracking system to monitor LLM provider performance, generation metrics, and user patterns. This enhancement provides insights into which providers are most used, their success rates, average generation times, and token usage patterns.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Other (please describe):

Related Issue

Fixes #93

Testing

  • Tested with Gemini API
  • Tested with Grok API
  • Tested on Windows
  • Tested on Linux
  • Tested on macOS
  • Added/updated tests (if applicable)

Checklist

  • My code follows the project's code style
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have tested this in a real Git repository
  • I have read the CONTRIBUTING.md guidelines

For Hacktoberfest Participants

  • This PR is submitted as part of Hacktoberfest 2025

Thank you for your contribution! 🎉

Summary by CodeRabbit

  • New Features
    • Added a new stats command to view usage: totals, success/failure rates, avg generation time, total cost, and token usage.
    • Provider ranking and optional --detailed view for per-provider metrics (uses, success rate, avg time, cost, tokens).
    • --reset flag to clear persisted statistics with an interactive confirmation and completion message.
    • Displays cache hit rate and first/last use timestamps when available.
    • Enhanced generation event tracking (timing, cache hits, cost/tokens) for more accurate stats.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

Adds persistent usage statistics: new types and a StatsManager, store integration to record/query GenerationEvent data, instrumentation in message generation (cache hit/miss, timing, tokens, cost), and a new stats CLI command to display and reset aggregated metrics.

Changes

Cohort / File(s) Summary
Types
pkg/types/types.go
Adds exported types: GenerationEvent, UsageStats, and ProviderStats representing per-generation events, aggregated usage metrics, and per-provider summaries (serializable to JSON).
Usage manager
internal/usage/usage.go
New StatsManager with thread-safe recording, persistence (JSON file), accessors (GetStats, GetMostUsedProvider, GetSuccessRate, GetCacheHitRate, GetProviderRanking), and ResetStats. Handles first-run absence of stats file.
Store integration
cmd/cli/store/store.go
Extends StoreMethods with usage *usage.StatsManager and adds methods delegating to it: GetUsageManager, RecordGenerationEvent, GetUsageStats, GetMostUsedProvider, GetOverallSuccessRate, GetCacheHitRate, GetProviderRanking, ResetUsageStats.
Message generation instrumentation
cmd/cli/createMsg.go
Adds startTime measurement, isFirstAttempt gating for cache checks/caching, records GenerationEvent on cache hits and after generation (with timing, token estimates, cost, success/error), and ensures recording failures/logs don't break main flow. Adjusts caching cost logic to use precomputed path for non-first attempts.
Stats CLI command
cmd/cli/stats.go
New Cobra statsCmd with --detailed and --reset flags. Displays overall stats, provider ranking, optional per-provider details, cache hit rate and timestamps when available; supports interactive reset confirmation.
CLI registration
cmd/cli/root.go
Registers statsCmd via rootCmd.AddCommand(statsCmd) alongside existing commands.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CreateMsg
    participant Store
    participant StatsManager
    participant Disk

    User->>CreateMsg: Run create-commit-msg
    CreateMsg->>CreateMsg: startTime = now()
    CreateMsg->>Store: Check cache (only on first attempt)
    alt Cache Hit (first attempt)
        CreateMsg->>Store: RecordGenerationEvent(CacheHit=true, CacheChecked=true)
        Store->>StatsManager: RecordGeneration(event)
        StatsManager->>Disk: Save stats
        CreateMsg-->>User: Return cached message
    else Cache Miss
        CreateMsg->>CreateMsg: Generate message
        CreateMsg->>CreateMsg: Compute generationTime, tokens, cost
        CreateMsg->>Store: RecordGenerationEvent(Success=..., details...)
        Store->>StatsManager: RecordGeneration(event)
        StatsManager->>Disk: Save stats
        alt isFirstAttempt
            CreateMsg->>Store: Cache message (with computed cost)
        end
        CreateMsg-->>User: Return generated message
    end
Loading
sequenceDiagram
    participant User
    participant StatsCLI
    participant Store
    participant StatsManager
    participant Disk

    User->>StatsCLI: Run stats command (--detailed / --reset)
    alt Reset flag
        StatsCLI->>User: Prompt confirmation
        User->>StatsCLI: Confirm
        StatsCLI->>Store: ResetUsageStats()
        Store->>StatsManager: ResetStats()
        StatsManager->>Disk: Write cleared stats
        StatsCLI-->>User: Confirm reset complete
    else Show stats
        StatsCLI->>Store: GetUsageStats()
        Store->>StatsManager: GetStats()
        StatsManager->>Disk: Read stats file (if present)
        StatsManager-->>Store: Return stats copy
        Store-->>StatsCLI: Return stats
        StatsCLI-->>User: Render overall and provider stats (detailed optional)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • internal/usage/usage.go: concurrency (RWMutex), persistence edge cases, JSON marshaling of new structs, correctness of aggregated metrics.
    • cmd/cli/createMsg.go: correctness of isFirstAttempt gating, cache interaction, and that recording does not alter primary control flow on errors.
    • cmd/cli/stats.go: formatting, handling empty-state, and correct usage of store APIs.

Possibly related PRs

Suggested reviewers

  • DFanso

Poem

🐰 I hopped through code with eager paws,
Counting tokens, times, and cause.
Cache or fresh, each event I chart,
Metrics tucked into every part.
A little rabbit, tallying the run—stats bloom beneath the sun. 🥕📊

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: generate stats for llms used" accurately describes the primary change in the changeset. The PR implements a comprehensive usage statistics tracking system for LLM providers, which directly aligns with the title's description of generating statistics for LLMs in use. The title is concise, clear, and specific enough that a developer scanning the history would understand that this change adds statistics generation capabilities for LLM usage tracking.
Linked Issues Check ✅ Passed The pull request fully addresses all coding objectives specified in linked issue #93. The implementation tracks the most used LLM provider through GetMostUsedProvider() and GetProviderRanking() methods [#93]; measures average generation time via ProviderStats and UsageStats structures storing timing metrics [#93]; records success and failure rates through GetSuccessRate() and the Success field in GenerationEvent [#93]; and tracks token usage per provider via ProviderStats fields and RecordGeneration() event recording [#93]. All changes across the store layer, usage manager, new types, stats command, and message generation tracking work together to fulfill these requirements comprehensively.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly aligned with the objectives in linked issue #93. The modifications to cmd/cli/createMsg.go add GenerationEvent recording to instrument generation tracking, which is essential for collecting the metrics required by the feature. The new stats command, usage manager, and data types are all core components of the statistics tracking system. The changes to cmd/cli/store/store.go expose the necessary statistics methods, and no cosmetic, unrelated refactoring, or unrelated bug fixes are present. Every change contributes directly to implementing the requested metrics and analytics functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/cli/createMsg.go (1)

389-759: Update hard-coded LLM pricing constants to Oct 2025 rates

Pricing is stale; current Oct 2025 rates show significant changes:

  • OpenAI GPT-4o: $5.00/$20.00/1M (input/output), code has $2.50/$10.00 (+100%)
  • Gemini 2.5 Flash: $0.30/$2.50/1M, code has $0.15/$0.60 (input +2x, output +4x)
  • Claude Sonnet: $3.00/$15.00/1M (matches code)
  • Grok 4: $3.00/$15.00/1M, code has $5.00/$15.00
  • Groq: $0.075–$1.00/$0.30–$3.00/1M (model-dependent), code uses flat $2.50/$10.00

Update estimateCost() in cmd/cli/createMsg.go with official rates. For Groq, consider defaulting to a conservative middle estimate or documenting the variance.

🧹 Nitpick comments (4)
cmd/cli/stats.go (1)

26-41: Reuse global Store when available; fall back to creating a new one

Avoid opening keyring/cache twice and keep state consistent with the rest of the CLI.

-    Store, err := store.NewStoreMethods()
-    if err != nil {
-      return fmt.Errorf("failed to initialize store: %w", err)
-    }
+    var s *store.StoreMethods
+    if Store != nil {
+      s = Store
+    } else {
+      var err error
+      s, err = store.NewStoreMethods()
+      if err != nil {
+        return fmt.Errorf("failed to initialize store: %w", err)
+      }
+    }
     reset, _ := cmd.Flags().GetBool("reset")
     if reset {
-      if err := resetStatistics(Store); err != nil {
+      if err := resetStatistics(s); err != nil {
         return err
       }
       return nil
     }
     detailed, _ := cmd.Flags().GetBool("detailed")
-    return displayStatistics(Store, detailed)
+    return displayStatistics(s, detailed)
internal/usage/usage.go (3)

56-73: Honor event timestamp when provided

Use the event’s timestamp if set; fall back to now. This preserves chronology if events are ever replayed.

- now := time.Now().UTC().Format(time.RFC3339)
+ now := time.Now().UTC().Format(time.RFC3339)
+ ts := event.Timestamp
+ if strings.TrimSpace(ts) == "" {
+   ts = now
+ }
 ...
- sm.stats.LastUse = now
+ sm.stats.LastUse = ts
 ...
- if sm.stats.FirstUse == "" {
-   sm.stats.FirstUse = now
+ if sm.stats.FirstUse == "" {
+   sm.stats.FirstUse = ts

Note: add strings import at top.


259-273: Use sort.Slice for clarity and O(n log n) complexity

Bubble-sort is fine for ≤6 providers, but sort.Slice is simpler and idiomatic.

- // Sort by usage count (descending)
- for i := 0; i < len(rankings)-1; i++ {
-   for j := i + 1; j < len(rankings); j++ {
-     if rankings[j].uses > rankings[i].uses {
-       rankings[i], rankings[j] = rankings[j], rankings[i]
-     }
-   }
- }
+ // Sort by usage count (descending)
+ sort.Slice(rankings, func(i, j int) bool {
+   return rankings[i].uses > rankings[j].uses
+ })

Note: add sort to imports.


227-239: Optional: write file atomically to prevent partial writes

Write to a temp file and rename to reduce corruption risk on crashes.

- data, err := json.MarshalIndent(sm.stats, "", "  ")
+ data, err := json.MarshalIndent(sm.stats, "", "  ")
  if err != nil {
    return fmt.Errorf("failed to marshal stats: %w", err)
  }
-
- return os.WriteFile(sm.filePath, data, 0600)
+ tmp := sm.filePath + ".tmp"
+ if err := os.WriteFile(tmp, data, 0600); err != nil {
+   return fmt.Errorf("failed to write temp stats file: %w", err)
+ }
+ return os.Rename(tmp, sm.filePath)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 609e976 and 928620f.

📒 Files selected for processing (6)
  • cmd/cli/createMsg.go (1 hunks)
  • cmd/cli/root.go (1 hunks)
  • cmd/cli/stats.go (1 hunks)
  • cmd/cli/store/store.go (3 hunks)
  • internal/usage/usage.go (1 hunks)
  • pkg/types/types.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
pkg/types/types.go (2)
cmd/cli/store/store.go (1)
  • LLMProvider (58-61)
internal/llm/provider.go (1)
  • Provider (24-29)
cmd/cli/stats.go (3)
cmd/cli/root.go (1)
  • Store (11-11)
cmd/cli/store/store.go (3)
  • NewStoreMethods (26-49)
  • StoreMethods (19-23)
  • LLMProvider (58-61)
pkg/types/types.go (2)
  • ProviderStats (161-172)
  • LLMProvider (5-5)
internal/usage/usage.go (2)
pkg/types/types.go (4)
  • UsageStats (146-158)
  • ProviderStats (161-172)
  • LLMProvider (5-5)
  • GenerationEvent (175-184)
utils/storeUtils.go (1)
  • GetConfigPath (30-68)
cmd/cli/createMsg.go (3)
pkg/types/types.go (2)
  • GenerationEvent (175-184)
  • Message (80-83)
internal/llm/provider.go (1)
  • Provider (24-29)
pkg/types/prompt.go (1)
  • BuildCommitPrompt (30-51)
cmd/cli/store/store.go (2)
internal/usage/usage.go (2)
  • StatsManager (16-20)
  • NewStatsManager (23-49)
pkg/types/types.go (3)
  • GenerationEvent (175-184)
  • UsageStats (146-158)
  • LLMProvider (5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Go Binary (windows-latest)
  • GitHub Check: Build Go Binary (macos-latest)
  • GitHub Check: Test (1.21)
🔇 Additional comments (3)
cmd/cli/root.go (1)

144-144: Stats command wiring looks good

statsCmd is correctly added to the root command.

cmd/cli/store/store.go (1)

39-48: Good integration of usage stats manager

StatsManager is initialized once and attached to StoreMethods; delegate methods keep cmd layer simple.

cmd/cli/stats.go (1)

70-76: Guard against divide-by-zero (future-proofing)

You already return early when TotalGenerations==0, so this is safe. Keep as-is.

Please add a tiny unit test covering the zero-stats path to prevent regressions later. I can draft it if helpful.

Comment on lines 174 to 184
// GenerationEvent represents a single commit message generation event for tracking.
type GenerationEvent struct {
Provider LLMProvider `json:"provider"`
Success bool `json:"success"`
GenerationTime float64 `json:"generation_time_ms"`
TokensUsed int `json:"tokens_used"`
Cost float64 `json:"cost"`
CacheHit bool `json:"cache_hit"`
Timestamp string `json:"timestamp"`
ErrorMessage string `json:"error_message,omitempty"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a “cache attempted” signal to avoid skewed hit-rate

Today, any non-hit gets counted as a miss, including regenerations where the cache wasn’t even checked. This inflates misses and depresses hit-rate.

Add a CacheChecked flag to GenerationEvent and only update cache hit/miss when it’s true.

 type GenerationEvent struct {
   Provider       LLMProvider `json:"provider"`
   Success        bool        `json:"success"`
   GenerationTime float64     `json:"generation_time_ms"`
   TokensUsed     int         `json:"tokens_used"`
   Cost           float64     `json:"cost"`
-  CacheHit       bool        `json:"cache_hit"`
+  CacheHit       bool        `json:"cache_hit"`
+  CacheChecked   bool        `json:"cache_checked"`
   Timestamp      string      `json:"timestamp"`
   ErrorMessage   string      `json:"error_message,omitempty"`
 }
🤖 Prompt for AI Agents
In pkg/types/types.go around lines 174 to 184, add a new boolean field
CacheChecked (json:"cache_checked") to the GenerationEvent struct so you can
distinguish between cache misses and cases where the cache was never queried;
then update any code paths that record cache metrics to only treat
CacheHit/Cache miss when CacheChecked is true (i.e., set CacheChecked=true when
the cache was actually queried and only count/update hit-rate when that flag is
true).

@DFanso DFanso self-requested a review October 28, 2025 08:36
@DFanso DFanso added enhancement New feature or request hacktoberfest Eligible for Hacktoberfest hacktoberfest-accepted Approved Hacktoberfest contribution go Pull requests that update go code labels Oct 28, 2025
DFanso

This comment was marked as duplicate.

DFanso

This comment was marked as duplicate.

Copy link
Owner

@DFanso DFanso left a comment

Choose a reason for hiding this comment

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

hey @aqsaaqeel can u review the coderabbit suggestions also

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
pkg/types/types.go (1)

174-185: Good: added CacheChecked to disambiguate cache misses

This unblocks accurate hit-rate accounting and fixes prior skew.

internal/usage/usage.go (1)

74-81: Correct: count hits/misses only when cache was checked

This fixes prior miscounting of regenerations as misses.

🧹 Nitpick comments (5)
cmd/cli/createMsg.go (2)

398-407: Prefer provider-call latency; zero tokens/cost on failures

  • GenerationTime currently includes pre-call overhead. Measure just provider latency for accuracy.
  • When err != nil, set TokensUsed and Cost to 0 to avoid inflating totals.

Apply:

- // Generate new message
- message, err := provider.Generate(ctx, changes, opts)
- generationTime := float64(time.Since(startTime).Nanoseconds()) / 1e6 // Convert to milliseconds
+ // Generate new message and measure provider latency only
+ genStart := time.Now()
+ message, err := provider.Generate(ctx, changes, opts)
+ generationTime := float64(time.Since(genStart).Nanoseconds()) / 1e6 // ms
@@
- // Estimate tokens and cost
- inputTokens := estimateTokens(types.BuildCommitPrompt(changes, opts))
- outputTokens := 100 // Estimate output tokens
- cost := estimateCost(providerType, inputTokens, outputTokens)
+ // Estimate tokens and cost (only on success)
+ var inputTokens, outputTokens int
+ var cost float64
+ if err == nil {
+   inputTokens = estimateTokens(types.BuildCommitPrompt(changes, opts))
+   outputTokens = 100 // rough estimate
+   cost = estimateCost(providerType, inputTokens, outputTokens)
+ }
@@
-    TokensUsed:     inputTokens + outputTokens,
-    Cost:           cost,
+    TokensUsed:     inputTokens + outputTokens,
+    Cost:           cost,

Also applies to: 409-417


379-383: Unify warning logs with pterm.Warning for consistency

Use pterm’s warning style instead of fmt.Printf.

- fmt.Printf("Warning: Failed to record usage statistics: %v\n", err)
+ pterm.Warning.Printf("Failed to record usage statistics: %v\n", err)
@@
- fmt.Printf("Warning: Failed to record usage statistics: %v\n", statsErr)
+ pterm.Warning.Printf("Failed to record usage statistics: %v\n", statsErr)
@@
- fmt.Printf("Warning: Failed to cache message: %v\n", cacheErr)
+ pterm.Warning.Printf("Failed to cache message: %v\n", cacheErr)

Also applies to: 414-417, 426-429

internal/usage/usage.go (3)

56-73: Honor event.Timestamp if provided (fall back to now)

Keeps persisted first/last usage aligned with event source time, not local clock.

- now := time.Now().UTC().Format(time.RFC3339)
+ now := time.Now().UTC().Format(time.RFC3339)
+ // Prefer the event timestamp when valid
+ ts := now
+ if t := strings.TrimSpace(event.Timestamp); t != "" {
+   if _, err := time.Parse(time.RFC3339, t); err == nil {
+     ts = t
+   }
+ }
@@
- sm.stats.LastUse = now
+ sm.stats.LastUse = ts
@@
- if sm.stats.FirstUse == "" {
-   sm.stats.FirstUse = now
+ if sm.stats.FirstUse == "" {
+   sm.stats.FirstUse = ts
@@
-   sm.stats.ProviderStats[event.Provider] = &types.ProviderStats{
-     Name:      event.Provider,
-     FirstUsed: now,
-   }
+   sm.stats.ProviderStats[event.Provider] = &types.ProviderStats{
+     Name:      event.Provider,
+     FirstUsed: ts,
+   }
@@
- providerStats.LastUsed = now
+ providerStats.LastUsed = ts

Add:

+import "strings"

at the top import block to support TrimSpace if not already present.


261-268: Use sort.Slice for simplicity and readability

Current nested loops are fine for small N, but sort.Slice is clearer.

- // Sort by usage count (descending)
- for i := 0; i < len(rankings)-1; i++ {
-   for j := i + 1; j < len(rankings); j++ {
-     if rankings[j].uses > rankings[i].uses {
-       rankings[i], rankings[j] = rankings[j], rankings[i]
-     }
-   }
- }
+ // Sort by usage count (descending)
+ sort.Slice(rankings, func(i, j int) bool { return rankings[i].uses > rankings[j].uses })

Don’t forget:

+import "sort"

Also applies to: 270-276


83-86: Averages include cached events; consider excluding for latency metrics

Including cached hits (often ~0 ms) can skew AverageGenerationTime down. Optionally compute a separate ProviderLatency average excluding cached events (e.g., when event.TokensUsed > 0).

Would you like a follow-up patch to add a ProviderLatencyMs metric derived only from non-cached events?

Also applies to: 107-110

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 928620f and 287d3ef.

📒 Files selected for processing (3)
  • cmd/cli/createMsg.go (1 hunks)
  • internal/usage/usage.go (1 hunks)
  • pkg/types/types.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
pkg/types/types.go (2)
cmd/cli/store/store.go (1)
  • LLMProvider (58-61)
internal/llm/provider.go (1)
  • Provider (24-29)
cmd/cli/createMsg.go (3)
pkg/types/types.go (2)
  • GenerationEvent (175-185)
  • Message (80-83)
internal/llm/provider.go (1)
  • Provider (24-29)
pkg/types/prompt.go (1)
  • BuildCommitPrompt (30-51)
internal/usage/usage.go (4)
pkg/types/types.go (4)
  • UsageStats (146-158)
  • ProviderStats (161-172)
  • LLMProvider (5-5)
  • GenerationEvent (175-185)
utils/storeUtils.go (1)
  • GetConfigPath (30-68)
cmd/cli/store/store.go (1)
  • LLMProvider (58-61)
internal/llm/provider.go (1)
  • Provider (24-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Build
  • GitHub Check: Build Go Binary (windows-latest)
🔇 Additional comments (4)
pkg/types/types.go (2)

145-158: UsageStats schema looks solid

Fields match aggregator logic; JSON tags are consistent.


160-173: ProviderStats design is coherent

Counters + averaged timing + success rate are appropriate for per-provider rollups.

cmd/cli/createMsg.go (2)

367-377: Instrumentation on cache hit is correct

Recording CacheHit=true and CacheChecked=true with a timestamp is exactly what's needed.


404-406: Good: CacheChecked reflects actual lookup attempts

Setting CacheChecked to isFirstAttempt avoids counting regenerations as misses.

@DFanso DFanso self-requested a review October 29, 2025 05:57
Copy link
Owner

@DFanso DFanso left a comment

Choose a reason for hiding this comment

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

Approved 🎊

@DFanso DFanso merged commit b75ec94 into DFanso:main Oct 29, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request go Pull requests that update go code hacktoberfest Eligible for Hacktoberfest hacktoberfest-accepted Approved Hacktoberfest contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add Metrics & Analytics

2 participants