Module: LanguageOperator::CLI::Commands::Agent::Logs
- Included in:
- Base
- Defined in:
- lib/language_operator/cli/commands/agent/logs.rb
Overview
Log streaming for agents
Class Method Summary collapse
Class Method Details
.included(base) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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 |
# File 'lib/language_operator/cli/commands/agent/logs.rb', line 12 def self.included(base) base.class_eval do desc 'logs NAME', 'Show agent execution logs' long_desc <<-DESC Stream agent execution logs in real-time. Use -f to follow logs continuously (like tail -f). Examples: langop agent logs my-agent langop agent logs my-agent -f DESC option :cluster, type: :string, desc: 'Override current cluster context' option :follow, type: :boolean, aliases: '-f', default: false, desc: 'Follow logs' option :tail, type: :numeric, default: 100, desc: 'Number of lines to show from the end' def logs(name) handle_command_error('get logs') do ctx = CLI::Helpers::ClusterContext.() # Get agent to determine the pod name agent = get_resource_or_exit(LanguageOperator::Constants::RESOURCE_AGENT, name) mode = agent.dig('spec', 'mode') || 'autonomous' # Build kubectl command for log streaming tail_arg = "--tail=#{[:tail]}" follow_arg = [:follow] ? '-f' : '' # For scheduled agents, logs come from CronJob pods # For autonomous agents, logs come from Deployment pods if mode == 'scheduled' # Get most recent job from cronjob else # Get pod from deployment end # Use normalized label selector for pod discovery label_selector = CLI::Helpers::LabelUtils.agent_pod_selector(name) # Use kubectl logs with label selector cmd = "#{ctx.kubectl_prefix} logs -l #{label_selector} #{tail_arg} #{follow_arg} --all-containers" Formatters::ProgressFormatter.info("Streaming logs for agent '#{name}'...") puts # Track threads and interruption state for cleanup stdout_thread = nil stderr_thread = nil interrupted = false # Install signal handler for graceful interruption original_int_handler = Signal.trap('INT') do interrupted = true stdout_thread&.terminate stderr_thread&.terminate puts "\n[Interrupted]" exit(0) end begin # Stream raw logs in real-time without formatting Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr| # Close unused stdin immediately to prevent resource leak stdin.close # Handle stdout (logs) stdout_thread = Thread.new do stdout.each_line do |line| break if interrupted puts line $stdout.flush end rescue IOError # Expected when stream is closed during interruption end # Handle stderr (errors) stderr_thread = Thread.new do stderr.each_line do |line| break if interrupted warn line end rescue IOError # Expected when stream is closed during interruption end # Wait for both streams to complete or interruption stdout_thread.join unless interrupted stderr_thread.join unless interrupted # Check exit status if not interrupted unless interrupted exit_status = wait_thr.value exit exit_status.exitstatus unless exit_status.success? end end ensure # Restore original signal handler Signal.trap('INT', original_int_handler) # Cleanup threads if they're still running stdout_thread&.terminate stderr_thread&.terminate end end end end end |