Create a custom log console.
This example shows how to create a log console to print log messages from a JupyterLab extension.
The default log console extension in JupyterLab obtains log outputs from the kernel context of the current active notebook. So you can either:
- Obtain the current active notebook and send message to his
Logger
instance (see the log message example). - Create your custom log console (covered in this example).
It is strongly recommended to read commands, command-palette, main-menu, widgets and react-widget examples before diving into this one.
To implement this log console you need to install the following packages:
@jupyterlab/logconsole
: Where you can find the different UI components and message format.@jupyterlab/rendermime
: Used to create renderers for various mime-types.@jupyterlab/nbformat
: Only necessary if you want to use the notebook output format as the type of message.
This example has two files. In the first one index.ts
, you will find all the logic of the extension. And the second one logLevelSwitcher.tsx
declares the React component used in the toolbar to switch between different log levels.
First of all, you will start by looking into the declaration of the extension:
// src/index.ts#L25-L35
const extension: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-examples/custom-log-console:plugin',
description: 'A minimal JupyterLab example to develop a custom log console.',
autoStart: true,
requires: [ICommandPalette, IRenderMimeRegistry, ILayoutRestorer],
activate: (
app: JupyterFrontEnd,
palette: ICommandPalette,
rendermime: IRenderMimeRegistry,
restorer: ILayoutRestorer
) => {
To create a new log console the IRenderMimeRegistry
token is required, which is necessary as a default rendermime in the LoggerRegistry
to render the outputs. Moreover you will need JupyterFrontEnd
to have access to some JupyterLab features, ICommandPalette
to register some commands. IMainMenu
allows to add some commands to the main menu. And ILayoutRestorer
can restore the extension layout after reloading the web page.
In the activate
function, the first step is to declare logConsolePanel
and logConsoleWidget
. Therefore, you can pass their reference to the commands. The commands will be able to interact with the widget even when deleting and creating a new one after closing the tab, as well as tracking them before launching the widget.
// src/index.ts#L38-L39
let logConsolePanel: LogConsolePanel | null = null;
let logConsoleWidget: MainAreaWidget<LogConsolePanel> | null = null;
The next step is to create a new widget when clicking on the button to open the custom log console. The function createLogConsoleWidget
has all the logic necessary to initialize a new LogConsoleWidget
and add it to the main area:
// src/index.ts#L72-L72
const createLogConsoleWidget = (): void => {
To initialize a new LogConsoleWidget
you have to create a LogConsolePanel
to hold the log messages. LogConsoleWidget
needs a LoggerRegistry
to add new logs to the panel. LoggerRegistry
requires two parameters: the default rendermime and the maximum length for the logs.
// src/index.ts#L73-L78
logConsolePanel = new LogConsolePanel(
new LoggerRegistry({
defaultRendermime: rendermime,
maxLength: 1000
})
);
The source
property identifies where the message comes from and is necessary to initialize the logger
object present on LogConsolePanel
. This object will let you send log messages to the log console and change the log level. In the logconsole-extension
available in core JupyterLab, source
identifies the kernel output of the active notebook. So when changing the active notebook, the log console change his logs. You can use any string for the value, but it is recommended to use the name of your extension.
// src/index.ts#L80-L80
logConsolePanel.source = 'custom-log-console';
Now you are ready to initialize a new MainAreaWidget
passing the logConsolePanel
as the content.
// src/index.ts#L82-L84
logConsoleWidget = new MainAreaWidget<LogConsolePanel>({
content: logConsolePanel
});
The last step of the function createLogConsoleWidget
is to establish how to proceed after a dispose request. The dispose
method is present in Lumino widgets to ensure that resources can be claimed by the garbage collector. In this case, this method is called when you close the tab for a MainAreaWidget
. So you need to delete the logConsolePanel
and logConsoleWidget
instances to clean all logs and be ready to initialize a new widget when you decide to open the tab again.
// src/index.ts#L108-L112
logConsoleWidget.disposed.connect(() => {
logConsoleWidget = null;
logConsolePanel = null;
commands.notifyCommandChanged();
});
To launch a new log console, you can add a new command. In this case, you can use the option isToggled
to make the button checkable to open and close the log console with the same button. In the execute
function there is an if
statement to check when closing and deleting the MainAreaWidget
, and when creating and opening a new one calling createLogConsoleWidget
.
// src/index.ts#L121-L132
commands.addCommand('jlab-examples/custom-log-console:open', {
label: 'Custom Log Console',
caption: 'Custom log console example.',
isToggled: () => logConsoleWidget !== null,
execute: () => {
if (logConsoleWidget) {
logConsoleWidget.dispose();
} else {
createLogConsoleWidget();
}
}
});
Finally, you can send log messages calling log
method present on the logger
property of logConsolePanel
. This method lets you send different types:
- HTML message with
IHtmlLog
:
// src/index.ts#L143-L149
const msg: IHtmlLog = {
type: 'html',
level: 'debug',
data: '<div>Hello world HTML!!</div>'
};
logConsolePanel?.logger?.log(msg);
- Raw text message with
ITextLog
// src/index.ts#L157-L163
const msg: ITextLog = {
type: 'text',
level: 'info',
data: 'Hello world text!!'
};
logConsolePanel?.logger?.log(msg);
- Cell output message with
IOutputLog
// src/index.ts#L171-L184
const data: nbformat.IOutput = {
output_type: 'display_data',
data: {
'text/plain': 'Hello world nbformat!!'
}
};
const msg: IOutputLog = {
type: 'output',
level: 'warning',
data
};
logConsolePanel?.logger?.log(msg);