class Trailer::Storage::CloudWatch

Attributes

client[RW]
messages[RW]
sequence_token[RW]

Public Class Methods

new() click to toggle source

Constructor.

# File lib/trailer/storage/cloud_watch.rb, line 12
def initialize
  self.messages = []
  self.client   = Aws::CloudWatchLogs::Client.new(region: Trailer.config.aws_region, credentials: credentials)
  ensure_log_group
  ensure_log_stream
end

Public Instance Methods

flush() click to toggle source

Sends all of the queued messages to CloudWatch, and resets the messages queue.

See stackoverflow.com/a/36901509

# File lib/trailer/storage/cloud_watch.rb, line 22
def flush
  return if messages.empty?

  events = {
    log_group_name:  Trailer.config.application_name,
    log_stream_name: Trailer.config.application_name,
    log_events:      messages,
    sequence_token:  sequence_token,
  }

  response            = client.put_log_events(events)
  self.sequence_token = response&.next_sequence_token
  self.messages       = []
rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException
  # Only one client at a time can write to the log. If another client has written before we get a chance,
  # the sequence token is invalidated, and we need to get a new one.
  self.sequence_token = log_stream[:upload_sequence_token]
  retry
end
write(data) click to toggle source

Queues the given hash for writing to CloudWatch.

@param data [Hash] A key-value hash of trace data to write to storage.

# File lib/trailer/storage/cloud_watch.rb, line 45
def write(data)
  return if data.empty?

  messages << {
    timestamp: (Time.now.utc.to_f.round(3) * 1000).to_i,
    message:   data&.to_json,
  }.compact
end

Private Instance Methods

credentials() click to toggle source

Returns an AWS credentials instance for writing to CloudWatch.

# File lib/trailer/storage/cloud_watch.rb, line 59
def credentials
  Aws::Credentials.new(Trailer.config.aws_access_key_id, Trailer.config.aws_secret_access_key)
end
ensure_log_group() click to toggle source

Creates the log group, if it doesn't already exist. Ideally we would paginate here in case the account has a lot of log groups with the same prefix, but it seems unlikely to happen.

# File lib/trailer/storage/cloud_watch.rb, line 65
def ensure_log_group
  existing = client.describe_log_groups.log_groups.find do |group|
    group.log_group_name == Trailer.config.application_name
  end

  client.create_log_group(log_group_name: Trailer.config.application_name) unless existing
rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
  # No need to do anything - probably caused by lack of pagination.
end
ensure_log_stream() click to toggle source

Create the log stream, if it doesn't already exist. Ideally we would paginate here in case the account has a lot of log streams.

# File lib/trailer/storage/cloud_watch.rb, line 77
def ensure_log_stream
  if (existing = log_stream)
    self.sequence_token = existing.upload_sequence_token
  else
    client.create_log_stream(
      log_group_name:  Trailer.config.application_name,
      log_stream_name: Trailer.config.application_name,
    )
  end
rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
  # No need to do anything - probably caused by lack of pagination.
end
log_stream() click to toggle source

Returns the current log stream, if one exists.

# File lib/trailer/storage/cloud_watch.rb, line 91
def log_stream
  client.describe_log_streams(
    log_group_name:         Trailer.config.application_name,
    log_stream_name_prefix: Trailer.config.application_name,
  ).log_streams.find do |stream|
    stream.log_stream_name == Trailer.config.application_name
  end
end