Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
68: Logger for Application Logging
When you're first building out a feature—say, a system that processes credit card payments for an online store—your first instinct for tracking what's happening is probably puts. It's fast, it's immediate, and it shows up right there in your terminal. I've seen countless developers leave a trail of puts "Payment processed for #{order_id}" throughout their codebase, thinking they've implemented "logging."
def process_payment(order)
puts "Attempting to charge #{order.amount} for Order ##{order.id}"
result = PaymentGateway.charge(order.amount)
if result.success?
puts "Payment successful!"
else
puts "Payment failed: #{result.error_message}"
end
end
The visibility gap in production
The problem with puts is that it's a blunt instrument. In your local development environment, it's fine. But once that code hits a production server, puts becomes a liability. If your app is running as a background service or inside a container, where does that output actually go? Often, it ends up in a generic system log file that's mixed with thousands of other lines of noise, or worse, it's swallowed entirely.
Beyond the destination, you have no way to filter the noise. Imagine your app is processing 10,000 orders an hour. You don't want to see "Payment successful!" 10,000 times in your logs—that's just clutter. But you absolutely need to know the second a payment fails. With puts, it's all or nothing. You can't tell the program, "Only tell me about the errors today, but keep the detailed debug info available if I need to turn it on later."
Bringing in the Logger class
This is why Ruby provides the Logger library. It's a standard part of the language, and it solves the destination and filtering problems in one go. Instead of printing to the screen, you create a logger instance that can write to a file, a string, or STDOUT, and you categorize every message by its severity.
require 'logger'
# I usually set this up as a global or a constant in a configuration file
$log = Logger.new('log/production.log')
# Now we can be specific about the importance of the message
def process_payment(order)
$log.info("Attempting to charge #{order.amount} for Order ##{order.id}")
result = PaymentGateway.charge(order.amount)
if result.success?
$log.info("Payment successful for Order ##{order.id}")
else
$log.error("Payment failed for Order ##{order.id}: #{result.error_message}")
end
rescue StandardError => e
$log.fatal("System crash during payment processing: #{e.message}")
end
Tuning the volume of your logs
The real magic happens when you realize you can change the "level" of the logger without changing a single line of your logic. The Logger class recognizes a hierarchy: DEBUG, INFO, WARN, ERROR, and FATAL.
If you're debugging a weird edge case in staging, you can set $log.level = Logger::DEBUG. Suddenly, every tiny detail you've logged is visible. But in production, you'll likely set $log.level = Logger::INFO or Logger::WARN. This tells Ruby to ignore everything below that threshold. All those DEBUG messages you left in the code to help you build the feature? They're still there, but they won't take up a single byte of disk space or slow down your app in production.
I'll give you a tip from the trenches: always include a unique identifier (like an order ID or user ID) in your logs. A log that says "Payment failed" is useless. A log that says "Payment failed for Order #5501" is a map that leads you straight to the bug.
📋 Practical Task
Implementing a Payment Gateway Audit Log
You are tasked with upgrading a legacy payment processing script that currently uses puts. You need to replace the print statements with a proper Logger implementation to make the system production-ready.
Requirements:
- Require the
loggerlibrary. - Create a logger instance that writes to a file named
payment_audit.log. - Replace the
putsstatement for the "starting process" message withinfo. - Replace the
putsstatement for the "payment failed" message witherror. - Add a
rescueblock to catch anyStandardErrorand log it asfatal. - Set the logger level to
Logger::INFOso thatdebugmessages (if any were added) would be ignored.
# Legacy code to be fixed:
def process_transaction(id, amount)
puts "Processing transaction #{id} for $#{amount}..."
# Simulate a random failure
if rand > 0.8
raise "Connection Timeout"
elsif rand > 0.5
puts "Transaction #{id} failed: Insufficient Funds"
return false
end
puts "Transaction #{id} completed successfully."
true
end
process_transaction("TXN123", 50.00)
There are no comments for now.