Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forwards logs to ec2 system log on windows #458

Merged
merged 5 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ source =
branch = True
source =
watchmaker
tests
parallel = true

[report]
Expand Down
5 changes: 4 additions & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mock==2.0.0
pytest==3.2.5;python_version=="2.6" or python_version=="3.3" # pyup: ==3.2.5
pytest==3.3.1;python_version=="2.7" or python_version>="3.4"
pytest-travis-fold==1.3.0
pytest-catchlog==1.2.2;python_version=="2.6"
pytest-cov==2.5.1
pytest-catchlog==1.2.2
pytest-mock==1.6.3;python_version=="2.6" # pyup: ==1.6.3
pytest-mock==1.6.3;python_version>="2.7"
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ classifiers =
[options]
install_requires =
click
defusedxml;platform_system=="Windows"
futures;python_version<"3"
six
PyYAML
Expand Down Expand Up @@ -63,6 +64,7 @@ exclude =
ignore = FI15,FI16,FI17,FI5,D107

[tool:pytest]
mock_use_standalone_module = true
norecursedirs =
.git
.tox
Expand Down
204 changes: 204 additions & 0 deletions src/watchmaker/logger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
unicode_literals, with_statement)

import collections
import errno
import io
import json
import logging
import logging.handlers
import os
import platform
import subprocess
import xml.etree.ElementTree

IS_WINDOWS = platform.system() == 'Windows'
MESSAGE_TYPES = ('Information', 'Warning', 'Error')

HAS_PYWIN32 = False
try:
Expand All @@ -15,6 +24,38 @@
except ImportError:
pass

EC2_CONFIG_DEPS = False
try:
import defusedxml.ElementTree
PROGRAM_FILES = os.environ.get('PROGRAMFILES', 'C:\\Program Files')
EC2_CONFIG = '\\'.join([
PROGRAM_FILES,
'Amazon\\Ec2ConfigService\\Settings\\Config.xml'
])
EC2_CONFIG_EVENT_LOG = '\\'.join([
PROGRAM_FILES,
'Amazon\\Ec2ConfigService\\Settings\\EventLogConfig.xml'
])
EC2_CONFIG_DEPS = IS_WINDOWS
except ImportError:
pass

EC2_LAUNCH_DEPS = False
try:
PROGRAM_DATA = os.environ.get('PROGRAMDATA', 'C:\\ProgramData')
EC2_LAUNCH_LOG_CONFIG = '\\'.join([
PROGRAM_DATA,
'Amazon\\EC2-Windows\\Launch\\Config\\EventLogConfig.json'
])
EC2_LAUNCH_SEND_EVENTS = '\\'.join([
PROGRAM_DATA,
'Amazon\\EC2-Windows\\Launch\\Scripts\\SendEventLogs.ps1'
])
assert IS_WINDOWS
EC2_LAUNCH_DEPS = True
except AssertionError:
pass

