diff --git a/docs/source/reference/event-logging.md b/docs/source/reference/event-logging.md index 19d08702..1dbf8b94 100644 --- a/docs/source/reference/event-logging.md +++ b/docs/source/reference/event-logging.md @@ -1,27 +1,27 @@ # Event logging and telemetry -JupyterHub can be configured to record structured events from a running server using Jupyter's [Telemetry System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this page. +JupyterHub can be configured to record structured events from a running server using Jupyter's [Events System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this page. ## How to emit events -Event logging is handled by its `Eventlog` object. This leverages Python's standing [logging] library to emit, filter, and collect event data. +Event logging is handled by its `EventLogger` object. This leverages Python's standing [logging] library to emit, filter, and collect event data. To begin recording events, you'll need to set two configurations: -> 1. `handlers`: tells the EventLog _where_ to route your events. This trait is a list of Python logging handlers that route events to the event log file. -> 2. `allows_schemas`: tells the EventLog _which_ events should be recorded. No events are emitted by default; all recorded events must be listed here. +> 1. `handlers`: tells the EventLogger _where_ to route your events. This trait is a list of Python logging handlers that route events to the event log file. +> 2. `allows_schemas`: tells the EventLogger _which_ events should be recorded. No events are emitted by default; all recorded events must be listed here. Here's a basic example: ``` import logging -c.EventLog.handlers = [ +c.EventLogger.handlers = [ logging.FileHandler('event.log'), ] -c.EventLog.allowed_schemas = [ - 'hub.jupyter.org/server-action' +c.EventLogger.allowed_schemas = [ + 'https://schema.jupyter.org/jupyterhub/events/server-action', ] ``` @@ -37,6 +37,15 @@ The output is a file, `"event.log"`, with events recorded as JSON data. server-actions ``` +:::{versionchanged} 5.0 +JupyterHub 5.0 changes from the deprecated jupyter-telemetry to jupyter-events. + +The main changes are: + +- `EventLog` configuration is now called `EventLogger` +- The `hub.jupyter.org/server-action` schema is now called `https://schema.jupyter.org/jupyterhub/events/server-action` + ::: + [json schemas]: https://json-schema.org/ [logging]: https://docs.python.org/3/library/logging.html -[telemetry system]: https://github.com/jupyter/telemetry +[events system]: https://jupyter-events.readthedocs.io diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 10a7d5e2..97f62c98 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -21,6 +21,7 @@ from datetime import datetime, timedelta, timezone from functools import partial from getpass import getuser from operator import itemgetter +from pathlib import Path from textwrap import dedent from typing import Optional from urllib.parse import unquote, urlparse, urlunparse @@ -29,7 +30,7 @@ import tornado.httpserver import tornado.options from dateutil.parser import parse as parse_date from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader -from jupyter_telemetry.eventlog import EventLog +from jupyter_events.logger import EventLogger from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.orm import joinedload from tornado import gen, web @@ -3251,13 +3252,10 @@ class JupyterHub(Application): def init_eventlog(self): """Set up the event logging system.""" - self.eventlog = EventLog(parent=self) + self.eventlog = EventLogger(parent=self) - for dirname, _, files in os.walk(os.path.join(here, 'event-schemas')): - for file in files: - if not file.endswith('.yaml'): - continue - self.eventlog.register_schema_file(os.path.join(dirname, file)) + for schema in (Path(here) / "event-schemas").glob("**/*.yaml"): + self.eventlog.register_event_schema(schema) def write_pid_file(self): pid = os.getpid() diff --git a/jupyterhub/event-schemas/server-actions/v1.yaml b/jupyterhub/event-schemas/server-actions/v1.yaml index 9023b105..06443cd1 100644 --- a/jupyterhub/event-schemas/server-actions/v1.yaml +++ b/jupyterhub/event-schemas/server-actions/v1.yaml @@ -1,4 +1,4 @@ -"$id": hub.jupyter.org/server-action +"$id": https://schema.jupyter.org/jupyterhub/events/server-action version: 1 title: JupyterHub server events description: | diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index 69331465..a5d33b9e 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -1128,10 +1128,13 @@ class BaseHandler(RequestHandler): SERVER_SPAWN_DURATION_SECONDS.labels( status=ServerSpawnStatus.success ).observe(time.perf_counter() - spawn_start_time) - self.eventlog.record_event( - 'hub.jupyter.org/server-action', - 1, - {'action': 'start', 'username': user.name, 'servername': server_name}, + self.eventlog.emit( + schema_id='https://schema.jupyter.org/jupyterhub/events/server-action', + data={ + 'action': 'start', + 'username': user.name, + 'servername': server_name, + }, ) proxy_add_start_time = time.perf_counter() spawner._proxy_pending = True @@ -1334,10 +1337,9 @@ class BaseHandler(RequestHandler): SERVER_STOP_DURATION_SECONDS.labels( status=ServerStopStatus.success ).observe(toc - tic) - self.eventlog.record_event( - 'hub.jupyter.org/server-action', - 1, - { + self.eventlog.emit( + schema_id='https://schema.jupyter.org/jupyterhub/events/server-action', + data={ 'action': 'stop', 'username': user.name, 'servername': server_name, diff --git a/jupyterhub/tests/test_eventlog.py b/jupyterhub/tests/test_eventlog.py index 0e83a840..416c3673 100644 --- a/jupyterhub/tests/test_eventlog.py +++ b/jupyterhub/tests/test_eventlog.py @@ -22,7 +22,7 @@ from traitlets.config import Config # { ( '', ) : { } } valid_events = [ ( - 'hub.jupyter.org/server-action', + 'https://schema.jupyter.org/jupyterhub/events/server-action', 1, dict(action='start', username='test-username', servername='test-servername'), ) @@ -32,7 +32,11 @@ valid_events = [ # { ( '', ) : { } } invalid_events = [ # Missing required keys - ('hub.jupyter.org/server-action', 1, dict(action='start')) + ( + 'https://schema.jupyter.org/jupyterhub/events/server-action', + 1, + dict(action='start'), + ) ] @@ -41,11 +45,11 @@ def eventlog_sink(app): """Return eventlog and sink objects""" sink = io.StringIO() handler = logging.StreamHandler(sink) - # Update the EventLog config with handler + # Update the EventLogger config with handler cfg = Config() - cfg.EventLog.handlers = [handler] + cfg.EventLogger.handlers = [handler] - with mock.patch.object(app.config, 'EventLog', cfg.EventLog): + with mock.patch.object(app.config, 'EventLogger', cfg.EventLogger): # recreate the eventlog object with our config app.init_eventlog() # return the sink from the fixture @@ -59,7 +63,7 @@ def test_valid_events(eventlog_sink, schema, version, event): eventlog, sink = eventlog_sink eventlog.allowed_schemas = [schema] # Record event - eventlog.record_event(schema, version, event) + eventlog.emit(schema_id=schema, data=event) # Inspect consumed event output = sink.getvalue() assert output @@ -75,4 +79,4 @@ def test_invalid_events(eventlog_sink, schema, version, event): # Make sure an error is thrown when bad events are recorded with pytest.raises(jsonschema.ValidationError): - recorded_event = eventlog.record_event(schema, version, event) + recorded_event = eventlog.emit(schema_id=schema, data=event) diff --git a/requirements.txt b/requirements.txt index 2a8a28ae..30b3028e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ certipy>=0.1.2 idna importlib_metadata>=3.6; python_version < '3.10' jinja2>=2.11.0 -jupyter_telemetry>=0.1.0 +jupyter_events oauthlib>=3.0 packaging pamela>=1.1.0; sys_platform != 'win32'