BUCKYVERSE

Ruby to Elixir Migration Patterns

This document outlines the patterns and strategies for migrating our Ruby-based ebook conversion system to Elixir. It provides concrete examples of how Ruby patterns in our codebase will map to Elixir patterns.

Table of Contents

  1. Command Execution Pattern
  2. Error Handling Pattern
  3. Configuration Management
  4. State Management
  5. Process Supervision
  6. Concurrency Patterns
  7. File System Operations
  8. Dependency Injection
  9. Testing Strategies
  10. Logging Approaches

1. Command Execution Pattern

Ruby Implementation

In our Ruby codebase, we use the CommandExecutor and CommandRunner classes to execute system commands:

# System::CommandExecutor
def run(command, error_msg = nil, return_output: false, timeout: nil, env: nil, 
        working_dir: nil, raise_on_error: true)
  # Validate command
  # Execute command with Open3.capture3
  # Handle errors and return results
end

# BookProcessing::Utilities::CommandRunner
def run_with_retry(command, error_msg = nil, retries: 3, delay: 2, backoff: 1.0, **options)
  # Retry logic for flaky commands
end

Elixir Implementation

In Elixir, we'll use a combination of System.cmd/3, Port, and Task for command execution:

defmodule Codexis.System.CommandExecutor do
  @moduledoc """
  Executes system commands with consistent error handling and logging.
  """
  
  @doc """
  Runs a system command with error handling and logging.
  
  ## Options
    * `:return_output` - Whether to return command output (default: false)
    * `:timeout` - Timeout in milliseconds (default: nil)
    * `:env` - Environment variables to set (default: nil)
    * `:working_dir` - Working directory (default: nil)
    * `:raise_on_error` - Whether to raise on error (default: true)
  
  ## Returns
    * `{:ok, result}` - Command succeeded, result is output if return_output is true, otherwise true
    * `{:error, reason}` - Command failed and raise_on_error is false
  
  ## Raises
    * `Codexis.System.CommandError` - Command failed and raise_on_error is true
  """
  def run(command, error_msg \\ nil, opts \\ []) do
    # Default options
    opts = Keyword.merge([
      return_output: false,
      timeout: nil,
      env: nil,
      working_dir: nil,
      raise_on_error: true
    ], opts)
    
    # Validate command
    with :ok <- validate_command(command) do
      try do
        # Log command execution
        Logger.debug("📦 Executing: #{command}")
        start_time = System.monotonic_time(:millisecond)
        
        # Execute command
        result = execute_command(command, opts)
        
        # Log success
        duration = System.monotonic_time(:millisecond) - start_time
        Logger.debug("✓ Command completed in #{duration}ms")
        
        # Return result
        {:ok, result}
      rescue
        e in Codexis.System.CommandError ->
          # Log error
          duration = System.monotonic_time(:millisecond) - start_time
          Logger.error("Command failed after #{duration}ms: #{Exception.message(e)}")
          
          if opts[:raise_on_error] do
            reraise e, __STACKTRACE__
          else
            {:error, Exception.message(e)}
          end
      end
    end
  end
  
  @doc """
  Runs a command with retry logic for flaky commands.
  
  ## Options
    * `:retries` - Number of retry attempts (default: 3)
    * `:delay` - Delay between retries in milliseconds (default: 2000)
    * `:backoff` - Multiplier for delay between retries (default: 1.5)
    * Other options are passed to `run/3`
  
  ## Returns
    * `{:ok, result}` - Command succeeded
    * `{:error, reason}` - Command failed after all retries
  """
  def run_with_retry(command, error_msg \\ nil, opts \\ []) do
    # Extract retry options
    retries = Keyword.get(opts, :retries, 3)
    delay = Keyword.get(opts, :delay, 2000)
    backoff = Keyword.get(opts, :backoff, 1.5)
    opts = Keyword.drop(opts, [:retries, :delay, :backoff])
    
    # Run with retry
    do_run_with_retry(command, error_msg, opts, retries, delay, backoff, 0)
  end
  
  # Private helper for retry logic
  defp do_run_with_retry(command, error_msg, opts, retries, delay, backoff, attempt) do
    case run(command, error_msg, Keyword.put(opts, :raise_on_error, false)) do
      {:ok, result} ->
        {:ok, result}
      
      {:error, reason} ->
        if attempt < retries do
          # Log retry attempt
          Logger.warn("Command failed, retrying (#{attempt + 1}/#{retries}) after #{delay}ms: #{command}")
          
          # Wait before retry
          Process.sleep(delay)
          
          # Retry with increased delay
          do_run_with_retry(
            command,
            error_msg,
            opts,
            retries,
            trunc(delay * backoff),
            backoff,
            attempt + 1
          )
        else
          # All retries failed
          error_msg = error_msg || "Command failed after #{retries} retries: #{reason}"
          
          if Keyword.get(opts, :raise_on_error, true) do
            raise Codexis.System.CommandError, message: error_msg
          else
            {:error, error_msg}
          end
        end
    end
  end
  
  # Execute command with System.cmd
  defp execute_command(command, opts) do
    # Split command into executable and args
    [executable | args] = String.split(command, " ", trim: true)
    
    # Prepare options for System.cmd
    cmd_opts = []
    cmd_opts = if opts[:env], do: [{:env, opts[:env]} | cmd_opts], else: cmd_opts
    cmd_opts = if opts[:working_dir], do: [{:cd, opts[:working_dir]} | cmd_opts], else: cmd_opts
    
    # Execute command
    {output, status} = System.cmd(executable, args, cmd_opts)
    
    # Handle result
    if status == 0 do
      if opts[:return_output], do: output, else: true
    else
      error_msg = opts[:error_msg] || "Command failed with status #{status}: #{command}"
      raise Codexis.System.CommandError, message: error_msg
    end
  end
  
  # Validate command
  defp validate_command(command) do
    cond do
      is_nil(command) ->
        {:error, "Command cannot be nil"}
      
      String.trim(command) == "" ->
        {:error, "Command cannot be empty"}
      
      true ->
        :ok
    end
  end
end

For more complex command execution with streaming output or long-running processes, we'll use Ports:

