Skip to content

Commit 386ff9e

Browse files
author
Kent Rancourt
committed
chore(logger): refactor for easier extensibility
1 parent bc33750 commit 386ff9e

49 files changed

Lines changed: 1556 additions & 860 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

controller/api/models.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
import etcd
1111
import importlib
1212
import logging
13-
import os
1413
import re
15-
import subprocess
1614
import time
1715
from threading import Thread
1816

@@ -251,9 +249,14 @@ def restart(self, **kwargs):
251249

252250
def _clean_app_logs(self):
253251
"""Delete application logs stored by the logger component"""
254-
path = os.path.join(settings.DEIS_LOG_DIR, self.id + '.log')
255-
if os.path.exists(path):
256-
os.remove(path)
252+
try:
253+
url = 'http://{}:{}/{}/'.format(settings.LOGGER_HOST, settings.LOGGER_PORT, self.id)
254+
requests.delete(url)
255+
except Exception as e:
256+
# Ignore errors deleting application logs. An error here should not interfere with
257+
# the overall success of deleting an application, but we should log it.
258+
err = 'Error deleting existing application logs: {}'.format(e)
259+
log_event(self, err, logging.WARNING)
257260

258261
def scale(self, user, structure): # noqa
259262
"""Scale containers up or down to match requested structure."""
@@ -518,11 +521,24 @@ def _default_scale(self, user, release):
518521

519522
def logs(self, log_lines=str(settings.LOG_LINES)):
520523
"""Return aggregated log data for this application."""
521-
path = os.path.join(settings.DEIS_LOG_DIR, self.id + '.log')
522-
if not os.path.exists(path):
524+
try:
525+
url = "http://{}:{}/{}?log_lines={}".format(settings.LOGGER_HOST, settings.LOGGER_PORT,
526+
self.id, log_lines)
527+
r = requests.get(url)
528+
# Handle HTTP request errors
529+
except requests.exceptions.RequestException as e:
530+
logger.error("Error accessing deis-logger using url '{}': {}".format(url, e))
531+
raise e
532+
# Handle logs empty or not found
533+
if r.status_code == 204 or r.status_code == 404:
534+
logger.info("GET {} returned a {} status code".format(url, r.status_code))
523535
raise EnvironmentError('Could not locate logs')
524-
data = subprocess.check_output(['tail', '-n', log_lines, path])
525-
return data
536+
# Handle unanticipated status codes
537+
if r.status_code != 200:
538+
logger.error("Error accessing deis-logger: GET {} returned a {} status code"
539+
.format(url, r.status_code))
540+
raise EnvironmentError('Error accessing deis-logger')
541+
return r.content
526542

527543
def run(self, user, command):
528544
"""Run a one-off command in an ephemeral app container."""

controller/api/tests/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11

22
from __future__ import unicode_literals
33
import logging
4-
import os
54

6-
from django.conf import settings
75
from django.test.client import RequestFactory, Client
86
from django.test.simple import DjangoTestSuiteRunner
97
import requests
@@ -40,9 +38,6 @@ def run_tests(self, test_labels, extra_tests=None, **kwargs):
4038
"""Run tests with all but critical log messages disabled."""
4139
# hide any log messages less than critical
4240
logging.disable(logging.CRITICAL)
43-
# also, create the log directory
44-
if not os.path.exists(settings.DEIS_LOG_DIR):
45-
os.makedirs(settings.DEIS_LOG_DIR)
4641
return super(SilentDjangoTestSuiteRunner, self).run_tests(
4742
test_labels, extra_tests, **kwargs)
4843

controller/api/tests/test_app.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import json
1010
import logging
1111
import mock
12-
import os.path
12+
import requests
1313

1414
from django.conf import settings
1515
from django.contrib.auth.models import User
@@ -89,40 +89,49 @@ def test_app_override_id(self):
8989
self.assertContains(response, 'This field must be unique.', status_code=400)
9090
return response
9191

