Skip to content

feat: format tx logs in light program test #1903

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Aug 6, 2025

Summary by CodeRabbit

  • New Features

    • Introduced enhanced transaction logging with detailed, explorer-style logs for all transactions, including instruction hierarchies, account changes, Light Protocol events, and compute usage.
    • Logs are automatically saved to a plain text file in the workspace target directory with configurable verbosity and color options.
    • Added configuration options to enable or disable enhanced logging and Light Protocol event logging.
    • Added comprehensive instruction decoding and formatted output for multiple Solana programs.
    • New documentation explains enhanced logging behavior and configuration.
  • Improvements

    • Added transaction counting for improved tracking during tests.
    • Unified and extended logging capabilities in transaction processing with conditional enhanced log output.
  • Bug Fixes

    • Refined console log output to avoid duplication when enhanced logging is enabled.
  • Documentation

    • Added a new documentation file describing the enhanced logging feature and configuration options.

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Walkthrough

This update introduces an enhanced logging system to the light-program-test library, adding detailed, explorer-style transaction logs with configurable verbosity, color, and protocol-specific parsing. The changes include new configuration and formatting modules, updates to test infrastructure to support transaction counting, and integration of the logging system into transaction processing. Documentation and dependencies are also updated to support these features.

Changes

Cohort / File(s) Change Summary
Documentation
CLAUDE.md
Added documentation describing the enhanced logging feature, its configuration, and usage details.
Dependency Management
Cargo.toml, sdk-libs/program-test/Cargo.toml
Added the tabled crate and several new dependencies (serde, serde_json, solana-transaction-status, bs58, light-sdk-types, tabled, chrono) to support logging and formatting features.
Logging System Core
sdk-libs/program-test/src/lib.rs, sdk-libs/program-test/src/logging/mod.rs
Introduced the public logging module and implemented the enhanced logging system, including file and optional console output, session management, and integration points.
Logging Configuration
sdk-libs/program-test/src/logging/config.rs
Added EnhancedLoggingConfig struct and LogVerbosity enum for flexible, environment-aware logging settings with builder methods and defaults.
Instruction Decoding
sdk-libs/program-test/src/logging/decoder.rs
Implemented detailed instruction decoding for Light Protocol and common Solana programs, including error handling and event extraction.
Log Formatting
sdk-libs/program-test/src/logging/formatter.rs
Added transaction formatting utilities for explorer-style, colorized, and hierarchical log output with account tables and pretty-printing.
Logging Types
sdk-libs/program-test/src/logging/types.rs
Defined comprehensive types for enhanced transaction logs, instructions, account changes, events, and parsing logic.
Test Infrastructure: Transaction Counter
sdk-libs/program-test/src/program_test/light_program_test.rs, program-tests/registry-test/tests/tests.rs
Added a transaction_counter field to LightProgramTest and initialized it in the constructor and test setup.
Test Infrastructure: Logging Integration
sdk-libs/program-test/src/program_test/config.rs, sdk-libs/program-test/src/program_test/rpc.rs
Extended ProgramTestConfig with logging options and integrated enhanced logging into transaction processing, including conditional console and file output, and unified log printing.

Sequence Diagram(s)

sequenceDiagram
    participant Tester as LightProgramTest
    participant RPC as RPC Methods
    participant Logger as Logging Module
    participant File as Log File
    participant Console as Console

    Tester->>RPC: process_transaction(tx)
    RPC->>Logger: log_transaction_enhanced(config, tx, result, signature, slot, counter)
    alt Enhanced logging enabled
        Logger->>File: Write formatted transaction log
        alt Console output enabled
            Logger->>Console: Print formatted transaction log
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

A rabbit with logs, so neat and bright,
Hopped through the code in the pale moonlight.
With colors and tables, and parsing so keen,
Each transaction’s story is now clearly seen.
From counters to configs, the details align—
Explorer-style logs, all by design!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/feat-light-program-test-logs

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 4

🔭 Outside diff range comments (1)
sdk-libs/program-test/src/program_test/rpc.rs (1)

179-190: Missing enhanced logging in process_transaction_with_context.

This method increments the transaction counter but doesn't call the enhanced logging functions, unlike process_transaction. This creates inconsistent logging behavior.

Consider adding enhanced logging similar to process_transaction:

 async fn process_transaction_with_context(
     &mut self,
     transaction: Transaction,
 ) -> Result<(Signature, Slot), RpcError> {
     let sig = *transaction.signatures.first().unwrap();
     self.transaction_counter += 1;
+    let transaction_for_logging = transaction.clone();
+    let signature = sig;
     let _res = self.context.send_transaction(transaction).map_err(|x| {
         if self.config.log_failed_tx {
             println!("{}", x.meta.pretty_logs());
         }
         RpcError::TransactionError(x.err)
     })?;
-    self.maybe_print_logs(_res.pretty_logs());

     let slot = self.context.get_sysvar::<Clock>().slot;
+    
+    // Add enhanced logging
+    if crate::logging::should_use_enhanced_logging(&self.config) {
+        crate::logging::log_transaction_enhanced(
+            &self.config,
+            &transaction_for_logging,
+            &Ok(_res.clone()),
+            &signature,
+            slot,
+            self.transaction_counter,
+        );
+    }
+    
+    self.maybe_print_logs(_res.pretty_logs());
     Ok((sig, slot))
 }
🧹 Nitpick comments (8)
CLAUDE.md (1)

6-8: Add language specifier to fenced code block.

The code block should specify the language for proper syntax highlighting.

-```
+```text
 target/light_program_test.log

</blockquote></details>
<details>
<summary>sdk-libs/program-test/src/program_test/rpc.rs (1)</summary><blockquote>

`449-481`: **Complex nested console logging logic could be simplified.**

The console logging logic has deep nesting with multiple conditions. Consider extracting this into a helper method for better readability.

