Class: Mistri::Sinks::Logger
- Inherits:
-
Object
- Object
- Mistri::Sinks::Logger
- Defined in:
- lib/mistri/sinks/logger.rb
Overview
Renders the event stream as one log line per meaningful beat, so a log tells each run's whole story: which tools ran with which arguments, how long each took, what every turn cost, and how the run ended. Deltas never log (whole blocks do).
Assigning Mistri.logger turns this on for every run in one line:
Mistri.logger = Rails.logger
Mistri.logger = Mistri::Sinks::Logger.new(logger, color: true)
Payloads (inputs, text, thinking, arguments, results, reports) log in full by default; content: false keeps the same story with metadata only, for logs whose payloads must stay out. level: floors the ordinary lines (warnings and errors keep their own levels).
Sinks the Agent builds through for_session skip origin-tagged events, because every agent logs its own events under its own tag: exactly once, however deep the nesting and whichever process runs it. A sink composed directly (agent.run(input, &sink)) is the only logger its run has, so it renders forwarded child events instead, origin first; framing (run, done) still comes only from the Agent.
Any object responding to info/warn/error works. Logging must never break a run: every entry point rescues, the first failure warns, and the sink goes quiet for the rest of its run. Tool events arrive from executor threads; the only mutable state (the turn counter) moves on loop-thread events, and interleaved lines stay whole as long as the host's logger serializes writes, which stdlib Logger and Rails do.
Class Method Summary collapse
-
.attach(id, label: nil) ⇒ Object
The global hookup: builds this run's sink from Mistri.logger, or nil.
Instance Method Summary collapse
- #call(event) ⇒ Object
-
#for_session(id, label: nil) ⇒ Object
A fresh sink for one run: same logger and options, this run's tag (a sub-agent's label, or the session id), its own turn count and clock.
-
#initialize(logger, session: nil, truncate: 200, color: false, level: :info, content: true, forwarded: :render) ⇒ Logger
constructor
truncate bounds every rendered value (nil for full payloads).
- #run_crashed(error) ⇒ Object
- #run_finished(result) ⇒ Object
-
#run_started(verb:, model:, tool_count:, input: nil) ⇒ Object
The framing the stream cannot carry: the Agent calls these around a run with the input, the model, and the final Result.
- #to_proc ⇒ Object
Constructor Details
#initialize(logger, session: nil, truncate: 200, color: false, level: :info, content: true, forwarded: :render) ⇒ Logger
truncate bounds every rendered value (nil for full payloads). session is the line tag, verbatim, so concurrent runs stay distinguishable. Options are validated here so a bad configuration fails at assignment time, not silently at run time.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/mistri/sinks/logger.rb', line 58 def initialize(logger, session: nil, truncate: 200, color: false, level: :info, content: true, forwarded: :render) raise ArgumentError, "level must be one of #{LEVELS.join(", ")}" unless LEVELS.include?(level) unless truncate.nil? || (truncate.is_a?(Integer) && truncate.positive?) raise ArgumentError, "truncate must be a positive Integer or nil" end raise ArgumentError, "forwarded must be :render or :skip" unless %i[render skip].include?(forwarded) @logger = logger @truncate = truncate @color = color @level = level @content = content @forwarded = forwarded @session = session && field(session, limit: 48) @turns = 0 @started = now @warned = false @tag = paint(@session ? "[mistri #{@session}]" : "[mistri]", :dim) end |
Class Method Details
.attach(id, label: nil) ⇒ Object
The global hookup: builds this run's sink from Mistri.logger, or nil. Contained, so a broken assignment costs the log, never the run.
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/mistri/sinks/logger.rb', line 92 def self.attach(id, label: nil) configured = Mistri.logger return nil unless configured sink = configured.is_a?(self) ? configured : new(configured) sink.for_session(id, label: label) rescue StandardError => e warn "mistri: logging sink construction failed (#{e.class}: #{e.}); run not logged" nil end |
Instance Method Details
#call(event) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mistri/sinks/logger.rb', line 103 def call(event) return if @warned return if event.origin && (@forwarded == :skip) return if SKIPPED[event.type] return if QUIET[event.type] && !floor_enabled? handler = LINES[event.type] prefix = event.origin ? "#{field(event.origin)} " : "" # Future event types degrade to a greppable line, never to silence. line = handler ? send(handler, event) : "#{event.type} #{body_of(event.content)}".rstrip write("#{prefix}#{line.first}", level: line.last) if line.is_a?(Array) write("#{prefix}#{line}") if line.is_a?(String) rescue StandardError => e quiet(e) end |
#for_session(id, label: nil) ⇒ Object
A fresh sink for one run: same logger and options, this run's tag (a sub-agent's label, or the session id), its own turn count and clock. Runs framed this way skip forwarded events, because each agent in the tree logs its own.
85 86 87 88 |
# File 'lib/mistri/sinks/logger.rb', line 85 def for_session(id, label: nil) self.class.new(@logger, session: label || id.to_s[0, 8], truncate: @truncate, color: @color, level: @level, content: @content, forwarded: :skip) end |
#run_crashed(error) ⇒ Object
147 148 149 150 151 |
# File 'lib/mistri/sinks/logger.rb', line 147 def run_crashed(error) write("#{paint("crashed", :red)} #{error.class}#{note(error.)}", level: :error) rescue StandardError => e quiet(e) end |
#run_finished(result) ⇒ Object
137 138 139 140 141 142 143 144 145 |
# File 'lib/mistri/sinks/logger.rb', line 137 def run_finished(result) status, level = status_of(result) return if level == :info && !floor_enabled? write("#{paint("done", :bold)} #{status} in #{clock(now - @started)}, " \ "#{count(@turns, "turn")}#{summary(result.usage)}", level: level) rescue StandardError => e quiet(e) end |
#run_started(verb:, model:, tool_count:, input: nil) ⇒ Object
The framing the stream cannot carry: the Agent calls these around a run with the input, the model, and the final Result. Each is total for the same reason call is: a logging failure inside a rescue block must never replace the host's own exception.
126 127 128 129 130 131 132 133 134 135 |
# File 'lib/mistri/sinks/logger.rb', line 126 def run_started(verb:, model:, tool_count:, input: nil) @turns = 0 @started = now return unless floor_enabled? ask = input && @content ? %( "#{trim(input)}") : "" write("#{paint(verb, :bold)}#{ask} (#{field(model)}, #{count(tool_count, "tool")})") rescue StandardError => e quiet(e) end |
#to_proc ⇒ Object
120 |
# File 'lib/mistri/sinks/logger.rb', line 120 def to_proc = method(:call).to_proc |