mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 01:54:09 +00:00
switch from jupyter-telemetry to jupyter-events
- id must be a URL - change `record_event` to `emit`
This commit is contained in:
@@ -1,27 +1,27 @@
|
|||||||
# Event logging and telemetry
|
# 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
|
## 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:
|
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.
|
> 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 EventLog _which_ events should be recorded. No events are emitted by default; all recorded events must be listed here.
|
> 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:
|
Here's a basic example:
|
||||||
|
|
||||||
```
|
```
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
c.EventLog.handlers = [
|
c.EventLogger.handlers = [
|
||||||
logging.FileHandler('event.log'),
|
logging.FileHandler('event.log'),
|
||||||
]
|
]
|
||||||
|
|
||||||
c.EventLog.allowed_schemas = [
|
c.EventLogger.allowed_schemas = [
|
||||||
'hub.jupyter.org/server-action'
|
'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
|
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/
|
[json schemas]: https://json-schema.org/
|
||||||
[logging]: https://docs.python.org/3/library/logging.html
|
[logging]: https://docs.python.org/3/library/logging.html
|
||||||
[telemetry system]: https://github.com/jupyter/telemetry
|
[events system]: https://jupyter-events.readthedocs.io
|
||||||
|
@@ -21,6 +21,7 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
from pathlib import Path
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import unquote, urlparse, urlunparse
|
from urllib.parse import unquote, urlparse, urlunparse
|
||||||
@@ -29,7 +30,7 @@ import tornado.httpserver
|
|||||||
import tornado.options
|
import tornado.options
|
||||||
from dateutil.parser import parse as parse_date
|
from dateutil.parser import parse as parse_date
|
||||||
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
|
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.exc import OperationalError, SQLAlchemyError
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from tornado import gen, web
|
from tornado import gen, web
|
||||||
@@ -3251,13 +3252,10 @@ class JupyterHub(Application):
|
|||||||
|
|
||||||
def init_eventlog(self):
|
def init_eventlog(self):
|
||||||
"""Set up the event logging system."""
|
"""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 schema in (Path(here) / "event-schemas").glob("**/*.yaml"):
|
||||||
for file in files:
|
self.eventlog.register_event_schema(schema)
|
||||||
if not file.endswith('.yaml'):
|
|
||||||
continue
|
|
||||||
self.eventlog.register_schema_file(os.path.join(dirname, file))
|
|
||||||
|
|
||||||
def write_pid_file(self):
|
def write_pid_file(self):
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
"$id": hub.jupyter.org/server-action
|
"$id": https://schema.jupyter.org/jupyterhub/events/server-action
|
||||||
version: 1
|
version: 1
|
||||||
title: JupyterHub server events
|
title: JupyterHub server events
|
||||||
description: |
|
description: |
|
||||||
|
@@ -1128,10 +1128,13 @@ class BaseHandler(RequestHandler):
|
|||||||
SERVER_SPAWN_DURATION_SECONDS.labels(
|
SERVER_SPAWN_DURATION_SECONDS.labels(
|
||||||
status=ServerSpawnStatus.success
|
status=ServerSpawnStatus.success
|
||||||
).observe(time.perf_counter() - spawn_start_time)
|
).observe(time.perf_counter() - spawn_start_time)
|
||||||
self.eventlog.record_event(
|
self.eventlog.emit(
|
||||||
'hub.jupyter.org/server-action',
|
schema_id='https://schema.jupyter.org/jupyterhub/events/server-action',
|
||||||
1,
|
data={
|
||||||
{'action': 'start', 'username': user.name, 'servername': server_name},
|
'action': 'start',
|
||||||
|
'username': user.name,
|
||||||
|
'servername': server_name,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
proxy_add_start_time = time.perf_counter()
|
proxy_add_start_time = time.perf_counter()
|
||||||
spawner._proxy_pending = True
|
spawner._proxy_pending = True
|
||||||
@@ -1334,10 +1337,9 @@ class BaseHandler(RequestHandler):
|
|||||||
SERVER_STOP_DURATION_SECONDS.labels(
|
SERVER_STOP_DURATION_SECONDS.labels(
|
||||||
status=ServerStopStatus.success
|
status=ServerStopStatus.success
|
||||||
).observe(toc - tic)
|
).observe(toc - tic)
|
||||||
self.eventlog.record_event(
|
self.eventlog.emit(
|
||||||
'hub.jupyter.org/server-action',
|
schema_id='https://schema.jupyter.org/jupyterhub/events/server-action',
|
||||||
1,
|
data={
|
||||||
{
|
|
||||||
'action': 'stop',
|
'action': 'stop',
|
||||||
'username': user.name,
|
'username': user.name,
|
||||||
'servername': server_name,
|
'servername': server_name,
|
||||||
|
@@ -22,7 +22,7 @@ from traitlets.config import Config
|
|||||||
# { ( '<schema id>', <version> ) : { <event_data> } }
|
# { ( '<schema id>', <version> ) : { <event_data> } }
|
||||||
valid_events = [
|
valid_events = [
|
||||||
(
|
(
|
||||||
'hub.jupyter.org/server-action',
|
'https://schema.jupyter.org/jupyterhub/events/server-action',
|
||||||
1,
|
1,
|
||||||
dict(action='start', username='test-username', servername='test-servername'),
|
dict(action='start', username='test-username', servername='test-servername'),
|
||||||
)
|
)
|
||||||
@@ -32,7 +32,11 @@ valid_events = [
|
|||||||
# { ( '<schema id>', <version> ) : { <event_data> } }
|
# { ( '<schema id>', <version> ) : { <event_data> } }
|
||||||
invalid_events = [
|
invalid_events = [
|
||||||
# Missing required keys
|
# 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"""
|
"""Return eventlog and sink objects"""
|
||||||
sink = io.StringIO()
|
sink = io.StringIO()
|
||||||
handler = logging.StreamHandler(sink)
|
handler = logging.StreamHandler(sink)
|
||||||
# Update the EventLog config with handler
|
# Update the EventLogger config with handler
|
||||||
cfg = Config()
|
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
|
# recreate the eventlog object with our config
|
||||||
app.init_eventlog()
|
app.init_eventlog()
|
||||||
# return the sink from the fixture
|
# return the sink from the fixture
|
||||||
@@ -59,7 +63,7 @@ def test_valid_events(eventlog_sink, schema, version, event):
|
|||||||
eventlog, sink = eventlog_sink
|
eventlog, sink = eventlog_sink
|
||||||
eventlog.allowed_schemas = [schema]
|
eventlog.allowed_schemas = [schema]
|
||||||
# Record event
|
# Record event
|
||||||
eventlog.record_event(schema, version, event)
|
eventlog.emit(schema_id=schema, data=event)
|
||||||
# Inspect consumed event
|
# Inspect consumed event
|
||||||
output = sink.getvalue()
|
output = sink.getvalue()
|
||||||
assert output
|
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
|
# Make sure an error is thrown when bad events are recorded
|
||||||
with pytest.raises(jsonschema.ValidationError):
|
with pytest.raises(jsonschema.ValidationError):
|
||||||
recorded_event = eventlog.record_event(schema, version, event)
|
recorded_event = eventlog.emit(schema_id=schema, data=event)
|
||||||
|
@@ -4,7 +4,7 @@ certipy>=0.1.2
|
|||||||
idna
|
idna
|
||||||
importlib_metadata>=3.6; python_version < '3.10'
|
importlib_metadata>=3.6; python_version < '3.10'
|
||||||
jinja2>=2.11.0
|
jinja2>=2.11.0
|
||||||
jupyter_telemetry>=0.1.0
|
jupyter_events
|
||||||
oauthlib>=3.0
|
oauthlib>=3.0
|
||||||
packaging
|
packaging
|
||||||
pamela>=1.1.0; sys_platform != 'win32'
|
pamela>=1.1.0; sys_platform != 'win32'
|
||||||
|
Reference in New Issue
Block a user