backupii 0.1.0.pre.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +37 -0
  4. data/bin/backupii +5 -0
  5. data/bin/docker_test +24 -0
  6. data/lib/backup/archive.rb +171 -0
  7. data/lib/backup/binder.rb +23 -0
  8. data/lib/backup/cleaner.rb +114 -0
  9. data/lib/backup/cli.rb +376 -0
  10. data/lib/backup/cloud_io/base.rb +40 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +301 -0
  12. data/lib/backup/cloud_io/s3.rb +256 -0
  13. data/lib/backup/compressor/base.rb +34 -0
  14. data/lib/backup/compressor/bzip2.rb +37 -0
  15. data/lib/backup/compressor/custom.rb +51 -0
  16. data/lib/backup/compressor/gzip.rb +76 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +139 -0
  19. data/lib/backup/config.rb +122 -0
  20. data/lib/backup/database/base.rb +89 -0
  21. data/lib/backup/database/mongodb.rb +189 -0
  22. data/lib/backup/database/mysql.rb +194 -0
  23. data/lib/backup/database/openldap.rb +97 -0
  24. data/lib/backup/database/postgresql.rb +134 -0
  25. data/lib/backup/database/redis.rb +179 -0
  26. data/lib/backup/database/riak.rb +82 -0
  27. data/lib/backup/database/sqlite.rb +57 -0
  28. data/lib/backup/encryptor/base.rb +29 -0
  29. data/lib/backup/encryptor/gpg.rb +745 -0
  30. data/lib/backup/encryptor/open_ssl.rb +76 -0
  31. data/lib/backup/errors.rb +55 -0
  32. data/lib/backup/logger/console.rb +50 -0
  33. data/lib/backup/logger/fog_adapter.rb +27 -0
  34. data/lib/backup/logger/logfile.rb +134 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/logger.rb +199 -0
  37. data/lib/backup/model.rb +478 -0
  38. data/lib/backup/notifier/base.rb +128 -0
  39. data/lib/backup/notifier/campfire.rb +63 -0
  40. data/lib/backup/notifier/command.rb +101 -0
  41. data/lib/backup/notifier/datadog.rb +107 -0
  42. data/lib/backup/notifier/flowdock.rb +101 -0
  43. data/lib/backup/notifier/hipchat.rb +118 -0
  44. data/lib/backup/notifier/http_post.rb +116 -0
  45. data/lib/backup/notifier/mail.rb +235 -0
  46. data/lib/backup/notifier/nagios.rb +67 -0
  47. data/lib/backup/notifier/pagerduty.rb +82 -0
  48. data/lib/backup/notifier/prowl.rb +70 -0
  49. data/lib/backup/notifier/pushover.rb +73 -0
  50. data/lib/backup/notifier/ses.rb +126 -0
  51. data/lib/backup/notifier/slack.rb +149 -0
  52. data/lib/backup/notifier/twitter.rb +57 -0
  53. data/lib/backup/notifier/zabbix.rb +62 -0
  54. data/lib/backup/package.rb +53 -0
  55. data/lib/backup/packager.rb +108 -0
  56. data/lib/backup/pipeline.rb +122 -0
  57. data/lib/backup/splitter.rb +75 -0
  58. data/lib/backup/storage/base.rb +72 -0
  59. data/lib/backup/storage/cloud_files.rb +158 -0
  60. data/lib/backup/storage/cycler.rb +73 -0
  61. data/lib/backup/storage/dropbox.rb +208 -0
  62. data/lib/backup/storage/ftp.rb +118 -0
  63. data/lib/backup/storage/local.rb +63 -0
  64. data/lib/backup/storage/qiniu.rb +68 -0
  65. data/lib/backup/storage/rsync.rb +251 -0
  66. data/lib/backup/storage/s3.rb +157 -0
  67. data/lib/backup/storage/scp.rb +67 -0
  68. data/lib/backup/storage/sftp.rb +82 -0
  69. data/lib/backup/syncer/base.rb +70 -0
  70. data/lib/backup/syncer/cloud/base.rb +180 -0
  71. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  72. data/lib/backup/syncer/cloud/local_file.rb +99 -0
  73. data/lib/backup/syncer/cloud/s3.rb +118 -0
  74. data/lib/backup/syncer/rsync/base.rb +55 -0
  75. data/lib/backup/syncer/rsync/local.rb +29 -0
  76. data/lib/backup/syncer/rsync/pull.rb +49 -0
  77. data/lib/backup/syncer/rsync/push.rb +206 -0
  78. data/lib/backup/template.rb +45 -0
  79. data/lib/backup/utilities.rb +235 -0
  80. data/lib/backup/version.rb +5 -0
  81. data/lib/backup.rb +141 -0
  82. data/templates/cli/archive +28 -0
  83. data/templates/cli/compressor/bzip2 +4 -0
  84. data/templates/cli/compressor/custom +7 -0
  85. data/templates/cli/compressor/gzip +4 -0
  86. data/templates/cli/config +123 -0
  87. data/templates/cli/databases/mongodb +15 -0
  88. data/templates/cli/databases/mysql +18 -0
  89. data/templates/cli/databases/openldap +24 -0
  90. data/templates/cli/databases/postgresql +16 -0
  91. data/templates/cli/databases/redis +16 -0
  92. data/templates/cli/databases/riak +17 -0
  93. data/templates/cli/databases/sqlite +11 -0
  94. data/templates/cli/encryptor/gpg +27 -0
  95. data/templates/cli/encryptor/openssl +9 -0
  96. data/templates/cli/model +26 -0
  97. data/templates/cli/notifier/zabbix +15 -0
  98. data/templates/cli/notifiers/campfire +12 -0
  99. data/templates/cli/notifiers/command +32 -0
  100. data/templates/cli/notifiers/datadog +57 -0
  101. data/templates/cli/notifiers/flowdock +16 -0
  102. data/templates/cli/notifiers/hipchat +16 -0
  103. data/templates/cli/notifiers/http_post +32 -0
  104. data/templates/cli/notifiers/mail +24 -0
  105. data/templates/cli/notifiers/nagios +13 -0
  106. data/templates/cli/notifiers/pagerduty +12 -0
  107. data/templates/cli/notifiers/prowl +11 -0
  108. data/templates/cli/notifiers/pushover +11 -0
  109. data/templates/cli/notifiers/ses +15 -0
  110. data/templates/cli/notifiers/slack +22 -0
  111. data/templates/cli/notifiers/twitter +13 -0
  112. data/templates/cli/splitter +7 -0
  113. data/templates/cli/storages/cloud_files +11 -0
  114. data/templates/cli/storages/dropbox +20 -0
  115. data/templates/cli/storages/ftp +13 -0
  116. data/templates/cli/storages/local +8 -0
  117. data/templates/cli/storages/qiniu +12 -0
  118. data/templates/cli/storages/rsync +17 -0
  119. data/templates/cli/storages/s3 +16 -0
  120. data/templates/cli/storages/scp +15 -0
  121. data/templates/cli/storages/sftp +15 -0
  122. data/templates/cli/syncers/cloud_files +22 -0
  123. data/templates/cli/syncers/rsync_local +20 -0
  124. data/templates/cli/syncers/rsync_pull +28 -0
  125. data/templates/cli/syncers/rsync_push +28 -0
  126. data/templates/cli/syncers/s3 +27 -0
  127. data/templates/general/links +3 -0
  128. data/templates/general/version.erb +2 -0
  129. data/templates/notifier/mail/failure.erb +16 -0
  130. data/templates/notifier/mail/success.erb +16 -0
  131. data/templates/notifier/mail/warning.erb +16 -0
  132. data/templates/storage/dropbox/authorization_url.erb +6 -0
  133. data/templates/storage/dropbox/authorized.erb +4 -0
  134. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  135. metadata +507 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ module Encryptor
