Skip to content

Commit b574608

Browse files
jkebingerclaude
andcommitted
Make logger dependencies optional and add stdlib Logger support
- Move semantic_logger from runtime to development dependency - Make semantic_logger optional with graceful fallback - Add stdlib Logger integration via stdlib_formatter method - Update README with stdlib Logger documentation - Improves flexibility for customer integration by removing required dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1cb26b2 commit b574608

5 files changed

Lines changed: 85 additions & 8 deletions

File tree

Gemfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ gem 'uuid'
99

1010
gem 'activesupport', '>= 4'
1111

12-
gem 'semantic_logger', '!= 4.16.0', require: "semantic_logger/sync"
13-
14-
group :development do
12+
group :development do
13+
gem 'semantic_logger', '!= 4.16.0', require: "semantic_logger/sync"
1514
gem 'allocation_stats'
1615
gem 'benchmark-ips'
1716
gem 'bundler'

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ end
6666

6767
## Dynamic Log Levels
6868

69-
Reforge supports dynamic log level management through SemanticLogger integration. This allows you to change log levels in real-time without redeploying your application.
69+
Reforge supports dynamic log level management for Ruby logging frameworks. This allows you to change log levels in real-time without redeploying your application.
70+
71+
Supported loggers:
72+
- SemanticLogger (optional dependency)
73+
- Ruby stdlib Logger
7074

7175
### Setup with SemanticLogger
7276

