0Pricing
Elixir & Phoenix: Scalable Backend Development · 课时

错误处理与结构化日志记录

实现可靠的错误处理策略,并学习使用结构化日志记录进行有效的调试和监控。

错误处理与结构化日志记录 是 CoddyKit 上的免费 Elixir & Phoenix: Scalable Backend Development 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Elixir & Phoenix: Scalable Backend Development 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Elixir & Phoenix: Scalable Backend Development 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Why Error Handling Matters

In any application, things can go wrong. Users might input incorrect data, external services could fail, or files might not exist.

Error handling is about anticipating these issues and designing your application to respond gracefully, preventing crashes and ensuring a smooth user experience.

Elixir's 'Let It Crash' Philosophy

Elixir, built on the Erlang VM, embraces a unique approach called 'Let It Crash'. Instead of trying to prevent every possible error, Elixir processes are designed to be isolated.

If a process crashes, a supervisor can detect it and restart it, ensuring high availability. However, this primarily applies to process-level failures. For expected functional outcomes, a different pattern is used.

Tuples: {:ok, value} or {:error, reason}

For operations that might succeed or fail predictably, Elixir often uses a pattern of returning tuples: {:ok, result} for success and {:error, reason} for failure.

This makes the outcome explicit and avoids exceptions for expected scenarios, leading to clearer code.

defmodule AccountService do
  def withdraw(balance, amount) when amount > 0 and balance >= amount do
    {:ok, balance - amount}
  end

  def withdraw(balance, amount) when amount <= 0 do
    {:error, :invalid_amount}
  end

  def withdraw(balance, amount) when balance < amount do
    {:error, :insufficient_funds}
  end
end

IO.puts "Withdrawal 1: #{inspect AccountService.withdraw(100, 50)}"
IO.puts "Withdrawal 2: #{inspect AccountService.withdraw(100, 150)}"
IO.puts "Withdrawal 3: #{inspect AccountService.withdraw(100, 0)}"

Handling Results with `case`

Once a function returns an {:ok, ...} or {:error, ...} tuple, you can use Elixir's powerful pattern matching with a case statement to handle both outcomes explicitly.

This makes your error handling paths very clear and readable.

defmodule PaymentProcessor do
  def process_payment(amount, account_balance) do
    case AccountService.withdraw(account_balance, amount) do
      {:ok, new_balance} ->
        IO.puts "Payment successful! New balance: #{new_balance}"
        {:ok, new_balance}
      {:error, :insufficient_funds} ->
        IO.puts "Payment failed: Insufficient funds."
        {:error, :payment_failed}
      {:error, reason} ->
        IO.puts "Payment failed: Unknown reason #{reason}."
        {:error, :payment_failed}
    end
  end
end

# Assuming AccountService from previous scene is available
# Example usage:
PaymentProcessor.process_payment(30, 100)
PaymentProcessor.process_payment(120, 100)

The Power of `with` for Chaining

When you have multiple operations that might return {:error, ...} tuples and depend on the success of previous steps, the with special form is incredibly useful.

It allows you to chain these operations, and if any step returns an {:error, ...}, the with block immediately stops and returns that error, simplifying complex flows.

defmodule OrderProcessor do
  def process_order(user_id, item_id, quantity) do
    with {:ok, user} <- get_user(user_id),
         {:ok, item} <- get_item(item_id),
         {:ok, total_cost} <- calculate_cost(item, quantity),
         {:ok, _} <- deduct_funds(user, total_cost) do
      {:ok, "Order processed successfully for user #{user.name}"}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp get_user(1), do: {:ok, %{name: "Alice"}}
  defp get_user(_), do: {:error, :user_not_found}

  defp get_item(101), do: {:ok, %{price: 20}}
  defp get_item(_), do: {:error, :item_not_found}

  defp calculate_cost(%{price: p}, q), do: {:ok, p * q}

  defp deduct_funds(%{name: "Alice"}, 40), do: {:ok, :deducted}
  defp deduct_funds(_, _), do: {:error, :payment_failed}
end

IO.puts "Processing good order: #{inspect OrderProcessor.process_order(1, 101, 2)}"
IO.puts "Processing bad order (item): #{inspect OrderProcessor.process_order(1, 999, 1)}"

Beyond Basic Logs: Structured Logging