Consider refactoring into a helper method:
```rust
fn log_to_console(&self, transaction: &Transaction, result: &TransactionResult, signature: &Signature, slot: u64, event: &Option<Vec<BatchPublicTransactionEvent>>) {
    if self.config.no_logs {
        return;
    }
    
    #[cfg(debug_assertions)]
    {
        if std::env::var("RUST_BACKTRACE").is_err() {
            return;
        }
        
        let use_enhanced = crate::logging::should_use_enhanced_logging(&self.config) 
            && self.config.enhanced_logging.log_events;
            
        if use_enhanced {
            crate::logging::log_transaction_enhanced_with_console(
                &self.config,
                transaction,
                result,
                signature,
                slot,
                self.transaction_counter,
                true,
            );
        } else {
            println!("{}", result.pretty_logs());
        }
        
        if self.config.log_light_protocol_events {
            if let Some(ref event_data) = event {
                println!("event:\n {:?}", event_data);
            }
        }
    }
}
sdk-libs/program-test/src/logging/mod.rs (2)

118-136: Consider adding error handling for file operations.

The current implementation silently ignores file I/O errors using let _ =. Consider at least logging errors to stderr for debugging purposes.

 fn write_to_log_file(content: &str) {
     // Ensure session is initialized
     initialize_log_file();
 
     let log_path = get_log_file_path();
 
     // Ensure parent directory exists
     if let Some(parent) = log_path.parent() {
-        let _ = std::fs::create_dir_all(parent);
+        if let Err(e) = std::fs::create_dir_all(parent) {
+            eprintln!("Warning: Failed to create log directory: {}", e);
+        }
     }
 
     // Strip ANSI color codes for file output
     let clean_content = strip_ansi_codes(content);
 
     // Append transaction log to existing file
     if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
-        let _ = writeln!(file, "{}", clean_content);
+        if let Err(e) = writeln!(file, "{}", clean_content) {
+            eprintln!("Warning: Failed to write to log file: {}", e);
+        }
+    } else {
+        eprintln!("Warning: Failed to open log file at {:?}", log_path);
     }
 }

71-93: Consider log rotation to prevent unbounded file growth.

The log file is only truncated at session start but appends indefinitely during the session. For long-running test suites, this could lead to very large log files.

Consider implementing:

  1. A maximum file size limit with rotation (e.g., light_program_test.log.1, light_program_test.log.2)
  2. A configuration option to control log retention
  3. Periodic cleanup of old log files
sdk-libs/program-test/src/logging/decoder.rs (2)

25-29: Use Solana SDK's system program ID constant instead of hardcoded string.

Replace the hardcoded string comparison with the SDK's built-in constant for better maintainability.

-        if account.pubkey.to_string() == "11111111111111111111111111111111" {
+        if account.pubkey == solana_sdk::system_program::ID {

109-109: Consider using try_into().ok() for more idiomatic error handling.

While the unwrap() is safe due to the length check, using ok() would be more consistent with Rust idioms.

-    let discriminator: [u8; 8] = data[0..8].try_into().unwrap();
+    let discriminator: [u8; 8] = data[0..8].try_into().expect("already checked length");
sdk-libs/program-test/src/logging/formatter.rs (1)

1162-1233: Consider extracting hardcoded account mappings to a configuration file or constants module.

The function contains many hardcoded test account addresses that could be better organized in a separate configuration for maintainability.

Consider creating a separate module or configuration file for account name mappings:

// In a separate config module
pub const KNOWN_ACCOUNTS: &[(&str, &str)] = &[
    ("FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy", "test program"),
    ("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT", "v1 state merkle tree"),
    // ... other mappings
];
sdk-libs/program-test/src/logging/types.rs (1)

253-255: Document or make configurable the hardcoded fee and compute limit values.

The hardcoded fee calculation (5000 lamports) and compute limit (1,400,000) should either be documented as estimates or made configurable.

-        // Calculate estimated fee (basic calculation: signatures * lamports_per_signature)
-        // Default Solana fee is 5000 lamports per signature
-        let estimated_fee = (transaction.signatures.len() as u64) * 5000;
+        // Calculate estimated fee (basic calculation: signatures * lamports_per_signature)
+        // Default Solana fee is 5000 lamports per signature
+        // Note: This is a simplified estimate and may not reflect actual fees
+        const LAMPORTS_PER_SIGNATURE: u64 = 5000;
+        let estimated_fee = (transaction.signatures.len() as u64) * LAMPORTS_PER_SIGNATURE;

-            compute_total: 1_400_000, // Default compute limit
+            compute_total: 1_400_000, // Default Solana compute unit limit

Also applies to: 343-343

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3b63d3 and f623572.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CLAUDE.md (1 hunks)
  • Cargo.toml (1 hunks)
  • program-tests/registry-test/tests/tests.rs (1 hunks)
  • sdk-libs/program-test/Cargo.toml (1 hunks)
  • sdk-libs/program-test/src/lib.rs (1 hunks)
  • sdk-libs/program-test/src/logging/config.rs (1 hunks)
  • sdk-libs/program-test/src/logging/decoder.rs (1 hunks)
  • sdk-libs/program-test/src/logging/formatter.rs (1 hunks)
  • sdk-libs/program-test/src/logging/mod.rs (1 hunks)
  • sdk-libs/program-test/src/logging/types.rs (1 hunks)
  • sdk-libs/program-test/src/program_test/config.rs (4 hunks)
  • sdk-libs/program-test/src/program_test/light_program_test.rs (2 hunks)
  • sdk-libs/program-test/src/program_test/rpc.rs (7 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md

6-6: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (21)
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-test"])
  • GitHub Check: lint
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (random-e2e-test, ["cargo-test-sbf -p e2e-test"])
  • GitHub Check: Test sdk-libs
  • GitHub Check: Test program-libs-fast
  • GitHub Check: system-programs (system-cpi-test-v2-other, ["cargo-test-sbf -p system-cpi-v2-test -- --skip funct...
  • GitHub Check: system-programs (account-compression, ["cargo-test-sbf -p account-compression-test"])
  • GitHub Check: system-programs (light-registry, ["cargo-test-sbf -p registry-test"])
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: system-programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-functional, ["cargo-test-sbf -p system-cpi-v2-test -- functio...
  • GitHub Check: system-programs (system-cpi-test-v2-event, ["cargo-test-sbf -p system-cpi-v2-test -- event::parse"])
  • GitHub Check: stateless-js-v1
  • GitHub Check: system-programs (light-compressed-token, ["cargo-test-sbf -p compressed-token-test"])
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test concurrent-merkle-tree
  • GitHub Check: system-programs (light-system-program, ["cargo-test-sbf -p system-test"])
  • GitHub Check: stateless-js-v2
  • GitHub Check: cli-v1
🔇 Additional comments (13)
Cargo.toml (1)

200-200: LGTM!

Clean addition of the tabled dependency to support enhanced transaction log formatting. The version and placement under the "Math and crypto" section are appropriate.

sdk-libs/program-test/src/logging/config.rs (4)

1-26: Well-designed configuration structure.

The EnhancedLoggingConfig struct provides a comprehensive set of options for controlling logging behavior. The field documentation is clear and the separation between enabling logging vs. event output is well thought out.


44-55: Logical verbosity levels.

The LogVerbosity enum provides a clear progression from brief to full detail, with good documentation explaining what each level includes.


88-96: Smart environment-based configuration.

The from_env() method using RUST_BACKTRACE as a signal for debug-level logging is a intuitive pattern that aligns with developer expectations.


98-109: Clean builder pattern implementation.

The with_logging() and without_logging() methods follow proper builder pattern conventions and provide a fluent API for configuration.

sdk-libs/program-test/src/lib.rs (1)

117-117: LGTM!

Clean addition of the logging module to expose the enhanced logging functionality at the crate root. The placement among other module declarations is appropriate.

program-tests/registry-test/tests/tests.rs (1)

196-196: LGTM!

Proper initialization of the new transaction_counter field to support enhanced logging. The initial value of 0 is appropriate for a counter.

sdk-libs/program-test/Cargo.toml (1)

54-59: Necessary dependencies for enhanced logging.

All six new dependencies are appropriately needed for the enhanced logging functionality:

  • serde/serde_json for configuration serialization
  • solana-transaction-status for transaction parsing
  • bs58 for base58 encoding/decoding
  • light-sdk-types for Light Protocol types
  • tabled for table formatting

Using workspace versions maintains consistency with the project's dependency management pattern.

sdk-libs/program-test/src/program_test/light_program_test.rs (1)

29-29: LGTM! Transaction counter field properly added.

The transaction_counter field is correctly initialized to zero and will track transactions for the enhanced logging system.

Also applies to: 62-62

CLAUDE.md (1)

1-29: Well-structured documentation for the enhanced logging feature.

The documentation clearly explains the logging system's features, configuration options, and file location. This will be helpful for users.

sdk-libs/program-test/src/program_test/config.rs (1)

12-12: Configuration changes properly implemented.

The new logging configuration fields and builder methods are well-structured and follow Rust conventions. The default initialization using EnhancedLoggingConfig::from_env() allows for flexible environment-based configuration.

Also applies to: 31-40, 90-100, 130-131

sdk-libs/program-test/src/program_test/rpc.rs (1)

144-167: Enhanced logging integration looks good.

The transaction counter increments and enhanced logging calls are properly placed in the transaction processing flow. The fallback to basic logging when enhanced logging is disabled ensures backward compatibility.

Also applies to: 343-447

sdk-libs/program-test/src/logging/mod.rs (1)

42-68: Robust log file path resolution with proper fallback.

The implementation correctly uses cargo metadata to find the workspace target directory and has a sensible fallback. This ensures consistent log file location across different working directories.

Comment on lines +96 to +115
fn strip_ansi_codes(text: &str) -> String {
// Simple regex-free approach to remove ANSI escape sequences
let mut result = String::with_capacity(text.len());
let mut chars = text.chars();

while let Some(ch) = chars.next() {
if ch == '\x1b' {
// Found escape character, skip until we find 'm' (end of color code)
for next_ch in chars.by_ref() {
if next_ch == 'm' {
break;
}
}
} else {
result.push(ch);
}
}

result
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

ANSI stripping implementation could miss edge cases.

The current implementation handles basic ANSI color codes but might miss other escape sequences. Consider using a dedicated library like strip-ansi-escapes for more robust handling.


🌐 Web query:

What are the common ANSI escape sequence patterns beyond color codes that should be handled when stripping ANSI from text?

💡 Result:

When stripping ANSI escape sequences from text, it's important to handle a variety of sequences beyond just color codes. ANSI escape sequences are used for a wide range of terminal control functions, not just colors. Here are some common patterns you should consider:

  1. CSI (Control Sequence Introducer) sequences:
    These start with ESC [, often represented as \x1b[ or \033[, and are followed by zero or more parameters (digits separated by semicolons) and terminated by a single final character (a letter). Examples include:

    • Cursor movement: ESC [ <n> A (cursor up), ESC [ <n> B (cursor down), etc.
    • Erasing parts of the screen or line: ESC [ <n> J, ESC [ <n> K
    • Set graphics rendition (SGR) codes for color and style: ESC [ <params> m
  2. OSC (Operating System Command) sequences:
    Start with ESC ], followed by a command number, a semicolon, an argument string, and terminated by either BEL (\x07) or ESC \. Example:

    • ESC ] 0;some title BEL (Set terminal title)
  3. Other escape sequences:

    • Single-character sequences: Usually ESC followed by a single character like ESC M (reverse linefeed)
    • DEL or other control sequences

Common regex pattern components for stripping ANSI sequences:

\033\[([0-9;]*[A-Za-z])      # CSI sequences
\033\][^\a]*\a               # OSC sequences ending with BEL
\033\][^\033]*(\033\\)       # OSC sequences ending with ESC \
\033[@-Z\\-_]                # Other 2-character sequences

Practical example for stripping ANSI codes:

\x1b\[[0-9;]*[A-Za-z]       # CSI (color, cursor control, etc.)
\x1b\][^\x07]*(\x07|\x1b\\) # OSC (terminal titles, etc.)
\x1b[@-_]                   # Other escape sequences

Summary of what to handle:

  • Control Sequence Introducer (CSI): ESC [ sequences
  • Operating System Command (OSC): ESC ] sequences
  • Single-character escape sequences: ESC plus one character
  • Other miscellaneous escape sequences

This broad handling ensures that all ANSI escape sequences, not just colors, are properly stripped from text.


Enhance ANSI stripping to cover all control sequences or use a dedicated crate

The current strip_ansi_codes only skips SGR color codes (ESC … m) and will miss many other ANSI sequences (cursor movements, terminal titles, single-char escapes, etc.). You should either extend your implementation or pull in a battle-tested library.

Suggested approaches:

• Use a comprehensive regex covering:
– CSI sequences: \x1B\[[0-9;]*[A-Za-z]
– OSC sequences: \x1B\][^\x07]*(\x07|\x1B\\)
– Other escapes: \x1B[@-Z\\-_]

Example in sdk-libs/program-test/src/logging/mod.rs:

use regex::Regex;
lazy_static! {
    static ref ANSI_RE: Regex = Regex::new(
        r"(?x)
        \x1B\[[0-9;]*[A-Za-z]       # CSI (colors, cursor, etc.)
      | \x1B\][^\x07]*(\x07|\x1B\\) # OSC (titles, etc.)
      | \x1B[@-Z\\-_]               # Other simple escapes
        "
    ).unwrap();
}

fn strip_ansi_codes(text: &str) -> String {
    ANSI_RE.replace_all(text, "").into_owned()
}

• Or adopt an existing crate such as strip-ansi-escapes or the console crate’s strip_ansi_codes function for guaranteed coverage of edge cases.

🤖 Prompt for AI Agents
In sdk-libs/program-test/src/logging/mod.rs around lines 96 to 115, the current
strip_ansi_codes function only removes SGR color codes and misses other ANSI
escape sequences. To fix this, replace the manual implementation with a
comprehensive regex that matches all ANSI control sequences including CSI, OSC,
and other escapes, or alternatively use a well-maintained crate like
strip-ansi-escapes or console's strip_ansi_codes function to ensure full
coverage of ANSI sequences.

@ananas-block ananas-block force-pushed the jorrit/feat-light-program-test-logs branch from f623572 to b979127 Compare August 6, 2025 10:29
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: 1

♻️ Duplicate comments (1)
sdk-libs/program-test/src/logging/mod.rs (1)

96-115: Enhance ANSI stripping to handle all escape sequences

The current implementation only removes SGR color codes (ending with 'm') but misses other ANSI sequences like cursor movements, terminal titles, and other control sequences.

Consider using a comprehensive regex pattern or a dedicated crate:

-fn strip_ansi_codes(text: &str) -> String {
-    // Simple regex-free approach to remove ANSI escape sequences
-    let mut result = String::with_capacity(text.len());
-    let mut chars = text.chars();
-
-    while let Some(ch) = chars.next() {
-        if ch == '\x1b' {
-            // Found escape character, skip until we find 'm' (end of color code)
-            for next_ch in chars.by_ref() {
-                if next_ch == 'm' {
-                    break;
-                }
-            }
-        } else {
-            result.push(ch);
-        }
-    }
-
-    result
-}
+fn strip_ansi_codes(text: &str) -> String {
+    use regex::Regex;
+    use once_cell::sync::Lazy;
+    
+    static ANSI_RE: Lazy<Regex> = Lazy::new(|| {
+        Regex::new(
+            r"(?x)
+            \x1B\[[0-9;]*[A-Za-z]       # CSI (colors, cursor, etc.)
+          | \x1B\][^\x07]*(\x07|\x1B\\) # OSC (titles, etc.)  
+          | \x1B[@-Z\\-_]               # Other simple escapes
+            "
+        ).unwrap()
+    });
+    
+    ANSI_RE.replace_all(text, "").into_owned()
+}

Or use the console crate:

use console::strip_ansi_codes;
🧹 Nitpick comments (3)
CLAUDE.md (1)

6-8: Add language specifier to fenced code block for proper syntax highlighting

The code block should specify the language for better rendering in markdown viewers.

-```
+```text
 target/light_program_test.log

</blockquote></details>
<details>
<summary>sdk-libs/program-test/src/logging/formatter.rs (1)</summary><blockquote>

`1162-1234`: **Consider extracting hardcoded account mappings to a configuration module**

The `get_account_name` function contains many hardcoded account name mappings. For better maintainability, consider moving these to a dedicated constant module or configuration file.


Example approach:
```rust
// In a new file: src/logging/known_accounts.rs
use once_cell::sync::Lazy;
use std::collections::HashMap;
use solana_sdk::pubkey::Pubkey;