@@ -127,6 +131,26 @@ on_worker_boot do
127131
end
128132
```
129133

134+
### With Ruby stdlib Logger
135+
136+
If you're using Ruby's standard library Logger, you can use a dynamic formatter:
137+
138+
```ruby
139+
require "logger"
140+
require "sdk-reforge"
141+
142+
client = Reforge::Client.new(
143+
sdk_key: ENV['REFORGE_BACKEND_SDK_KEY'],
144+
logger_key: 'log-levels.default' # optional, this is the default
145+
)
146+
147+
logger = Logger.new($stdout)
148+
logger.level = Logger::DEBUG # Set to most verbose level, Reforge will handle filtering
149+
logger.formatter = client.log_level_client.stdlib_formatter('MyApp')
150+
```
151+
152+
The formatter will check dynamic log levels from Reforge and only output logs that meet the configured threshold.
153+
130154
### Configuration
131155

132156
In Reforge Launch, create a `LOG_LEVEL_V2` config with your desired key (default: `log-levels.default`). The config will be evaluated with the following context:

lib/reforge/log_level_client.rb

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@ def initialize(base_client)
1919
@base_client = base_client
2020
end
2121

22+
# Map from Ruby stdlib Logger severity levels to our LogLevel symbols
23+
# Ruby Logger levels: DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4, UNKNOWN=5
24+
STDLIB_LOGGER_LEVELS = {
25+
0 => :debug, # Logger::DEBUG
26+
1 => :info, # Logger::INFO
27+
2 => :warn, # Logger::WARN
28+
3 => :error, # Logger::ERROR
29+
4 => :fatal, # Logger::FATAL
30+
5 => :fatal # Logger::UNKNOWN (treat as fatal)
31+
}.freeze
32+
2233
# Check if a log message should be logged based on severity and logger path
23-
# @param severity [Integer] SemanticLogger severity level (0-5)
34+
# @param severity [Integer] Logger severity level (0-5 for SemanticLogger, 0-5 for stdlib Logger)
2435
# @param path [String] Logger path/name
2536
# @return [Boolean] true if the message should be logged
2637
def should_log?(severity, path)
@@ -32,13 +43,47 @@ def should_log?(severity, path)
3243
# SemanticLogger filter integration
3344
# @param log [SemanticLogger::Log] The log entry to filter
3445
# @return [Boolean] true if the log should be output
46+
# Note: This method requires semantic_logger gem to be installed
3547
def semantic_filter(log)
48+
unless defined?(SemanticLogger)
49+
LOG.warn "semantic_filter called but SemanticLogger is not loaded. Install the 'semantic_logger' gem to use this feature."
50+
return true # Allow all logs through if SemanticLogger is not available
51+
end
52+
3653
class_path = class_path_name(log.name)
3754
level = SemanticLogger::Levels.index(log.level)
3855
log.named_tags.merge!({ path: class_path })
3956
should_log?(level, class_path)
4057
end
4158

59+
# Returns a formatter proc for use with Ruby stdlib Logger
60+
# Usage:
61+
# logger = Logger.new($stdout)
62+
# logger.formatter = client.log_level_client.stdlib_formatter('MyApp')
63+
# @param logger_name [String] The name/path of the logger
64+
# @return [Proc] A formatter proc that respects dynamic log levels
65+
def stdlib_formatter(logger_name)
66+
proc do |severity, datetime, progname, msg|
67+
# Convert Logger severity string to integer (DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4)
68+
severity_int = case severity
69+
when 'DEBUG' then 0
70+
when 'INFO' then 1
71+
when 'WARN' then 2
72+
when 'ERROR' then 3
73+
when 'FATAL', 'UNKNOWN' then 4
74+
else 1
75+
end
76+
77+
# Check if we should log this message
78+
if should_log?(severity_int, logger_name)
79+
# Default formatting
80+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S.%L')}] #{severity} -- #{progname}: #{msg}\n"
81+
else
82+
nil # Don't output the log
83+
end
84+
end
85+
end
86+
4287
# Get the log level for a given logger name
4388
# Returns a LogLevel symbol (:trace, :debug, :info, :warn, :error, :fatal)
4489
# Defaults to :debug if no config is found

lib/sdk-reforge.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ module Reforge
55
VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').strip
66
end
77

8-
require 'semantic_logger'
8+
begin
9+
require 'semantic_logger'
10+
rescue LoadError
11+
# semantic_logger is optional - only needed for dynamic log level filtering
12+
end
13+
914
require 'reforge/internal_logger'
1015
require 'concurrent/atomics'
1116
require 'concurrent'

sdk-reforge.gemspec

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
1111
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
1212
s.require_paths = ["lib".freeze]
1313
s.authors = ["Jeff Dwyer".freeze]
14-
s.date = "2025-10-07"
14+
s.date = "2025-10-23"
1515
s.description = "Feature Flags, Live Config as a service".freeze
1616
s.email = "jeff.dwyer@reforge.com.cloud".freeze
1717
s.extra_rdoc_files = [
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
2020
"README.md"
2121
]
2222
s.files = [
23+
".DS_Store",
2324
".envrc.sample",
2425
".github/CODEOWNERS",
2526
".github/pull_request_template.md",
@@ -73,6 +74,8 @@ Gem::Specification.new do |s|
7374
"lib/reforge/internal_logger.rb",
7475
"lib/reforge/javascript_stub.rb",
7576
"lib/reforge/local_config_parser.rb",
77+
"lib/reforge/log_level.rb",
78+
"lib/reforge/log_level_client.rb",
7679
"lib/reforge/murmer3.rb",
7780
"lib/reforge/options.rb",
7881
"lib/reforge/periodic_sync.rb",
@@ -116,6 +119,7 @@ Gem::Specification.new do |s|
116119
"test/test_internal_logger.rb",
117120
"test/test_javascript_stub.rb",
118121
"test/test_local_config_parser.rb",
122+
"test/test_log_level_client.rb",
119123
"test/test_logger_initialization.rb",
120124
"test/test_options.rb",
121125
"test/test_prefab.rb",
@@ -138,7 +142,7 @@ Gem::Specification.new do |s|
138142
s.add_runtime_dependency(%q<ld-eventsource>.freeze, [">= 0"])
139143
s.add_runtime_dependency(%q<uuid>.freeze, [">= 0"])
140144
s.add_runtime_dependency(%q<activesupport>.freeze, [">= 4"])
141-
s.add_runtime_dependency(%q<semantic_logger>.freeze, ["!= 4.16.0"])
145+
s.add_development_dependency(%q<semantic_logger>.freeze, ["!= 4.16.0"])
142146
s.add_development_dependency(%q<allocation_stats>.freeze, [">= 0"])
143147
s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
144148
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])

0 commit comments

Comments
 (0)