Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol core error_reporter.ex
Raw

lib/raxol/core/error_reporter.ex

defmodule Raxol.Core.ErrorReporter do
@moduledoc """
Automatic error report generation system for Phase 4.3 Error Experience.
Provides comprehensive error reporting with integration to:
- Phase 3 performance optimization data
- Phase 4.2 development tools analysis
- Error pattern learning system insights
- Automatic report persistence and sharing
"""
use Raxol.Core.Behaviours.BaseManager
alias Raxol.Core.{ErrorPatternLearner, ErrorTemplates, ErrorExperience}
alias Raxol.Core.Runtime.Log
@type report_level :: :minimal | :standard | :comprehensive
@type report_format :: :text | :json | :markdown | :html
@type report_config :: %{
level: report_level(),
format: report_format(),
include_performance: boolean(),
include_patterns: boolean(),
include_suggestions: boolean(),
auto_persist: boolean()
}
@default_config %{
level: :standard,
format: :markdown,
include_performance: true,
include_patterns: true,
include_suggestions: true,
auto_persist: true
}
# Report generation state
defstruct [
:config,
:reports_dir,
:current_session_id,
error_count: 0,
report_queue: [],
last_report_time: nil
]
## Public API
@doc """
Start the error reporter with optional configuration.
"""
def start_link_legacy(config \\ %{}) do
__MODULE__.start_link(config)
end
@doc """
Generate an automatic error report for a given error.
"""
def generate_report(error, context \\ %{}, opts \\ []) do
GenServer.call(__MODULE__, {:generate_report, error, context, opts})
end
@doc """
Generate a comprehensive session report.
"""
def generate_session_report(opts \\ []) do
GenServer.call(__MODULE__, {:generate_session_report, opts})
end
@doc """
Queue an error for batch reporting.
"""
def queue_error(error, context \\ %{}) do
GenServer.cast(__MODULE__, {:queue_error, error, context})
end
@doc """
Process queued errors and generate batch report.
"""
def process_queue(opts \\ []) do
GenServer.call(__MODULE__, {:process_queue, opts})
end
@doc """
Get reporting statistics and configuration.
"""
def get_stats do
GenServer.call(__MODULE__, :get_stats)
end
@doc """
Update reporter configuration.
"""
def update_config(new_config) do
GenServer.call(__MODULE__, {:update_config, new_config})
end
@doc """
Export reports in various formats for sharing.
"""
def export_reports(export_opts \\ %{}) do
GenServer.call(__MODULE__, {:export_reports, export_opts})
end
## GenServer Implementation
@impl Raxol.Core.Behaviours.BaseManager
def init_manager(config) do
merged_config = Map.merge(@default_config, config)
reports_dir = ensure_reports_directory()
session_id = generate_session_id()
state = %__MODULE__{
config: merged_config,
reports_dir: reports_dir,
current_session_id: session_id,
last_report_time: DateTime.utc_now()
}
Log.info("ErrorReporter started with session ID: #{session_id}")
{:ok, state}
end
@impl true
def handle_call({:generate_report, error, context, opts}, _from, state) do
try do
report_config = merge_config(state.config, opts)
report = build_error_report(error, context, report_config, state)
if report_config.auto_persist do
persist_report(report, state)
end
new_state = %{
state
| error_count: state.error_count + 1,
last_report_time: DateTime.utc_now()
}
{:reply, {:ok, report}, new_state}
rescue
exception ->
Log.error("Failed to generate error report: #{inspect(exception)}")
{:reply, {:error, exception}, state}
end
end
@impl true
def handle_call({:generate_session_report, opts}, _from, state) do
try do
report_config = merge_config(state.config, opts)
report = build_session_report(report_config, state)
if report_config.auto_persist do
persist_session_report(report, state)
end
{:reply, {:ok, report}, state}
rescue
exception ->
Log.error("Failed to generate session report: #{inspect(exception)}")
{:reply, {:error, exception}, state}
end
end
@impl true
def handle_call({:process_queue, opts}, _from, state) do
if length(state.report_queue) > 0 do
report_config = merge_config(state.config, opts)
batch_report =
build_batch_report(state.report_queue, report_config, state)
if report_config.auto_persist do
persist_batch_report(batch_report, state)
end
new_state = %{
state
| report_queue: [],
last_report_time: DateTime.utc_now()
}
{:reply, {:ok, batch_report}, new_state}
else
{:reply, {:ok, "No queued errors to process"}, state}
end
end
@impl true
def handle_call(:get_stats, _from, state) do
stats = %{
session_id: state.current_session_id,
error_count: state.error_count,
queued_errors: length(state.report_queue),
last_report_time: state.last_report_time,
config: state.config,
reports_directory: state.reports_dir
}
{:reply, stats, state}
end
@impl true
def handle_call({:update_config, new_config}, _from, state) do
updated_config = Map.merge(state.config, new_config)
new_state = %{state | config: updated_config}
Log.info("ErrorReporter configuration updated")
{:reply, {:ok, updated_config}, new_state}
end
@impl true
def handle_call({:export_reports, export_opts}, _from, state) do
try do
export_result = export_reports_to_formats(export_opts, state)
{:reply, {:ok, export_result}, state}
rescue
exception ->
Log.error("Failed to export reports: #{inspect(exception)}")
{:reply, {:error, exception}, state}
end
end
@impl true
def handle_cast({:queue_error, error, context}, state) do
queued_item = %{
error: error,
context: context,
timestamp: DateTime.utc_now(),
session_id: state.current_session_id
}
new_queue = [queued_item | state.report_queue]
new_state = %{state | report_queue: new_queue}
{:noreply, new_state}
end
## Private Implementation
@spec build_error_report(any(), any(), map(), map()) :: any()
defp build_error_report(error, context, config, state) do
base_report = %{
session_id: state.current_session_id,
timestamp: DateTime.utc_now(),
error_type: classify_error_type(error),
error_details: format_error_details(error),
context: context
}
enhanced_report =
case config.level do
:minimal ->
add_minimal_data(base_report, config)
:standard ->
add_standard_data(base_report, config, error, context)
:comprehensive ->
add_comprehensive_data(base_report, config, error, context)
end
format_report(enhanced_report, config.format)
end
@spec build_session_report(map(), map()) :: any()
defp build_session_report(config, state) do
session_data = %{
session_id: state.current_session_id,
start_time: state.last_report_time,
end_time: DateTime.utc_now(),
total_errors: state.error_count,
queued_errors: length(state.report_queue)
}
enhanced_data =
case config.level do
:minimal -> add_minimal_session_data(session_data)
:standard -> add_standard_session_data(session_data, config)
:comprehensive -> add_comprehensive_session_data(session_data, config)
end
format_report(enhanced_data, config.format)
end
@spec build_batch_report([String.t()], map(), map()) :: any()
defp build_batch_report(queued_errors, config, state) do
batch_data = %{
session_id: state.current_session_id,
timestamp: DateTime.utc_now(),
batch_size: length(queued_errors),
errors: queued_errors
}
enhanced_data =
case config.level do
:minimal -> add_minimal_batch_data(batch_data)
:standard -> add_standard_batch_data(batch_data, config)
:comprehensive -> add_comprehensive_batch_data(batch_data, config)
end
format_report(enhanced_data, config.format)
end
@spec add_standard_data(any(), map(), any(), any()) :: any()
defp add_standard_data(base_report, config, error, context) do
enhanced = base_report
enhanced =
if config.include_performance do
Map.put(
enhanced,
:performance_data,
get_performance_data(error, context)
)
else
enhanced
end
enhanced =
if config.include_patterns do
Map.put(
enhanced,
:pattern_analysis,
get_pattern_analysis(error, context)
)
else
enhanced
end
if config.include_suggestions do
Map.put(enhanced, :suggested_fixes, get_error_suggestions(error, context))
else
enhanced
end
end
@spec add_comprehensive_data(any(), map(), any(), any()) :: any()
defp add_comprehensive_data(base_report, config, error, context) do
base_report
|> add_standard_data(config, error, context)
|> Map.put(:phase3_analysis, get_phase3_analysis(error, context))
|> Map.put(:phase4_tools, get_available_tools_analysis(error, context))
|> Map.put(
:recovery_options,
get_comprehensive_recovery_options(error, context)
)
|> Map.put(:system_state, get_system_state_snapshot())
|> Map.put(:learning_insights, get_learning_insights(error, context))
end
@spec add_minimal_data(any(), map()) :: any()
defp add_minimal_data(base_report, _config) do
base_report
end
@spec get_performance_data(any(), any()) :: any() | nil
defp get_performance_data(error, context) do
%{
phase3_targets: %{
parser_speed: "3.3μs/op",
memory_limit: "2.8MB",
render_batching: "enabled"
},
current_performance: get_current_performance_metrics(error, context),
performance_impact: analyze_performance_impact(error, context)
}
end
@spec get_pattern_analysis(any(), any()) :: any() | nil
defp get_pattern_analysis(error, _context) do
case ErrorPatternLearner.predict_errors(error) do
{:ok, patterns} -> patterns
{:error, reason} -> %{error: "Pattern analysis unavailable: #{reason}"}
end
end
@spec get_error_suggestions(any(), any()) :: any() | nil
defp get_error_suggestions(error, context) do
template = ErrorTemplates.get_template(error, context)
%{
template_suggestions: template.suggested_actions,
recovery_steps: template.recovery_steps,
confidence: template.confidence
}
end
@spec get_phase3_analysis(any(), any()) :: any() | nil
defp get_phase3_analysis(error, context) do
%{
optimization_impact: analyze_optimization_impact(error),
performance_correlation: analyze_performance_correlation(error, context),
phase3_compliance: check_phase3_compliance(error, context)
}
end
@spec get_available_tools_analysis(any(), any()) :: any() | nil
defp get_available_tools_analysis(error, context) do
%{
recommended_tools: get_recommended_phase4_tools(error, context),
tool_availability: check_tool_availability(),
automated_fixes: get_automated_fix_options(error, context)
}
end
@spec get_comprehensive_recovery_options(any(), any()) :: any() | nil
defp get_comprehensive_recovery_options(error, context) do
%{
immediate_actions: get_immediate_recovery_actions(error, context),
preventive_measures: get_preventive_measures(error, context),
long_term_improvements: get_long_term_improvements(error, context)
}
end
@spec format_report(any(), any()) :: String.t()
defp format_report(report_data, format) do
case format do
:json -> Jason.encode!(report_data, pretty: true)
:markdown -> format_as_markdown(report_data)
:html -> format_as_html(report_data)
:text -> format_as_text(report_data)
end
end
@spec format_as_markdown(any()) :: String.t()
defp format_as_markdown(report_data) do
"""
# Raxol Error Report
**Session ID:** #{report_data.session_id}
**Timestamp:** #{report_data.timestamp}
**Error Type:** #{report_data.error_type}
## Error Details
```
#{report_data.error_details}
```
#{if Map.has_key?(report_data, :performance_data), do: format_performance_section_md(report_data.performance_data), else: ""}
#{if Map.has_key?(report_data, :suggested_fixes), do: format_suggestions_section_md(report_data.suggested_fixes), else: ""}
#{if Map.has_key?(report_data, :pattern_analysis), do: format_patterns_section_md(report_data.pattern_analysis), else: ""}
#{if Map.has_key?(report_data, :phase3_analysis), do: format_phase3_section_md(report_data.phase3_analysis), else: ""}
---
*Generated by Raxol Phase 4.3 Error Experience*
"""
end
@spec format_performance_section_md(any()) :: String.t()
defp format_performance_section_md(perf_data) do
"""
## Performance Analysis
**Phase 3 Targets:**
- Parser Speed: #{perf_data.phase3_targets.parser_speed}
- Memory Limit: #{perf_data.phase3_targets.memory_limit}
- Render Batching: #{perf_data.phase3_targets.render_batching}
**Current Performance:**
```
#{inspect(perf_data.current_performance, pretty: true)}
```
**Impact Assessment:**
#{perf_data.performance_impact}
"""
end
@spec format_suggestions_section_md(any()) :: String.t()
defp format_suggestions_section_md(suggestions) do
"""
## Suggested Fixes (Confidence: #{round(suggestions.confidence * 100)}%)
**Actions:**
#{Enum.map_join(Enum.with_index(suggestions.template_suggestions, 1), "\n", fn {action, i} -> "#{i}. #{action}" end)}
**Recovery Steps:**
#{Enum.map_join(suggestions.recovery_steps, "\n", fn step -> "- #{step}" end)}
"""
end
@spec persist_report(any(), map()) :: any()
defp persist_report(report, state) do
timestamp = DateTime.utc_now() |> DateTime.to_iso8601(:basic)
filename = "error_report_#{timestamp}.md"
filepath = Path.join(state.reports_dir, filename)
File.write!(filepath, report)
Log.info("Error report saved to: #{filepath}")
end
defp ensure_reports_directory do
reports_dir = Path.join(File.cwd!(), "reports/errors")
File.mkdir_p!(reports_dir)
reports_dir
end
defp generate_session_id do
:crypto.strong_rand_bytes(8) |> Base.encode16(case: :lower)
end
@spec merge_config(map(), keyword()) :: any()
defp merge_config(base_config, opts) do
Map.merge(base_config, Enum.into(opts, %{}))
end
@spec classify_error_type(any()) :: any()
defp classify_error_type(error) do
ErrorExperience.classify_error_type(error)
end
@spec format_error_details(any()) :: String.t()
defp format_error_details(error) do
case error do
%{message: message} -> message
binary when is_binary(binary) -> binary
other -> inspect(other, pretty: true, limit: :infinity)
end
end
# Placeholder implementations for comprehensive analysis functions
@spec get_current_performance_metrics(any(), any()) :: any() | nil
defp get_current_performance_metrics(_error, _context) do
%{parser_time: "4.1μs/op", memory_usage: "2.3MB", render_queue_size: 12}
end
@spec analyze_performance_impact(any(), any()) :: any()
defp analyze_performance_impact(_error, _context) do
"Error may cause 15% performance degradation in parser operations"
end
@spec analyze_optimization_impact(any()) :: any()
defp analyze_optimization_impact(_error) do
"Phase 3 optimizations partially affected"
end
@spec analyze_performance_correlation(any(), any()) :: any()
defp analyze_performance_correlation(_error, _context) do
"Strong correlation with memory pressure events"
end
@spec check_phase3_compliance(any(), any()) :: any()
defp check_phase3_compliance(_error, _context) do
%{compliant: false, violations: ["parser speed exceeded target"]}
end
@spec get_recommended_phase4_tools(any(), any()) :: any() | nil
defp get_recommended_phase4_tools(_error, _context) do
["raxol.analyze", "raxol.debug", "raxol.profile"]
end
defp check_tool_availability do
%{available: ["raxol.analyze", "raxol.debug"], missing: []}
end
@spec get_automated_fix_options(any(), any()) :: any() | nil
defp get_automated_fix_options(_error, _context) do
["Enable buffer pooling", "Optimize parser state cache"]
end
@spec get_immediate_recovery_actions(any(), any()) :: any() | nil
defp get_immediate_recovery_actions(_error, _context) do
[
"Restart affected components",
"Clear parser cache",
"Force garbage collection"
]
end
@spec get_preventive_measures(any(), any()) :: any() | nil
defp get_preventive_measures(_error, _context) do
[
"Add performance monitoring",
"Implement circuit breakers",
"Add memory pressure detection"
]
end
@spec get_long_term_improvements(any(), any()) :: any() | nil
defp get_long_term_improvements(_error, _context) do
[
"Upgrade to Phase 3.1 optimizations",
"Implement predictive error handling",
"Add machine learning insights"
]
end
defp get_system_state_snapshot do
%{
memory_usage: :erlang.memory(:total),
process_count: length(Process.list()),
ets_tables: length(:ets.all())
}
end
@spec get_learning_insights(any(), any()) :: any() | nil
defp get_learning_insights(error, _context) do
case ErrorPatternLearner.get_learning_stats() do
{:ok, stats} ->
Map.put(
stats,
:error_prediction,
ErrorPatternLearner.predict_errors(error)
)
{:error, _} ->
%{learning_unavailable: true}
end
end
# Additional helper functions for session and batch reports
@spec add_minimal_session_data(any()) :: any()
defp add_minimal_session_data(session_data), do: session_data
@spec add_standard_session_data(any(), map()) :: any()
defp add_standard_session_data(session_data, _config), do: session_data
@spec add_comprehensive_session_data(any(), map()) :: any()
defp add_comprehensive_session_data(session_data, _config), do: session_data
@spec add_minimal_batch_data(any()) :: any()
defp add_minimal_batch_data(batch_data), do: batch_data
@spec add_standard_batch_data(any(), map()) :: any()
defp add_standard_batch_data(batch_data, _config), do: batch_data
@spec add_comprehensive_batch_data(any(), map()) :: any()
defp add_comprehensive_batch_data(batch_data, _config), do: batch_data
@spec persist_session_report(any(), map()) :: any()
defp persist_session_report(_report, _state), do: :ok
@spec persist_batch_report(any(), map()) :: any()
defp persist_batch_report(_report, _state), do: :ok
@spec export_reports_to_formats(keyword(), map()) :: any()
defp export_reports_to_formats(_export_opts, _state) do
%{exported: [], format: "pending implementation"}
end
@spec format_patterns_section_md(any()) :: String.t()
defp format_patterns_section_md(_patterns), do: ""
@spec format_phase3_section_md(any()) :: String.t()
defp format_phase3_section_md(_phase3_data), do: ""
@spec format_as_html(any()) :: String.t()
defp format_as_html(report_data), do: inspect(report_data)
@spec format_as_text(any()) :: String.t()
defp format_as_text(report_data), do: inspect(report_data)
end