pub static KNOWN_ACCOUNTS: Lazy<HashMap<Pubkey, &'static str>> = Lazy::new(|| {
    let mut map = HashMap::new();
    
    // Test accounts
    map.insert(
        "smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT".parse().unwrap(),
        "v1 state merkle tree"
    );
    // ... add other mappings
    
    map
});

Then simplify the function:

fn get_account_name(&self, pubkey: &Pubkey) -> String {
    KNOWN_ACCOUNTS.get(pubkey)
        .map(|&name| name.to_string())
        .unwrap_or_else(|| {
            if pubkey.is_on_curve() {
                "user account".to_string()
            } else {
                "pda account".to_string()
            }
        })
}
sdk-libs/program-test/src/logging/types.rs (1)

471-480: Deduplicate the get_program_name function

This function duplicates similar logic from decoder.rs (lines 944-956). Consider consolidating into a single shared utility function.

Move to a shared module:

// In src/logging/utils.rs or similar
pub fn get_program_name(program_id: &Pubkey) -> String {
    match program_id.to_string().as_str() {
        id if id == system_program::ID.to_string() => "System Program".to_string(),
        "ComputeBudget111111111111111111111111111111" => "Compute Budget".to_string(),
        "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" => "Light System Program".to_string(),
        "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" => "Account Compression".to_string(),
        "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m" => "Compressed Token Program".to_string(),
        "FNt7byTHev1k5x2cXZLBr8TdWiC3zoP5vcnZR4P682Uy" => "Test Program".to_string(),
        _ => {
            let pubkey_str = program_id.to_string();
            format!("Program {}", &pubkey_str[..8])
        }
    }
}

