Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend hanami_context_logging to handle non-hash ContextProvider#context #5

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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:
hanami-context-logging (0.1.1)
hanami-context-logging (0.1.2)

GEM
remote: https://rubygems.org/
Expand Down
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,35 +41,67 @@ Or install it yourself as:
## Usage

### About context_provider
context_provider is simply any object that responds to `#context` which returns a hash. The below are all valid context providers
context_provider is simply any object that responds to `#context` which returns a hash OR is convertible to a hash via #to_h. The below are all valid context providers

**Struct**
```
ContextProviderStruct = Struct.new(:context)
provider = ContextProviderStruct.new(a_context: 'a_value')
provider.context # returns context, all good
provider.context # returns hash, all good
```

**Class**
```
# is a hash
class ContextProviderClass
def self.context
{ a_context: 'a_value' }
end
end
provider = ContextProviderClass
provider.context # returns context, all good
provider.context # returns hash, all good

# can be converted to hash via to_h
class Context
def to_h
{ a_context: 'a_value' }
end
end

class ContextProviderClass
def self.context
Context.new
end
end
provider = ContextProviderClass
provider.context.to_h # convertible to hash, all good
```

**Object**
```
# is a hash
class ContextProviderClass
def context
{ a_context: 'a_value' }
end
end
provider = ContextProviderClass.new
provider.context # returns hash, all good

# can be converted to hash via to_h
class Context
def to_h
{ a_context: 'a_value' }
end
end
provider = ContextProviderClass.new
provider.context # returns context, all good

class ContextProviderClass
def self.context
Context.new
end
end
provider = ContextProviderClass
provider.context.to_h # convertible to hash, all good
```

The logger allows you to define your own context provider. A use case for context provider is when the context is not yet known during initialization, but will be known during logging. For example
Expand Down
7 changes: 6 additions & 1 deletion lib/hanami_context_logging/formatter/with_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ def formatted_contexts
end

def contexts
provider_context = if @context_provider.context.is_a?(Hash)
@context_provider.context
else
@context_provider.context.to_h
Comment on lines +20 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can always call to_h since Hash#to_h returns itself

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what @tsungtingdu and I also mentioned, but I thought this is more intentional :keke:

end
{}.merge(
@context_provider.context,
provider_context,
@ad_hoc_context
)
end
Expand Down
7 changes: 6 additions & 1 deletion lib/hanami_context_logging/formatter/with_context_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ def _format(hash)
end

def contexts
provider_context = if @context_provider.context.is_a?(Hash)
@context_provider.context
else
@context_provider.context.to_h
end
{}.merge(
@context_provider.context,
provider_context,
@ad_hoc_context
)
end
Expand Down
3 changes: 2 additions & 1 deletion lib/hanami_context_logging/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ module HanamiContextLogging
class Logger < Hanami::Logger
# A logger that has the same interface as Hanami::Logger, except
# ContextLogger accepts one more option, called :context_provider
# context_provider is just any object that responds to #context which returns a hash
# context_provider is just any object that responds to #context which returns a hash,
# OR returns an object that can be converted to hash via to_h
#
# We first need to extract this option, otherwise Hanami::Logger cannot be initialized due to unrecognized option
# and pad the formatter to be :with_context by default
Expand Down
36 changes: 36 additions & 0 deletions spec/hanami_context_logging/logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@
stream.rewind
expect(stream.read).to include('any_key_1=any_value_1', 'any_key_2=any_value_2', 'random message')
end

context 'when context_provider#context is an object that responds to #to_h' do
let(:mock_context) do
double(to_h:
{
any_key_1: 'any_value_1',
any_key_2: 'any_value_2'
}
)
end

it 'logs message including the context given from provider' do
logger.info 'random message'

stream.rewind
expect(stream.read).to include('any_key_1=any_value_1', 'any_key_2=any_value_2', 'random message')
end
end
end

describe 'with with_context_json formatter' do
Expand All @@ -26,6 +44,24 @@
stream.rewind
expect(JSON.parse(stream.read)).to include('any_key_1' => 'any_value_1', 'any_key_2' => 'any_value_2', 'message' => 'random message')
end

context 'when context_provider#context is an object that responds to #to_h' do
let(:mock_context) do
double(to_h:
{
any_key_1: 'any_value_1',
any_key_2: 'any_value_2'
}
)
end

it 'logs message including the context given from provider' do
logger.info 'random message'

stream.rewind
expect(JSON.parse(stream.read)).to include('any_key_1' => 'any_value_1', 'any_key_2' => 'any_value_2', 'message' => 'random message')
end
end
end

describe '#with_context' do
Expand Down