Skip to content

Commit

Permalink
Add support for ec2 specific contextual fields
Browse files Browse the repository at this point in the history
  • Loading branch information
nir0s committed Apr 18, 2018
1 parent 00f7fa3 commit 0a60a92
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ Note that the following documentation relates to the code currently in the maste
* Handler agnosticism
* Differentiate user events and system logs
* Auto-enrich with useful data (hostname, pid, etc..)
* Enrich with AWS EC2 metadata (instance-id, instance-type, region and ipv4) if available
* Easily provide contexual data
* Dynamic severity levels
* Context binding to prevent repetition
* Dynamic severity levels
* Retroactive logging (WIP)
* Assist in user tracing (via auto-provided context ids)
* Zero logging exceptions. There should be zero logging exceptions causing the app to crash but rather Wryte should log errors whenever logging errors occur.
Expand Down Expand Up @@ -260,7 +261,7 @@ wryter.critical('Logging nested dicts', {'key': 'value', 'nested': { 'key1': 'va

```

#### Reserved Fields
#### Reserved Contextual Fields

The following are fields reserved to Wryte:

Expand All @@ -272,9 +273,18 @@ The following are fields reserved to Wryte:
* `level`
* `type`
* `name`
* `ec2_instance_id` (optional)
* `ec2_instance_type` (optional)
* `ec2_region` (optional)
* `ec2_ipv4` (optional)

Note that even if the fields are provided in a log message, they are bound to be overriden by Wryte.

#### Enriching with AWS EC2 instance context

The optional fields mentioned above are added to the context if the `WRYTE_EC2_ENABLED` env var is set.

Note that if the data is unattainable for any reason, Wryte will spit out an error message stating so when the logger is instantiated.

#### Binding contextual information to a logger

Expand Down
6 changes: 6 additions & 0 deletions tests/test_wryte.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import sys
import uuid
import shlex
Expand Down Expand Up @@ -193,3 +194,8 @@ def test_set_level_from_log(self):

def test_cli(self):
_invoke('main info My Message x=y')

def test_aws(self):
os.environ['WRYTE_EC2_ENABLED'] = 'true'
w = Wryte(name=str(uuid.uuid4()))
w.info('Message')
44 changes: 38 additions & 6 deletions wryte.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
import logging
from datetime import datetime

try:
# Python 2
import urllib2 as urllib
except ImportError:
# Python 3
import urllib.request as urllib

try:
import colorama
from colorama import Fore, Style
Expand Down Expand Up @@ -156,7 +163,8 @@ def __init__(self,
bare=False,
jsonify=False,
color=True,
simple=False):
simple=False,
enable_ec2=False):
"""Instantiate a logger instance.
Either a JSON or a Console handler will be added to the logger
Expand All @@ -177,8 +185,8 @@ def __init__(self,
self.color = color
self.simple = simple

self._log = self._get_base(self.logger_name, hostname)
self.logger = self._logger(self.logger_name)
self._log = self._get_base(self.logger_name, hostname, enable_ec2)

if not bare:
self._configure_handlers(level, jsonify)
Expand All @@ -190,20 +198,44 @@ def _logger(name):
logger = logging.getLogger(name)
return logger

@staticmethod
def _get_base(name, hostname):
def _get_base(self, name, hostname, enable_ec2=False):
"""Generate base fields for each log message.
This is evaluated once when the logger's instance is instantiated.
It is then later copied by each log message.
"""
return {
def fetch_ec2(attribute):
try:
return urllib.urlopen(
'http://169.254.169.254/latest/meta-data/{0}'.format(
attribute)).read().decode()
# Yuch. But shouldn't take a risk that any exception will raise
except Exception:
return None

# TODO: Document that these are only generated once.
base = {
'name': name,
'hostname': hostname or socket.gethostname(),
'pid': os.getpid(),
'type': 'log'
'type': 'log',
}

if self._env('EC2_ENABLED') or enable_ec2:
# To test that ec2 data is actually attainable
instance_id = fetch_ec2('instance-id')
if instance_id:
base['ec2_instance_id'] = instance_id
base['ec2_instance_type'] = fetch_ec2('instance-type')
base['ec2_region'] = fetch_ec2('placement/availability-zone')
base['ec2_ipv4'] = fetch_ec2('local-ipv4')
else:
self.logger.error(
'WRYTE EC2 env var set but EC2 metadata endpoint is '
'unavailable or the data could not be retrieved.')

return base

@staticmethod
def _get_timestamp():
# `now()` needs to compensate for timezones, and so it takes much
Expand Down

0 comments on commit 0a60a92

Please sign in to comment.