Then import and use in both files.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f623572 and b979127.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CLAUDE.md (1 hunks)
  • Cargo.toml (1 hunks)
  • program-tests/registry-test/tests/tests.rs (1 hunks)
  • sdk-libs/program-test/Cargo.toml (1 hunks)
  • sdk-libs/program-test/src/lib.rs (1 hunks)
  • sdk-libs/program-test/src/logging/config.rs (1 hunks)
  • sdk-libs/program-test/src/logging/decoder.rs (1 hunks)
  • sdk-libs/program-test/src/logging/formatter.rs (1 hunks)
  • sdk-libs/program-test/src/logging/mod.rs (1 hunks)
  • sdk-libs/program-test/src/logging/types.rs (1 hunks)
  • sdk-libs/program-test/src/program_test/config.rs (4 hunks)
  • sdk-libs/program-test/src/program_test/light_program_test.rs (2 hunks)
  • sdk-libs/program-test/src/program_test/rpc.rs (7 hunks)
✅ Files skipped from review due to trivial changes (2)
  • sdk-libs/program-test/src/lib.rs
  • Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (6)
  • sdk-libs/program-test/Cargo.toml
  • sdk-libs/program-test/src/program_test/light_program_test.rs
  • sdk-libs/program-test/src/program_test/rpc.rs
  • program-tests/registry-test/tests/tests.rs
  • sdk-libs/program-test/src/logging/config.rs
  • sdk-libs/program-test/src/program_test/config.rs
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md