defmodule Codexis.System.CommandServer do
  @moduledoc """
  GenServer for managing long-running commands with streaming output.
  """
  use GenServer
  
  # Client API
  
  @doc """
  Starts a command server for the given command.
  """
  def start_link(command, opts \\ []) do
    GenServer.start_link(__MODULE__, {command, opts})
  end
  
  @doc """
  Gets the current output of the command.
  """
  def get_output(pid) do
    GenServer.call(pid, :get_output)
  end
  
  @doc """
  Stops the command.
  """
  def stop(pid) do
    GenServer.stop(pid)
  end
  
  # Server callbacks
  
  @impl true
  def init({command, opts}) do
    # Split command into executable and args
    [executable | args] = String.split(command, " ", trim: true)
    
    # Prepare port options
    port_opts = [:binary, :exit_status, {:line, 1024}, {:args, args}]
    
    # Open port
    port = Port.open({:spawn_executable, executable}, port_opts)
    
    # Initialize state
    state = %{
      port: port,
      command: command,
      output: [],
      status: :running,
      opts: opts
    }
    
    {:ok, state}
  end
  
  @impl true
  def handle_info({port, {:data, data}}, %{port: port} = state) do
    # Log output
    Logger.debug("Command output: #{data}")
    
    # Update state with new output
    new_state = %{state | output: [data | state.output]}
    
    # Notify subscribers if configured
    if state.opts[:notify_pid] do
      send(state.opts[:notify_pid], {:command_output, data})
    end
    
    {:noreply, new_state}
  end
  
  @impl true
  def handle_info({port, {:exit_status, status}}, %{port: port} = state) do
    # Log exit status
    Logger.info("Command exited with status #{status}: #{state.command}")
    
    # Update state with exit status
    new_state = %{state | status: (if status == 0, do: :success, else: :error)}
    
    # Notify subscribers if configured
    if state.opts[:notify_pid] do
      send(state.opts[:notify_pid], {:command_exit, status})
    end
    
    {:noreply, new_state}
  end
  
  @impl true
  def handle_call(:get_output, _from, state) do
    # Return the collected output in original order
    output = state.output |> Enum.reverse() |> Enum.join("\n")
    {:reply, {state.status, output}, state}
  end
  
  @impl true
  def terminate(_reason, state) do
    # Close port if still open
    if Port.info(state.port) do
      Port.close(state.port)
    end
    
    :ok
  end
end

For concurrent command execution, we'll use Tasks:

defmodule Codexis.System.CommandRunner do
  @moduledoc """
  Provides book-specific command execution functionality.
  """
  
  @doc """
  Runs multiple commands in parallel.
  
  ## Returns
    * `{:ok, results}` - All commands succeeded
    * `{:error, failed_commands}` - Some commands failed
  """
  def run_parallel(commands, opts \\ []) do
    # Start a task for each command
    tasks = Enum.map(commands, fn {name, cmd} ->
      Task.async(fn ->
        {name, Codexis.System.CommandExecutor.run(cmd, nil, opts)}
      end)
    end)
    
    # Wait for all tasks to complete
    results = Task.await_many(tasks, Keyword.get(opts, :timeout, 30_000))
    
    # Separate successful and failed commands
    {successful, failed} = Enum.split_with(results, fn {_, result} ->
      match?({:ok, _}, result)
    end)
    
    # Return results
    if Enum.empty?(failed) do
      {:ok, Map.new(successful, fn {name, {:ok, result}} -> {name, result} end)}
    else
      failed_map = Map.new(failed, fn {name, {:error, reason}} -> {name, reason} end)
      {:error, failed_map}
    end
  end
end

2. Error Handling Pattern

Ruby Implementation

In our Ruby codebase, we use a hierarchy of error classes and the ErrorHandler module:

# BookProcessing::ErrorHandler
module ErrorHandler
  class Error < StandardError; end
  class CommandError < Error; end
  class FileSystemError < Error; end
  
  def self.handle(error, logger, options = {})
    # Log error
    # Attempt recovery
    # Re-raise or return based on options
  end
end

# Usage
begin
  # Some operation
rescue => e
  ErrorHandler.handle(e, logger, raise: true)
end

Elixir Implementation

In Elixir, we'll use a combination of custom error structs, pattern matching, and the {:ok, result} | {:error, reason} convention:

defmodule Codexis.Error do
  @moduledoc """
  Error handling utilities and error types for the ELib system.
  """
  
  @doc """
  Defines a new error type with the given fields.
  """
  defmacro deferror(name, fields \\ []) do
    quote do
      defmodule unquote(name) do
        @moduledoc """
        Error type: #{unquote(name)}
        """
        defexception unquote(fields) ++ [:message, :context]
        
        @doc """
        Creates a new error with the given message and context.
        """
        def new(message, context \\ %{}) do
          %__MODULE__{message: message, context: context}
        end
        
        @doc """
        Formats the error message with context.
        """
        def message(%__MODULE__{message: message, context: context}) do
          if map_size(context) == 0 do
            message
          else
            context_str = context
              |> Enum.map(fn {k, v} -> "#{k}: #{inspect(v)}" end)
              |> Enum.join(", ")
            "#{message} (Context: #{context_str})"
          end
        end
      end
    end
  end
  
  # Define error types
  defmodule Types do
    require Codexis.Error
    
    Codexis.Error.deferror(CommandError)
    Codexis.Error.deferror(FileSystemError)
    Codexis.Error.deferror(ConfigurationError)
    Codexis.Error.deferror(ConversionError)
    Codexis.Error.deferror(ValidationError)
  end
  
  @doc """
  Handles an error with standardized logging and optional recovery.
  
  ## Options
    * `:raise` - Whether to raise the error (default: false)
    * `:recovery` - Function to attempt recovery (default: nil)
    * `:context` - Additional context to add to the error (default: %{})
  
  ## Returns
    * `{:ok, result}` - Recovery was successful
    * `{:error, error}` - Error occurred and no recovery was attempted or recovery failed
  """
  def handle(error, logger, opts \\ []) do
    # Default options
    opts = Keyword.merge([
      raise: false,
      recovery: nil,
      context: %{}
    ], opts)
    
    # Add context to the error
    error = add_context(error, opts[:context])
    
    # Log the error
    log_error(error, logger)
    
    # Attempt recovery if provided
    if recovery = opts[:recovery] do
      try do
        logger.info("Attempting error recovery...")
        case recovery.(error) do
          {:ok, result} ->
            logger.info("Recovery successful")
            {:ok, result}
          
          {:error, reason} ->
            logger.info("Recovery procedure completed but did not resolve the error: #{inspect(reason)}")
            {:error, error}
        end
      rescue
        recovery_error ->
          logger.error("Recovery failed: #{Exception.message(recovery_error)}")
          {:error, error}
      end
    else
      # No recovery attempted
      {:error, error}
    end
  after
    # Raise if requested
    if opts[:raise] do
      raise error
    end
  end
  
  # Add context to an error
  defp add_context(error, context) when map_size(context) == 0, do: error
  defp add_context(%{context: existing_context} = error, new_context) do
    %{error | context: Map.merge(existing_context, new_context)}
  end
  defp add_context(error, context) do
    # For standard exceptions without a context field
    Map.put(error, :context, context)
  end
  
  # Log an error with appropriate level and details
  defp log_error(error, logger) do
    # Format the error message
    message = Exception.message(error)
    
    # Log with appropriate level based on error type
    cond do
      match?(%Types.CommandError{}, error) ->
        logger.error("Command Error: #{message}")
      
      match?(%Types.FileSystemError{}, error) ->
        logger.error("File System Error: #{message}")
      
      match?(%Types.ConfigurationError{}, error) ->
        logger.error("Configuration Error: #{message}")
      
      match?(%Types.ConversionError{}, error) ->
        logger.error("Conversion Error: #{message}")
      
      match?(%Types.ValidationError{}, error) ->
        logger.error("Validation Error: #{message}")
      
      true ->
        logger.error("Error: #{message}")
    end
    
    # Log stacktrace at debug level
    logger.debug("Stacktrace: #{Exception.format_stacktrace(__STACKTRACE__)}")
  end
