Skip to content

Commit

Permalink
Merge pull request #1354 from appsignal/logger-tagged-without-block
Browse files Browse the repository at this point in the history
Allow `#tagged` to be called without a block
  • Loading branch information
unflxw authored Dec 18, 2024
2 parents 0d83055 + c518fa4 commit 5f987a4
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 5 deletions.
11 changes: 11 additions & 0 deletions .changesets/allow--logger-tagged--to-be-called-without-a-block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
bump: patch
type: fix
---

Allow `Appsignal::Logger#tagged` to be called without a block, in the same way as `ActiveSupport::TaggedLogging`:

```ruby
Appsignal::Logger.new("rails").tagged("some tag").info("message")
# => logs "[some tag] message"
```
29 changes: 24 additions & 5 deletions lib/appsignal/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Logger < ::Logger
# @param format Format to use to parse log line attributes.
# @param attributes Default attributes for all log lines.
# @return [void]
def initialize(group, level: INFO, format: PLAINTEXT, attributes: {})
def initialize(group, level: INFO, format: PLAINTEXT, attributes: {}, tags: [])
raise TypeError, "group must be a string" unless group.is_a? String

@group = group
Expand All @@ -38,7 +38,7 @@ def initialize(group, level: INFO, format: PLAINTEXT, attributes: {})
@mutex = Mutex.new
@default_attributes = attributes
@appsignal_attributes = {}
@tags = []
@tags = tags
end

# We support the various methods in the Ruby
Expand Down Expand Up @@ -156,10 +156,19 @@ def tagged(*tags)
# as separate arguments. Flatten the tags argument array to deal with them
# indistinctly.
tags = tags.flatten

# If called without a block, return a new logger that always logs with the
# given set of tags.
return with_tags(tags) unless block_given?

# If called with a block, modify the current logger to log with the given
# set of tags for the duration of the block.
@tags.append(*tags)
yield self
ensure
@tags.pop(tags.length)
begin
yield self
ensure
@tags.pop(tags.length)
end
end

# Listen to ActiveSupport tagged logging tags set with `Rails.config.log_tags`.
Expand Down Expand Up @@ -197,6 +206,16 @@ def silence(_severity = ERROR, &block)

private

def with_tags(tags)
Logger.new(
@group,
:level => @level,
:format => @format,
:attributes => @default_attributes,
:tags => @tags + tags
)
end

attr_reader :default_attributes, :appsignal_attributes

def add_with_attributes(severity, message, group, attributes)
Expand Down
107 changes: 107 additions & 0 deletions spec/lib/appsignal/logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,113 @@
Appsignal::Utils::Data.generate({})
)
end

it "accepts tags in #tagged as an array" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] Some message"),
Appsignal::Utils::Data.generate({})
)

logger.tagged(["My tag", "My other tag"]) do
logger.info("Some message")
end
end

# Calling `#tagged` without a block is not supported by
# `ActiveSupport::TaggedLogging` in Rails 6 and earlier. Only run this
# in builds where Rails is not present, or builds where Rails 7 or later
# is present.
if !DependencyHelper.rails_present? || DependencyHelper.rails7_present?
describe "when calling #tagged without a block" do
it "returns a new logger with the tags added" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] Some message"),
Appsignal::Utils::Data.generate({})
)

logger.tagged("My tag", "My other tag").info("Some message")
end

it "does not modify the original logger" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] Some message"),
Appsignal::Utils::Data.generate({})
)

new_logger = logger.tagged("My tag", "My other tag")
new_logger.info("Some message")

expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("Some message"),
Appsignal::Utils::Data.generate({})
)

logger.info("Some message")
end

it "can be chained" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] [My third tag] Some message"),
Appsignal::Utils::Data.generate({})
)

logger.tagged("My tag", "My other tag").tagged("My third tag").info("Some message")
end

it "can be chained before a block invocation" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] [My third tag] Some message"),
Appsignal::Utils::Data.generate({})
)

# We must explicitly use the logger passed to the block,
# as the logger returned from the first #tagged invocation
# is a new instance of the logger.
logger.tagged("My tag", "My other tag").tagged("My third tag") do |logger|
logger.info("Some message")
end
end

it "can be chained after a block invocation" do
expect(Appsignal::Extension).to receive(:log)
.with(
"group",
3,
0,
a_string_starting_with("[My tag] [My other tag] [My third tag] Some message"),
Appsignal::Utils::Data.generate({})
)

logger.tagged("My tag", "My other tag") do
logger.tagged("My third tag").info("Some message")
end
end
end
end
end

describe Appsignal::Logger do
Expand Down

0 comments on commit 5f987a4

Please sign in to comment.