92-
def test_app_actions(self):
92+
@mock.patch('requests.get')
93+
def test_app_actions(self, mock_get):
9394
url = '/v1/apps'
9495
body = {'id': 'autotest'}
9596
response = self.client.post(url, json.dumps(body), content_type='application/json',
9697
HTTP_AUTHORIZATION='token {}'.format(self.token))
9798
self.assertEqual(response.status_code, 201)
9899
app_id = response.data['id'] # noqa
99-
# test logs
100-
if not os.path.exists(settings.DEIS_LOG_DIR):
101-
os.mkdir(settings.DEIS_LOG_DIR)
102-
path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log')
103-
# HACK: remove app lifecycle logs
104-
if os.path.exists(path):
105-
os.remove(path)
106-
url = '/v1/apps/{app_id}/logs'.format(**locals())
107-
response = self.client.get(url,
108-
HTTP_AUTHORIZATION='token {}'.format(self.token))
100+
101+
# test logs - 204 from deis-logger
102+
mock_response = mock.Mock()
103+
mock_response.status_code = 204
104+
mock_get.return_value = mock_response
105+
url = "/v1/apps/{app_id}/logs".format(**locals())
106+
response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
109107
self.assertEqual(response.status_code, 204)
110-
self.assertEqual(response.data, 'No logs for {}'.format(app_id))
111-
# write out some fake log data and try again
112-
with open(path, 'a') as f:
113-
f.write(FAKE_LOG_DATA)
114-
response = self.client.get(url,
115-
HTTP_AUTHORIZATION='token {}'.format(self.token))
108+
self.assertEqual(response.data, "No logs for {}".format(app_id))
109+
110+
# test logs - 404 from deis-logger
111+
mock_response.status_code = 404
112+
response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
113+
self.assertEqual(response.status_code, 204)
114+
self.assertEqual(response.data, "No logs for {}".format(app_id))
115+
116+
# test logs - unanticipated status code from deis-logger
117+
mock_response.status_code = 400
118+
response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
119+
self.assertEqual(response.status_code, 500)
120+
self.assertEqual(response.data, "Error accessing logs for {}".format(app_id))
121+
122+
# test logs - success accessing deis-logger
123+
mock_response.status_code = 200
124+
mock_response.content = FAKE_LOG_DATA
125+
response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
116126
self.assertEqual(response.status_code, 200)
117127
self.assertEqual(response.data, FAKE_LOG_DATA)
118128

119-
# test with log_lines
120-
response = self.client.get(url + "?log_lines=1",
121-
HTTP_AUTHORIZATION='token {}'.format(self.token))
122-
self.assertEqual(response.status_code, 200)
123-
self.assertEqual(response.data, FAKE_LOG_DATA.splitlines(True)[4])
129+
# test logs - HTTP request error while accessing deis-logger
130+
mock_get.side_effect = requests.exceptions.RequestException('Boom!')
131+
response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
132+
self.assertEqual(response.status_code, 500)
133+
self.assertEqual(response.data, "Error accessing logs for {}".format(app_id))
124134

125-
os.remove(path)
126135
# TODO: test run needs an initial build
127136

128137
@mock.patch('api.models.logger')

controller/api/views.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
from api import authentication, models, permissions, serializers, viewsets
1818

19+
import requests
20+
1921

2022
class UserRegistrationViewSet(GenericViewSet,
2123
mixins.CreateModelMixin):
@@ -208,10 +210,19 @@ def logs(self, request, **kwargs):
208210
return Response(app.logs(request.query_params.get('log_lines',
209211
str(settings.LOG_LINES))),
210212
status=status.HTTP_200_OK, content_type='text/plain')
211-
except EnvironmentError:
212-
return Response("No logs for {}".format(app.id),
213-
status=status.HTTP_204_NO_CONTENT,
213+
except requests.exceptions.RequestException:
214+
return Response("Error accessing logs for {}".format(app.id),
215+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
214216
content_type='text/plain')
217+
except EnvironmentError as e:
218+
if e.message == 'Error accessing deis-logger':
219+
return Response("Error accessing logs for {}".format(app.id),
220+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
221+
content_type='text/plain')
222+
else:
223+
return Response("No logs for {}".format(app.id),
224+
status=status.HTTP_204_NO_CONTENT,
225+
content_type='text/plain')
215226

216227
def run(self, request, **kwargs):
217228
app = self.get_object()

controller/conf.d/confd_settings.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ keys = [
1111
"/deis/domains",
1212
"/deis/platform",
1313
"/deis/scheduler",
14+
"/deis/logs",
1415
]
1516
reload_cmd = "/app/bin/reload"

controller/deis/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@
280280
ETCD_HOST, ETCD_PORT = os.environ.get('ETCD', '127.0.0.1:4001').split(',')[0].split(':')
281281

282282
# default deis settings
283-
DEIS_LOG_DIR = os.path.abspath(os.path.join(__file__, '..', '..', 'logs'))
284283
LOG_LINES = 1000
285284
TEMPDIR = tempfile.mkdtemp(prefix='deis')
286285
DEIS_DOMAIN = 'deisapp.local'
@@ -307,6 +306,10 @@
307306
REGISTRY_HOST = 'localhost'
308307
REGISTRY_PORT = 5000
309308

309+
# logger settings
310+
LOGGER_HOST = 'localhost'
311+
LOGGER_PORT = 8088
312+
310313
# check if we can register users with `deis register`
311314
REGISTRATION_ENABLED = True
312315

controller/templates/confd_settings.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
}
4444
}
4545