6-6: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (22)
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test sdk-libs
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test program-libs-slow
  • GitHub Check: Test concurrent-merkle-tree
  • GitHub Check: system-programs (system-cpi-test-v2-event, ["cargo-test-sbf -p system-cpi-v2-test -- event::parse"])
  • GitHub Check: system-programs (light-compressed-token, ["cargo-test-sbf -p compressed-token-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-functional, ["cargo-test-sbf -p system-cpi-v2-test -- functio...
  • GitHub Check: system-programs (system-cpi-test-v2-other, ["cargo-test-sbf -p system-cpi-v2-test -- --skip funct...
  • GitHub Check: system-programs (light-system-program, ["cargo-test-sbf -p system-test"])
  • GitHub Check: system-programs (light-registry, ["cargo-test-sbf -p registry-test"])
  • GitHub Check: system-programs (account-compression, ["cargo-test-sbf -p account-compression-test"])
  • GitHub Check: system-programs (random-e2e-test, ["cargo-test-sbf -p e2e-test"])
  • GitHub Check: system-programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test"])
  • GitHub Check: stateless-js-v2
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-test"])
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: stateless-js-v1
  • GitHub Check: lint
  • GitHub Check: cli-v2
  • GitHub Check: cli-v1
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +220 to +226
/// Parse Invoke instruction data - display data hashes directly
fn parse_invoke_instruction(data: &[u8], accounts: &[AccountMeta]) -> InstructionParseResult {
// Skip the 4-byte vec length prefix that Anchor adds
if data.len() < 4 {
return Err("Instruction data too short for Anchor prefix".into());
}
let instruction_data = InstructionDataInvoke::try_from_slice(&data[4..])?;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Document the inconsistent 4-byte Anchor prefix handling across instruction types

The parse_invoke_instruction and parse_invoke_cpi_instruction functions skip a 4-byte Anchor vector length prefix, while parse_invoke_cpi_readonly_instruction and parse_invoke_cpi_account_info_instruction don't. This inconsistency should be documented or unified.

If this is intentional due to different serialization approaches, consider adding comments to clarify:

 fn parse_invoke_cpi_readonly_instruction(
     data: &[u8],
     accounts: &[AccountMeta],
 ) -> InstructionParseResult {
+    // Note: This instruction type doesn't have the 4-byte Anchor prefix
     let instruction_data = InstructionDataInvokeCpiWithReadOnly::try_from_slice(data)?;

Also applies to: 363-368, 505-509, 637-642


🏁 Script executed:

#!/bin/bash
# Check if the InstructionData types have different serialization attributes
ast-grep --pattern 'struct InstructionDataInvokeCpiWithReadOnly {
  $$$
}'

ast_grep --pattern 'struct InstructionDataInvokeCpi {
  $$$
}'

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Search for struct definitions and derive attributes for InstructionData types
rg "struct InstructionDataInvoke" -A 5
rg "struct InstructionDataInvokeCpi" -A 5
rg "struct InstructionDataInvokeCpiWithReadOnly" -A 5
rg "struct InstructionDataInvokeCpiAccountInfo" -A 5

Length of output: 7363


🏁 Script executed:

#!/bin/bash
# Extract parse_invoke functions to verify prefix handling
rg -n -A 5 "fn parse_invoke_instruction" sdk-libs/program-test/src/logging/decoder.rs
rg -n -A 5 "fn parse_invoke_cpi_instruction" sdk-libs/program-test/src/logging/decoder.rs
rg -n -A 5 "fn parse_invoke_cpi_readonly_instruction" sdk-libs/program-test/src/logging/decoder.rs
rg -n -A 5 "fn parse_invoke_cpi_account_info_instruction" sdk-libs/program-test/src/logging/decoder.rs

Length of output: 1609


Document Anchor prefix exception in CPI readonly & account_info parsers

To avoid confusion around why only some parsers skip the 4-byte Anchor vec prefix, add a clarifying comment to the CPI-readonly and CPI-account_info parsing functions.

Locations to update in sdk-libs/program-test/src/logging/decoder.rs:

  • parse_invoke_cpi_readonly_instruction (lines 505–509)
  • parse_invoke_cpi_account_info_instruction (lines 637–642)

Suggested diff:

 fn parse_invoke_cpi_readonly_instruction(
     data: &[u8],
     accounts: &[AccountMeta],
 ) -> InstructionParseResult {
+    // This instruction data is serialized directly (no 4-byte Anchor vec length prefix)
     let instruction_data = InstructionDataInvokeCpiWithReadOnly::try_from_slice(data)?;
     …
 }
 
 fn parse_invoke_cpi_account_info_instruction(
     data: &[u8],
     accounts: &[AccountMeta],
     program_id: &Pubkey,
 ) -> InstructionParseResult {
+    // This instruction data is serialized directly (no 4-byte Anchor vec length prefix)
     let instruction_data = InstructionDataInvokeCpiWithAccountInfo::try_from_slice(data)?;
     …
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Parse Invoke instruction data - display data hashes directly
fn parse_invoke_instruction(data: &[u8], accounts: &[AccountMeta]) -> InstructionParseResult {
// Skip the 4-byte vec length prefix that Anchor adds
if data.len() < 4 {
return Err("Instruction data too short for Anchor prefix".into());
}
let instruction_data = InstructionDataInvoke::try_from_slice(&data[4..])?;
/// In sdk-libs/program-test/src/logging/decoder.rs
fn parse_invoke_cpi_readonly_instruction(
data: &[u8],
accounts: &[AccountMeta],
) -> InstructionParseResult {
// This instruction data is serialized directly (no 4-byte Anchor vec length prefix)
let instruction_data = InstructionDataInvokeCpiWithReadOnly::try_from_slice(data)?;
// … rest of function unchanged
}
fn parse_invoke_cpi_account_info_instruction(
data: &[u8],
accounts: &[AccountMeta],
program_id: &Pubkey,
) -> InstructionParseResult {
// This instruction data is serialized directly (no 4-byte Anchor vec length prefix)
let instruction_data = InstructionDataInvokeCpiWithAccountInfo::try_from_slice(data)?;
// … rest of function unchanged
}
🤖 Prompt for AI Agents
In sdk-libs/program-test/src/logging/decoder.rs around lines 505 to 509 and 637
to 642, add clarifying comments in the functions
parse_invoke_cpi_readonly_instruction and
parse_invoke_cpi_account_info_instruction to document the reason for skipping
the 4-byte Anchor vec prefix. This comment should explain that the prefix is
specific to Anchor and why only some parsers handle it by skipping those bytes,
to avoid confusion for future readers.

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: 1

♻️ Duplicate comments (1)
sdk-libs/program-test/src/logging/mod.rs (1)

103-122: The ANSI stripping implementation is incomplete and matches the previous review concern.

As identified in the previous review, the current strip_ansi_codes function only handles SGR color codes ending with 'm' and will miss many other ANSI escape sequences like cursor movements, terminal titles, and other control sequences.

The previous review suggestion to use a comprehensive regex or a dedicated crate like strip-ansi-escapes remains valid and should be implemented to ensure proper ANSI sequence removal from log files.

🧹 Nitpick comments (2)
sdk-libs/program-test/src/logging/mod.rs (2)

44-70: Consider error handling improvements for cargo metadata command execution.

The get_log_file_path() function executes cargo metadata command but silently falls back to a default path when the command fails. While this provides resilience, it might mask legitimate issues.

Consider adding debug logging or warnings when the cargo metadata command fails to help with troubleshooting in development environments.

 fn get_log_file_path() -> PathBuf {
     // Always use cargo workspace target directory
     use std::process::Command;
-    if let Ok(output) = Command::new("cargo")
+    let output = Command::new("cargo")
         .arg("metadata")
         .arg("--format-version=1")
         .arg("--no-deps")
-        .output()
-    {
+        .output();
+    
+    if let Ok(output) = output {
         if output.status.success() {
             if let Ok(metadata) = String::from_utf8(output.stdout) {
                 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&metadata) {
                     if let Some(target_directory) = json["target_directory"].as_str() {
                         let mut path = PathBuf::from(target_directory);
                         path.push("light_program_test.log");
                         return path;
                     }
                 }
             }
         }
+    } else {
+        #[cfg(debug_assertions)]
+        eprintln!("Warning: Failed to get cargo metadata, using fallback log path");
     }
 
     // Fallback to current directory's target
     let mut path = PathBuf::from("target");
     path.push("light_program_test.log");
     path
 }

28-28: Consider using a more specific chrono import.

The bare use chrono; import brings in the entire chrono crate namespace. Consider importing only the specific types needed.

-use chrono;
+use chrono::{DateTime, Utc};

And update the usage:

-            let datetime = chrono::DateTime::from_timestamp(timestamp as i64, 0)
-                .unwrap_or_else(|| chrono::Utc::now());
+            let datetime = DateTime::from_timestamp(timestamp as i64, 0)
+                .unwrap_or_else(|| Utc::now());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b979127 and 8eefe48.

📒 Files selected for processing (4)
  • sdk-libs/program-test/Cargo.toml (1 hunks)
  • sdk-libs/program-test/src/logging/mod.rs (1 hunks)
  • sdk-libs/program-test/src/program_test/light_program_test.rs (3 hunks)
  • sdk-libs/program-test/src/program_test/rpc.rs (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • sdk-libs/program-test/src/program_test/light_program_test.rs
  • sdk-libs/program-test/Cargo.toml
  • sdk-libs/program-test/src/program_test/rpc.rs
⏰ 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). (22)
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test concurrent-merkle-tree
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: Test sdk-libs
  • GitHub Check: Test program-libs-slow
  • GitHub Check: stateless-js-v2
  • GitHub Check: system-programs (random-e2e-test, ["cargo-test-sbf -p e2e-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-other, ["cargo-test-sbf -p system-cpi-v2-test -- --skip funct...
  • GitHub Check: cli-v1
  • GitHub Check: system-programs (system-cpi-test-v2-functional, ["cargo-test-sbf -p system-cpi-v2-test -- functio...
  • GitHub Check: cli-v2
  • GitHub Check: system-programs (light-compressed-token, ["cargo-test-sbf -p compressed-token-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-event, ["cargo-test-sbf -p system-cpi-v2-test -- event::parse"])
  • GitHub Check: system-programs (light-system-program, ["cargo-test-sbf -p system-test"])
  • GitHub Check: system-programs (light-registry, ["cargo-test-sbf -p registry-test"])
  • GitHub Check: system-programs (account-compression, ["cargo-test-sbf -p account-compression-test"])
  • GitHub Check: system-programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test"])
  • GitHub Check: stateless-js-v1
  • GitHub Check: lint
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-test"])
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
sdk-libs/program-test/src/logging/mod.rs (3)

175-177: Early return logic is correct.

The early return when enhanced logging is disabled is appropriate and prevents unnecessary computation when logging is not needed.


41-41: Good use of std::sync::Once for session initialization.

The use of Once to ensure the log file is initialized only once per session is appropriate and thread-safe.


200-202: No changes needed: no_logs correctly overrides enhanced logging

All usages confirm that no_logs is treated as a global “disable all logging” flag and always takes precedence over enhanced_logging.enabled. The should_use_enhanced_logging function’s config.enhanced_logging.enabled && !config.no_logs logic is therefore correct.

Comment on lines +132 to +134
if let Some(parent) = log_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Silent error handling could mask permission issues.

The directory creation and file operations use let _ = to ignore errors, which could mask important issues like permission problems or disk space issues that would prevent logging from working.

Consider at minimum logging errors in debug mode:

     // Ensure parent directory exists
     if let Some(parent) = log_path.parent() {
-        let _ = std::fs::create_dir_all(parent);
+        if let Err(e) = std::fs::create_dir_all(parent) {
+            #[cfg(debug_assertions)]
+            eprintln!("Warning: Failed to create log directory: {}", e);
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if let Some(parent) = log_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
// Ensure parent directory exists
if let Some(parent) = log_path.parent() {
if let Err(e) = std::fs::create_dir_all(parent) {
#[cfg(debug_assertions)]
eprintln!("Warning: Failed to create log directory: {}", e);
}
}
🤖 Prompt for AI Agents
In sdk-libs/program-test/src/logging/mod.rs around lines 132 to 134, the code
ignores errors from creating parent directories using `let _ =`, which can hide
permission or disk space issues. Modify the code to handle the result of
`create_dir_all` by logging any errors encountered, at least in debug mode, so
that failures in directory creation are visible and can be diagnosed.

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: 1

♻️ Duplicate comments (2)
sdk-libs/program-test/src/logging/mod.rs (2)

102-121: ANSI stripping implementation still incomplete despite previous feedback

The current implementation only handles basic SGR color codes ending with 'm' and misses other ANSI escape sequences like cursor movement, OSC sequences, and other control codes. This is a duplicate of the previous review comment that hasn't been addressed.

The previous review comment about comprehensive ANSI stripping still applies. Consider implementing the suggested regex-based approach or using a dedicated crate:

+use regex::Regex;
+use std::sync::OnceLock;
+
+static ANSI_REGEX: OnceLock<Regex> = OnceLock::new();
+
 /// Strip ANSI escape codes from string for plain text log files
 fn strip_ansi_codes(text: &str) -> String {
-    // Simple regex-free approach to remove ANSI escape sequences
-    let mut result = String::with_capacity(text.len());
-    let mut chars = text.chars();
-
-    while let Some(ch) = chars.next() {
-        if ch == '\x1b' {
-            // Found escape character, skip until we find 'm' (end of color code)
-            for next_ch in chars.by_ref() {
-                if next_ch == 'm' {
-                    break;
-                }
-            }
-        } else {
-            result.push(ch);
-        }
-    }
-
-    result
+    let regex = ANSI_REGEX.get_or_init(|| {
+        Regex::new(r"(?x)
+            \x1B\[[0-9;]*[A-Za-z]       # CSI (colors, cursor, etc.)
+          | \x1B\][^\x07]*(\x07|\x1B\\) # OSC (titles, etc.)  
+          | \x1B[@-Z\\-_]               # Other simple escapes
+        ").unwrap()
+    });
+    regex.replace_all(text, "").into_owned()
 }

131-133: Error handling still ignores failures despite previous feedback

The directory creation still uses let _ = to ignore errors, which could mask permission issues or disk space problems. This is a duplicate of the previous review comment.

     // Ensure parent directory exists
     if let Some(parent) = log_path.parent() {
-        let _ = std::fs::create_dir_all(parent);
+        if let Err(e) = std::fs::create_dir_all(parent) {
+            #[cfg(debug_assertions)]
+            eprintln!("Warning: Failed to create log directory: {}", e);
+        }
     }
🧹 Nitpick comments (2)
sdk-libs/program-test/src/logging/mod.rs (2)

43-69: Consider caching the log path to avoid repeated cargo metadata calls

The get_log_file_path() function calls cargo metadata every time it's invoked, which could be expensive if called frequently. Since the target directory is unlikely to change during execution, consider caching the result.

+use std::sync::OnceLock;
+
+static LOG_PATH_CACHE: OnceLock<PathBuf> = OnceLock::new();
+
 /// Get the log file path in target directory
 fn get_log_file_path() -> PathBuf {
+    LOG_PATH_CACHE.get_or_init(|| {
         // Always use cargo workspace target directory
         use std::process::Command;
         if let Ok(output) = Command::new("cargo")
             .arg("metadata")
             .arg("--format-version=1")
             .arg("--no-deps")
             .output()
         {
             if output.status.success() {
                 if let Ok(metadata) = String::from_utf8(output.stdout) {
                     if let Ok(json) = serde_json::from_str::<serde_json::Value>(&metadata) {
                         if let Some(target_directory) = json["target_directory"].as_str() {
                             let mut path = PathBuf::from(target_directory);
                             path.push("light_program_test.log");
                             return path;
                         }
                     }
                 }
             }
         }
     
         // Fallback to current directory's target
         let mut path = PathBuf::from("target");
         path.push("light_program_test.log");
         path
+    }).clone()
 }

87-90: Handle potential timestamp conversion edge case

The chrono::DateTime::from_timestamp() could fail for extreme timestamp values. While the fallback to chrono::Utc::now() handles this, consider logging a debug warning to help with troubleshooting.

             // Format timestamp as readable date
-            let datetime = chrono::DateTime::from_timestamp(timestamp as i64, 0)
-                .unwrap_or_else(chrono::Utc::now);
+            let datetime = chrono::DateTime::from_timestamp(timestamp as i64, 0)
+                .unwrap_or_else(|| {
+                    #[cfg(debug_assertions)]
+                    eprintln!("Warning: Invalid timestamp {}, using current time", timestamp);
+                    chrono::Utc::now()
+                });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eefe48 and 40c2461.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • sdk-libs/program-test/src/logging/mod.rs (1 hunks)
⏰ 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). (22)
  • GitHub Check: stateless-js-v2
  • GitHub Check: stateless-js-v1
  • GitHub Check: Test batched-merkle-tree-simulate
  • GitHub Check: cli-v1
  • GitHub Check: Test concurrent-merkle-tree
  • GitHub Check: Test program-libs-fast
  • GitHub Check: Test sdk-libs
  • GitHub Check: Test program-libs-slow
  • GitHub Check: cli-v2
  • GitHub Check: lint
  • GitHub Check: system-programs (sdk-anchor-test-program, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -...
  • GitHub Check: system-programs (sdk-test-program, ["cargo-test-sbf -p sdk-test"])
  • GitHub Check: system-programs (random-e2e-test, ["cargo-test-sbf -p e2e-test"])
  • GitHub Check: system-programs (light-compressed-token, ["cargo-test-sbf -p compressed-token-test"])
  • GitHub Check: system-programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-event, ["cargo-test-sbf -p system-cpi-v2-test -- event::parse"])
  • GitHub Check: system-programs (light-system-program, ["cargo-test-sbf -p system-test"])
  • GitHub Check: system-programs (light-registry, ["cargo-test-sbf -p registry-test"])
  • GitHub Check: system-programs (system-cpi-test-v2-functional, ["cargo-test-sbf -p system-cpi-v2-test -- functio...
  • GitHub Check: system-programs (system-cpi-test-v2-other, ["cargo-test-sbf -p system-cpi-v2-test -- --skip funct...
  • GitHub Check: system-programs (account-compression, ["cargo-test-sbf -p account-compression-test"])
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
sdk-libs/program-test/src/logging/mod.rs (4)

1-39: Well-structured module with clear documentation and appropriate visibility

The module documentation effectively describes the purpose and behavior of the enhanced logging system. The public module exports and imports are well-organized, providing a clean API surface for consumers.


145-162: Good API design with delegation pattern

The main log_transaction_enhanced function properly delegates to the more flexible version with console control, providing a clean default behavior while allowing customization when needed.


174-176: Early return optimization is appropriate

The early return when enhanced logging is disabled prevents unnecessary processing, which is efficient and correct.


199-201: Logic for should_use_enhanced_logging is correct as implemented
Verified its usage in sdk-libs/program-test/src/program_test/rpc.rs; it correctly gates enhanced logging when config.enhanced_logging.enabled is true and config.no_logs is false. No changes needed.

Comment on lines +139 to +141
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
let _ = writeln!(file, "{}", clean_content);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

File write errors should also be handled consistently

Similar to the directory creation issue, file write errors are silently ignored. Consider applying the same error handling pattern for consistency.

     // Append transaction log to existing file
     if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
-        let _ = writeln!(file, "{}", clean_content);
+        if let Err(e) = writeln!(file, "{}", clean_content) {
+            #[cfg(debug_assertions)]
+            eprintln!("Warning: Failed to write to log file: {}", e);
+        }
     }
🤖 Prompt for AI Agents
In sdk-libs/program-test/src/logging/mod.rs around lines 139 to 141, the file
write operation using writeln! currently ignores any errors. Update the code to
handle errors from writeln! consistently with the directory creation error
handling pattern by checking the result and logging or returning the error
appropriately instead of silently ignoring it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant