Class: RailsAiContext::Tools::ReadLogs
- Defined in:
- lib/rails_ai_context/tools/read_logs.rb
Constant Summary collapse
- MAX_READ_BYTES =
1MB
1_048_576- MAX_LINES =
500- LEVEL_HIERARCHY =
{ "DEBUG" => 0, "INFO" => 1, "WARN" => 2, "ERROR" => 3, "FATAL" => 4 }.freeze
Constants inherited from BaseTool
BaseTool::DEFAULT_SESSION, BaseTool::MAX_SESSIONS, BaseTool::MAX_SESSION_ID_LENGTH, BaseTool::SESSION_CONTEXT, BaseTool::SHARED_CACHE
Class Method Summary collapse
Methods inherited from BaseTool
abstract!, abstract?, api_only_app?, api_only_note, cache_key, cached_context, config, current_session, #dedupe_put_patch_routes, detail_param?, error_response, evict_oldest_sessions, extract_method_source_from_file, extract_method_source_from_string, find_closest_match, fuzzy_find_key, guide_row, inherited, introspection_warnings_note, invalid_detail_note, normalize_detail, not_found_response, paginate, rails_app, rails_env_name, registered_tools, reset_all_caches!, reset_cache!, session_from, session_params, session_queries, session_record, session_reset!, static_tier_banner, static_tier_refusal, text_response, touch_session, unavailable_note, with_session, with_session_for
Methods included from SectionFetch
Class Method Details
.call(lines: nil, level: "all", file: nil, search: nil, server_context: nil, **_extra) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/rails_ai_context/tools/read_logs.rb', line 47 def self.call(lines: nil, level: "all", file: nil, search: nil, server_context: nil, **_extra) warnings = [] # Normalize and validate lines original_lines = lines lines = (lines || config.log_lines).to_i if original_lines int_val = original_lines.to_i if int_val < 1 lines = 1 warnings << "lines must be >= 1, using 1" elsif int_val > MAX_LINES lines = MAX_LINES warnings << "lines clamped to #{MAX_LINES} (was #{int_val})" else lines = int_val end end # Validate level level = level.to_s.strip.upcase level = "all" if level.empty? valid_levels = LEVEL_HIERARCHY.keys + [ "ALL" ] unless valid_levels.include?(level.upcase) return text_response("Unknown level: '#{level}'. Valid values: #{valid_levels.join(', ')}") end level = level == "ALL" ? "all" : level # Resolve log file path = resolve_log_file(file) available = available_log_files unless path msg = if available.any? "Log file '#{file || "#{rails_env_name}.log"}' not found.\nAvailable log files: #{available.join(', ')}" else "No log files found in log/. Your app may log to stdout (common in Docker/container environments)." end return text_response(msg) end # Tail the file raw_lines = tail_file(path, lines) if raw_lines.empty? return text_response("# Log: #{File.basename(path)}\nLog file is empty.\n\n---\nAvailable log files: #{available.join(', ')}") end # Detect format and filter by level format = detect_format(raw_lines) filtered = filter_by_level(raw_lines, level, format) # Apply search filter if search && !search.strip.empty? search_term = search.strip filtered = filtered.select { |line| line.downcase.include?(search_term.downcase) } end if filtered.empty? return text_response("# Log: #{File.basename(path)}\nNo entries matching level:#{level}#{" search:\"#{search}\"" if search}.\n\n---\nAvailable log files: #{available.join(', ')}") end # Redact sensitive data redacted = filtered.map { |line| RailsAiContext::Redaction.redact_log_line(line) } # Format output file_size = File.size(path) size_label = if file_size > 1_000_000 "#{(file_size / 1_000_000.0).round(1)} MB" elsif file_size > 1_000 "#{(file_size / 1_000.0).round(1)} KB" else "#{file_size} B" end level_label = level == "all" ? "all levels" : "#{level}+" output = [ "# Log: #{File.basename(path)}" ] output << "Size: #{size_label} | Showing last #{count_phrase(redacted.size, "line")} | Level: #{level_label}" warnings.each { |w| output << "**Warning:** #{w}" } if warnings.any? output << "" output << "```" output.concat(redacted) output << "```" output << "" output << "---" output << "Available log files: #{available.join(', ')}" text_response(output.join("\n")) end |