Skip to content

Document event logging #1260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

HERE = osp.abspath(osp.dirname(__file__))
sys.path.insert(0, osp.join(HERE, "..", ""))
from jupyter_server import JUPYTER_SERVER_EVENTS
from jupyter_server._version import version_info

# -- General configuration ------------------------------------------------
Expand Down Expand Up @@ -50,7 +51,7 @@
except ImportError:
pass

myst_enable_extensions = ["html_image"]
myst_enable_extensions = ["html_image", "substitution", "colon_fence"]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
Expand Down Expand Up @@ -385,3 +386,11 @@ def setup(app):
with open(destination, "w") as f:
f.write(CONFIG_HEADER)
f.write(ServerApp().document_config_options())


# Create a markdown list of the core events emitted by Jupyter Server.
event_list_md = ""
for event in JUPYTER_SERVER_EVENTS:
event_list_md += f"* `{event}`\n"

myst_substitutions = {"jupyter_server_events": event_list_md}
64 changes: 64 additions & 0 deletions docs/source/developers/emitting-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Emitting events

Jupyter Server comes equipped with an `event_logger` for capturing structured events from a running server.

## Emit a custom event from an extension

The core `EventLogger` in Jupyter Server's `ServerApp` can be used to emit events from extensions.

First, define an event schema. We recommend that you make the `$id` field
a valid URI and incorporate the version as part of the URI, e.g.:

```yaml
$id: https://my.extension.org/myextension/myevent/v1.yaml
version: 1
title: My Event
description: |
Some information about my event
type: object
properties:
thing:
title: Thing
description: A random thing.
type: string
required:
- thing
```

We also recommend that you write this schema to a file in your extension repository in a logical location, e.g. `myextension/events/myevent/v1.yaml`.

Then, you can easily register your event on the core serverapp's event logger from your extension:

```python
import pathlib

def _load_jupyter_server_extension(serverapp: ServerApp):
...
# Register the event schema from its local filepath.
schema = pathlib.Path("myextension/events/myevent/v1.yaml")
serverapp.event_logger.register_event_schema(schema)
...
```

Once the event schema is registered, it is ready to be emitted. Note that the core `event_logger` is included in Jupyter Server's tornado settings and listed as a property of the `JupyterHandler` API.

:::{attention}
While you can (technically) create your own instance of an `EventLogger` from your extension, users/operators will likely only collect events from the instance created by Jupyter Server's `ServerApp`. If you would like to provide your own instance, be sure to clearly document how to configure your extension to collect your events.
:::

As an example, you can emit an event from a custom request handler by calling `self.event_logger.emit(...)`:

```python
from jupyter_server.base.handlers import JupyterHandler

class MyExtensionHandler(JupyterHandler):

def get(self):
...
self.event_logger.emit(
schema_id="https://my.extension.org/myextension/myevent/v1.yaml",
data={
"thing": "`GET` method was called."
}
)
```
1 change: 1 addition & 0 deletions docs/source/developers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ These pages target people writing Jupyter Web applications and server extensions
extensions
savehooks
contents
emitting-events
websocket-protocols
API Docs <../api/modules>
1 change: 1 addition & 0 deletions docs/source/operators/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ These pages are targeted at people using, configuring, and/or deploying multiple
public-server
security
configuring-logging
logging-events
48 changes: 48 additions & 0 deletions docs/source/operators/logging-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Event logging

Jupyter Server emits the following list of events (via [`jupyter_events`](https://jupyter-events.readthedocs.io/en/latest/))

{{ jupyter_server_events }}

## Collecting emitted events

Configure the `EventLogger` to begin emitting events from Jupyter Server

```python
from logging import FileHandler

event_handler = FileHandler("events.log")

c.EventLogger.handlers = [event_handler]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? I believe when I tried this, I found I had to use register_handler(), otherwise the _logger attribute doesn't get set.

Ah, is this because this is a config entry and the EventLogger instance does not yet exist, in which case its initializer will do the right thing? I had been trying to set the handlers list after EventLogger's instantiation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, is this because this is a config entry

Yeah, that's exactly right. This is a config syntax. If you do this after instantiation, the handlers trait does not update.

```

## Filtering events

By default, all events are emitted to all handlers. If you would like to filter a specific set of
events to a handler, use Python's `logging.Filter`.

See the following example:

```python
from logging import Filter
from logging import FileHandler


class ContentsFilter(Filter):

contents_events = [
"https://events.jupyter.org/jupyter_server/contents_service/v1"
]

def filter(self, record):
if record.msg['__schema__'] in self.contents_events:
return True
return False


contents_filter = ContentsFilter()
event_handler = FileHandler("events.log")
event_handler.addFilter(contents_filter)

c.EventLogger.handlers = [event_handler]
```
2 changes: 2 additions & 0 deletions jupyter_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
DEFAULT_JUPYTER_SERVER_PORT = 8888
JUPYTER_SERVER_EVENTS_URI = "https://events.jupyter.org/jupyter_server"
DEFAULT_EVENTS_SCHEMA_PATH = pathlib.Path(__file__).parent / "event_schemas"
JUPYTER_SERVER_EVENTS = ["https://events.jupyter.org/jupyter_server/contents_service/v1"]


del os

Expand Down
6 changes: 2 additions & 4 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
DEFAULT_JUPYTER_SERVER_PORT,
DEFAULT_STATIC_FILES_PATH,
DEFAULT_TEMPLATE_PATH_LIST,
JUPYTER_SERVER_EVENTS,
JUPYTER_SERVER_EVENTS_URI,
__version__,
)
Expand Down Expand Up @@ -1962,10 +1963,7 @@ def init_event_logger(self):
# Load the core Jupyter Server event schemas
# All event schemas must start with Jupyter Server's
# events URI, `JUPYTER_SERVER_EVENTS_URI`.
schema_ids = [
"https://events.jupyter.org/jupyter_server/contents_service/v1",
]
for schema_id in schema_ids:
for schema_id in JUPYTER_SERVER_EVENTS:
# Get the schema path from the schema ID.
rel_schema_path = schema_id.replace(JUPYTER_SERVER_EVENTS_URI + "/", "") + ".yaml"
schema_path = DEFAULT_EVENTS_SCHEMA_PATH / rel_schema_path
Expand Down