-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
None
-
None
-
False
-
-
False
-
Not Started
-
Not Started
-
Not Started
-
Not Started
-
Not Started
-
Not Started
-
-
Summary
When backend-worker or backend-listener processes crash due to an unhandled exception, the actual error message is not logged. Instead, a log shifting failed. closed stream error appears because the Backend logger is initialized with a file path and log rotation enabled, which conflicts with container logging to stdout.
Problem Description
The Backend.logger is initialized with /tmp/backend_logger.log and log rotation enabled (shift_age=10), inherited from the container image's default CONFIG_LOG_PATH=/tmp/.
When an exception occurs during a crash:
- Exception is caught and Worker.logger.notify(exception) is called
- This delegates to Backend.logger.notify(exception)
- Backend.logger attempts to write the error AND rotate the log file
- During a crash, file descriptors close, causing "log shifting failed. closed stream"
- The actual exception message never appears in logs
Logging Architecture in Apisonator
Three Distinct Logging Systems
| Logger | Purpose | Used By |
|---|---|---|
| Backend.logger | Application-level logging and exceptions | Listener (exceptions), Worker (exceptions), Cron (all) |
| Worker.logger | Worker activity logging (job processing) | Worker only |
| Request Logger(s) | HTTP request/response logging | Listener only |
Backend Logger (Backend.logger)
Configuration:
- Environment variables: CONFIG_LOG_PATH or CONFIG_LOG_FILE
- Code: lib/3scale/backend/configuration.rb:99-113 and lib/3scale/backend/logging.rb:34-39
Initialization logic:
lib/3scale/backend/configuration.rb:99-113 log_file = config.log_file if !log_file || log_file.empty? log_path = config.log_path config.log_file = if log_path && !log_path.empty? if File.stat(log_path).ftype == 'directory' "#{log_path}/backend_logger.log" else log_path end else ENV['CONFIG_LOG_FILE'] || STDOUT end end
lib/3scale/backend/logging.rb:34-39 def enable_logging Logging.enable! on: self.singleton_class, with_args: [configuration.log_file, 10] do |logger| # 10 = log rotation enabled logger.define_singleton_method(:notify, logger_notify_proc(logger)) end end
Special notify() method:
- Used specifically for exception handling
- Chains to external error reporting (Bugsnag if configured)
- Worker.logger.notify() delegates to Backend.logger.notify()
Worker Logger (Worker.logger)
Configuration:
- Environment variable: CONFIG_WORKERS_LOG_FILE
- Code: lib/3scale/backend/logging/worker.rb:85-97
Key behavior:
- Regular logging (info, debug): Uses its own logger instance → CONFIG_WORKERS_LOG_FILE
- Exception logging via notify(): Delegates to Backend.logger.notify()
lib/3scale/backend/logging/worker.rb:95, 101-103 def configure_logging(worker_class, workers_log_file) ... logger.define_singleton_method(:notify, backend_logger_notify_proc) end def backend_logger_notify_proc Backend.logger.method(:notify).to_proc end
Request Logger
Configuration:
- Environment variable: CONFIG_REQUEST_LOGGERS (comma-separated: text, json)
- Used by listener only for HTTP request/response logging
Environment Variables
Logging Configuration
| Variable | Purpose | Default (container) | Used By |
|---|---|---|---|
| CONFIG_LOG_PATH | Directory path for Backend logger file | /tmp/ | Listener, Worker, Cron |
| CONFIG_LOG_FILE | Explicit file path for Backend logger (overrides LOG_PATH) | unset | Listener, Worker, Cron |
| CONFIG_WORKERS_LOG_FILE | Log file for Worker activity | /dev/stdout | Worker only |
| CONFIG_REQUEST_LOGGERS | HTTP request logger formats | text | Listener only |
| CONFIG_WORKERS_LOGGER_FORMATTER | Worker log format | :text | Worker only |
External Error Reporting
| Variable | Purpose | Default |
|---|---|---|
| CONFIG_HOPTOAD_SERVICE | External error service type (bugsnag, default) | unset |
| CONFIG_HOPTOAD_API_KEY | API key for external error service | unset |
Logging Behavior by Component
Listener (bin/3scale_backend start)
| Log Type | Logger | Destination | Config | Affected by Bug? |
|---|---|---|---|---|
| HTTP requests | Request Logger | stdout | CONFIG_REQUEST_LOGGERS | No |
| Web server errors | Web server -x flag | stdout | -x /dev/stdout | No |
| Application exceptions | Backend.logger.notify() | /tmp/backend_logger.log | CONFIG_LOG_PATH=/tmp/ | YES |
| General app logs | Backend.logger | /tmp/backend_logger.log | CONFIG_LOG_PATH=/tmp/ | YES |
Worker (bin/3scale_backend_worker)
| Log Type | Logger | Destination | Config | Affected by Bug? |
|---|---|---|---|---|
| Job activity logs | Worker.logger | stdout | CONFIG_WORKERS_LOG_FILE=/dev/stdout | No |
| Exceptions | Worker.logger.notify() → Backend.logger.notify() | /tmp/backend_logger.log | CONFIG_LOG_PATH=/tmp/ | YES |
| General app logs | Backend.logger | /tmp/backend_logger.log | CONFIG_LOG_PATH=/tmp/ | YES |
Cron (backend-cron)
| Log Type | Logger | Destination | Config | Affected by Bug? |
|---|---|---|---|---|
| All logs | Backend.logger | stdout | Sets CONFIG_LOG_PATH='dev/stdout' in code | No |
Root Cause Analysis
Why the Bug Occurs
In containers (from Dockerfile openshift/distro/ubi/9/Dockerfile:79):
CONFIG_LOG_PATH=/tmp/
This causes:
config.log_file = "#{log_path}/backend_logger.log" # "/tmp/backend_logger.log"
Backend.logger is created with rotation:
Logging.enable! on: self.singleton_class, with_args: ["/tmp/backend_logger.log", 10] # 10 = shift_age (rotation enabled)
The problem:
Ruby's Logger.new(filepath, shift_age) with a file path string enables log rotation. When using STDOUT constant, Ruby recognizes it as an IO stream and skips rotation.
Logger.new("/tmp/backend_logger.log", 10) # File path = rotation enabled ❌ Logger.new(STDOUT) # IO constant = NO rotation ✅
Why notify() Logs to a Different Location
The notify() method is designed to integrate with external error reporting services like Bugsnag:
- When CONFIG_HOPTOAD_SERVICE=bugsnag and CONFIG_HOPTOAD_API_KEY are set, exceptions are sent to Bugsnag
- The notify() method chains both local logging (Backend.logger.error) AND external reporting (Bugsnag.notify)
- Code reference: lib/3scale/backend/logging.rb:41-47
def logger_notify_proc(logger) external_notify_proc = Logging::External.notify_proc proc do |exception, *args, &block| logger.error('Exception') { {exception: {class: exception.class, message: exception.message, backtrace: exception.backtrace[0..3]}} } external_notify_proc&.call(exception, *args, &block) end end
Worker.logger delegates its notify() to Backend.logger to ensure exceptions go through the centralized error reporting pipeline.
Must-Gather Verification
Verified in must-gather from THREESCALE-11940:
- Both listener and worker pods use image: registry.redhat.io/3scale-amp2/backend-rhel8@sha256:eb2288de900dc42ead755b28cdb2509b25ecc8dd256781beaa4a2a86e7cbe532
- Neither pod explicitly sets CONFIG_LOG_PATH environment variable
- Both inherit CONFIG_LOG_PATH=/tmp/ from Dockerfile
- Both are affected by the bug for exception logging
Solutions
Solution 1: Fix Midstream Dockerfile (Recommended)
Reference: Midstream Dockerfile
Change from:
CONFIG_LOG_PATH=/tmp/
To:
CONFIG_LOG_PATH=
This causes Backend.logger to use STDOUT constant (no rotation), preventing the bug.
Solution 2: Alternative - Explicit CONFIG_LOG_FILE
Set:
CONFIG_LOG_FILE=/dev/stdout
But using STDOUT constant (via empty CONFIG_LOG_PATH) is cleaner.
Suggestion: Code Fix (Upstream)
Modify lib/3scale/backend/logging.rb to detect /dev/stdout and /dev/stderr paths and use IO constants:
def enable_logging log_file = configuration.log_file args = if log_file == '/dev/stdout' [STDOUT] # No rotation elsif log_file == '/dev/stderr' [STDERR] # No rotation else [log_file, 10] # Enable rotation for regular files end Logging.enable! on: self.singleton_class, with_args: args do |logger| logger.define_singleton_method(:notify, logger_notify_proc(logger)) end end
Impact
- Listener: HTTP request logs work fine (go to stdout), but application exceptions are masked
- Worker: Activity logs work fine (go to stdout), but exceptions are masked
- Both components show "log shifting failed. closed stream" instead of actual error messages during crashes
- Makes debugging production issues extremely difficult
- links to
-
RHEA-2025:155899
Red Hat 3scale API Management 2.16.1 Release - Container Images
- mentioned on