Skip to content

Commit b50c101

Browse files
authored
Merge pull request #2691 from govuk-forms/add-start-stop-logs-tasks
Log start and end for rake tasks
2 parents 11c146b + 4cb70f8 commit b50c101

5 files changed

Lines changed: 142 additions & 1 deletion

File tree

app/lib/application_logger.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ def as_hash(msg, attribs = {})
3232
msg = yield if block_given?
3333

3434
begin
35-
message_to_hash(msg).merge(attribs || {}).merge(CurrentLoggingAttributes.attributes).compact
35+
message_to_hash(msg)
36+
.merge(attribs || {})
37+
.merge(CurrentLoggingAttributes.attributes)
38+
.merge(CurrentTaskLoggingAttributes.attributes)
39+
.compact
3640
rescue NameError
3741
# if logs aren't attached to a request, CurrentLoggingAttributes will be uninitialized
3842
message_to_hash(msg).merge(attribs || {})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class CurrentTaskLoggingAttributes < ActiveSupport::CurrentAttributes
2+
attribute :task_name
3+
end

lib/tasks/logging.rake

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
require "English"
2+
3+
Rake::Task.define_task(:environment).enhance do
4+
task_finished_normally = false
5+
6+
at_exit do
7+
unless task_finished_normally
8+
exit_cause = $ERROR_INFO ? $ERROR_INFO.class.name : "Signal or exit"
9+
10+
Rails.logger.error "Task terminated early", {
11+
task: Rake.application.top_level_tasks.first,
12+
exit_cause:,
13+
}
14+
end
15+
end
16+
17+
Rake.application.top_level_tasks.each do |task_name|
18+
task_name_clean = task_name.gsub(/\[.*\]$/, "")
19+
next unless Rake::Task.task_defined?(task_name_clean)
20+
21+
task = Rake::Task[task_name_clean]
22+
original_actions = task.actions.dup
23+
task.actions.clear
24+
25+
task.enhance do |t, args|
26+
CurrentTaskLoggingAttributes.task_name = task_name_clean
27+
Rails.logger.info "Task started", { args: args.to_a }
28+
29+
begin
30+
original_actions.each { |action| action.call(t, args) }
31+
Rails.logger.info "Task finished"
32+
rescue SystemExit => e
33+
Rails.logger.error "Task aborted", { exit_message: e.message }
34+
raise e
35+
rescue StandardError => e
36+
Rails.logger.error "Task failed", { exception: [e.class.name, e.message] }
37+
raise e
38+
ensure
39+
task_finished_normally = true
40+
end
41+
end
42+
end
43+
end

spec/lib/application_logger_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@
2626
end
2727
end
2828

29+
context "when CurrentTaskLoggingAttributes has attributes set" do
30+
before do
31+
CurrentTaskLoggingAttributes.task_name = "task:example"
32+
logger.info("A message")
33+
end
34+
35+
it "includes the message as a field" do
36+
expect(log_line["message"]).to eq "A message"
37+
end
38+
39+
it "includes attributes with values on the log line" do
40+
expect(log_line["task_name"]).to eq "task:example"
41+
end
42+
43+
it "does not include attributes without values on the log line" do
44+
expect(log_line.keys).not_to include "user_id"
45+
end
46+
end
47+
2948
context "when a hash is passed as an argument" do
3049
before do
3150
CurrentLoggingAttributes.request_id = "a-request-id"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require "rake"
2+
require "rails_helper"
3+
4+
RSpec.describe "Logging Rake Tasks" do
5+
let(:rake) { Rake::Application.new }
6+
7+
before do
8+
Rake.application = rake
9+
10+
Rake::Task.define_task(:environment)
11+
my_task
12+
13+
allow(rake).to receive(:top_level_tasks).and_return(%w[my_task])
14+
15+
load Rails.root.join("lib/tasks/logging.rake")
16+
17+
rake["environment"].invoke
18+
end
19+
20+
context "when the task runs successfully" do
21+
let(:my_task) { Rake::Task.define_task(my_task: :environment) }
22+
23+
it "logs when the task starts and finishes" do
24+
expect(Rails.logger).to receive(:info).with("Task started", hash_including(:args))
25+
26+
expect(Rails.logger).to receive(:info).with("Task finished")
27+
28+
rake["my_task"].invoke
29+
end
30+
31+
it "logs the arguments passed in to the task" do
32+
expect(Rails.logger).to receive(:info).with("Task started", hash_including(args: %w[arg1 arg2]))
33+
34+
allow(Rails.logger).to receive(:info).with("Task finished")
35+
36+
rake["my_task"].invoke("arg1", "arg2")
37+
end
38+
end
39+
40+
context "when the task raises an error" do
41+
let(:my_task) do
42+
Rake::Task.define_task(my_task: :environment) do
43+
raise StandardError, "Something went wrong"
44+
end
45+
end
46+
47+
it "logs an error with the exception" do
48+
allow(Rails.logger).to receive(:info).with("Task started", hash_including(:args))
49+
expect(Rails.logger).to receive(:error).with(
50+
"Task failed",
51+
{ exception: ["StandardError", "Something went wrong"] },
52+
)
53+
54+
expect { rake["my_task"].invoke }.to raise_error(StandardError, "Something went wrong")
55+
end
56+
end
57+
58+
context "when the task is aborted with SystemExit" do
59+
let(:my_task) do
60+
Rake::Task.define_task(my_task: :environment) do
61+
raise SystemExit.new(0, "exit")
62+
end
63+
end
64+
65+
it "logs an error with the exit message" do
66+
allow(Rails.logger).to receive(:info).with("Task started", hash_including(:args))
67+
expect(Rails.logger).to receive(:error).with("Task aborted", { exit_message: "exit" })
68+
69+
expect { rake["my_task"].invoke }.to output(/exit/).to_stderr
70+
end
71+
end
72+
end

0 commit comments

Comments
 (0)