LOG_LEVELS = collections.defaultdict(
lambda: logging.DEBUG, # log level if key is not in this dict
{
Expand Down Expand Up @@ -95,3 +136,166 @@ def prepare_logging(log_dir, log_level):
ehdlr.setLevel(level)
ehdlr.setFormatter(logging.Formatter(logformat))
logging.getLogger().addHandler(ehdlr)

if HAS_PYWIN32 and EC2_CONFIG_DEPS:
try:
_enable_ec2_config_event_log()
_configure_ec2_config_event_log()
except (IOError, OSError) as exc:
if exc.errno == errno.ENOENT:
# PY2/PY3-compatible check for FileNotFoundError
# EC2_CONFIG or EC2_LOG_CONFIG do not exist
pass
else:
raise

if HAS_PYWIN32 and EC2_LAUNCH_DEPS:
try:
_configure_ec2_launch_event_log()
_schedule_ec2_launch_event_log()
except (IOError, OSError) as exc:
if exc.errno == errno.ENOENT:
# PY2/PY3-compatible check for FileNotFoundError
# EC2_LAUNCH_LOG_CONFIG or 'powershell.exe' do not exist
pass
else:
raise
except subprocess.CalledProcessError:
# EC2_LAUNCH_SEND_EVENTS does not exist
pass


def _enable_ec2_config_event_log():
"""Enable EC2Config forwarding of Event Logs to EC2 System Log."""
ec2_config = xml.etree.ElementTree.ElementTree(
xml.etree.ElementTree.Element('Ec2ConfigurationSettings'))

with io.open(EC2_CONFIG) as fh_:
ec2_config = defusedxml.ElementTree.parse(
fh_,
forbid_dtd=True
)

plugins = ec2_config.getroot().find('Plugins').findall('Plugin')
for plugin in plugins:
if plugin.find('Name').text == 'Ec2EventLog':
plugin.find('State').text = 'Enabled'
break

with io.open(EC2_CONFIG, mode='wb') as fh_:
ec2_config.write(fh_)


def _configure_ec2_config_event_log():
"""Configure EC2Config to forward Event Log entries for Watchmaker."""
ec2_log_config = xml.etree.ElementTree.ElementTree(
xml.etree.ElementTree.Element('EventLogConfig'))

with io.open(EC2_CONFIG_EVENT_LOG) as fh_:
ec2_log_config = defusedxml.ElementTree.parse(
fh_,
forbid_dtd=True
)

events_present = set()
events = ec2_log_config.getroot().findall('Event')
# Check if the event is already present
for event in events:
if (
event.find('ErrorType').text in MESSAGE_TYPES and
event.find('Category').text == 'Application' and
event.find('AppName').text == 'Watchmaker'
):
events_present.add(event.find('ErrorType').text)

# Add missing events
events_missing = events_present.symmetric_difference(MESSAGE_TYPES)
for msg_type in events_missing:
event = xml.etree.ElementTree.SubElement(
ec2_log_config.getroot(),
'Event',
{}
)
category = xml.etree.ElementTree.SubElement(
event,
'Category',
{}
)
error_type = xml.etree.ElementTree.SubElement(
event,
'ErrorType',
{}
)
num_entries = xml.etree.ElementTree.SubElement(
event,
'NumEntries',
{}
)
last_message_time = xml.etree.ElementTree.SubElement(
event,
'LastMessageTime',
{}
)
app_name = xml.etree.ElementTree.SubElement(
event,
'AppName',
{}
)
category.text = 'Application'
error_type.text = msg_type
num_entries.text = '999999'
last_message_time.text = '2008-09-10T00:00:00.000Z'
app_name.text = 'Watchmaker'

if events_missing:
with io.open(EC2_CONFIG_EVENT_LOG, mode='wb') as fh_:
ec2_log_config.write(fh_)


def _configure_ec2_launch_event_log():
"""Configure EC2Launch to forward Event Log entries for Watchmaker."""
event_config = {}
with io.open(EC2_LAUNCH_LOG_CONFIG) as fh_:
event_config = json.load(fh_)

events_present = set()
events = event_config.get('events', [])
# Check if the event is already present
for event in events:
if (
event.get('level') in MESSAGE_TYPES and
event.get('logName') == 'Application' and
event.get('source') == 'Watchmaker'
):
events_present.add(event.get('level'))

# Add missing events
events_missing = events_present.symmetric_difference(MESSAGE_TYPES)
for msg_type in events_missing:
event = {
'logName': 'Application',
'source': 'Watchmaker',
'level': msg_type,
'numEntries': '999'
}
events += [event]

if events_missing:
event_config['events'] = events
with io.open(EC2_LAUNCH_LOG_CONFIG, mode='w') as fh_:
json.dump(event_config, fh_, indent=4)


def _schedule_ec2_launch_event_log():
"""Schedule EC2Launch to forward Event Logs to EC2 System Log."""
return subprocess.check_call([
'powershell.exe',
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
EC2_LAUNCH_SEND_EVENTS,
'-Schedule',
'| out-null'
])
Loading