Current section
Files
Jump to
Current section
Files
lib/jido/agent/server_output.ex
defmodule Jido.Agent.Server.Output do
@moduledoc false
use ExDbug, enabled: false
require Logger
alias Jido.Signal
alias Jido.Signal.Dispatch
alias Jido.Agent.Server.State, as: ServerState
alias Jido.Util
@type log_level ::
:debug | :info | :notice | :warning | :error | :critical | :alert | :emergency
@signal_log_levels %{
categories: %{
cmd: :debug,
event: :info,
err: :error,
out: :debug
},
specific: %{
# Command signals
"jido.agent.cmd.state": :debug,
"jido.agent.cmd.queuesize": :debug,
# Event signals
"jido.agent.event.started": :info,
"jido.agent.event.stopped": :info,
"jido.agent.event.transition.succeeded": :debug,
"jido.agent.event.transition.failed": :warning,
"jido.agent.event.queue.overflow": :warning,
"jido.agent.event.queue.cleared": :debug,
"jido.agent.event.process.started": :debug,
"jido.agent.event.process.restarted": :notice,
"jido.agent.event.process.terminated": :notice,
"jido.agent.event.process.failed": :warning,
# Error signals
"jido.agent.err.execution.error": :error,
# Output signals
"jido.agent.out.instruction.result": :debug,
"jido.agent.out.signal.result": :debug
}
}
# No level by default - determined by signal type
@default_dispatch {:logger, []}
@doc """
Logs a message at the specified level if it meets the agent's log level threshold.
"""
@spec log(ServerState.t(), log_level(), String.t(), keyword()) ::
:ok | {:ignored, :level_too_low}
def log(%ServerState{} = state, level, message, metadata \\ []) when is_atom(level) do
# Add agent context to metadata
metadata = add_agent_metadata(state, metadata)
# Use Jido.Util.cond_log to conditionally log based on threshold level
Util.cond_log(state.log_level, level, message, metadata)
end
@doc """
Emits a signal through the specified dispatch mechanism.
Automatically determines the appropriate log level based on signal type.
"""
@spec emit(Signal.t() | nil, ServerState.t(), keyword()) :: any()
def emit(signal, state, opts \\ [])
def emit(nil, _state, _opts) do
dbug("No signal provided")
{:error, :no_signal}
end
def emit(%Signal{} = signal, state, opts) do
# Determine appropriate level based on signal type
level = get_level_for_signal(signal)
# Configure dispatch with the appropriate level
base_dispatch = Keyword.get(opts, :dispatch) || signal.jido_dispatch || @default_dispatch
dispatch_config = add_level_to_dispatch(base_dispatch, level)
dbug("Emitting", signal: signal, level: level)
dbug("Using dispatch config", dispatch_config: dispatch_config)
log(
state,
:debug,
"Emit Signal [#{signal.id}] of type '#{signal.type}' from source '#{signal.source}'"
)
Dispatch.dispatch(signal, dispatch_config)
end
def emit(_invalid, _state, _opts) do
{:error, :invalid_signal}
end
# Private helper functions
# Add agent metadata to the log metadata
defp add_agent_metadata(%ServerState{} = state, metadata) do
if state.agent && state.agent.id do
metadata
|> Keyword.put_new(:agent_id, state.agent.id)
|> Keyword.put_new(:agent_status, state.status)
else
metadata
end
end
# Get the appropriate log level for a signal based on its type
defp get_level_for_signal(%Signal{type: type}) do
# Try to get specific level for this exact signal type
specific_level = @signal_log_levels.specific[String.to_atom(type)]
if specific_level do
specific_level
else
# Fall back to category-based level
case type do
"jido.agent.cmd." <> _ -> @signal_log_levels.categories.cmd
"jido.agent.event." <> _ -> @signal_log_levels.categories.event
"jido.agent.err." <> _ -> @signal_log_levels.categories.err
"jido.agent.out." <> _ -> @signal_log_levels.categories.out
# Default to info if we can't determine
_ -> :info
end
end
end
# Add level to dispatch configuration
defp add_level_to_dispatch({target, dispatch_opts}, level) do
{target, Keyword.put_new(dispatch_opts, :level, level)}
end
defp add_level_to_dispatch(other, _level), do: other
end