FEATURE: Attach backup log as upload (#13849)

Discourse automatically sends a private message after backup or
restore finished. The private message used to contain the log inline
even when it was very long. A very long log can create issues because
the length of the post will be over the maximum allowed length of a
post. When that happens, Discourse will try to create an upload with
the logs. If that fails, it will trim the log and inline it.
This commit is contained in:
Bianca Nenciu 2021-08-03 20:06:50 +03:00 committed by GitHub
parent e67670c1e4
commit e2c415457c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 15 deletions

View File

@ -2984,9 +2984,7 @@ en:
Here's the log:
``` text
%{logs}
```
backup_failed:
title: "Backup Failed"
@ -2996,9 +2994,7 @@ en:
Here's the log:
``` text
%{logs}
```
restore_succeeded:
title: "Restore Succeeded"
@ -3008,9 +3004,7 @@ en:
Here's the log:
``` text
%{logs}
```
restore_failed:
title: "Restore Failed"
@ -3020,9 +3014,7 @@ en:
Here's the log:
``` text
%{logs}
```
bulk_invite_succeeded:
title: "Bulk Invite Succeeded"

View File

@ -321,9 +321,8 @@ module BackupRestore
log "Notifying '#{@user.username}' of the end of the backup..."
status = @success ? :backup_succeeded : :backup_failed
post = SystemMessage.create_from_system_user(
@user, status, logs: Discourse::Utils.pretty_logs(@logs)
)
logs = Discourse::Utils.logs_markdown(@logs, user: @user)
post = SystemMessage.create_from_system_user(@user, status, logs: logs)
if @user.id == Discourse::SYSTEM_USER_ID
post.topic.invite_group(@user, Group[:admins])

View File

@ -148,10 +148,8 @@ module BackupRestore
log "Notifying '#{user.username}' of the end of the restore..."
status = @success ? :restore_succeeded : :restore_failed
SystemMessage.create_from_system_user(
user, status,
logs: Discourse::Utils.pretty_logs(@logger.logs)
)
logs = Discourse::Utils.logs_markdown(@logger.logs, user: user)
post = SystemMessage.create_from_system_user(user, status, logs: logs)
else
log "Could not send notification to '#{@user_info[:username]}' " \
"(#{@user_info[:email]}), because the user does not exist."

View File

@ -42,6 +42,49 @@ module Discourse
logs.join("\n")
end
def self.logs_markdown(logs, user:, filename: 'log.txt')
# Reserve 250 characters for the rest of the text
max_logs_length = SiteSetting.max_post_length - 250
pretty_logs = Discourse::Utils.pretty_logs(logs)
# If logs are short, try to inline them
if pretty_logs.size < max_logs_length
return <<~TEXT
```text
#{pretty_logs}
```
TEXT
end
# Try to create an upload for the logs
upload = Dir.mktmpdir do |dir|
File.write(File.join(dir, filename), pretty_logs)
zipfile = Compression::Zip.new.compress(dir, filename)
File.open(zipfile) do |file|
UploadCreator.new(
file,
File.basename(zipfile),
type: 'backup_logs',
for_export: 'true'
).create_for(user.id)
end
end
if upload.persisted?
return UploadMarkdown.new(upload).attachment_markdown
else
Rails.logger.warn("Failed to upload the backup logs file: #{upload.errors.full_messages}")
end
# If logs are long and upload cannot be created, show trimmed logs
<<~TEXT
```text
...
#{pretty_logs.last(max_logs_length)}
```
TEXT
end
def self.atomic_write_file(destination, contents)
begin
return if File.read(destination) == contents

View File

@ -16,4 +16,59 @@ describe BackupRestore::Backuper do
expect(backuper.send(:get_parameterized_title)).to eq("coding-horror")
end
describe '#notify_user' do
before do
freeze_time Time.zone.parse('2010-01-01 12:00')
end
it 'includes logs if short' do
SiteSetting.max_export_file_size_kb = 1
SiteSetting.export_authorized_extensions = "tar.gz"
silence_stdout do
backuper = BackupRestore::Backuper.new(Discourse.system_user.id)
expect { backuper.send(:notify_user) }
.to change { Topic.private_messages.count }.by(1)
.and change { Upload.count }.by(0)
end
expect(Topic.last.first_post.raw).to include("```text\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```")
end
it 'include upload if log is long' do
SiteSetting.max_post_length = 250
silence_stdout do
backuper = silence_stdout { BackupRestore::Backuper.new(Discourse.system_user.id) }
expect { backuper.send(:notify_user) }
.to change { Topic.private_messages.count }.by(1)
.and change { Upload.where(original_filename: "log.txt.zip").count }.by(1)
end
expect(Topic.last.first_post.raw).to include("[log.txt.zip|attachment]")
end
it 'includes trimmed logs if log is long and upload cannot be saved' do
SiteSetting.max_post_length = 348
SiteSetting.max_export_file_size_kb = 1
SiteSetting.export_authorized_extensions = "tar.gz"
silence_stdout do
backuper = BackupRestore::Backuper.new(Discourse.system_user.id)
1.upto(10).each do |i|
backuper.send(:log, "Line #{i}")
end
expect { backuper.send(:notify_user) }
.to change { Topic.private_messages.count }.by(1)
.and change { Upload.count }.by(0)
end
expect(Topic.last.first_post.raw).to include("```text\n...\n[2010-01-01 12:00:00] Line 10\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```")
end
end
end