Skip to content

Commit

Permalink
Merge pull request #1 from legalthings/logger-without-monolog
Browse files Browse the repository at this point in the history
Logging without monolog so we can handle exceptions from aws
  • Loading branch information
svenstm authored Aug 14, 2017
2 parents a6bde4b + fa48fd4 commit b33567a
Show file tree
Hide file tree
Showing 4 changed files with 382 additions and 125 deletions.
38 changes: 13 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,19 @@ $config = [
]
],
'group_name' => 'group_name',
'instance_name' => 'instance_name',
'channel_name' => 'channel_name'
'stream_name' => 'stream_name'
];

$logger = new CloudWatchLogger($config);

$logger->info('test_info', ['hello' => 'world']);
$logger->log(['hello' => 'world']);
/*
outputs within the group 'group_name' and instance 'instance_name' on CloudWatch:

[2017-08-08 13:23:44] channel_name.INFO: my_notice
outputs within the group 'group_name' and instance 'stream_name' on CloudWatch:

{
"hello": "world"
}
*/

$logger->notice('test_notice', ['foo' => 'bar']);
$logger->warn('test_warn', ['flag' => true]);
$logger->error('test_error', ['line' => 111]);
$logger->debug('test_debug', ['debugging' => false]);
```


Expand All @@ -83,23 +76,18 @@ $logger->debug('test_debug', ['debugging' => false]);
'group_name' => 'group_name',

// required
'instance_name' => 'instance_name',
'stream_name' => 'stream_name',

// optional
'channel_name' => 'channel_name',

// optional, defaults to 90
'retention_days' => 3,
'options' => [
// defaults to infinite
'retention_days' => 7,

// optional, defaults to 10000 and may not be greater than 10000
'batch_size' => 5000,

// optional
'tags' => [
'application' => 'php-test-app-1'
],
// retry logging when receiving error (invalid token sequence exception), defaults to 5
'error_max_retry' => 3,

// optional, defaults to '[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n'
'format' => '[%datetime%] %message% %context%\n'
// delay to wait for before retrying logging in microseconds, defaults to 100000 microseconds (0.1 seconds)
'error_retry_delay' => 0
]
]
```
260 changes: 260 additions & 0 deletions src/CloudWatchClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
<?php

namespace LegalThings;

use InvalidArgumentException;
use Aws\CloudWatchLogs\Exception\CloudWatchLogsException;
use Aws\CloudWatchLogs\CloudWatchLogsClient as Client;
use Aws\Result;

class CloudWatchClient
{
/**
* @var object
*/
public $config;

/**
* @var Client
*/
public $client;

/**
* @var string
*/
protected $sequence_token;


/**
* Class constructor
*
* @param object|array $config
* @param Client $client
*/
public function __construct($config, $client = null)
{
$this->config = (object)$config;

$this->client = $client ?: $this->create($this->config);
}

/**
* Create a client
*
* @param object $config
*
* @return Logger $logger
*/
protected function create($config)
{
$this->validateConfig($config);

return new Client((array)$config->aws);
}

/**
* Validate config
*
* @param object $config
*/
protected function validateConfig($config)
{
if (!isset($config->aws)) {
throw new InvalidArgumentException("CloudWatchClient config 'aws' not given");
}
}


/**
* Log data in CloudWatch
* Will automatically create missing groups and streams as needed
* This is the recommended function to log data as it keeps track of sequence tokens
*
* @param string|array|object $data
* @param string $group
* @param string $stream
* @param array|object $options ['retention_days' => 90]
*
* @return Result
*/
public function log($data, $group, $stream, $options = null)
{
if (!isset($this->sequence_token)) {
$created = $this->createGroupAndStream($group, $stream, $options);
if (isset($created['stream']) && isset($created['stream']['uploadSequenceToken'])) {
$this->sequence_token = $created['stream']['uploadSequenceToken'];
}
}

$message = $data;

if (is_array($data) || is_object($data)) {
// can only log strings to CloudWatch
$message = json_encode((array)$data);
}

$events = [['message' => $message, 'timestamp' => time() * 1000]];

try {
$result = $this->putLogEvents($events, $group, $stream, $this->sequence_token);
} catch (CloudWatchLogsException $e) {
// something went wrong, invalidate sequence token so we can fetch the actual token from aws on subsequent calls
$this->sequence_token = null;
throw $e;
}

$this->sequence_token = $result->get('nextSequenceToken');

return $result;
}

/**
* Create group and stream if they didn't exist yet
*
* @param string $group
* @param string $stream
* @param object $options ['retention_days' => 90]
*
* @return array ['stream' => array|null, 'group' => array|null]
*/
protected function createGroupAndStream($group, $stream, $options = null)
{
$existingGroup = $this->getLogGroup($group);

if (!isset($existingGroup)) {
$this->createLogGroup($group);

$retention = isset($options) && isset($options->retention_days) ? $options->retention_days : null;
$this->putRetentionPolicy($group, $retention);
}

$existingStream = $this->getLogStream($group, $stream);
if (!isset($existingStream)) {
$this->createStream($group, $stream);
}

return [
'group' => $existingGroup,
'stream' => $existingStream
];
}


/**
* Put log events
*
* @param array $events
* @param string $group
* @param string $stream
* @param string sequenceToken Omit if the stream does not exist yet
*
* @return Result
*/
public function putLogEvents($events, $group, $stream, $sequenceToken = null)
{
$data = [
'logEvents' => $events,
'logGroupName' => $group,
'logStreamName' => $stream
];

if (isset($sequenceToken)) {
$data['sequenceToken'] = $sequenceToken;
}

return $this->client->putLogEvents($data);
}


/**
* Create log group
*
* @param string $group
*
* return Result
*/
public function createLogGroup($group)
{
return $this->client->createLogGroup(['logGroupName' => $group]);
}

/**
* Get log group information
*
* @param string $group
*
* @return array|null
*/
public function getLogGroup($group)
{
$groups = $this->client->describeLogGroups([
'logGroupNamePrefix' => $group
])->get('logGroups');

foreach ($groups as $data) {
if ($data['logGroupName'] !== $group) {
continue;
}

return $data;
}
}


/**
* Put retention policy
*
* @param string $group
* @param int|null $retention Omit for indefinitely
*
* return Result
*/
public function putRetentionPolicy($group, $retention = null)
{
return $this->client->putRetentionPolicy([
'logGroupName' => $group,
'retentionInDays' => $retention
]);
}


/**
* Create stream
*
* @param string $group
* @param string $stream
*
* return Result
*/
public function createStream($group, $stream)
{
return $this->client->createLogStream([
'logGroupName' => $group,
'logStreamName' => $stream
]);
}

/**
* Get stream information
*
* @param string $group
* @param string $stream
*
* @return array|null
*/
public function getLogStream($group, $stream)
{
$streams = $this->client->describeLogStreams([
'logGroupName' => $group,
'logStreamNamePrefix' => $stream
])->get('logStreams');

foreach ($streams as $data) {
if ($data['logStreamName'] !== $stream) {
continue;
}

return $data;
}
}
}
Loading

0 comments on commit b33567a

Please sign in to comment.