end

For function results, we'll use the {:ok, result} | {:error, reason} pattern:

defmodule Codexis.Formats.PDF do
  @moduledoc """
  Handles PDF generation from LaTeX source.
  """
  
  @doc """
  Builds a PDF from LaTeX source.
  
  ## Returns
    * `{:ok, pdf_path}` - PDF was successfully generated
    * `{:error, reason}` - PDF generation failed
  """
  def build(meta, opts \\ []) do
    with {:ok, _} <- process_images(meta),
         {:ok, _} <- compile_latex(meta),
         {:ok, pdf_path} <- move_pdf(meta) do
      # Open PDF if requested
      if Keyword.get(opts, :open_after_build, true) do
        _ = open_pdf(pdf_path)
      end
      
      {:ok, pdf_path}
    end
  end
  
  # Process images for LaTeX
  defp process_images(meta) do
    # Implementation
    {:ok, processed_count}
  end
  
  # Compile LaTeX to PDF
  defp compile_latex(meta) do
    # Implementation
    {:ok, output}
  end
  
  # Move PDF to output directory
  defp move_pdf(meta) do
    # Implementation
    {:ok, pdf_path}
  end
  
  # Open PDF with viewer
  defp open_pdf(pdf_path) do
    # Implementation - non-critical, so we don't propagate errors
    case Codexis.System.CommandExecutor.run("open -a Skim #{pdf_path}", nil, raise_on_error: false) do
      {:ok, _} -> :ok
      {:error, _} -> :error
    end
  end
end

3. Configuration Management

Ruby Implementation

In our Ruby codebase, we use a combination of YAML files and environment variables:

# Configuration loading
config = YAML.load_file('config.yml')
config.merge!(YAML.load_file("config.#{ENV['ENVIRONMENT']}.yml")) if ENV['ENVIRONMENT']

# Usage
output_dir = config['output_dir'] || 'output'

Elixir Implementation

In Elixir, we'll use the built-in configuration system with runtime configuration support:

# config/config.exs
import Config

config :elib, :general,
  output_dir: "output",
  temp_dir: "tmp"

config :elib, :pdf,
  engine: :xelatex,
  options: ["-interaction=nonstopmode"]

# Import environment specific config
import_config "#{config_env()}.exs"

# config/runtime.exs
import Config

if config_env() == :prod do
  # Production-specific configuration
  config :elib, :general,
    output_dir: System.get_env("OUTPUT_DIR") || "/var/lib/elib/output"
end

For accessing configuration:

defmodule Codexis.Config do
  @moduledoc """
  Configuration utilities for the ELib system.
  """
  
  @doc """
  Gets a configuration value with support for nested keys.
  
  ## Examples
      iex> Codexis.Config.get([:general, :output_dir])
      "output"
      
      iex> Codexis.Config.get([:pdf, :engine])
      :xelatex
  """
  def get(keys, default \\ nil) do
    get_in(Application.get_all_env(:elib), List.wrap(keys)) || default
  end
  
  @doc """
  Gets a configuration value with support for nested keys and environment variables.
  
  ## Examples
      iex> Codexis.Config.get_env([:general, :output_dir], "OUTPUT_DIR", "/tmp")
      "/var/lib/elib/output" # If OUTPUT_DIR is set
      
      iex> Codexis.Config.get_env([:general, :output_dir], "OUTPUT_DIR", "/tmp")
      "output" # If OUTPUT_DIR is not set but config is
      
      iex> Codexis.Config.get_env([:missing], "MISSING", "/tmp")
      "/tmp" # If neither is set
  """
  def get_env(keys, env_var, default \\ nil) do
    System.get_env(env_var) || get(keys, default)
  end
  
  @doc """
  Validates the configuration against a schema.
  
  ## Examples
      iex> Codexis.Config.validate([:pdf], %{
      ...>   engine: [type: :atom, required: true],
      ...>   options: [type: :list, required: false]
      ...> })
      :ok
  """
  def validate(prefix, schema) do
    config = get(prefix, %{})
    
    Enum.reduce_while(schema, :ok, fn {key, validations}, :ok ->
      value = Map.get(config, key)
      
      cond do
        # Check required
        Keyword.get(validations, :required, false) && is_nil(value) ->
          {:halt, {:error, "Required configuration #{prefix}.#{key} is missing"}}
        
        # Check type if value exists
        !is_nil(value) && Keyword.has_key?(validations, :type) ->
          expected_type = Keyword.get(validations, :type)
          actual_type = type_of(value)
          
          if actual_type == expected_type do
            {:cont, :ok}
          else
            {:halt, {:error, "Configuration #{prefix}.#{key} has wrong type: expected #{expected_type}, got #{actual_type}"}}
          end
        
        true ->
          {:cont, :ok}
      end
    end)
  end
  
  # Helper to get the type of a value
  defp type_of(value) when is_binary(value), do: :string
  defp type_of(value) when is_integer(value), do: :integer
  defp type_of(value) when is_float(value), do: :float
  defp type_of(value) when is_boolean(value), do: :boolean
  defp type_of(value) when is_atom(value), do: :atom
  defp type_of(value) when is_list(value), do: :list
  defp type_of(value) when is_map(value), do: :map
  defp type_of(_), do: :unknown