5
+ class OpenSSL < Base
6
+ ##
7
+ # The password that'll be used to encrypt the backup. This
8
+ # password will be required to decrypt the backup later on.
9
+ attr_accessor :password
10
+
11
+ ##
12
+ # The password file to use to encrypt the backup.
13
+ attr_accessor :password_file
14
+
15
+ ##
16
+ # Determines whether the 'base64' should be used or not
17
+ attr_accessor :base64
18
+
19
+ ##
20
+ # Determines whether the 'salt' flag should be used
21
+ attr_accessor :salt
22
+
23
+ ##
24
+ # Creates a new instance of Backup::Encryptor::OpenSSL and
25
+ # sets the password attribute to what was provided
26
+ def initialize(&block)
27
+ super
28
+
29
+ @base64 ||= false
30
+ @salt ||= true
31
+ @password_file ||= nil
32
+
33
+ instance_eval(&block) if block_given?
34
+ end
35
+
36
+ ##
37
+ # This is called as part of the procedure run by the Packager.
38
+ # It sets up the needed options to pass to the openssl command,
39
+ # then yields the command to use as part of the packaging procedure.
40
+ # Once the packaging procedure is complete, it will return
41
+ # so that any clean-up may be performed after the yield.
42
+ def encrypt_with
43
+ log!
44
+ yield "#{utility(:openssl)} #{options}", ".enc"
45
+ end
46
+
47
+ private
48
+
49
+ ##
50
+ # Uses the 256bit AES encryption cipher, which is what the
51
+ # US Government uses to encrypt information at the "Top Secret" level.
52
+ #
53
+ # The -base64 option will make the encrypted output base64 encoded,
54
+ # this makes the encrypted file readable using text editors
55
+ #
56
+ # The -salt option adds strength to the encryption
57
+ #
58
+ # Always sets a password option, if even no password is given,
59
+ # but will prefer the password_file option if both are given.
60
+ def options
61
+ opts = ["aes-256-cbc"]
62
+ opts << "-base64" if @base64
63
+ opts << "-salt" if @salt
64
+
65
+ opts <<
66
+ if @password_file.to_s.empty?
67
+ "-k #{Shellwords.escape(@password)}"
68
+ else
69
+ "-pass file:#{@password_file}"
70
+ end
71
+
72
+ opts.join(" ")
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ # Provides cascading errors with formatted messages.
5
+ # See the specs for details.
6
+ module NestedExceptions
7
+ def self.included(klass)
8
+ klass.extend(Module.new do
9
+ def wrap(wrapped_exception, msg = nil)
10
+ new(msg, wrapped_exception)
11
+ end
12
+ end)
13
+ end
14
+
15
+ def initialize(obj = nil, wrapped_exception = nil)
16
+ @wrapped_exception = wrapped_exception
17
+ msg = (obj.respond_to?(:to_str) ? obj.to_str : obj.to_s)
18
+ .gsub(%r{^ *}, " ").strip
19
+ msg = clean_name(self.class.name) + (msg.empty? ? "" : ": #{msg}")
20
+
21
+ if wrapped_exception
22
+ msg << "\n--- Wrapped Exception ---\n"
23
+ class_name = clean_name(wrapped_exception.class.name)
24
+ msg << class_name + ": " unless
25
+ wrapped_exception.message.start_with? class_name
26
+ msg << wrapped_exception.message
27
+ end
28
+
29
+ super(msg)
30
+ set_backtrace(wrapped_exception.backtrace) if wrapped_exception
31
+ end
32
+
33
+ def exception(obj = nil)
34
+ return self if obj.nil? || equal?(obj)
35
+
36
+ ex = self.class.new(obj, @wrapped_exception)
37
+ ex.set_backtrace(backtrace) unless ex.backtrace
38
+ ex
39
+ end
40
+
41
+ private
42
+
43
+ def clean_name(name)
44
+ name.sub(%r{^Backup::}, "")
45
+ end
46
+ end
47
+
48
+ class Error < StandardError
49
+ include NestedExceptions
50
+ end
51
+
52
+ class FatalError < Exception
53
+ include NestedExceptions
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ class Logger
5
+ class Console
6
+ class Options
7
+ ##
8
+ # Disables all console output.
9
+ #
10
+ # This may also be set on the command line using +--quiet+.
11
+ #
12
+ # If +--no-quiet+ is used on the command line, console output
13
+ # will be enabled and any setting here will be ignored.
14
+ #
15
+ # @param [Boolean, nil]
16
+ # @return [Boolean, nil] Default: +false+
17
+ attr_reader :quiet
18
+
19
+ def initialize
20
+ @quiet = false
21
+ end
22
+
23
+ def enabled?
24
+ !quiet
25
+ end
26
+
27
+ def quiet=(val)
28
+ @quiet = val unless quiet.nil?
29
+ end
30
+ end
31
+
32
+ COLORS = {
33
+ info: "\e[32m%s\e[0m", # green
34
+ warn: "\e[33m%s\e[0m", # yellow
35
+ error: "\e[31m%s\e[0m" # red
36
+ }.freeze
37
+
38
+ def initialize(_options = nil)
39
+ $stdout.sync = $stderr.sync = true
40
+ end
41
+
42
+ def log(message)
43
+ io = message.level == :info ? $stdout : $stderr
44
+ lines = message.formatted_lines
45
+ lines.map! { |line| COLORS[message.level] % line } if io.tty?
46
+ io.puts lines
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require only the logger
4
+ require "formatador"
5
+ require "fog/core/logger"
6
+
7
+ module Backup
8
+ class Logger
9
+ module FogAdapter
10
+ class << self
11
+ # Logged as :info so these won't generate warnings.
12
+ # This is mostly to keep STDOUT clean and to provide
13
+ # supplemental messages for our own warnings.
14
+ # These will generally occur during retry attempts.
15
+ def write(message)
16
+ Logger.info message.chomp
17
+ end
18
+
19
+ def tty?
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ Fog::Logger[:warning] = Backup::Logger::FogAdapter
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ class Logger
5
+ class Logfile
6
+ class Error < Backup::Error; end
7
+
8
+ class Options
9
+ ##
10
+ # Enable the use of Backup's log file.
11
+ #
12
+ # While not necessary, as this is +true+ by default,
13
+ # this may also be set on the command line using +--logfile+.
14
+ #
15
+ # The use of Backup's log file may be disabled using the
16
+ # command line option +--no-logfile+.
17
+ #
18
+ # If +--no--logfile+ is used on the command line, then the
19
+ # log file will be disabled and any setting here will be ignored.
20
+ #
21
+ # @param [Boolean, nil]
22
+ # @return [Boolean, nil] Default: +true+
23
+ attr_reader :enabled
24
+
25
+ ##
26
+ # Path to directory where Backup's logfile will be written.
27
+ #
28
+ # This may be given as an absolute path, or a path relative
29
+ # to Backup's +--root-path+ (which defaults to +~/Backup+).
30
+ #
31
+ # This may also be set on the command line using +--log-path+.
32
+ # If set on the command line, any setting here will be ignored.
33
+ #
34
+ # @param [String]
35
+ # @return [String] Default: 'log'
36
+ attr_reader :log_path
37
+
38
+ ##
39
+ # Backup's logfile in which backup logs can be written
40
+ #
41
+ # As there is already a log_path, this can simply be just a file name
42
+ # that will be created (If not exists) on log_path directory
43
+ #
44
+ # This may also be set on the command line using +--log-file+.
45
+ # If set on the command line, any setting here will be ignored.
46
+ #
47
+ # @param [String]
48
+ # @return [String] Default: 'backup.log'
49
+ attr_reader :log_file
50
+
51
+ ##
52
+ # Size in bytes to truncate logfile to before backup jobs are run.
53
+ #
54
+ # This is done once before all +triggers+, so the maximum logfile size
55
+ # would be this value plus whatever the jobs produce.
56
+ #
57
+ # @param [Integer]
58
+ # @return [Integer] Default: +500_000+
59
+ attr_accessor :max_bytes
60
+
61
+ def initialize
62
+ @enabled = true
63
+ @log_path = ""
64
+ @max_bytes = 500_000
65
+ end
66
+
67
+ def enabled?
68
+ !!enabled
69
+ end
70
+
71
+ def enabled=(val)
72
+ @enabled = val unless enabled.nil?
73
+ end
74
+
75
+ def log_path=(val)
76
+ @log_path = val.to_s.strip if log_path.empty?
77
+ end
78
+ end
79
+
80
+ def initialize(options)
81
+ @options = options
82
+ @logfile = setup_logfile
83
+ truncate!
84
+ end
85
+
86
+ def log(message)
87
+ File.open(@logfile, "a") { |f| f.puts message.formatted_lines }
88
+ end
89
+
90
+ private
91
+
92
+ ##
93
+ # Returns the full path to the log file, based on the configured
94
+ # @options.log_path, and ensures the path to the log file exists.
95
+ def setup_logfile
96
+ # strip any trailing '/' in case the user supplied this as part of
97
+ # an absolute path, so we can match it against File.expand_path()
98
+ path = @options.log_path.chomp("/")
99
+ if path.empty?
100
+ path = File.join(Backup::Config.root_path, "log")
101
+ elsif path != File.expand_path(path)
102
+ path = File.join(Backup::Config.root_path, path)
103
+ end
104
+ FileUtils.mkdir_p(path)
105
+ log_file = @options.log_file || "backup.log"
106
+ path = File.join(path, log_file)
107
+ if File.exist?(path) && !File.writable?(path)
108
+ raise Error, "Log File at '#{path}' is not writable"
109
+ end
110
+
111
+ path
112
+ end
113
+
114
+ ##
115
+ # Truncates the logfile to @options.max_bytes
116
+ def truncate!
117
+ return unless File.exist?(@logfile)
118
+
119
+ if File.stat(@logfile).size > @options.max_bytes
120
+ FileUtils.cp(@logfile, @logfile + "~")
121
+ File.open(@logfile + "~", "r") do |io_in|
122
+ File.open(@logfile, "w") do |io_out|
123
+ io_in.seek(-@options.max_bytes, IO::SEEK_END) && io_in.gets
124
+ while (line = io_in.gets)
125
+ io_out.puts line
126
+ end
127
+ end
128
+ end
129
+ FileUtils.rm_f(@logfile + "~")
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backup
4
+ class Logger
5
+ class Syslog
6
+ class Options
7
+ ##
8
+ # Enables logging to the system's Syslog compatible logger.
9
+ #
10
+ # This may also be enabled using +--syslog+ on the command line.
11
+ #
12
+ # If +--no-syslog+ is used on the command line, this will be
13
+ # disabled and any settings here will be ignored.
14
+ #
15
+ # @param [Boolean, nil]
16
+ # @return [Boolean, nil] Default: +false+
17
+ attr_reader :enabled
18
+
19
+ ##
20
+ # Specify the identification string to be used with Syslog.
21
+ #
22
+ # @param [String]
23
+ # @return [String] Default: 'backup'
24
+ attr_accessor :ident
25
+
26
+ ##
27
+ # Specify the options to be used with Syslog.
28
+ #
29
+ # See the Ruby Standard Library documentation for +Syslog+ for more
30
+ # info. http://rdoc.info/stdlib/syslog/Syslog.open
31
+ #
32
+ # Note that setting this to +nil+ will cause this to default
33
+ # to a setting of +Syslog::LOG_PID | Syslog::LOG_CONS+
34
+ #
35
+ # @param [Integer]
36
+ # @return [Integer] Default: +Syslog::LOG_PID+
37
+ attr_accessor :options
38
+
39
+ ##
40
+ # Specify the facility to be used with Syslog.
41
+ #
42
+ # See the Ruby Standard Library documentation for +Syslog+ for more
43
+ # info. http://rdoc.info/stdlib/syslog/Syslog.open
44
+ #
45
+ # Note that setting this to +nil+ will cause this to default
46
+ # to a setting of +Syslog::LOG_USER+
47
+ #
48
+ # @param [Integer]
49
+ # @return [Integer] Default: +Syslog::LOG_LOCAL0+
50
+ attr_accessor :facility
51
+
52
+ ##
53
+ # Specify the priority level to be used for +:info+ messages.
54
+ #
55
+ # See the Ruby Standard Library documentation for +Syslog+ for more
56
+ # info. http://rdoc.info/stdlib/syslog/Syslog.log
57
+ #
58
+ # @param [Integer]
59
+ # @return [Integer] Default: +Syslog::LOG_INFO+
60
+ attr_accessor :info
61
+
62
+ ##
63
+ # Specify the priority level to be used for +:warn+ messages.
64
+ #
65
+ # See the Ruby Standard Library documentation for +Syslog+ for more
66
+ # info. http://rdoc.info/stdlib/syslog/Syslog.log
67
+ #
68
+ # @param [Integer]
69
+ # @return [Integer] Default: +Syslog::LOG_WARNING+
70
+ attr_accessor :warn
71
+
72
+ ##
73
+ # Specify the priority level to be used for +:error+ messages.
74
+ #
75
+ # See the Ruby Standard Library documentation for +Syslog+ for more
76
+ # info. http://rdoc.info/stdlib/syslog/Syslog.log
77
+ #
78
+ # @param [Integer]
79
+ # @return [Integer] Default: +Syslog::LOG_ERR+
80
+ attr_accessor :error
81
+
82
+ def initialize
83
+ @enabled = false
84
+ @ident = "backup"
85
+ @options = ::Syslog::LOG_PID
86
+ @facility = ::Syslog::LOG_LOCAL0
87
+ @info = ::Syslog::LOG_INFO
88
+ @warn = ::Syslog::LOG_WARNING
89
+ @error = ::Syslog::LOG_ERR
90
+ end
91
+
92
+ def enabled?
93
+ !!enabled
94
+ end
95
+
96
+ def enabled=(val)
97
+ @enabled = val unless enabled.nil?
98
+ end
99
+ end
100
+
101
+ def initialize(options)
102
+ @options = options
103
+ end
104
+
105
+ ##
106
+ # Message lines are sent without formatting (timestamp, level),
107
+ # since Syslog will provide it's own timestamp and priority.
108
+ def log(message)
109
+ level = @options.send(message.level)
110
+ ::Syslog.open(@options.ident, @options.options, @options.facility) do |s|
111
+ message.lines.each { |line| s.log(level, "%s", line) }
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "backup/logger/console"
4
+ require "backup/logger/logfile"
5
+ require "backup/logger/syslog"
6
+ require "backup/logger/fog_adapter"
7
+
8
+ module Backup
9
+ class Logger
10
+ class Config
11
+ Logger = Struct.new(:class, :options) do
12
+ def enabled?
13
+ options.enabled?
14
+ end
15
+ end
16
+
17
+ DSL = Struct.new(:ignores, :console, :logfile, :syslog) do
18
+ def ignore_warning(str_or_regexp)
19
+ ignores << str_or_regexp
20
+ end
21
+ end
22
+
23
+ attr_reader :ignores, :loggers, :dsl
24
+
25
+ def initialize
26
+ @ignores = []
27
+ @loggers = [
28
+ Logger.new(Console, Console::Options.new),
29
+ Logger.new(Logfile, Logfile::Options.new),
30
+ Logger.new(Syslog, Syslog::Options.new)
31
+ ]
32
+ @dsl = DSL.new(ignores, *loggers.map(&:options))
33
+ end
34
+ end
35
+
36
+ ##
37
+ # All messages sent to the Logger are stored in Logger.messages
38
+ # and sent to all enabled logger's #log method as Message objects.
39
+ Message = Struct.new(:time, :level, :lines) do
40
+ ##
41
+ # Returns an Array of the message lines in the following format:
42
+ #
43
+ # [YYYY/MM/DD HH:MM:SS][level] message line text
44
+ def formatted_lines
45
+ timestamp = time.strftime("%Y/%m/%d %H:%M:%S")
46
+ lines.map { |line| "[#{timestamp}][#{level}] #{line}" }
47
+ end
48
+
49
+ def matches?(ignores)
50
+ text = lines.join("\n")
51
+ ignores.any? do |obj|
52
+ obj.is_a?(Regexp) ? text.match(obj) : text.include?(obj)
53
+ end
54
+ end
55
+ end
56
+
57
+ class << self
58
+ extend Forwardable
59
+ def_delegators :logger,
60
+ :start!, :abort!, :info, :warn, :error,
61
+ :messages, :has_warnings?, :has_errors?
62
+
63
+ ##
64
+ # Allows the Logger to be configured.
65
+ #
66
+ # # shown with their default values
67
+ # Backup::Logger.configure do
68
+ # # Console options:
69
+ # console.quiet = false
70
+ #
71
+ # # Logfile options:
72
+ # logfile.enabled = true
73
+ # logfile.log_path = 'log'
74
+ # logfile.max_bytes = 500_000
75
+ #
76
+ # # Syslog options:
77
+ # syslog.enabled = false
78
+ # syslog.ident = 'backup'
79
+ # syslog.options = Syslog::LOG_PID
80
+ # syslog.facility = Syslog::LOG_LOCAL0
81
+ # syslog.info = Syslog::LOG_INFO
82
+ # syslog.warn = Syslog::LOG_WARNING
83
+ # syslog.error = Syslog::LOG_ERR
84
+ #
85
+ # # Ignore Warnings:
86
+ # # Converts :warn level messages to level :info
87
+ # ignore_warning 'that contains this string'
88
+ # ignore_warning /that matches this regexp/
89
+ # end
90
+ #
91
+ # See each Logger's Option class for details.
92
+ # @see Console::Options
93
+ # @see Logfile::Options
94
+ # @see Syslog::Options
95
+ def configure(&block)
96
+ config.dsl.instance_eval(&block)
97
+ end
98
+
99
+ ##
100
+ # Called after each backup model/trigger has been performed.
101
+ def clear!
102
+ @logger = nil
103
+ logger.start!
104
+ end
105
+
106
+ private
107
+
108
+ def config
109
+ @config ||= Config.new
110
+ end
111
+
112
+ def logger
113
+ @logger ||= new(config)
114
+ end
115
+
116
+ def reset!
117
+ @config = @logger = nil
118
+ end
119
+ end
120
+
121
+ MUTEX = Mutex.new
122
+
123
+ ##
124
+ # Returns an Array of Message objects for all logged messages received.
125
+ # These are used to attach log files to Mail notifications.
126
+ attr_reader :messages
127
+
128
+ def initialize(config)
129
+ @config = config
130
+ @messages = []
131
+ @loggers = []
132
+ @has_warnings = @has_errors = false
133
+ end
134
+
135
+ ##
136
+ # Sends a message to the Logger using the specified log level.
137
+ # +obj+ may be any Object that responds to #to_s (i.e. an Exception)
138
+ [:info, :warn, :error].each do |level|
139
+ define_method level do |obj|
140
+ MUTEX.synchronize { log(obj, level) }
141
+ end
142
+ end
143
+
144
+ ##
145
+ # Returns true if any +:warn+ level messages have been received.
146
+ def has_warnings?
147
+ @has_warnings
148
+ end
149
+
150
+ ##
151
+ # Returns true if any +:error+ level messages have been received.
152
+ def has_errors?
153
+ @has_errors
154
+ end
155
+
156
+ ##
157
+ # The Logger is available as soon as Backup is loaded, and stores all
158
+ # messages it receives. Since the Logger may be configured via the
159
+ # command line and/or the user's +config.rb+, no messages are sent
160
+ # until configuration can be completed. (see CLI#perform)
161
+ #
162
+ # Once configuration is completed, this method is called to activate
163
+ # all enabled loggers and send them any messages that have been received
164
+ # up to this point. From this point onward, these loggers will be sent
165
+ # all messages as soon as they're received.
166
+ def start!
167
+ @config.loggers.each do |logger|
168
+ @loggers << logger.class.new(logger.options) if logger.enabled?
169
+ end
170
+ messages.each do |message|
171
+ @loggers.each { |logger| logger.log(message) }
172
+ end
173
+ end
174
+
175
+ ##
176
+ # If errors are encountered by Backup::CLI while preparing to perform
177
+ # the backup jobs, this method is called to dump all messages to the
178
+ # console before Backup exits.
179
+ def abort!
180
+ console = Console.new
181
+ console.log(messages.shift) until messages.empty?
182
+ end
183
+
184
+ private
185
+
186
+ def log(obj, level)
187
+ message = Message.new(Time.now.utc, level, obj.to_s.split("\n"))
188
+
189
+ if message.level == :warn && message.matches?(@config.ignores)
190
+ message.level = :info
191
+ end
192
+ @has_warnings ||= message.level == :warn
193
+ @has_errors ||= message.level == :error
194
+
195
+ messages << message
196
+ @loggers.each { |logger| logger.log(message) }
197
+ end
198
+ end
199
+ end