Skip to content

Commit aa6f4c2

Browse files
committed
Added syslog events for app lifecycle.
1 parent d8bf404 commit aa6f4c2

4 files changed

Lines changed: 76 additions & 9 deletions

File tree

api/models.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import unicode_literals
88
import importlib
9+
import logging
910
import os
1011
import subprocess
1112

@@ -24,6 +25,9 @@
2425
from api import fields, tasks
2526
from provider import import_provider_module
2627

28+
29+
logger = logging.getLogger(__name__)
30+
2731
# import user-defined configuration management module
2832
CM = importlib.import_module(settings.CM_MODULE)
2933

@@ -443,6 +447,11 @@ def run(self, command, **kwargs):
443447
return tasks.run_node.delay(self, command).wait()
444448

445449

450+
def log_event(app, msg, level=logging.INFO):
451+
msg = "{}: {}".format(app.id, msg)
452+
logger.log(level, msg)
453+
454+
446455
@python_2_unicode_compatible
447456
class App(UuidAuditedModel):
448457
"""
@@ -544,6 +553,7 @@ def run(self, command):
544553
'deis/slugrunner'])
545554
env_args = ' '.join(["-e '{k}={v}'".format(**locals())
546555
for k, v in release.config.values.items()])
556+
log_event(self, "deis run '{}'".format(command))
547557
command = "sudo docker run {env_args} {docker_args} {command}".format(**locals())
548558
return node.run(command)
549559

@@ -557,6 +567,8 @@ def scale(self, app, structure, **kwargs):
557567
# increment new container nums off the most recent container
558568
all_containers = app.container_set.all().order_by('-created')
559569
container_num = 1 if not all_containers else all_containers[0].num + 1
570+
msg = 'Containers scaled ' + ' '.join(
571+
"{}={}".format(k, v) for k, v in requested_containers.items())
560572
# iterate and scale by container type (web, worker, etc)
561573
changed = False
562574
for container_type in requested_containers.keys():
@@ -591,6 +603,7 @@ def scale(self, app, structure, **kwargs):
591603
containers.append(c)
592604
container_num += 1
593605
diff -= 1
606+
log_event(app, msg)
594607
return changed
595608

596609
def balance(self, formation, **kwargs):
@@ -599,6 +612,7 @@ def balance(self, formation, **kwargs):
599612
# get the next container number (e.g. web.19)
600613
container_num = 1 if not all_containers else all_containers[0].num + 1
601614
changed = False
615+
app = None
602616
# iterate by unique container type
603617
for container_type in set([c.type for c in all_containers]):
604618
# map node container counts => { 2: [b3, b4], 3: [ b1, b2 ] }
@@ -636,6 +650,8 @@ def balance(self, formation, **kwargs):
636650
ct = len(n.container_set.filter(type=container_type))
637651
n_map.setdefault(ct, []).append(n)
638652
changed = True
653+
if app:
654+
log_event(app, 'Containers balanced')
639655
return changed
640656

641657

@@ -716,7 +732,7 @@ class Meta:
716732
unique_together = (('app', 'uuid'),)
717733

718734
def __str__(self):
719-
return "{0}-{1}".format(self.app.id, self.sha[8:])
735+
return "{0}-{1}".format(self.app.id, self.sha[:7])
720736

721737

722738
@python_2_unicode_compatible
@@ -746,7 +762,7 @@ class Meta:
746762
unique_together = (('app', 'uuid'),)
747763

748764
def __str__(self):
749-
return "{0}-{1}".format(self.app.id, self.sha)
765+
return "{0}-{1}".format(self.app.id, self.sha[:7])
750766

751767
@classmethod
752768
def push(cls, push):
@@ -877,9 +893,30 @@ def _purge_user_from_cm(**kwargs):
877893
kwargs['instance'].purge()
878894

879895

880-
# use django signals to synchronize database updates with
881-
# the configuration management backend
896+
def _log_build_created(**kwargs):
897+
if kwargs.get('created'):
898+
build = kwargs['instance']
899+
log_event(build.app, "Build {} created".format(build))
900+
901+
902+
def _log_release_created(**kwargs):
903+
if kwargs.get('created'):
904+
release = kwargs['instance']
905+
log_event(release.app, "Release {} created".format(release))
906+
907+
908+
def _log_config_updated(**kwargs):
909+
config = kwargs['instance']
910+
log_event(config.app, "Config {} updated".format(config))
911+
912+
913+
# Connect Django model signals
914+
# Sync database updates with the configuration management backend
882915
post_save.connect(_publish_to_cm, sender=App, dispatch_uid='api.models')
883916
post_save.connect(_publish_to_cm, sender=Formation, dispatch_uid='api.models')
884917
post_save.connect(_publish_user_to_cm, sender=User, dispatch_uid='api.models')
885918
post_delete.connect(_purge_user_from_cm, sender=User, dispatch_uid='api.models')
919+
# Log significant app-related events
920+
post_save.connect(_log_build_created, sender=Build, dispatch_uid='api.models')
921+
post_save.connect(_log_release_created, sender=Release, dispatch_uid='api.models')
922+
post_save.connect(_log_config_updated, sender=Config, dispatch_uid='api.models')

api/tests/__init__.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11

22
from __future__ import unicode_literals
3-
4-
# add patch support to built-in django test client
3+
import logging
54

65
from django.test.client import RequestFactory, Client
6+
from django.test.simple import DjangoTestSuiteRunner
77

88

9+
# add patch support to built-in django test client
10+
911
def construct_patch(self, path, data='',
1012
content_type='application/octet-stream', **extra):
1113
"""Construct a PATCH request."""
@@ -27,6 +29,18 @@ def send_patch(self, path, data='', content_type='application/octet-stream',
2729
RequestFactory.patch = construct_patch
2830
Client.patch = send_patch
2931

32+
33+
class SilentDjangoTestSuiteRunner(DjangoTestSuiteRunner):
34+
"""Prevents api log messages from cluttering the console during tests."""
35+
36+
def run_tests(self, test_labels, extra_tests=None, **kwargs):
37+
"""Run tests with all but critical log messages disabled."""
38+
# hide any log messages less than critical
39+
logging.disable(logging.CRITICAL)
40+
return super(SilentDjangoTestSuiteRunner, self).run_tests(
41+
test_labels, extra_tests, **kwargs)
42+
43+
3044
from .test_app import * # noqa
3145
from .test_auth import * # noqa
3246
from .test_build import * # noqa

api/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ def create(self, request, **kwargs):
380380
assign_perm(self.perm, user, app)
381381
app.publish()
382382
tasks.converge_controller.apply_async().wait()
383+
models.log_event(app, "User {} was granted access to {}".format(user, app))
383384
return Response(status=status.HTTP_201_CREATED)
384385

385386
def destroy(self, request, **kwargs):
@@ -391,6 +392,7 @@ def destroy(self, request, **kwargs):
391392
remove_perm(self.perm, user, app)
392393
app.publish()
393394
tasks.converge_controller.apply_async().wait()
395+
models.log_event(app, "User {} was revoked access to {}".format(user, app))
394396
return Response(status=status.HTTP_204_NO_CONTENT)
395397
else:
396398
return Response(status=status.HTTP_404_NOT_FOUND)

deis/settings.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@
193193
# URLs that end with slashes are ugly
194194
APPEND_SLASH = False
195195

196+
# Determine where to send syslog messages
197+
if os.path.exists('/dev/log'): # Linux rsyslog
198+
SYSLOG_ADDRESS = '/dev/log'
199+
elif os.path.exists('/var/log/syslog'): # Mac OS X syslog
200+
SYSLOG_ADDRESS = '/var/log/syslog'
201+
else: # default SysLogHandler address
202+
SYSLOG_ADDRESS = ('localhost', 514)
203+
196204
# A sample logging configuration. The only tangible logging
197205
# performed by this configuration is to send an email to
198206
# the site admins on every HTTP 500 error when DEBUG=False.
@@ -228,26 +236,32 @@
228236
'level': 'ERROR',
229237
'filters': ['require_debug_false'],
230238
'class': 'django.utils.log.AdminEmailHandler'
231-
}
239+
},
240+
'rsyslog': {
241+
'class': 'logging.handlers.SysLogHandler',
242+
'address': SYSLOG_ADDRESS,
243+
'facility': 'local0',
244+
},
232245
},
233246
'loggers': {
234247
'django': {
235248
'handlers': ['null'],
236-
'propagate': True,
237249
'level': 'INFO',
250+
'propagate': True,
238251
},
239252
'django.request': {
240253
'handlers': ['console', 'mail_admins'],
241254
'level': 'WARNING',
242255
'propagate': True,
243256
},
244257
'api': {
245-
'handlers': ['console', 'mail_admins'],
258+
'handlers': ['console', 'mail_admins', 'rsyslog'],
246259
'level': 'INFO',
247260
'propagate': True,
248261
},
249262
}
250263
}
264+
TEST_RUNNER = 'api.tests.SilentDjangoTestSuiteRunner'
251265

252266

253267
# celery task execution settings

0 commit comments

Comments
 (0)