Replies: 24 comments
-
It looks like Sidekiq tries to add context information in its own way and does not use builtin features such as Recommend patching the Sidekiq logging class using an initializer and make it use tags instead of a custom tags mechanism. Then you can also get rid of the string output, and log the individual elements as json attributes that do not have to be parsed by the centralized log system. Have you looked at RocketJob ? It has complete support for Semantic Logger built-in from the ground up. It logs each classes name along with the duration of each task, etc.. |
Beta Was this translation helpful? Give feedback.
-
Thanks for support! Got it, RocketJob looks very promising. To be fair you should include Sidekiq Pro feature in the comparision table. Went with a sample like this # config/initializers/sidekiq.rb
module Sidekiq
module Middleware
module Server
class Logging
SPACE = ' '.freeze
def call(worker, item, queue)
Sidekiq::Logging.with_context(log_context(worker, item)) do
# Apply payload to semantic logger
# * we are loosing formatter capabilities
logger.with_payload(log_payload(worker, item)) do
begin
start = Time.now
logger.info("start".freeze)
yield
logger.info("done: #{elapsed(start)} sec")
rescue Exception
logger.info("fail: #{elapsed(start)} sec")
raise
end
end
end
end
private
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
# attribute to expose the underlying thing.
def log_context(worker, item)
klass = item['wrapped'.freeze] || worker.class.to_s
"#{klass} JID-#{item['jid'.freeze]}#{" BID-#{item['bid'.freeze]}" if item['bid'.freeze]}"
end
# This additional method formats context as payload
def log_payload(worker, item)
{
class_name: item['wrapped'.freeze] || worker.class.to_s,
jid: item['jid'.freeze],
tid: Thread.current.object_id.to_s(36),
context: context
}
end
def context
c = Thread.current[:sidekiq_context]
" #{c.join(SPACE)}" if c && c.any?
end
def elapsed(start)
(Time.now - start).round(3)
end
def logger
Sidekiq.logger
end
end
end
end
end |
Beta Was this translation helpful? Give feedback.
-
Thanks for the code extract, happy to hear it works now. By supporting RocketJob Pro you will also be supporting the development efforts on Semantic Logger and Symmetric Encryption. |
Beta Was this translation helpful? Give feedback.
-
@prikha thanks for the snippet. Is this something that can be contributed to Sidekiq? so it generically works with "plain" logs as well more sophisticated ones, like SemanticLogger or maybe others? (are |
Beta Was this translation helpful? Give feedback.
-
p.s. I hope that any rivalry between Sidekiq and RocketJob can be put aside for the purpose of better logging, irrespective of which product / logger is used ;-) |
Beta Was this translation helpful? Give feedback.
-
@gingerlime Hi, I had a quick run over sidekiq logging code - it seems very specific to what they are doing. Generalizing it for better logging solutions(better than plain ruby logger) should be a right direction imho. However For me it seems there are plenty of options like message formatting which is done right. It would be great to have an opportunity to define not a custom formatter, but a block of code which consumes all message events and actually calls logger(in all its variety). Anyway - there is not a right place for that discussion. We may open an issue at relevant repo. |
Beta Was this translation helpful? Give feedback.
-
Thanks @prikha ... I'm not sure I completely understand what's the best way forward here. Regardless if Sidekiq isn't super-flexible with custom loggers, I still wonder why, if I just want a plain-text file log output, I would somehow lose some of the context info that Sidekiq outputs. And with any other logger, it just works out-of-the-box (right?) |
Beta Was this translation helpful? Give feedback.
-
@prikha @reidmorrison sorry to bother you guys, but I'm looking at some examples, but can't quite wrap my head around it fully. If I look at the JSON formatter, then the usage section says I should assign the formatter to the sidekiq logger. The formatter itself inherits from Sidekiq's Pretty formatter, which outputs everything as a string. However, when I try to use the standard pretty formatter by assigning it, SemanticLogger either doesn't use it, or ignores it? I don't see all the info I'd expect in my logs # config/initializers/sidekiq.rb
Sidekiq.logger.formatter = Sidekiq::Logging::Pretty.new log output:
Without SemanticLogger
I can create a middleware like @prikha suggested, or using Rails tags similar to this? But I just wonder where's the conflict, or why the full log record isn't logged correctly by default? when I'm using the ... sorry if it's a silly question, but I'm genuinely confused about how this all fits together, or doesn't as the case might be ;-) |
Beta Was this translation helpful? Give feedback.
-
Sidekiq creates its own custom logger that is not compatible with standard loggers. The only way around it is to "monkey-patch" sidekiq as per the example above to use semantic logger instead of using its own custom logger. If Sidekiq followed either Semantic Logger or the built-in Rails logger approach of using |
Beta Was this translation helpful? Give feedback.
-
@reidmorrison regarding Correct me if I'm wrong, but the sidekiq logger is compatible with the Ruby Logger (at least according to the wiki page), and it does work with other logging libraries, doesn't it? Is there a change to the sidekiq logger that would make it compatible with standard loggers? then maybe it's worth submitting a PR to sidekiq to bring this compatibility? I'm really just trying to understand why or what's not compatible and hopefully find a way for SemanticLogger and Sidekiq logger to work together "out of the box" if possible. |
Beta Was this translation helpful? Give feedback.
-
This is what I'm using now (similar to the snippet above, but slightly different), and it seems to work with rails using module Sidekiq
module Middleware
module Server
class TaggedLogging
SPACE = ' '.freeze
def call(worker, item, _queue)
logger.tagged(log_context(worker, item)) do
begin
start = Time.zone.now
logger.info('start'.freeze)
yield
logger.info("done: #{elapsed(start)} sec")
rescue Exception
logger.info("fail: #{elapsed(start)} sec")
raise
end
end
end
private
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
# attribute to expose the underlying thing.
def log_context(worker, item)
klass = item['wrapped'.freeze] || worker.class.to_s
"#{klass} JID-#{item['jid'.freeze]}#{" BID-#{item['bid'.freeze]}" if item['bid'.freeze]}"
end
def context
c = Thread.current[:sidekiq_context]
" #{c.join(SPACE)}" if c && c.any?
end
def elapsed(start)
(Time.zone.now - start).round(3)
end
def logger
Sidekiq.logger
end
end
end
end
end
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Sidekiq::Middleware::Server::TaggedLogging
end
end |
Beta Was this translation helpful? Give feedback.
-
Ok, I confirm the Sidekiq logger is weird. If you replace it by
So, all information is lost with regular Logger as well. Recently there was a way to customize the job logger class, investiguating atm. EDIT: okay basically with Sidekiq you are supposed to use a custom format in order to get things logged: "#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n" |
Beta Was this translation helpful? Give feedback.
-
Based on @gingerlime 's idea, I was able to customize the logging via overwriting class CustomSidekiqJobLogger < Sidekiq::JobLogger
def call(item, queue, &block)
logger.tagged(*context_tags(item)) do
super(item, queue, &block)
end
end
private
def context_tags(item)
[
item['jid'],
item['class']
]
end
def logger
Rails.logger
end
end
Sidekiq.configure_server do |config|
config.options[:job_logger] = CustomSidekiqJobLogger
end |
Beta Was this translation helpful? Give feedback.
-
For anyone else finding this thread, none of the solutions offered here or #80 worked for me. Semantic logger was preventing Sidekiq from outputting anything at all. I added |
Beta Was this translation helpful? Give feedback.
-
I'm still playing around with this, but with Sidekiq 6.0.1 @lacco example can be boiled down to class TaggedJobLogger < Sidekiq::JobLogger
def call(item, queue)
@logger.tagged(Sidekiq::Context.current) do
super(item, queue)
end
end
end
...
Sidekiq.configure_server do |config|
...
config.options[:job_logger] = TaggedJobLogger
...
end @bobvanderlinden is this more or less what you had in mind ? |
Beta Was this translation helpful? Give feedback.
-
Yes, indeed. I was thinking it might be nice to use such a tagged logger for Sidekiq by default. |
Beta Was this translation helpful? Give feedback.
-
Notice, that |
Beta Was this translation helpful? Give feedback.
-
Converting this issue into a discussion since it affects a lot of applications and there is interest in creating a workable solution. |
Beta Was this translation helpful? Give feedback.
-
Here's the lowest impact solution I was able to come up with for Sidekiq 6.x to get tagged log entries without patching Sidekiq. It involves subclassing class SidekiqSemanticLogger < SemanticLogger::Logger
def log(log, message = nil, progname = nil, &block)
return super unless log.is_a?(SemanticLogger::Log)
# The Sidekiq thread ID (tid) is not included in the context, so we add it here.
log.named_tags[:sidekiq] = Sidekiq::Context.current.merge(tid: base_sidekiq_formatter.tid)
super
end
private
# We want to use the original implementation of Sidekiq::Logger::Formatters::Base#tid
# to ensure that we get the same thread ID as Sidekiq uses for its UI.
def base_sidekiq_formatter
@base_sidekiq_formatter ||= Sidekiq::Logger::Formatters::Base.new
end
end
# ----
Sidekiq.logger = SidekiqSemanticLogger.new('Sidekiq') This example nests the Sidekiq context and tid under a |
Beta Was this translation helpful? Give feedback.
-
For the next person that comes along looking for a solution BUT is using Sidekiq 7.x -- The API was pretty profoundly refactored and some of the above approaches will no longer work. The commit that introduces the change(s) is here: sidekiq/sidekiq@29dca70#diff-6985e251e1b3fe0ce1f47369a1ede593aab9b27757ac7ff6e16591bc0318b034 And the important bit is probably the doc: https://github.com/sidekiq/sidekiq/blob/main/docs/capsule.md The TL;DR is that class CustomSidekiqJobLogger < Sidekiq::JobLogger
def call(item, queue, &block)
@logger.tagged(*context_tags(item)) do
super(item, queue, &block)
end
end
private
def context_tags(item)
[
item['jid'],
item['class']
]
end
end
Sidekiq.configure_server do |config|
# ...
config.capsule("default") do |capsule|
capsule.config[:job_logger] = CustomSidekiqJobLogger
end
# ... Last but not least: thanks to everyone for their input in the above thread, and the previous issue that this was born of! 😄 |
Beta Was this translation helpful? Give feedback.
-
Thank you to everyone for your efforts on adding structured logging into Sidekiq with Semantic Logger. Let me know your thoughts and further ideas to introduce structured logging into Sidekiq. The next step will be to figure out the patches needed for newer Sidekiq versions. |
Beta Was this translation helpful? Give feedback.
-
For anyone that wants to try and supply feedback on the Sidekiq patches to make it work with structured logging, change your gem "rails_semantic_logger", git: "https://github.com/reidmorrison/rails_semantic_logger", branch: "sidekiq" It currently supports Sidekiq 4, 5, and 6. Will start working on Sidekiq 7 next. Welcome feedback on the messages coming from Sidekiq now, and for anyone else with centralized logging, the structured logs (json format) are much more useful. |
Beta Was this translation helpful? Give feedback.
-
Rails Semantic Logger v4.15.0 has been published with support for all major versions of Sidekiq. |
Beta Was this translation helpful? Give feedback.
-
Released Rails Semantic Logger v4.17.0 with better Sidekiq support, using configuration instead of patching where possible. Also supports the recently released Sidekiq 7.3. Would be great to get everyone's feedback. Created this conversation to get us started: reidmorrison/rails_semantic_logger#228 |
Beta Was this translation helpful? Give feedback.
-
Following the readme I`ve added semantic logger to Sidekiq.
Sidekiq::Logging.logger = SemanticLogger[Sidekiq] if defined?(Sidekiq)
And it occurred that i`ve lost the most valuable data - worker class name.
The log before:
2017-02-12T19:18:43.908Z 22548 TID-osbolpr4g SomeWorker JID- INFO: start 2017-02-12T19:18:43.912Z 22548 TID-osbolpr4g SomeWorker JID- INFO: done: 0.003 sec
The log after:
{"name":"Sidekiq","pid":19857,"thread":"47328718486600","level":"info","level_index":2,"host":"web01","application":"Semantic Logger","message":"start","timestamp":"2017-02-12T19:32:00.599277Z"} {"name":"Sidekiq","pid":19857,"thread":"47328718486600","level":"info","level_index":2,"host":"web01","application":"Semantic Logger","message":"done: 0.002 sec","timestamp":"2017-02-12T19:32:00.601137Z"}
Is there a known way to capture the name of the calling worker class. Or maybe we should provider some facilities for such a broad use-case?
Beta Was this translation helpful? Give feedback.
All reactions