46-
# move log directory out of /app/deis
47-
DEIS_LOG_DIR = '/data/logs'
46+
LOGGER_HOST = '{{ getv "/deis/logs/host"}}'
4847

4948
{{ if exists "/deis/controller/registrationMode" }}
5049
REGISTRATION_MODE = '{{ getv "/deis/controller/registrationMode" }}'

controller/tests/controller_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func TestController(t *testing.T) {
1818
"/deis/registry/host",
1919
"/deis/registry/port",
2020
"/deis/platform/domain",
21+
"/deis/logs/host",
2122
}
2223
setdir := []string{
2324
"/deis/controller",

deisctl/cmd/cmd.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
const (
2222
// PlatformCommand is shorthand for "all the Deis components."
2323
PlatformCommand string = "platform"
24-
// StatelessPlatformCommand is shorthand for the components except store-*, database, and logger.
24+
// StatelessPlatformCommand is shorthand for the components except store-* and database.
2525
StatelessPlatformCommand string = "stateless-platform"
2626
swarm string = "swarm"
2727
mesos string = "mesos"
@@ -142,10 +142,8 @@ func startDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup,
142142

143143
// start logging subsystem first to collect logs from other components
144144
fmt.Fprintln(out, "Logging subsystem...")
145-
if !stateless {
146-
b.Start([]string{"logger"}, wg, out, err)
147-
wg.Wait()
148-
}
145+
b.Start([]string{"logger"}, wg, out, err)
146+
wg.Wait()
149147
b.Start([]string{"logspout"}, wg, out, err)
150148
wg.Wait()
151149

@@ -319,11 +317,7 @@ func installDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGrou
319317
}
320318

321319
fmt.Fprintln(out, "Logging subsystem...")
322-
if stateless {
323-
b.Create([]string{"logspout"}, wg, out, err)
324-
} else {
325-
b.Create([]string{"logger", "logspout"}, wg, out, err)
326-
}
320+
b.Create([]string{"logger", "logspout"}, wg, out, err)
327321
wg.Wait()
328322

329323
fmt.Fprintln(out, "Control plane...")

deisctl/cmd/cmd_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func TestStartStatelessPlatform(t *testing.T) {
262262
t.Parallel()
263263

264264
b := backendStub{}
265-
expected := []string{"logspout", "registry@*", "controller",
265+
expected := []string{"logger", "logspout", "registry@*", "controller",
266266
"builder", "publisher", "router@*", "registry@*", "controller",
267267
"builder", "publisher", "router@*"}
268268

@@ -575,7 +575,7 @@ func TestInstallStatelessPlatform(t *testing.T) {
575575
b := backendStub{}
576576
cb := mock.ConfigBackend{}
577577

578-
expected := []string{"logspout", "registry@1",
578+
expected := []string{"logger", "logspout", "registry@1",
579579
"controller", "builder", "publisher", "router@1", "router@2", "router@3"}
580580

581581
Install([]string{"stateless-platform"}, &b, &cb, fakeCheckKeys)

0 commit comments

Comments
 (0)