end

4. State Management

Ruby Implementation

In our Ruby codebase, we use instance variables and mutable objects to manage state:

class PdfBuilder
  def initialize(metadata)
    @metadata = metadata
    @output_path = nil
    @build_status = :pending
  end
  
  def build
    @build_status = :building
    # Build logic
    @output_path = "path/to/output.pdf"
    @build_status = :completed
    @output_path
  end
  
  def status
    @build_status
  end
end

# Usage
builder = PdfBuilder.new(metadata)
output_path = builder.build
puts "Build status: #{builder.status}"

Elixir Implementation

In Elixir, we'll use immutable data structures and processes to manage state:

defmodule Codexis.Formats.PDF.Builder do
  @moduledoc """
  Handles building PDF files from LaTeX source.
  """
  
  @doc """
  Creates a new builder struct with initial state.
  """
  def new(metadata) do
    %{
      meta metadata,
      output_path: nil,
      build_status: :pending,
      start_time: nil,
      end_time: nil
    }
  end
  
  @doc """
  Builds a PDF from the given builder state.
  
  ## Returns
    * `{:ok, updated_builder}` - Build succeeded
    * `{:error, reason, updated_builder}` - Build failed
  """
  def build(builder) do
    # Update state to building
    builder = %{builder | build_status: :building, start_time: DateTime.utc_now()}
    
    # Build logic
    case do_build(builder) do
      {:ok, output_path} ->
        # Update state to completed
        updated_builder = %{builder | 
          build_status: :completed, 
          output_path: output_path,
          end_time: DateTime.utc_now()
        }
        {:ok, updated_builder}
      
      {:error, reason} ->
        # Update state to failed
        updated_builder = %{builder | 
          build_status: :failed,
          end_time: DateTime.utc_now()
        }
        {:error, reason, updated_builder}
    end
  end
  
  # Private build implementation
  defp do_build(builder) do
    # Implementation
    {:ok, "path/to/output.pdf"}
  end
end

For long-lived state, we'll use GenServer:

defmodule Codexis.Formats.PDF.BuildServer do
  @moduledoc """
  GenServer for managing PDF build state.
  """
  use GenServer
  
  # Client API
  
  @doc """
  Starts a build server for the given metadata.
  """
  def start_link(metadata) do
    GenServer.start_link(__MODULE__, metadata)
  end
  
  @doc """
  Builds a PDF.
  """
  def build(pid) do
    GenServer.call(pid, :build)
  end
  
  @doc """
  Gets the current build status.
  """
  def status(pid) do
    GenServer.call(pid, :status)
  end
  
  # Server callbacks
  
  @impl true
  def init(metadata) do
    {:ok, Codexis.Formats.PDF.Builder.new(metadata)}
  end
  
  @impl true
  def handle_call(:build, _from, state) do
    case Codexis.Formats.PDF.Builder.build(state) do
      {:ok, updated_state} ->
        {:reply, {:ok, updated_state.output_path}, updated_state}
      
      {:error, reason, updated_state} ->
        {:reply, {:error, reason}, updated_state}
    end
  end
  
  @impl true
  def handle_call(:status, _from, state) do
    {:reply, state.build_status, state}
  end
end

5. Process Supervision

Ruby Implementation

In our Ruby codebase, we don't have a direct equivalent to OTP supervision, but we use error handling and recovery:

def build_all_formats(metadata)
  begin
    build_pdf(metadata)
  rescue => e
    log_error("PDF build failed: #{e.message}")
  end
  
  begin
    build_epub(metadata)
  rescue => e
    log_error("EPUB build failed: #{e.message}")
  end
  
  # Continue with other formats
end

Elixir Implementation

In Elixir, we'll use OTP supervision trees to manage processes and handle failures:

