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
OptionisNone—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
recoverblock catches unexpected exceptions instead of letting them bubble up unhandled.
内容的提问来源于stack exchange,提问作者Julian Wilson

