Skip to content

Commit e630b05

Browse files
authored
Merge pull request #1 from rcthomas/traitlets-and-logging
Use traitlets logger with JupyterHub format
2 parents 3e51020 + e375eec commit e630b05

File tree

2 files changed

+43
-23
lines changed

2 files changed

+43
-23
lines changed

jupyterhub_idle_culler/__init__.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import asyncio
77
import json
8+
import logging
89
import os
910
import ssl
1011
import sys
@@ -18,7 +19,7 @@
1819
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
1920
from tornado.httputil import url_concat
2021
from tornado.ioloop import IOLoop, PeriodicCallback
21-
from tornado.log import app_log
22+
from tornado.log import LogFormatter
2223
from traitlets import Bool, Int, Unicode, default
2324
from traitlets.config import Application
2425

@@ -81,6 +82,7 @@ async def cull_idle(
8182
url,
8283
api_token,
8384
inactive_limit,
85+
logger,
8486
cull_users=False,
8587
remove_named_servers=False,
8688
max_age=0,
@@ -109,8 +111,8 @@ async def cull_idle(
109111
f"{internal_certs_location}/hub-ca/hub-ca.crt",
110112
)
111113

112-
app_log.debug("ssl_enabled is Enabled: %s", ssl_enabled)
113-
app_log.debug("internal_certs_location is %s", internal_certs_location)
114+
logger.debug("ssl_enabled is Enabled: %s", ssl_enabled)
115+
logger.debug("internal_certs_location is %s", internal_certs_location)
114116
defaults["ssl_options"] = ssl_context
115117

116118
AsyncHTTPClient.configure(None, defaults=defaults)
@@ -155,7 +157,7 @@ async def fetch_paginated(req):
155157
next_info = resp_model["_pagination"]["next"]
156158
if next_info:
157159
page_no += 1
158-
app_log.info(f"Fetching page {page_no} {next_info['url']}")
160+
logger.info(f"Fetching page {page_no} {next_info['url']}")
159161
# submit next request
160162
req.url = next_info["url"]
161163
resp_future = asyncio.ensure_future(fetch(req))
@@ -164,7 +166,7 @@ async def fetch_paginated(req):
164166
item_count += 1
165167
yield item
166168

167-
app_log.debug(f"Fetched {item_count} items from {url} in {page_no} pages")
169+
logger.debug(f"Fetched {item_count} items from {url} in {page_no} pages")
168170

169171
# Starting with jupyterhub 1.3.0 the users can be filtered in the server
170172
# using the `state` filter parameter. "ready" means all users who have any
@@ -189,7 +191,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
189191
if server_name:
190192
log_name = f"{user['name']}/{server_name}"
191193
if server.get("pending"):
192-
app_log.warning(
194+
logger.warning(
193195
f"Not culling server {log_name} with pending {server['pending']}"
194196
)
195197
return False
@@ -202,7 +204,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
202204
# but let's check just to be safe.
203205

204206
if not server.get("ready", bool(server["url"])):
205-
app_log.warning(
207+
logger.warning(
206208
f"Not culling not-ready not-pending server {log_name}: {server}"
207209
)
208210
return False
@@ -250,7 +252,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
250252
)
251253
)
252254
if should_cull:
253-
app_log.info(
255+
logger.info(
254256
f"Culling server {log_name} (inactive for {format_td(inactive)})"
255257
)
256258

@@ -259,7 +261,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
259261
# so that we can still be compatible with jupyterhub 0.8
260262
# which doesn't define the 'started' field
261263
if age is not None and age.total_seconds() >= max_age:
262-
app_log.info(
264+
logger.info(
263265
"Culling server %s (age: %s, inactive for %s)",
264266
log_name,
265267
format_td(age),
@@ -268,7 +270,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
268270
should_cull = True
269271

270272
if not should_cull:
271-
app_log.debug(
273+
logger.debug(
272274
"Not culling server %s (age: %s, inactive for %s)",
273275
log_name,
274276
format_td(age),
@@ -305,7 +307,7 @@ async def handle_server(user, server_name, server, max_age, inactive_limit):
305307
)
306308
resp = await fetch(req)
307309
if resp.code == 202:
308-
app_log.warning(f"Server {log_name} is slow to stop")
310+
logger.warning(f"Server {log_name} is slow to stop")
309311
# return False to prevent culling user with pending shutdowns
310312
return False
311313
return True
@@ -349,7 +351,7 @@ async def handle_user(user):
349351
# some servers are still running, cannot cull users
350352
still_alive = len(results) - sum(results)
351353
if still_alive:
352-
app_log.debug(
354+
logger.debug(
353355
"Not culling user %s with %i servers still alive",
354356
user["name"],
355357
still_alive,
@@ -380,21 +382,21 @@ async def handle_user(user):
380382
) and (cull_admin_users or not user_is_admin)
381383

382384
if should_cull:
383-
app_log.info(f"Culling user {user['name']} " f"(inactive for {inactive})")
385+
logger.info(f"Culling user {user['name']} " f"(inactive for {inactive})")
384386

385387
if max_age and not should_cull:
386388
# only check created if max_age is specified
387389
# so that we can still be compatible with jupyterhub 0.8
388390
# which doesn't define the 'started' field
389391
if age is not None and age.total_seconds() >= max_age:
390-
app_log.info(
392+
logger.info(
391393
f"Culling user {user['name']} "
392394
f"(age: {format_td(age)}, inactive for {format_td(inactive)})"
393395
)
394396
should_cull = True
395397

396398
if not should_cull:
397-
app_log.debug(
399+
logger.debug(
398400
f"Not culling user {user['name']} "
399401
f"(created: {format_td(age)}, last active: {format_td(inactive)})"
400402
)
@@ -424,7 +426,7 @@ async def handle_user(user):
424426
async for user in fetch_paginated(req):
425427
n_idle += 1
426428
futures.append((user["name"], handle_user(user)))
427-
app_log.debug(f"Got {n_idle} users with inactive servers")
429+
logger.debug(f"Got {n_idle} users with inactive servers")
428430

429431
if state_filter:
430432
params["state"] = "ready"
@@ -440,18 +442,18 @@ async def handle_user(user):
440442
futures.append((user["name"], handle_user(user)))
441443

442444
if state_filter:
443-
app_log.debug(f"Got {n_users} users with ready servers")
445+
logger.debug(f"Got {n_users} users with ready servers")
444446
else:
445-
app_log.debug(f"Got {n_users} users")
447+
logger.debug(f"Got {n_users} users")
446448

447449
for name, f in futures:
448450
try:
449451
result = await f
450452
except Exception:
451-
app_log.exception(f"Error processing {name}")
453+
logger.exception(f"Error processing {name}")
452454
else:
453455
if result:
454-
app_log.debug("Finished culling %s", name)
456+
logger.debug("Finished culling %s", name)
455457

456458

457459
class IdleCuller(Application):
@@ -577,6 +579,22 @@ def _default_cull_every(self):
577579
config=True,
578580
)
579581

582+
_log_formatter_cls = LogFormatter
583+
584+
@default('log_level')
585+
def _log_level_default(self):
586+
return logging.INFO
587+
588+
@default('log_datefmt')
589+
def _log_datefmt_default(self):
590+
"""Exclude date from default date format"""
591+
return "%Y-%m-%d %H:%M:%S"
592+
593+
@default('log_format')
594+
def _log_format_default(self):
595+
"""override default log format to include time"""
596+
return "%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s %(module)s:%(lineno)d]%(end_color)s %(message)s"
597+
580598
max_age = Int(
581599
0,
582600
help=dedent(
@@ -671,7 +689,7 @@ def start(self):
671689
try:
672690
AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
673691
except ImportError as e:
674-
app_log.warning(
692+
self.log.warning(
675693
f"Could not load pycurl: {e}\n"
676694
"pycurl is recommended if you have a large number of users."
677695
)
@@ -682,6 +700,7 @@ def start(self):
682700
url=self.url,
683701
api_token=api_token,
684702
inactive_limit=self.timeout,
703+
logger=self.log,
685704
cull_users=self.cull_users,
686705
remove_named_servers=self.remove_named_servers,
687706
max_age=self.max_age,

tests/test_idle_culler.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from subprocess import check_output
44
from unittest import mock
55

6+
from tornado.log import app_log
67
from jupyterhub_idle_culler import utcnow
78

89

@@ -32,15 +33,15 @@ async def test_cull_idle(cull_idle, start_users, admin_request):
3233
assert await count_active_users(admin_request) == 0
3334
await start_users(3)
3435
assert await count_active_users(admin_request) == 3
35-
await cull_idle(inactive_limit=300)
36+
await cull_idle(inactive_limit=300, logger=app_log)
3637
# no change
3738
assert await count_active_users(admin_request) == 3
3839

3940
# time travel into the future, everyone should be culled
4041
with mock.patch(
4142
"jupyterhub_idle_culler.utcnow", lambda: utcnow() + timedelta(seconds=600)
4243
):
43-
await cull_idle(inactive_limit=300)
44+
await cull_idle(inactive_limit=300, logger=app_log)
4445
assert await count_active_users(admin_request) == 0
4546

4647

0 commit comments

Comments
 (0)