Repository: deis/deis

Issue:
feat(client): add `deis logs -n`
how can I get more logs using `deis logs --app=<appname>`

i can see in the API there is settings.`settings.LOG_LINES`... can it be passed as a parameter somehow so we can get as much log as we want?

mainly wanted to give developers access to deis without having to give them ssh access.



Discussion:
It's hardcoded at 1000 currently: https://github.com/deis/deis/blob/master/controller/deis/settings.py#L271
You could rebuild the deis/controller image, having changed the value in settings.py.

Longer-term, we should change it to accept a `--lines=` parameter from `deis logs`, or at least respect a LOG_LINES environment variable passed in to the container.

> Longer-term, we should change it to accept a --lines= parameter from deis logs

+1 to an API-level log-lines parameter, with a fallback to `settings.LOG_LINES`.  Tagging as "easy-fix".

ultimately this will be easily resolved once we address #985.

In a similar vein, how about adding a tail / follow option?

@krancour #465 tracks that discussion :)

Like said by @krancour , follow option to `deis logs` would be awesome. Today I ssh into host and `jounalctl -fu`, no better way ?

not at the moment. This is due to the limitations of WSGI, which cannot handle asynchronous requests out of the box.


Resolving patch:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -633,12 +633,20 @@ def apps_logs(self, args):
         Options:
           -a --app=<app>
             the uniquely identifiable name for the application.
+          -n --lines=<lines>
+            the number of lines to display
         """
         app = args.get('--app')
         if not app:
             app = self._session.app
-        response = self._dispatch('get',
-                                  "/v1/apps/{}/logs".format(app))
+
+        url = "/v1/apps/{}/logs".format(app)
+
+        log_lines = args.get('--lines')
+        if log_lines:
+            url += "?log_lines={}".format(log_lines)
+
+        response = self._dispatch('get', url)
         if response.status_code == requests.codes.ok:
             # strip the last newline character
             for line in response.json().split('\n')[:-1]:
diff --git a/controller/api/models.py b/controller/api/models.py
--- a/controller/api/models.py
+++ b/controller/api/models.py
@@ -337,12 +337,12 @@ def _default_scale(self, user, release):
 
         self.scale(user, structure)
 
-    def logs(self):
+    def logs(self, log_lines):
         """Return aggregated log data for this application."""
         path = os.path.join(settings.DEIS_LOG_DIR, self.id + '.log')
         if not os.path.exists(path):
             raise EnvironmentError('Could not locate logs')
-        data = subprocess.check_output(['tail', '-n', str(settings.LOG_LINES), path])
+        data = subprocess.check_output(['tail', '-n', log_lines, path])
         return data
 
     def run(self, user, command):
diff --git a/controller/api/views.py b/controller/api/views.py
--- a/controller/api/views.py
+++ b/controller/api/views.py
@@ -150,7 +150,9 @@ def scale(self, request, **kwargs):
     def logs(self, request, **kwargs):
         app = self.get_object()
         try:
-            return Response(app.logs(), status=status.HTTP_200_OK, content_type='text/plain')
+            return Response(app.logs(request.query_params.get('log_lines',
+                                     str(settings.LOG_LINES))),
+                            status=status.HTTP_200_OK, content_type='text/plain')
         except EnvironmentError:
             return Response("No logs for {}".format(app.id),
                             status=status.HTTP_204_NO_CONTENT,


Test patch:
diff --git a/controller/api/tests/test_app.py b/controller/api/tests/test_app.py
--- a/controller/api/tests/test_app.py
+++ b/controller/api/tests/test_app.py
@@ -121,6 +121,13 @@ def test_app_actions(self):
                                    HTTP_AUTHORIZATION='token {}'.format(self.token))
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.data, FAKE_LOG_DATA)
+
+        # test with log_lines
+        response = self.client.get(url + "?log_lines=1",
+                                   HTTP_AUTHORIZATION='token {}'.format(self.token))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.data, FAKE_LOG_DATA.splitlines(True)[4])
+
         os.remove(path)
         # TODO: test run needs an initial build
 
diff --git a/tests/apps_test.go b/tests/apps_test.go
--- a/tests/apps_test.go
+++ b/tests/apps_test.go
@@ -18,6 +18,7 @@ var (
 	appsRunCmd             = "apps:run echo Hello, 世界"
 	appsOpenCmd            = "apps:open --app={{.AppName}}"
 	appsLogsCmd            = "apps:logs --app={{.AppName}}"
+	appsLogsLimitCmd       = "apps:logs --app={{.AppName}} -n 1"
 	appsInfoCmd            = "apps:info --app={{.AppName}}"
 	appsDestroyCmd         = "apps:destroy --app={{.AppName}} --confirm={{.AppName}}"
 	appsDestroyCmdNoApp    = "apps:destroy --confirm={{.AppName}}"
@@ -97,6 +98,9 @@ func appsLogsTest(t *testing.T, params *utils.DeisTestConfig) {
 	utils.CurlApp(t, *params)
 	utils.Execute(t, cmd, params, false, "created initial release")
 	utils.Execute(t, cmd, params, false, "listening on 5000...")
+
+	utils.Execute(t, appsLogsLimitCmd, params, false, "")
+
 	if err := utils.Chdir(".."); err != nil {
 		t.Fatal(err)
 	}