You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala异步服务类日志设计与错误处理优化咨询

Hey there! As someone who’s worked with Scala and Play Framework a lot, I totally get wanting to make your async service methods more robust—especially when you need to differentiate between specific failure scenarios. Let’s walk through improving your getAccessToken method with better error handling and proper logging.


1. Define Custom Error Types

First, let’s create a sealed trait and case objects to represent your two error scenarios. This makes your code self-documenting and lets you handle each failure case explicitly later:

sealed trait AccessTokenError
case object UserNotFound extends AccessTokenError
case object AccessTokenMissing extends AccessTokenError

Sealed traits ensure all possible error cases are defined in the same file, so the compiler can help you cover all scenarios when handling errors down the line.

2. Refactor the Method with Explicit Error Handling

Instead of returning Future[Option[String]], we’ll return Future[Either[AccessTokenError, String]]. This way, you don’t just know that something went wrong—you know what went wrong.

Here’s the refactored method, with comments explaining each step:

import play.api.Logger
import scala.concurrent.Future

class UserService(userDAO: UserDAO) extends Logger { // Mix in Play's built-in Logging trait

  def getAccessToken(id: BSONObjectID): Future[Either[AccessTokenError, String]] = {
    logger.debug(s"Attempting to fetch access token for user ID: ${id.stringify}")

    userDAO.find(id).map {
      case None =>
        logger.warn(s"Failed to fetch access token: User with ID ${id.stringify} not found")
        Left(UserNotFound)
      case Some(user) =>
        user.settings.flatMap(_.accessToken) match {
          case None =>
            logger.warn(s"Failed to fetch access token: User ${id.stringify} exists but has no access token set")
            Left(AccessTokenMissing)
          case Some(token) =>
            logger.info(s"Successfully fetched access token for user ${id.stringify}")
            Right(token)
        }
    }
    // Catch unexpected exceptions (e.g., DB connection failures)
    .recover {
      case ex: Exception =>
        logger.error(s"Unexpected error fetching access token for user ${id.stringify}", ex)
        // You could add a third error type like `UnexpectedError` if needed
        Left(UserNotFound) // Fallback for unhandled issues, or define a specific error
    }
  }
}

3. Add Structured Logging

Play’s Logger trait gives you access to different log levels, which helps with debugging and monitoring:

  • Debug: Log method invocation details (great for tracing flow during development)
  • Info: Log successful operations (tracks normal behavior in production)
  • Warn: Log expected failure scenarios (user not found, token missing)
  • Error: Log unexpected exceptions (database crashes, runtime errors) with full stack traces

Pro tip: Never log sensitive data like the actual access token value—we’re only confirming we retrieved it successfully here. Also, debug logs are often disabled in production, so avoid expensive operations in those lines.

4. Example Usage in a Controller

When using this method in a Play controller, you can map the Either result to appropriate HTTP responses:

import play.api.mvc._
import scala.concurrent.ExecutionContext

class UserController(userService: UserService)(implicit ec: ExecutionContext) extends BaseController {

  def getToken(id: String) = Action.async {
    BSONObjectID.parse(id).fold(
      _ => Future.successful(BadRequest("Invalid user ID format")),
      userId => userService.getAccessToken(userId).map {
        case Right(token) => Ok(token)
        case Left(UserNotFound) => NotFound("User not found")
        case Left(AccessTokenMissing) => BadRequest("Access token not set for this user")
      }
    )
  }
}

Key Improvements Over the Original

  • Explicit Error Differentiation: No more guessing why an Option is None—you get a clear, typed reason.
  • Actionable Logs: Each success/failure case has a meaningful message, making it easy to debug issues in production.
  • Robustness: The recover block catches unexpected exceptions instead of letting them bubble up unhandled.

内容的提问来源于stack exchange,提问作者Julian Wilson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:00:36