Traditional logs often look like plain text messages, which are hard for machines to parse and query. Structured logging addresses this by outputting logs in a consistent, machine-readable format, usually JSON.

This makes it much easier to filter, analyze, and visualize your log data using tools like Kibana or Splunk, providing deeper insights into your application's behavior.

Your Go-To: The `Logger` Module

Elixir comes with a powerful built-in logging facility: the Logger module. It allows you to emit log messages at different severity levels, such as :debug, :info, :warn, and :error.

By default, Logger prints to the console, but it's highly configurable to send logs to files, external services, or other destinations.

defmodule MyApp do
  require Logger

  def start_process(id) do
    Logger.info "Starting process with ID: #{id}"
    # ... some work ...
    if id == 101 do
      Logger.warn "Process #{id} encountered a minor issue."
    else
      Logger.debug "Process #{id} completed successfully."
    end
  end

  def critical_error(message) do
    Logger.error "Critical error detected: #{message}"
  end
end

MyApp.start_process(100)
MyApp.start_process(101)
MyApp.critical_error("Database connection lost")

Contextual Logs with Metadata

One of the best features of Logger for structured logging is the ability to easily add metadata (extra key-value pairs) to your log messages.

This metadata provides crucial context, like a user_id, request_id, or specific parameters, making it much easier to trace issues and understand what happened during an event.

defmodule WebRequestLogger do
  require Logger

  def log_request(method, path, user_id, duration_ms) do
    Logger.info "Request processed",
      method: method,
      path: path,
      user_id: user_id,
      duration: "#{duration_ms}ms"
  end

  def log_error(error_message, user_id, request_id) do
    Logger.error error_message,
      user_id: user_id,
      request_id: request_id,
      component: :api_handler
  end
end

WebRequestLogger.log_request("GET", "/users/1", 123, 55)
WebRequestLogger.log_error("Invalid API key", 456, "abc-123")

Controlling Log Output with Levels

Each log message has a level (debug, info, warn, error, critical). You can configure your Elixir application's Logger to only output messages at or above a certain level.

For example, in a production environment, you might set the level to :info to avoid excessive :debug logs, while in development, you might set it to :debug for full visibility.

  • :debug: Detailed information, useful for debugging.
  • :info: General operational messages.
  • :warn: Potentially harmful situations.
  • :error: Error events that might still allow the application to continue.
  • :critical: Severe error events that likely cause an application to abort.

Check Your Understanding

Which of these are benefits of using structured logging in Elixir applications?

Lesson Summary: Robustness & Insight

In this lesson, you've learned to build more robust Elixir applications and gain better insights into their behavior:

  • Understood Elixir's approach to error handling with the 'Let It Crash' philosophy.
  • Mastered the use of {:ok, value} and {:error, reason} tuples for explicit functional error handling.
  • Utilized the with special form to elegantly chain operations that might fail.
  • Discovered the advantages of structured logging for machine-readable, searchable logs.
  • Learned to use Elixir's Logger module, including adding valuable metadata to your logs and configuring log levels.

These techniques are fundamental for developing production-ready, observable Elixir systems!

常见问题解答

「错误处理与结构化日志记录」课时是免费的吗?

是的 — 「错误处理与结构化日志记录」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Elixir & Phoenix: Scalable Backend Development 课程的其余内容,请升级到 CoddyKit PRO。 Elixir & Phoenix: Scalable Backend Development 课程共包含 4 节课。

「错误处理与结构化日志记录」这节课中我会学到什么?

实现可靠的错误处理策略,并学习使用结构化日志记录进行有效的调试和监控。 你通过在浏览器中直接运行的动手代码来练习 Elixir & Phoenix: Scalable Backend Development,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Elixir & Phoenix: Scalable Backend Development 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Elixir & Phoenix: Scalable Backend Development 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「错误处理与结构化日志记录」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Elixir & Phoenix: Scalable Backend Development 课中编写并运行代码吗?

能。每节 Elixir & Phoenix: Scalable Backend Development 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Elixir 基准测试与性能分析
  2. 使用 Telemetry 与指标进行监控
  3. 错误处理与结构化日志记录
  4. 使用 OpenTelemetry 进行分布式追踪
← 返回 Elixir & Phoenix: Scalable Backend Development