module AWS module S3 # A bucket can be set to log the requests made on it. By default logging is turned off. You can check if a bucket has logging enabled: # # Bucket.logging_enabled_for? 'jukebox' # # => false # # Enabling it is easy: # # Bucket.enable_logging_for('jukebox') # # Unless you specify otherwise, logs will be written to the bucket you want to log. The logs are just like any other object. By default they will start with the prefix 'log-'. You can customize what bucket you want the logs to be delivered to, as well as customize what the log objects' key is prefixed with by setting the target_bucket and target_prefix option: # # Bucket.enable_logging_for( # 'jukebox', 'target_bucket' => 'jukebox-logs' # ) # # Now instead of logging right into the jukebox bucket, the logs will go into the bucket called jukebox-logs. # # Once logs have accumulated, you can access them using the logs method: # # pp Bucket.logs('jukebox') # [#, # #, # #] # # Each log has a lines method that gives you information about each request in that log. All the fields are available # as named methods. More information is available in Logging::Log::Line. # # logs = Bucket.logs('jukebox') # log = logs.first # line = log.lines.first # line.operation # # => 'REST.GET.LOGGING_STATUS' # line.request_uri # # => 'GET /jukebox?logging HTTP/1.1' # line.remote_ip # # => "67.165.183.125" # # Disabling logging is just as simple as enabling it: # # Bucket.disable_logging_for('jukebox') module Logging # Logging status captures information about the calling bucket's logging settings. If logging is enabled for the bucket # the status object will indicate what bucket the logs are written to via the target_bucket method as well as # the logging key prefix with via target_prefix. # # See the documentation for Logging::Management::ClassMethods for more information on how to get the logging status of a bucket. class Status include SelectiveAttributeProxy attr_reader :enabled alias_method :logging_enabled?, :enabled def initialize(attributes = {}) #:nodoc: attributes = {'target_bucket' => nil, 'target_prefix' => nil}.merge(attributes) @enabled = attributes.has_key?('logging_enabled') @attributes = attributes.delete('logging_enabled') || attributes end def to_xml #:nodoc: Builder.new(self).to_s end private attr_reader :attributes class Builder < XmlGenerator #:nodoc: attr_reader :logging_status def initialize(logging_status) @logging_status = logging_status super() end def build xml.tag!('BucketLoggingStatus', 'xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do if logging_status.target_bucket && logging_status.target_prefix xml.LoggingEnabled do xml.TargetBucket logging_status.target_bucket xml.TargetPrefix logging_status.target_prefix end end end end end end # A bucket log exposes requests made on the given bucket. Lines of the log represent a single request. The lines of a log # can be accessed with the lines method. # # log = Bucket.logs_for('marcel').first # log.lines # # More information about the logged requests can be found in the documentation for Log::Line. class Log def initialize(log_object) #:nodoc: @log = log_object end # Returns the lines for the log. Each line is wrapped in a Log::Line. if RUBY_VERSION >= '1.8.7' def lines log.value.lines.map {|line| Line.new(line)} end else def lines log.value.map {|line| Line.new(line)} end end memoized :lines def path log.path end def inspect #:nodoc: "#<%s:0x%s '%s'>" % [self.class.name, object_id, path] end private attr_reader :log # Each line of a log exposes the raw line, but it also has method accessors for all the fields of the logged request. # # The list of supported log line fields are listed in the S3 documentation: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/LogFormat.html # # line = log.lines.first # line.remote_ip # # => '72.21.206.5' # # If a certain field does not apply to a given request (for example, the key field does not apply to a bucket request), # or if it was unknown or unavailable, it will return nil. # # line.operation # # => 'REST.GET.BUCKET' # line.key # # => nil class Line < String DATE = /\[([^\]]+)\]/ QUOTED_STRING = /"([^"]+)"/ REST = /(\S+)/ LINE_SCANNER = /#{DATE}|#{QUOTED_STRING}|#{REST}/ cattr_accessor :decorators @@decorators = Hash.new {|hash, key| hash[key] = lambda {|entry| CoercibleString.coerce(entry)}} cattr_reader :fields @@fields = [] class << self def field(name, offset, type = nil, &block) #:nodoc: decorators[name] = block if block_given? fields << name class_eval(<<-EVAL, __FILE__, __LINE__) def #{name} value = parts[#{offset} - 1] if value == '-' nil else self.class.decorators[:#{name}].call(value) end end memoized :#{name} EVAL end # Time.parse doesn't like %d/%B/%Y:%H:%M:%S %z so we have to transform it unfortunately def typecast_time(datetime) #:nodoc: datetime.sub!(%r|^(\w{2})/(\w{3})/(\w{4})|, '\2 \1 \3') if Date.constants.include?('ABBR_MONTHS') datetime.sub!(month, Date::ABBR_MONTHS[month.downcase].to_s) end datetime.sub!(':', ' ') Time.parse(datetime) end end def initialize(line) #:nodoc: super(line) @parts = parse end field(:owner, 1) {|entry| Owner.new('id' => entry) } field :bucket, 2 field(:time, 3) {|entry| typecast_time(entry)} field :remote_ip, 4 field(:requestor, 5) {|entry| Owner.new('id' => entry) } field :request_id, 6 field :operation, 7 field :key, 8 field :request_uri, 9 field :http_status, 10 field :error_code, 11 field :bytes_sent, 12 field :object_size, 13 field :total_time, 14 field :turn_around_time, 15 field :referrer, 16 field :user_agent, 17 # Returns all fields of the line in a hash of the form :field_name => :field_value. # # line.attributes.values_at(:bucket, :key) # # => ['marcel', 'kiss.jpg'] def attributes self.class.fields.inject({}) do |attribute_hash, field| attribute_hash[field] = send(field) attribute_hash end end private attr_reader :parts def parse scan(LINE_SCANNER).flatten.compact end end end module Management #:nodoc: def self.included(klass) #:nodoc: klass.extend(ClassMethods) klass.extend(LoggingGrants) end module ClassMethods # Returns the logging status for the bucket named name. From the logging status you can determine the bucket logs are delivered to # and what the bucket object's keys are prefixed with. For more information see the Logging::Status class. # # Bucket.logging_status_for 'marcel' def logging_status_for(name = nil, status = nil) if name.is_a?(Status) status = name name = nil end path = path(name) << '?logging' status ? put(path, {}, status.to_xml) : Status.new(get(path).parsed) end alias_method :logging_status, :logging_status_for # Enables logging for the bucket named name. You can specify what bucket to log to with the 'target_bucket' option as well # as what prefix to add to the log files with the 'target_prefix' option. Unless you specify otherwise, logs will be delivered to # the same bucket that is being logged and will be prefixed with log-. def enable_logging_for(name = nil, options = {}) name = bucket_name(name) default_options = {'target_bucket' => name, 'target_prefix' => 'log-'} options = default_options.merge(options) grant_logging_access_to_target_bucket(options['target_bucket']) logging_status(name, Status.new(options)) end alias_method :enable_logging, :enable_logging_for # Disables logging for the bucket named name. def disable_logging_for(name = nil) logging_status(bucket_name(name), Status.new) end alias_method :disable_logging, :disable_logging_for # Returns true if logging has been enabled for the bucket named name. def logging_enabled_for?(name = nil) logging_status(bucket_name(name)).logging_enabled? end alias_method :logging_enabled?, :logging_enabled_for? # Returns the collection of logs for the bucket named name. # # Bucket.logs_for 'marcel' # # Accepts the same options as Bucket.find, such as :max_keys and :marker. def logs_for(name = nil, options = {}) if name.is_a?(Hash) options = name name = nil end name = bucket_name(name) logging_status = logging_status_for(name) return [] unless logging_status.logging_enabled? objects(logging_status.target_bucket, options.merge(:prefix => logging_status.target_prefix)).map do |log_object| Log.new(log_object) end end alias_method :logs, :logs_for end module LoggingGrants #:nodoc: def grant_logging_access_to_target_bucket(target_bucket) acl = acl(target_bucket) acl.grants << ACL::Grant.grant(:logging_write) acl.grants << ACL::Grant.grant(:logging_read_acp) acl(target_bucket, acl) end end def logging_status self.class.logging_status_for(name) end def enable_logging(*args) self.class.enable_logging_for(name, *args) end def disable_logging(*args) self.class.disable_logging_for(name, *args) end def logging_enabled? self.class.logging_enabled_for?(name) end def logs(options = {}) self.class.logs_for(name, options) end end end end end