defmodule Codexis.Application do
  @moduledoc """
  The ELib Application.
  """
  use Application
  
  @impl true
  def start(_type, _args) do
    children = [
      # Registry for dynamic processes
      {Registry, keys: :unique, name: Codexis.Registry},
      
      # Supervisor for build processes
      {Codexis.BuildSupervisor, []},
      
      # Supervisor for format-specific processes
      {Codexis.FormatsSupervisor, []},
      
      # PubSub for event broadcasting
      {Phoenix.PubSub, name: Codexis.PubSub}
    ]
    
    opts = [strategy: :one_for_one, name: Codexis.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

defmodule Codexis.BuildSupervisor do
  @moduledoc """
  Supervisor for build processes.
  """
  use DynamicSupervisor
  
  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end
  
  @impl true
  def init(_init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
  
  @doc """
  Starts a build process for the given metadata.
  """
  def start_build(metadata) do
    # Create a unique ID for this build
    build_id = UUID.uuid4()
    
    # Child spec for the build process
    child_spec = {Codexis.Build.BuildServer, {build_id, metadata}}
    
    # Start the build process under this supervisor
    case DynamicSupervisor.start_child(__MODULE__, child_spec) do
      {:ok, pid} -> {:ok, build_id, pid}
      {:error, reason} -> {:error, reason}
    end
  end
end

defmodule Codexis.FormatsSupervisor do
  @moduledoc """
  Supervisor for format-specific processes.
  """
  use Supervisor
  
  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end
  
  @impl true
  def init(_init_arg) do
    children = [
      # PDF format supervisor
      {Codexis.Formats.PDF.Supervisor, []},
      
      # EPUB format supervisor
      {Codexis.Formats.EPUB.Supervisor, []},
      
      # MOBI format supervisor
      {Codexis.Formats.MOBI.Supervisor, []}
    ]
    
    Supervisor.init(children, strategy: :one_for_one)
  end
end

defmodule Codexis.Formats.PDF.Supervisor do
  @moduledoc """
  Supervisor for PDF-specific processes.
  """
  use DynamicSupervisor
  
  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end
  
  @impl true
  def init(_init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
  
  @doc """
  Starts a PDF build process for the given build ID and metadata.
  """
  def start_builder(build_id, metadata) do
    # Child spec for the PDF builder
    child_spec = {Codexis.Formats.PDF.BuildServer, {build_id, metadata}}
    
    # Start the PDF builder under this supervisor
    case DynamicSupervisor.start_child(__MODULE__, child_spec) do
      {:ok, pid} -> {:ok, pid}
      {:error, reason} -> {:error, reason}
    end
  end
end

6. Concurrency Patterns

Ruby Implementation

In our Ruby codebase, we use threads for limited concurrency:

def build_formats_concurrently(metadata, formats)
  threads = formats.map do |format|
    Thread.new do
      begin
        send("build_#{format}", metadata)
      rescue => e
        log_error("#{format.upcase} build failed: #{e.message}")
        nil
      end
    end
  end
  
  # Wait for all threads to complete
  results = threads.map(&:value)
  
  # Filter out nil results (failed builds)
  results.compact
end

Elixir Implementation

In Elixir, we'll use Tasks, GenServers, and other OTP constructs for concurrency:

defmodule Codexis.Build.FormatBuilder do
  @moduledoc """
  Handles building multiple formats concurrently.
  """
  
  @doc """
  Builds multiple formats concurrently.
  
  ## Returns
    * `{:ok, results}` - All formats built successfully
    * `{:partial, results, errors}` - Some formats built successfully, some failed
    * `{:error, errors}` - All formats failed
  """
  def build_formats(metadata, formats) do
    # Start a task for each format
    tasks = Enum.map(formats, fn format ->
      Task.async(fn ->
        {format, build_format(format, metadata)}
      end)
    end)
    
    # Wait for all tasks to complete
    results = Task.await_many(tasks, 60_000)
    
    # Separate successful and failed builds
    {successful, failed} = Enum.split_with(results, fn {_, result} ->
      match?({:ok, _}, result)
    end)
    
    # Format results
    success_map = Map.new(successful, fn {format, {:ok, path}} -> {format, path} end)
    error_map = Map.new(failed, fn {format, {:error, reason}} -> {format, reason} end)
    
    # Return appropriate result based on success/failure
    cond do
      Enum.empty?(failed) ->
        {:ok, success_map}
      
      Enum.empty?(successful) ->
        {:error, error_map}
      
      true ->
        {:partial, success_map, error_map}
    end
  end
  
  # Build a specific format
  defp build_format(:pdf, metadata) do
    Codexis.Formats.PDF.build(metadata)
  end
  
  defp build_format(:epub, metadata) do
    Codexis.Formats.EPUB.build(metadata)
  end
  
  defp build_format(:mobi, metadata) do
    Codexis.Formats.MOBI.build(metadata)
  end
  
  defp build_format(format, _metadata) do
    {:error, "Unsupported format: #{format}"}
  end
end

For more complex concurrency patterns, we'll use GenStage:

defmodule Codexis.Pipeline do
  @moduledoc """
  Defines a processing pipeline for book conversion.
  """
  
  @doc """
  Creates a new pipeline for the given metadata.
  """
  def new(metadata, formats) do
    # Create a producer for the source content
    {:ok, producer} = Codexis.Pipeline.ContentProducer.start_link(metadata)
    
    # Create processors for each stage
    {:ok, preprocessor} = Codexis.Pipeline.Preprocessor.start_link()
    
    # Create consumers for each format
    consumers = Enum.map(formats, fn format ->
      {:ok, pid} = format_consumer(format).start_link()
      pid
    end)
    
    # Connect the stages
    GenStage.sync_subscribe(preprocessor, to: producer)
    
    Enum.each(consumers, fn consumer ->
      GenStage.sync_subscribe(consumer, to: preprocessor)
    end)
    
    # Return the pipeline
    %{
      metadata: metadata,
      formats: formats,
      producer: producer,
      preprocessor: preprocessor,
      consumers: consumers
    }
  end
  
  # Get the appropriate consumer module for a format
  defp format_consumer(:pdf), do: Codexis.Pipeline.PDFConsumer
  defp format_consumer(:epub), do: Codexis.Pipeline.EPUBConsumer
  defp format_consumer(:mobi), do: Codexis.Pipeline.MOBIConsumer
end

defmodule Codexis.Pipeline.ContentProducer do
  @moduledoc """
  GenStage producer that provides content chunks for processing.
  """
  use GenStage
  
  def start_link(metadata) do
    GenStage.start_link(__MODULE__, metadata)
  end
  
  @impl true
  def init(metadata) do
    # Load content from metadata
    {:ok, content} = Codexis.Content.load(metadata)
    
    # Split content into chunks
    chunks = Codexis.Content.chunk(content)
    
    # Initialize state
    {:producer, %{chunks: chunks, meta metadata}}
  end
  
  @impl true
  def handle_demand(demand, %{chunks: chunks} = state) when demand > 0 do
    # Take demanded chunks
    {events, remaining} = Enum.split(chunks, demand)
    
    # Update state
    {:noreply, events, %{state | chunks: remaining}}
  end
end

defmodule Codexis.Pipeline.Preprocessor do
  @moduledoc """
  GenStage producer-consumer that preprocesses content chunks.
  """
  use GenStage
  
  def start_link do
    GenStage.start_link(__MODULE__, :ok)
  end
  
  @impl true
  def init(:ok) do
    {:producer_consumer, %{}}
  end
  
  @impl true
  def handle_events(events, _from, state) do
    # Process each chunk
    processed = Enum.map(events, &preprocess_chunk/1)
    
    # Forward processed chunks
    {:noreply, processed, state}
  end
  
  # Preprocess a content chunk
  defp preprocess_chunk(chunk) do
    # Implementation
    processed_chunk = chunk
    processed_chunk
  end
end

defmodule Codexis.Pipeline.PDFConsumer do
  @moduledoc """
  GenStage consumer that generates PDF output.
  """
  use GenStage
  
  def start_link do
    GenStage.start_link(__MODULE__, :ok)
  end
  
  @impl true
  def init(:ok) do
    {:consumer, %{chunks: []}}
  end
  
  @impl true
  def handle_events(events, _from, state) do
    # Collect chunks
    updated_state = %{state | chunks: state.chunks ++ events}
    
    # Process if we have all chunks
    if Enum.empty?(events) do
      # Generate PDF from all chunks
      generate_pdf(updated_state.chunks)
    end
    
    {:noreply, [], updated_state}
  end
  
  # Generate PDF from chunks
  defp generate_pdf(chunks) do
    # Implementation
    :ok
  end
end

7. File System Operations

Ruby Implementation

In our Ruby codebase, we use standard Ruby file operations:

def ensure_directory(path)
  FileUtils.mkdir_p(path) unless Dir.exist?(path)
end

def copy_file(source, destination)
  FileUtils.cp(source, destination)
end

def write_file(path, content)
  File.write(path, content)
end

def read_file(path)
  File.read(path)
end

Elixir Implementation

In Elixir, we'll use the File module and create a dedicated file system service:

defmodule Codexis.FileSystem do
  @moduledoc """
  File system operations for the ELib system.
  """
  
  @doc """
  Ensures a directory exists, creating it if necessary.
  
  ## Returns
    * `{:ok, path}` - Directory exists or was created
    * `{:error, reason}` - Failed to create directory
  """
  def ensure_directory(path) do
    case File.dir?(path) do
      true ->
        {:ok, path}
      
      false ->
        case File.mkdir_p(path) do
          :ok -> {:ok, path}
          {:error, reason} -> {:error, reason}
        end
    end
  end
  
  @doc """
  Copies a file from source to destination.
  
  ## Returns
    * `{:ok, destination}` - File was copied
    * `{:error, reason}` - Failed to copy file
  """
  def copy_file(source, destination) do
    # Ensure destination directory exists
    with {:ok, _} <- ensure_directory(Path.dirname(destination)),
         :ok <- File.copy(source, destination) do
      {:ok, destination}
    end
  end
  
  @doc """
  Writes content to a file.
  
  ## Returns
    * `{:ok, path}` - Content was written
    * `{:error, reason}` - Failed to write content
  """
  def write_file(path, content) do
    # Ensure directory exists
    with {:ok, _} <- ensure_directory(Path.dirname(path)),
         :ok <- File.write(path, content) do
      {:ok, path}
    end
  end
  
  @doc """
  Reads content from a file.
  
  ## Returns
    * `{:ok, content}` - Content was read
    * `{:error, reason}` - Failed to read content
  """
  def read_file(path) do
    case File.read(path) do
      {:ok, content} -> {:ok, content}
      {:error, reason} -> {:error, reason}
    end
  end
  
  @doc """
  Lists files in a directory matching a pattern.
  
  ## Returns
    * `{:ok, files}` - Files were listed
    * `{:error, reason}` - Failed to list files
  """
  def list_files(path, pattern \\ "*") do
    case File.dir?(path) do
      true ->
        files = path
          |> Path.join(pattern)
          |> Path.wildcard()
          |> Enum.filter(&File.regular?/1)
        
        {:ok, files}
      
      false ->
        {:error, :enoent}
    end
  end
  
  @doc """
  Removes a file or directory.
  
  ## Options
    * `:recursive` - Whether to remove directories recursively (default: false)
  
  ## Returns
    * `{:ok, path}` - Path was removed
    * `{:error, reason}` - Failed to remove path
  """
  def remove(path, opts \\ []) do
    recursive = Keyword.get(opts, :recursive, false)
    
    cond do
      File.regular?(path) ->
        case File.rm(path) do
          :ok -> {:ok, path}
          {:error, reason} -> {:error, reason}
        end
      
      File.dir?(path) && recursive ->
        case File.rm_rf(path) do
          {:ok, _} -> {:ok, path}
          {:error, reason} -> {:error, reason}
        end
      
      File.dir?(path) ->
        case File.rmdir(path) do
          :ok -> {:ok, path}
          {:error, reason} -> {:error, reason}
        end
      
      true ->
        {:error, :enoent}
    end
  end
end

8. Dependency Injection

Ruby Implementation

In our Ruby codebase, we use constructor injection:

class PdfBuilder
  def initialize(metadata, command_runner: nil, logger: nil)
    @metadata = metadata
    @command_runner = command_runner || BookProcessing::Utilities::CommandRunner.new
    @logger = logger || BookProcessing::LoggerSetup.default_logger
  end
  
  def build
    # Use @command_runner and @logger
  end
end

# Usage
builder = PdfBuilder.new(
  metadata,
  command_runner: custom_runner,
  logger: custom_logger
)

Elixir Implementation

In Elixir, we'll use function parameters and structs for dependency injection:

defmodule Codexis.Formats.PDF do
  @moduledoc """
  Handles PDF generation from LaTeX source.
  """
  
  @doc """
  Creates a new PDF builder with the given dependencies.
  
  ## Options
    * `:command_executor` - Command executor to use (default: Codexis.System.CommandExecutor)
    * `:logger` - Logger to use (default: Logger)
    * `:file_system` - File system service to use (default: Codexis.FileSystem)
  """
  def new(metadata, opts \\ []) do
    %{
      metadata: metadata,
      command_executor: Keyword.get(opts, :command_executor, Codexis.System.CommandExecutor),
      logger: Keyword.get(opts, :logger, Logger),
      file_system: Keyword.get(opts, :file_system, Codexis.FileSystem)
    }
  end
  
  @doc """
  Builds a PDF using the given builder.
  
  ## Returns
    * `{:ok, pdf_path}` - PDF was successfully generated
    * `{:error, reason}` - PDF generation failed
  """
  def build(builder) do
    # Use injected dependencies
    builder.logger.info("Building PDF for #{builder.metadata.title}")
    
    with {:ok, _} <- process_images(builder),
         {:ok, _} <- compile_latex(builder),
         {:ok, pdf_path} <- move_pdf(builder) do
      {:ok, pdf_path}
    end
  end
  
  # Process images for LaTeX
  defp process_images(builder) do
    # Use builder.command_executor and builder.file_system
    {:ok, processed_count}
  end
  
  # Compile LaTeX to PDF
  defp compile_latex(builder) do
    # Use builder.command_executor
    {:ok, output}
  end
  
  # Move PDF to output directory
  defp move_pdf(builder) do
    # Use builder.file_system
    {:ok, pdf_path}
  end
end

# Usage
builder = Codexis.Formats.PDF.new(
  metadata,
  command_executor: MockCommandExecutor,
  logger: MockLogger,
  file_system: MockFileSystem
)

{:ok, pdf_path} = Codexis.Formats.PDF.build(builder)

For more complex dependency injection, we can use a context pattern:

defmodule Codexis.Context do
  @moduledoc """
  Provides a context for dependency injection.
  """
  
  @doc """
  Creates a new context with the given dependencies.
  """
  def new(opts \\ []) do
    %{
      command_executor: Keyword.get(opts, :command_executor, Codexis.System.CommandExecutor),
      logger: Keyword.get(opts, :logger, Logger),
      file_system: Keyword.get(opts, :file_system, Codexis.FileSystem),
      config: Keyword.get(opts, :config, Codexis.Config)
    }
  end
  
  @doc """
  Gets a dependency from the context.
  """
  def get(context, key) do
    Map.get(context, key)
  end
  
  @doc """
  Updates a dependency in the context.
  """
  def put(context, key, value) do
    Map.put(context, key, value)
  end
end

# Usage
context = Codexis.Context.new(logger: MockLogger)
Codexis.Formats.PDF.build(metadata, context)

9. Testing Strategies

Ruby Implementation

In our Ruby codebase, we use RSpec for testing:

RSpec.describe PdfBuilder do
  let(:metadata) { double("Metadata", title: "Test Book") }
  let(:command_runner) { double("CommandRunner") }
  let(:logger) { double("Logger", info: nil, error: nil) }
  
  subject { described_class.new(metadata, command_runner: command_runner, logger: logger) }
  
  describe "#build" do
    before do
      allow(command_runner).to receive(:run).and_return(true)
    end
    
    it "builds a PDF successfully" do
      expect(subject.build).to eq("path/to/output.pdf")
    end
    
    context "when command fails" do
      before do
        allow(command_runner).to receive(:run).and_raise("Command failed")
      end
      
      it "raises an error" do
        expect { subject.build }.to raise_error("Command failed")
      end
    end
  end
end

Elixir Implementation

In Elixir, we'll use ExUnit for testing with mocks and dependency injection:

defmodule Codexis.Formats.PDFTest do
  use ExUnit.Case, async: true
  
  # Define test doubles
  defmodule MockCommandExecutor do
    def run(_command, _error_msg \\ nil, _opts \\ []) do
      {:ok, true}
    end
  end
  
  defmodule MockLogger do
    def info(_msg), do: :ok
    def error(_msg), do: :ok
  end
  
  defmodule MockFileSystem do
    def ensure_directory(_path), do: {:ok, "test/dir"}
    def copy_file(_source, destination), do: {:ok, destination}
    def write_file(_path, _content), do: {:ok, "test/file.txt"}
    def read_file(_path), do: {:ok, "test content"}
  end
  
  # Test metadata
  @metadata %{title: "Test Book", author: "Test Author"}
  
  describe "build/1" do
    test "builds a PDF successfully" do
      # Create builder with mocks
      builder = Codexis.Formats.PDF.new(
        @metadata,
        command_executor: MockCommandExecutor,
        logger: MockLogger,
        file_system: MockFileSystem
      )
      
      # Test build function
      assert {:ok, pdf_path} = Codexis.Formats.PDF.build(builder)
      assert is_binary(pdf_path)
    end
    
    test "handles command failure" do
      # Create mock that simulates failure
      defmodule FailingCommandExecutor do
        def run(_command, _error_msg \\ nil, _opts \\ []) do
          {:error, "Command failed"}
        end
      end
      
      # Create builder with failing mock
      builder = Codexis.Formats.PDF.new(
        @metadata,
        command_executor: FailingCommandExecutor,
        logger: MockLogger,
        file_system: MockFileSystem
      )
      
      # Test build function with failure
      assert {:error, reason} = Codexis.Formats.PDF.build(builder)
      assert reason =~ "Command failed"
    end
  end
end

For more complex testing, we can use Mox:

defmodule Codexis.Test.Mocks do
  # Define behaviors for mocks
  defmodule CommandExecutor do
    @callback run(String.t(), String.t() | nil, keyword()) :: 
      {:ok, boolean() | String.t()} | {:error, String.t()}
    
    @callback run_with_retry(String.t(), String.t() | nil, keyword()) ::
      {:ok, boolean() | String.t()} | {:error, String.t()}
  end
  
  defmodule FileSystem do
    @callback ensure_directory(String.t()) :: {:ok, String.t()} | {:error, atom()}
    @callback copy_file(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()}
    @callback write_file(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()}
    @callback read_file(String.t()) :: {:ok, String.t()} | {:error, atom()}
  end
end

# In test_helper.exs
Mox.defmock(Codexis.Test.MockCommandExecutor, for: Codexis.Test.Mocks.CommandExecutor)
Mox.defmock(Codexis.Test.MockFileSystem, for: Codexis.Test.Mocks.FileSystem)

# In test file
defmodule Codexis.Formats.PDFTest do
  use ExUnit.Case, async: true
  import Mox
  
  # Verify mocks after each test
  setup :verify_on_exit!
  
  # Test metadata
  @metadata %{title: "Test Book", author: "Test Author"}
  
  describe "build/1" do
    test "builds a PDF successfully" do
      # Set up expectations
      Codexis.Test.MockCommandExecutor
      |> expect(:run, fn _cmd, _err, _opts -> {:ok, true} end)
      |> expect(:run, fn _cmd, _err, _opts -> {:ok, true} end)
      
      Codexis.Test.MockFileSystem
      |> expect(:ensure_directory, fn _path -> {:ok, "test/dir"} end)
      |> expect(:copy_file, fn _src, dest -> {:ok, dest} end)
      
      # Create builder with mocks
      builder = Codexis.Formats.PDF.new(
        @metadata,
        command_executor: Codexis.Test.MockCommandExecutor,
        logger: MockLogger,
        file_system: Codexis.Test.MockFileSystem
      )
      
      # Test build function
      assert {:ok, pdf_path} = Codexis.Formats.PDF.build(builder)
      assert is_binary(pdf_path)
    end
  end
end

10. Logging Approaches

Ruby Implementation

In our Ruby codebase, we use a custom logger setup:

module BookProcessing
  module LoggerSetup
    def self.default_logger
      logger = Logger.new(STDOUT)
      logger.level = ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO
      logger.formatter = proc do |severity, datetime, progname, msg|
        "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
      end
      logger
    end
    
    def self.file_logger(path)
      logger = Logger.new(path)
      logger.level = Logger::DEBUG
      logger.formatter = proc do |severity, datetime, progname, msg|
        "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
      end
      logger
    end
  end
end

# Usage
logger = BookProcessing::LoggerSetup.default_logger
logger.info("Starting build")
logger.debug("Debug information")

Elixir Implementation

In Elixir, we'll use the built-in Logger with custom backends and metadata:

# config/config.exs
import Config

config :logger,
  level: :info,
  format: "$time $metadata[$level] $message\n",
  metadata: [:build_id, :format, :module]

# For development
if config_env() == :dev do
  config :logger, :console,
    format: "[$level] $message\n"
end

# For production
if config_env() == :prod do
  config :logger, :console,
    level: :info,
    format: "$time [$level] $message\n"
  
  config :logger, :file,
    path: "/var/log/elib/elib.log",
    level: :info,
    rotate: %{max_bytes: 52428800, keep: 5}
end

# In application code
defmodule Codexis.Logging do
  @moduledoc """
  Logging utilities for the ELib system.
  """
  require Logger
  
  @doc """
  Sets up logging for a build.
  """
  def setup_build_logging(build_id) do
    # Set up file logging for this build
    log_path = Path.join([
      Codexis.Config.get([:general, :log_dir], "log"),
      "#{build_id}.log"
    ])
    
    # Ensure log directory exists
    :ok = File.mkdir_p(Path.dirname(log_path))
    
    # Add file backend for this build
    {:ok, _} = Logger.add_backend({LoggerFileBackend, :build_log})
    :ok = Logger.configure_backend({LoggerFileBackend, :build_log},
      path: log_path,
      level: :debug,
      metadata_filter: [build_id: build_id]
    )
    
    # Return log path
    {:ok, log_path}
  end
  
  @doc """
  Logs a message with build context.
  """
  def log(level, message, metadata \\ []) do
    Logger.log(level, message, metadata)
  end
  
  @doc """
  Logs the start of a build step.
  """
  def log_step_start(step, metadata \\ []) do
    Logger.info("Starting step: #{step}", metadata)
  end
  
  @doc """
  Logs the completion of a build step.
  """
  def log_step_complete(step, metadata \\ []) do
    Logger.info("Completed step: #{step}", metadata)
  end
  
  @doc """
  Logs a build error.
  """
  def log_build_error(error, metadata \\ []) do
    Logger.error("Build error: #{inspect(error)}", metadata)
  end
end

# Usage in application code
defmodule Codexis.Formats.PDF do
  require Logger
  
  def build(metadata) do
    # Set metadata for all log messages in this process
    Logger.metadata(format: :pdf, build_id: metadata.build_id)
    
    Logger.info("Building PDF for #{metadata.title}")
    
    # Build logic
    
    Logger.info("PDF build complete")
    {:ok, pdf_path}
  end
end

For structured logging and metrics, we can use Telemetry:

defmodule Codexis.Telemetry do
  @moduledoc """
  Telemetry utilities for the ELib system.
  """
  
  @doc """
  Executes a function with telemetry events.
  
  ## Examples
      iex> Codexis.Telemetry.span([:elib, :pdf, :build], %{book_id: 123}, fn ->
      ...>   # Build logic
      ...>   {:ok, "path/to/pdf"}
      ...> end)
  """
  def span(event_prefix, metadata, function) do
    start_time = System.monotonic_time()
    start_event = event_prefix ++ [:start]
    
    :telemetry.execute(start_event, %{system_time: System.system_time()}, metadata)
    
    try do
      result = function.()
      end_time = System.monotonic_time()
      duration = end_time - start_time
      
      # Determine event name based on result
      event_name = case result do
        {:ok, _} -> event_prefix ++ [:success]
        {:error, _} -> event_prefix ++ [:failure]
        _ -> event_prefix ++ [:complete]
      end
      
      # Include result in metadata
      result_metadata = Map.put(metadata, :result, result)
      
      # Execute telemetry event
      :telemetry.execute(
        event_name,
        %{
          duration: duration,
          system_time: System.system_time()
        },
        result_metadata
      )
      
      result
    rescue
      error ->
        end_time = System.monotonic_time()
        duration = end_time - start_time
        
        # Execute exception event
        :telemetry.execute(
          event_prefix ++ [:exception],
          %{
            duration: duration,
            system_time: System.system_time()
          },
          Map.merge(metadata, %{
            kind: :error,
            error: error,
            stacktrace: __STACKTRACE__
          })
        )
        
        reraise error, __STACKTRACE__
    end
  end
end

# Usage in application code
defmodule Codexis.Formats.PDF do
  def build(metadata) do
    Codexis.Telemetry.span([:elib, :formats, :pdf, :build], %{book_id: metadata.id}, fn ->
      # Build logic
      {:ok, pdf_path}
    end)
  end
end

# Telemetry handler setup
defmodule Codexis.TelemetryHandlers do
  def setup do
    # Attach handlers for logging
    :telemetry.attach(
      "elib-pdf-build-handler",
      [:elib, :formats, :pdf, :build, :start],
      &handle_pdf_build_start/4,
      nil
    )
    
    :telemetry.attach(
      "elib-pdf-build-success-handler",
      [:elib, :formats, :pdf, :build, :success],
      &handle_pdf_build_success/4,
      nil
    )
    
    # Attach handlers for metrics
    :telemetry.attach(
      "elib-build-duration-handler",
      [:elib, :formats, :*],
      &handle_build_duration/4,
      nil
    )
  end
  
  # Handler functions
  def handle_pdf_build_start(_event, _measurements, metadata, _config) do
    Logger.info("Starting PDF build for book #{metadata.book_id}")
  end
  
  def handle_pdf_build_success(_event, measurements, metadata, _config) do
    Logger.info("PDF build successful for book #{metadata.book_id} in #{measurements.duration / 1_000_000} ms")
  end
  
  def handle_build_duration(_event, measurements, metadata, _config) do
    # Record metrics
    :telemetry_metrics_counter.inc([:elib, :build, :count], 1)
    :telemetry_metrics_summary.observe([:elib, :build, :duration], measurements.duration / 1_000_000)
  end
end