1# Copyright (c) 2014 Amazon.com, Inc. or its affiliates.  All Rights Reserved
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the
5# "Software"), to deal in the Software without restriction, including
6# without limitation the rights to use, copy, modify, merge, publish, dis-
7# tribute, sublicense, and/or sell copies of the Software, and to permit
8# persons to whom the Software is furnished to do so, subject to the fol-
9# lowing conditions:
10#
11# The above copyright notice and this permission notice shall be included
12# in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21#
22
23import boto
24from boto.connection import AWSQueryConnection
25from boto.regioninfo import RegionInfo
26from boto.exception import JSONResponseError
27from boto.logs import exceptions
28from boto.compat import json
29
30
31class CloudWatchLogsConnection(AWSQueryConnection):
32    """
33    Amazon CloudWatch Logs Service API Reference
34    This is the Amazon CloudWatch Logs API Reference . Amazon
35    CloudWatch Logs is a managed service for real time monitoring and
36    archival of application logs. This guide provides detailed
37    information about Amazon CloudWatch Logs actions, data types,
38    parameters, and errors. For detailed information about Amazon
39    CloudWatch Logs features and their associated API calls, go to the
40    `Amazon CloudWatch Logs Developer Guide`_.
41
42    Use the following links to get started using the Amazon CloudWatch
43    API Reference :
44
45
46    + `Actions`_: An alphabetical list of all Amazon CloudWatch Logs
47      actions.
48    + `Data Types`_: An alphabetical list of all Amazon CloudWatch
49      Logs data types.
50    + `Common Parameters`_: Parameters that all Query actions can use.
51    + `Common Errors`_: Client and server errors that all actions can
52      return.
53    + `Regions and Endpoints`_: Itemized regions and endpoints for all
54      AWS products.
55
56
57    In addition to using the Amazon CloudWatch Logs API, you can also
58    use the following SDKs and third-party libraries to access Amazon
59    CloudWatch Logs programmatically.
60
61
62    + `AWS SDK for Java Documentation`_
63    + `AWS SDK for .NET Documentation`_
64    + `AWS SDK for PHP Documentation`_
65    + `AWS SDK for Ruby Documentation`_
66
67
68    Developers in the AWS developer community also provide their own
69    libraries, which you can find at the following AWS developer
70    centers:
71
72
73    + `AWS Java Developer Center`_
74    + `AWS PHP Developer Center`_
75    + `AWS Python Developer Center`_
76    + `AWS Ruby Developer Center`_
77    + `AWS Windows and .NET Developer Center`_
78    """
79    APIVersion = "2014-03-28"
80    DefaultRegionName = "us-east-1"
81    DefaultRegionEndpoint = "logs.us-east-1.amazonaws.com"
82    ServiceName = "CloudWatchLogs"
83    TargetPrefix = "Logs_20140328"
84    ResponseError = JSONResponseError
85
86    _faults = {
87        "LimitExceededException": exceptions.LimitExceededException,
88        "DataAlreadyAcceptedException": exceptions.DataAlreadyAcceptedException,
89        "ResourceInUseException": exceptions.ResourceInUseException,
90        "ServiceUnavailableException": exceptions.ServiceUnavailableException,
91        "InvalidParameterException": exceptions.InvalidParameterException,
92        "ResourceNotFoundException": exceptions.ResourceNotFoundException,
93        "ResourceAlreadyExistsException": exceptions.ResourceAlreadyExistsException,
94        "OperationAbortedException": exceptions.OperationAbortedException,
95        "InvalidSequenceTokenException": exceptions.InvalidSequenceTokenException,
96    }
97
98    def __init__(self, **kwargs):
99        region = kwargs.pop('region', None)
100        if not region:
101            region = RegionInfo(self, self.DefaultRegionName,
102                                self.DefaultRegionEndpoint)
103
104        if 'host' not in kwargs or kwargs['host'] is None:
105            kwargs['host'] = region.endpoint
106
107        super(CloudWatchLogsConnection, self).__init__(**kwargs)
108        self.region = region
109
110    def _required_auth_capability(self):
111        return ['hmac-v4']
112
113    def create_log_group(self, log_group_name):
114        """
115        Creates a new log group with the specified name. The name of
116        the log group must be unique within a region for an AWS
117        account. You can create up to 100 log groups per account.
118
119        You must use the following guidelines when naming a log group:
120
121        + Log group names can be between 1 and 512 characters long.
122        + Allowed characters are az, AZ, 09, '_' (underscore), '-'
123          (hyphen), '/' (forward slash), and '.' (period).
124
125
126
127        Log groups are created with a default retention of 14 days.
128        The retention attribute allow you to configure the number of
129        days you want to retain log events in the specified log group.
130        See the `SetRetention` operation on how to modify the
131        retention of your log groups.
132
133        :type log_group_name: string
134        :param log_group_name:
135
136        """
137        params = {'logGroupName': log_group_name, }
138        return self.make_request(action='CreateLogGroup',
139                                 body=json.dumps(params))
140
141    def create_log_stream(self, log_group_name, log_stream_name):
142        """
143        Creates a new log stream in the specified log group. The name
144        of the log stream must be unique within the log group. There
145        is no limit on the number of log streams that can exist in a
146        log group.
147
148        You must use the following guidelines when naming a log
149        stream:
150
151        + Log stream names can be between 1 and 512 characters long.
152        + The ':' colon character is not allowed.
153
154        :type log_group_name: string
155        :param log_group_name:
156
157        :type log_stream_name: string
158        :param log_stream_name:
159
160        """
161        params = {
162            'logGroupName': log_group_name,
163            'logStreamName': log_stream_name,
164        }
165        return self.make_request(action='CreateLogStream',
166                                 body=json.dumps(params))
167
168    def delete_log_group(self, log_group_name):
169        """
170        Deletes the log group with the specified name. Amazon
171        CloudWatch Logs will delete a log group only if there are no
172        log streams and no metric filters associated with the log
173        group. If this condition is not satisfied, the request will
174        fail and the log group will not be deleted.
175
176        :type log_group_name: string
177        :param log_group_name:
178
179        """
180        params = {'logGroupName': log_group_name, }
181        return self.make_request(action='DeleteLogGroup',
182                                 body=json.dumps(params))
183
184    def delete_log_stream(self, log_group_name, log_stream_name):
185        """
186        Deletes a log stream and permanently deletes all the archived
187        log events associated with it.
188
189        :type log_group_name: string
190        :param log_group_name:
191
192        :type log_stream_name: string
193        :param log_stream_name:
194
195        """
196        params = {
197            'logGroupName': log_group_name,
198            'logStreamName': log_stream_name,
199        }
200        return self.make_request(action='DeleteLogStream',
201                                 body=json.dumps(params))
202
203    def delete_metric_filter(self, log_group_name, filter_name):
204        """
205        Deletes a metric filter associated with the specified log
206        group.
207
208        :type log_group_name: string
209        :param log_group_name:
210
211        :type filter_name: string
212        :param filter_name: The name of the metric filter.
213
214        """
215        params = {
216            'logGroupName': log_group_name,
217            'filterName': filter_name,
218        }
219        return self.make_request(action='DeleteMetricFilter',
220                                 body=json.dumps(params))
221
222    def delete_retention_policy(self, log_group_name):
223        """
224
225
226        :type log_group_name: string
227        :param log_group_name:
228
229        """
230        params = {'logGroupName': log_group_name, }
231        return self.make_request(action='DeleteRetentionPolicy',
232                                 body=json.dumps(params))
233
234    def describe_log_groups(self, log_group_name_prefix=None,
235                            next_token=None, limit=None):
236        """
237        Returns all the log groups that are associated with the AWS
238        account making the request. The list returned in the response
239        is ASCII-sorted by log group name.
240
241        By default, this operation returns up to 50 log groups. If
242        there are more log groups to list, the response would contain
243        a `nextToken` value in the response body. You can also limit
244        the number of log groups returned in the response by
245        specifying the `limit` parameter in the request.
246
247        :type log_group_name_prefix: string
248        :param log_group_name_prefix:
249
250        :type next_token: string
251        :param next_token: A string token used for pagination that points to
252            the next page of results. It must be a value obtained from the
253            response of the previous `DescribeLogGroups` request.
254
255        :type limit: integer
256        :param limit: The maximum number of items returned in the response. If
257            you don't specify a value, the request would return up to 50 items.
258
259        """
260        params = {}
261        if log_group_name_prefix is not None:
262            params['logGroupNamePrefix'] = log_group_name_prefix
263        if next_token is not None:
264            params['nextToken'] = next_token
265        if limit is not None:
266            params['limit'] = limit
267        return self.make_request(action='DescribeLogGroups',
268                                 body=json.dumps(params))
269
270    def describe_log_streams(self, log_group_name,
271                             log_stream_name_prefix=None, next_token=None,
272                             limit=None):
273        """
274        Returns all the log streams that are associated with the
275        specified log group. The list returned in the response is
276        ASCII-sorted by log stream name.
277
278        By default, this operation returns up to 50 log streams. If
279        there are more log streams to list, the response would contain
280        a `nextToken` value in the response body. You can also limit
281        the number of log streams returned in the response by
282        specifying the `limit` parameter in the request.
283
284        :type log_group_name: string
285        :param log_group_name:
286
287        :type log_stream_name_prefix: string
288        :param log_stream_name_prefix:
289
290        :type next_token: string
291        :param next_token: A string token used for pagination that points to
292            the next page of results. It must be a value obtained from the
293            response of the previous `DescribeLogStreams` request.
294
295        :type limit: integer
296        :param limit: The maximum number of items returned in the response. If
297            you don't specify a value, the request would return up to 50 items.
298
299        """
300        params = {'logGroupName': log_group_name, }
301        if log_stream_name_prefix is not None:
302            params['logStreamNamePrefix'] = log_stream_name_prefix
303        if next_token is not None:
304            params['nextToken'] = next_token
305        if limit is not None:
306            params['limit'] = limit
307        return self.make_request(action='DescribeLogStreams',
308                                 body=json.dumps(params))
309
310    def describe_metric_filters(self, log_group_name,
311                                filter_name_prefix=None, next_token=None,
312                                limit=None):
313        """
314        Returns all the metrics filters associated with the specified
315        log group. The list returned in the response is ASCII-sorted
316        by filter name.
317
318        By default, this operation returns up to 50 metric filters. If
319        there are more metric filters to list, the response would
320        contain a `nextToken` value in the response body. You can also
321        limit the number of metric filters returned in the response by
322        specifying the `limit` parameter in the request.
323
324        :type log_group_name: string
325        :param log_group_name:
326
327        :type filter_name_prefix: string
328        :param filter_name_prefix: The name of the metric filter.
329
330        :type next_token: string
331        :param next_token: A string token used for pagination that points to
332            the next page of results. It must be a value obtained from the
333            response of the previous `DescribeMetricFilters` request.
334
335        :type limit: integer
336        :param limit: The maximum number of items returned in the response. If
337            you don't specify a value, the request would return up to 50 items.
338
339        """
340        params = {'logGroupName': log_group_name, }
341        if filter_name_prefix is not None:
342            params['filterNamePrefix'] = filter_name_prefix
343        if next_token is not None:
344            params['nextToken'] = next_token
345        if limit is not None:
346            params['limit'] = limit
347        return self.make_request(action='DescribeMetricFilters',
348                                 body=json.dumps(params))
349
350    def get_log_events(self, log_group_name, log_stream_name,
351                       start_time=None, end_time=None, next_token=None,
352                       limit=None, start_from_head=None):
353        """
354        Retrieves log events from the specified log stream. You can
355        provide an optional time range to filter the results on the
356        event `timestamp`.
357
358        By default, this operation returns as much log events as can
359        fit in a response size of 1MB, up to 10,000 log events. The
360        response will always include a `nextForwardToken` and a
361        `nextBackwardToken` in the response body. You can use any of
362        these tokens in subsequent `GetLogEvents` requests to paginate
363        through events in either forward or backward direction. You
364        can also limit the number of log events returned in the
365        response by specifying the `limit` parameter in the request.
366
367        :type log_group_name: string
368        :param log_group_name:
369
370        :type log_stream_name: string
371        :param log_stream_name:
372
373        :type start_time: long
374        :param start_time: A point in time expressed as the number milliseconds
375            since Jan 1, 1970 00:00:00 UTC.
376
377        :type end_time: long
378        :param end_time: A point in time expressed as the number milliseconds
379            since Jan 1, 1970 00:00:00 UTC.
380
381        :type next_token: string
382        :param next_token: A string token used for pagination that points to
383            the next page of results. It must be a value obtained from the
384            `nextForwardToken` or `nextBackwardToken` fields in the response of
385            the previous `GetLogEvents` request.
386
387        :type limit: integer
388        :param limit: The maximum number of log events returned in the
389            response. If you don't specify a value, the request would return as
390            much log events as can fit in a response size of 1MB, up to 10,000
391            log events.
392
393        :type start_from_head: boolean
394        :param start_from_head:
395
396        """
397        params = {
398            'logGroupName': log_group_name,
399            'logStreamName': log_stream_name,
400        }
401        if start_time is not None:
402            params['startTime'] = start_time
403        if end_time is not None:
404            params['endTime'] = end_time
405        if next_token is not None:
406            params['nextToken'] = next_token
407        if limit is not None:
408            params['limit'] = limit
409        if start_from_head is not None:
410            params['startFromHead'] = start_from_head
411        return self.make_request(action='GetLogEvents',
412                                 body=json.dumps(params))
413
414    def put_log_events(self, log_group_name, log_stream_name, log_events,
415                       sequence_token=None):
416        """
417        Uploads a batch of log events to the specified log stream.
418
419        Every PutLogEvents request must include the `sequenceToken`
420        obtained from the response of the previous request. An upload
421        in a newly created log stream does not require a
422        `sequenceToken`.
423
424        The batch of events must satisfy the following constraints:
425
426        + The maximum batch size is 32,768 bytes, and this size is
427          calculated as the sum of all event messages in UTF-8, plus 26
428          bytes for each log event.
429        + None of the log events in the batch can be more than 2 hours
430          in the future.
431        + None of the log events in the batch can be older than 14
432          days or the retention period of the log group.
433        + The log events in the batch must be in chronological ordered
434          by their `timestamp`.
435        + The maximum number of log events in a batch is 1,000.
436
437        :type log_group_name: string
438        :param log_group_name:
439
440        :type log_stream_name: string
441        :param log_stream_name:
442
443        :type log_events: list
444        :param log_events: A list of events belonging to a log stream.
445
446        :type sequence_token: string
447        :param sequence_token: A string token that must be obtained from the
448            response of the previous `PutLogEvents` request.
449
450        """
451        params = {
452            'logGroupName': log_group_name,
453            'logStreamName': log_stream_name,
454            'logEvents': log_events,
455        }
456        if sequence_token is not None:
457            params['sequenceToken'] = sequence_token
458        return self.make_request(action='PutLogEvents',
459                                 body=json.dumps(params))
460
461    def put_metric_filter(self, log_group_name, filter_name, filter_pattern,
462                          metric_transformations):
463        """
464        Creates or updates a metric filter and associates it with the
465        specified log group. Metric filters allow you to configure
466        rules to extract metric data from log events ingested through
467        `PutLogEvents` requests.
468
469        :type log_group_name: string
470        :param log_group_name:
471
472        :type filter_name: string
473        :param filter_name: The name of the metric filter.
474
475        :type filter_pattern: string
476        :param filter_pattern:
477
478        :type metric_transformations: list
479        :param metric_transformations:
480
481        """
482        params = {
483            'logGroupName': log_group_name,
484            'filterName': filter_name,
485            'filterPattern': filter_pattern,
486            'metricTransformations': metric_transformations,
487        }
488        return self.make_request(action='PutMetricFilter',
489                                 body=json.dumps(params))
490
491    def put_retention_policy(self, log_group_name, retention_in_days):
492        """
493
494
495        :type log_group_name: string
496        :param log_group_name:
497
498        :type retention_in_days: integer
499        :param retention_in_days: Specifies the number of days you want to
500            retain log events in the specified log group. Possible values are:
501            1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 547, 730.
502
503        """
504        params = {
505            'logGroupName': log_group_name,
506            'retentionInDays': retention_in_days,
507        }
508        return self.make_request(action='PutRetentionPolicy',
509                                 body=json.dumps(params))
510
511    def set_retention(self, log_group_name, retention_in_days):
512        """
513        Sets the retention of the specified log group. Log groups are
514        created with a default retention of 14 days. The retention
515        attribute allow you to configure the number of days you want
516        to retain log events in the specified log group.
517
518        :type log_group_name: string
519        :param log_group_name:
520
521        :type retention_in_days: integer
522        :param retention_in_days: Specifies the number of days you want to
523            retain log events in the specified log group. Possible values are:
524            1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 547, 730.
525
526        """
527        params = {
528            'logGroupName': log_group_name,
529            'retentionInDays': retention_in_days,
530        }
531        return self.make_request(action='SetRetention',
532                                 body=json.dumps(params))
533
534    def test_metric_filter(self, filter_pattern, log_event_messages):
535        """
536        Tests the filter pattern of a metric filter against a sample
537        of log event messages. You can use this operation to validate
538        the correctness of a metric filter pattern.
539
540        :type filter_pattern: string
541        :param filter_pattern:
542
543        :type log_event_messages: list
544        :param log_event_messages:
545
546        """
547        params = {
548            'filterPattern': filter_pattern,
549            'logEventMessages': log_event_messages,
550        }
551        return self.make_request(action='TestMetricFilter',
552                                 body=json.dumps(params))
553
554    def make_request(self, action, body):
555        headers = {
556            'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action),
557            'Host': self.region.endpoint,
558            'Content-Type': 'application/x-amz-json-1.1',
559            'Content-Length': str(len(body)),
560        }
561        http_request = self.build_base_http_request(
562            method='POST', path='/', auth_path='/', params={},
563            headers=headers, data=body)
564        response = self._mexe(http_request, sender=None,
565                              override_num_retries=10)
566        response_body = response.read().decode('utf-8')
567        boto.log.debug(response_body)
568        if response.status == 200:
569            if response_body:
570                return json.loads(response_body)
571        else:
572            json_body = json.loads(response_body)
573            fault_name = json_body.get('__type', None)
574            exception_class = self._faults.get(fault_name, self.ResponseError)
575            raise exception_class(response.status, response.reason,
576                                  body=json_body)
577