Python / Boto - 在没有序列令牌的情况下写入AWS CloudWatch Log

时间:2015-06-17 17:01:17

标签: python amazon-web-services boto

我正在尝试使用Python和Boto框架将日志发送到AWS CloudWatch Logs。我这样做:

res=logs.put_log_events("FOO", "BAR",
     [{'timestamp':int(round(time.time() * 1000)),
       'message':time.strftime("%m/%d/%Y %H:%M:%S")+' Scheduled  monitoring check' }], 
     sequence_token=None)

每次运行时都会出错:

boto.logs.exceptions.InvalidSequenceTokenException: InvalidSequenceTokenException: 400 Bad Request
{u'message': u'The given sequenceToken is invalid. The next expected sequenceToken is: 49540113336360065754596906019042392283494234157161146226', u'expectedSequenceToken': u'49540113336360065754596906019042392283494234157161146226', u'__type': u'InvalidSequenceTokenException'}

存储该令牌对我来说有点不切实际。这没有意义,为什么我只能附加到日志流?

我怎样才能解决这个问题?

6 个答案:

答案 0 :(得分:4)

您可以通过describe_log_streams()首先查找uploadSequenceToken来解决这个问题:

本质上,该过程是您使用logStreamNamePrefix专门标识要附加到的日志流。然后从响应中解析uploadSequenceToken。

  

响应语法

 {
     'logStreams': [
         {
             'logStreamName': 'string',
             'creationTime': 123,
             'firstEventTimestamp': 123,
             'lastEventTimestamp': 123,
             'lastIngestionTime': 123,
             'uploadSequenceToken': 'string',
             'arn': 'string',
             'storedBytes': 123
         },
     ],
     'nextToken': 'string'
 }
     

返回与指定日志组关联的所有日志流。响应中返回的列表按日志流名称进行ASCII排序。

     

默认情况下,此操作最多返回50个日志流。如果要列出更多日志流,则响应将在响应正文中包含nextToken值。您还可以通过在请求中指定limit参数来限制响应中返回的日志流的数量。此操作每秒限制为五个事务,之后事务将受到限制。

     

请求语法

response = client.describe_log_streams(
    logGroupName='string',
    logStreamNamePrefix='string',
    orderBy='LogStreamName'|'LastEventTime',
    descending=True|False,
    nextToken='string',
    limit=123
)

答案 1 :(得分:3)

你无法做到这一点:

  

每个PutLogEvents请求必须包含从前一个请求的响应中获得的sequenceToken。在新创建的日志流中上载不需要sequenceToken。

source

答案 2 :(得分:3)

用有根据的猜测来回答为什么部分:这是可扩展异步服务的本质。

如果亚马逊要求您维护序列号,那么他们永远无法在多个实例中扩展其CloudWatch服务,同时仍能保证您的日志显示在完全与它们发生的顺序相同(并想象在调试问题时有多烦人的无序日志条目)。时钟,网络延迟或沿着记录接收器路径的其他延迟的任何微小偏差都会引入排序问题。

但是,由于他们要求您提供序列号,突然他们可以轻松地扩展他们的服务,只需merge-sort传入日志条目,同时仍然保留正确的日志顺序,您的日志顺序。

答案 3 :(得分:3)

the docs中所述:

您还可以从expectedSequenceToken的{​​{1}}字段中获取序列令牌。

但是,问题在于boto3在the issue中已讨论过,但异常中没有InvalidSequenceTokenException字段:

Boto3不支持从异常中解析其他参数,它仅添加代码和消息。标记为功能请求,我认为这是我们应该添加的内容,但是目前最好的解决方法是解析错误消息。

很明显,解析消息以获取令牌不是理想的,因为消息的格式可能会更改。但这提供了一个简单的工作解决方案,而无需调用expectedSequenceToken

describe_log_streams

该函数将创建组和流(如果不存在)。

答案 4 :(得分:1)

这是我在其他答案的帮助下创建的日志类,不需要日志:DescribeLogStreams IAM 权限。这是一个可以导入的独立模块(在类初始化时传入 boto3 会话)。

import time

class CloudWatch:
    def __init__(self, boto3, log_group):
        self.client = boto3.client("logs")
        self.log_group = log_group
        self.sequence_token = None

    def log(self, message):
        
        print(message) # Delete this if you don't want stdout as well.

        log_stream = time.strftime('%Y-%m-%d')

        event_log = {
            'logGroupName': self.log_group,
            'logStreamName': log_stream,
            'logEvents': [
                {
                    'timestamp': int(round(time.time() * 1000)),
                    'message': message
                }
            ],
        }

        if self.sequence_token is not None:
            event_log.update({"sequenceToken" : self.sequence_token})

        for _ in range(3):
            try:
                response = self.client.put_log_events(**event_log)
                self.sequence_token = response["nextSequenceToken"]
                return
            except self.client.exceptions.ResourceNotFoundException:
                try:
                    self.client.create_log_group(logGroupName=self.log_group)
                except self.client.exceptions.ResourceAlreadyExistsException:
                    pass
                try:
                    self.client.create_log_stream(logGroupName=self.log_group, logStreamName=log_stream)
                except self.client.exceptions.ResourceAlreadyExistsException:
                    pass
            except self.client.exceptions.InvalidSequenceTokenException as e:
                event_log.update({"sequenceToken" : e.response["Error"]["Message"].split("is: ")[-1]})
                continue
            except self.client.exceptions.DataAlreadyAcceptedException:
                return

答案 5 :(得分:0)

AWS Cloud Watch Putlog事件代码

import boto3
import time


client = boto3.client('logs')

LOG_GROUP='cloudwatch_customlog'
LOG_STREAM='{}-{}'.format(time.strftime('%Y-%m-%d'),'logstream')

try:
   client.create_log_group(logGroupName=LOG_GROUP)
except client.exceptions.ResourceAlreadyExistsException:
   pass

try:
   client.create_log_stream(logGroupName=LOG_GROUP, logStreamName=LOG_STREAM)
except client.exceptions.ResourceAlreadyExistsException:
   pass

response = client.describe_log_streams(
   logGroupName=LOG_GROUP,
   logStreamNamePrefix=LOG_STREAM
)

event_log = {
   'logGroupName': LOG_GROUP,
   'logStreamName': LOG_STREAM,
   'logEvents': [
       {
           'timestamp': int(round(time.time() * 1000)),
           'message': time.strftime('%Y-%m-%d %H:%M:%S')+'\t Your custom log messages'
       }
   ],
}

if 'uploadSequenceToken' in response['logStreams'][0]:
   event_log.update({'sequenceToken': response['logStreams'][0] ['uploadSequenceToken']})

response = client.put_log_events(**event_log)
print(response)