Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
contextual_logger (0.2.1)
contextual_logger (0.3.0)
json

GEM
Expand Down
2 changes: 1 addition & 1 deletion contextual_logger.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |spec|
spec.name = 'contextual_logger'
spec.version = '0.2.1'
spec.version = '0.3.0'
spec.license = 'MIT'
spec.date = '2018-10-12'
spec.summary = 'Add context to your logger'
Expand Down
19 changes: 11 additions & 8 deletions lib/contextual_logger.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
# frozen_string_literal: true

require 'json'
require_relative './contextual_logger/context/handler'

module ContextualLogger
def self.new(logger)
logger.extend(self)
end

def global_context=(context)
Thread.current[THREAD_CONTEXT_NAMESPACE] = context
ContextualLogger::Context::Handler.new(context).set!
end

def with_context(context)
previous_context = Thread.current[THREAD_CONTEXT_NAMESPACE] || {}
Thread.current[THREAD_CONTEXT_NAMESPACE] = previous_context.merge(context)
yield if block_given?
context_handler = ContextualLogger::Context::Handler.new(current_context_for_thread.merge(context))
context_handler.set!
if block_given?
yield
else
context_handler
end
ensure
Thread.current[THREAD_CONTEXT_NAMESPACE] = previous_context
context_handler.reset! if block_given?
end

def current_context_for_thread
Thread.current[THREAD_CONTEXT_NAMESPACE] || {}
ContextualLogger::Context::Handler.current_context
end

def format_message(severity, timestamp, progname, message, context)
Expand Down Expand Up @@ -81,8 +86,6 @@ def write_entry_to_log(severity, timestamp, progname, message, context)

private

THREAD_CONTEXT_NAMESPACE = 'ContextualLoggerCurrentLoggingContext'

def message_with_context(context, message, severity, timestamp, progname)
context.merge(
message: message,
Expand Down
28 changes: 28 additions & 0 deletions lib/contextual_logger/context/handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module ContextualLogger
module Context
class Handler
THREAD_CONTEXT_NAMESPACE = 'ContextualLoggerCurrentLoggingContext'

attr_reader :previous_context, :context

def self.current_context
Thread.current[THREAD_CONTEXT_NAMESPACE] || {}
end

def initialize(context, previous_context: nil)
@previous_context = previous_context || self.class.current_context
@context = context
end

def set!
Thread.current[THREAD_CONTEXT_NAMESPACE] = context
end

def reset!
Thread.current[THREAD_CONTEXT_NAMESPACE] = previous_context
end
end
end
end
32 changes: 32 additions & 0 deletions spec/lib/context/handler_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require 'spec_helper'
require 'contextual_logger'

describe ContextualLogger::Context::Handler do
let(:context) { { service: "hello_world", integration: "google" } }
subject(:handler) { ContextualLogger::Context::Handler.new(context) }

it { is_expected.to respond_to(:set!) }
it { is_expected.to respond_to(:reset!) }

it 'sets the thread context on set!' do
previous_context = ContextualLogger::Context::Handler.current_context
handler.set!

expect(ContextualLogger::Context::Handler.current_context).to_not eq(previous_context)
expect(ContextualLogger::Context::Handler.current_context).to eq(context)

handler.reset!
end

it 'resets the thread context on reset!' do
initial_context = ContextualLogger::Context::Handler.current_context
handler.set!
new_context = ContextualLogger::Context::Handler.current_context
handler.reset!

expect(ContextualLogger::Context::Handler.current_context).to_not eq(new_context)
expect(ContextualLogger::Context::Handler.current_context).to eq(initial_context)
end
end
78 changes: 78 additions & 0 deletions spec/lib/contextual_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
before do
Time.now_override = Time.now
@logger = ContextualLogger.new(Logger.new('/dev/null'))
@logger.global_context = {}
end

it 'should respond to with_context' do
Expand Down Expand Up @@ -169,6 +170,83 @@
end
end
end

it 'returns the output of the block passed in' do
expect(@logger.with_context(service: 'test_service') { 6 }).to eq(6)
end
end

describe 'with_context without block' do
it 'returns a context handler' do
expect(@logger.with_context(service: 'test_service')).to be_a(ContextualLogger::Context::Handler)
end

it 'prints out the wrapper context with logging' do
expected_log_line = {
service: 'test_service',
message: 'this is a test',
severity: 'INFO',
timestamp: Time.now,
progname: nil
}.to_json

expect_any_instance_of(Logger::LogDevice).to receive(:write).with("#{expected_log_line}\n")

handler = @logger.with_context(service: 'test_service')
expect(@logger.info('this is a test')).to eq(true)
handler.reset!
end

it 'merges inline context into wrapper context when logging' do
expected_log_line = {
service: 'test_service',
file: 'this_file.json',
message: 'this is a test',
severity: 'INFO',
timestamp: Time.now,
progname: nil
}.to_json

expect_any_instance_of(Logger::LogDevice).to receive(:write).with("#{expected_log_line}\n")

handler = @logger.with_context(service: 'test_service')
expect(@logger.info('this is a test', file: 'this_file.json')).to eq(true)
handler.reset!
end

it 'takes inline context over wrapper context when logging' do
expected_log_line = {
service: 'test_service_2',
message: 'this is a test',
severity: 'INFO',
timestamp: Time.now,
progname: nil
}.to_json

expect_any_instance_of(Logger::LogDevice).to receive(:write).with("#{expected_log_line}\n")

handler = @logger.with_context(service: 'test_service')
expect(@logger.info('this is a test', service: 'test_service_2')).to eq(true)
handler.reset!
end

it 'combines tiered contexts when logging' do
expected_log_line = {
service: 'test_service',
file: 'this_file.json',
message: 'this is a test',
severity: 'INFO',
timestamp: Time.now,
progname: nil
}.to_json

expect_any_instance_of(Logger::LogDevice).to receive(:write).with("#{expected_log_line}\n")

handler1 = @logger.with_context(service: 'test_service')
@logger.with_context(file: 'this_file.json')
expect(@logger.info('this is a test')).to eq(true)
handler1.reset!
end
end

describe 'global_context' do
Expand Down