jupyter_telemetry-0.1.0/0000755€ro.Ÿ€q{Μ0000000000013644117566020532 5ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/CHANGELOG.md0000644€ro.Ÿ€q{Μ0000000057513644062035022340 0ustar stslveANT\Domain Users00000000000000# CHANGELOG ## v0.1.0 - Ensure unique loggers [#45](https://github.com/jupyter/telemetry/pull/45) - Allow overriding timestamp of event [#43](https://github.com/jupyter/telemetry/pull/43) ## v0.0.2 - Fix passing list of logging handlers to EventLog via config files. Thanks to [@Zsailer](https://github.com/zsailer) via [#17](https://github.com/jupyter/telemetry/pull/17) jupyter_telemetry-0.1.0/LICENSE0000644€ro.Ÿ€q{Μ0000000276313644061531021535 0ustar stslveANT\Domain Users00000000000000BSD 3-Clause License Copyright (c) 2019, Project Jupyter All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jupyter_telemetry-0.1.0/MANIFEST.in0000644€ro.Ÿ€q{Μ0000000014313644061531022254 0ustar stslveANT\Domain Users00000000000000include LICENSE include *.md include *requirements.txt include package.json graft tests graft src jupyter_telemetry-0.1.0/PKG-INFO0000644€ro.Ÿ€q{Μ0000001072113644117566021630 0ustar stslveANT\Domain Users00000000000000Metadata-Version: 2.1 Name: jupyter_telemetry Version: 0.1.0 Summary: Jupyter telemetry library Home-page: http://jupyter.org Author: Jupyter Development Team Author-email: jupyter@googlegroups.com License: BSD Description: # Telemetry [![CircleCI](https://circleci.com/gh/jupyter/telemetry.svg?style=svg)](https://circleci.com/gh/jupyter/telemetry) [![codecov](https://codecov.io/gh/jupyter/telemetry/branch/master/graph/badge.svg)](https://codecov.io/gh/jupyter/telemetry) [![Documentation Status](https://readthedocs.org/projects/jupyter-telemetry/badge/?version=latest)](https://jupyter-telemetry.readthedocs.io/en/latest/?badge=latest) *Telemetry for Jupyter Applications and extensions.* > Telemetry (tΙ™ΛˆlemΙ™trΔ“): the process of recording and transmitting the readings of an instrument. [Oxford Dictionaries] Jupyter Telemetry enables Jupyter Applications (e.g. Jupyter Server, Jupyter Notebook, JupyterLab, JupyterHub, etc.) to record **events**β€”i.e. actions by application usersβ€”and transmit them to remote (or local) destinations as **structured** data. It works with Python's standard `logging` library to handle the transmission of events allowing users to send events to local files, over the web, etc. ## Install Jupyter's Telemetry library can be installed from PyPI. ``` pip install jupyter_telemetry ``` ## Basic Usage Telemetry provides a configurable traitlets object, `EventLog`, for structured event-logging in Python. It leverages Python's standard `logging` library for filtering, handling, and recording events. All events are validated (using [jsonschema](https://pypi.org/project/jsonschema/)) against registered [JSON schemas](https://json-schema.org/). Let's look at a basic example of an `EventLog`. ```python import logging from jupyter_telemetry import EventLog eventlog = EventLog( # Use logging handlers to route where events # should be record. handlers=[ logging.FileHandler('events.log') ], # List schemas of events that should be recorded. allowed_schemas=[ 'uri.to.event.schema' ] ) ``` EventLog has two configurable traits: * `handlers`: a list of Python's `logging` handlers. * `allowed_schemas`: a list of event schemas to record. Event schemas must be registered with the `EventLog` for events to be recorded. An event schema looks something like: ```json { "$id": "url.to.event.schema", "title": "My Event", "description": "All events must have a name property.", "type": "object", "properties": { "name": { "title": "Name", "description": "Name of event", "type": "string" } }, "required": ["name"], "version": 1 } ``` 2 fields are required: * `$id`: a valid URI to identify the schema (and possibly fetch it from a remote address). * `version`: the version of the schema. The other fields follow standard JSON schema structure. Schemas can be registered from a Python `dict` object, a file, or a URL. This example loads the above example schema from file. ```python # Register the schema. eventlog.register_schema_file('schema.json') ``` Events are recorded using the `record_event` method. This method validates the event data and routes the JSON string to the Python `logging` handlers listed in the `EventLog`. ```python # Record an example event. event = {'name': 'example event'} eventlog.record_event( schema_id='url.to.event.schema', version=1, event=event ) ``` Keywords: Jupyter,JupyterLab Platform: Linux Platform: Mac OS X Platform: Windows Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.5 Description-Content-Type: text/markdown jupyter_telemetry-0.1.0/README.md0000644€ro.Ÿ€q{Μ0000000621413644061531022002 0ustar stslveANT\Domain Users00000000000000# Telemetry [![CircleCI](https://circleci.com/gh/jupyter/telemetry.svg?style=svg)](https://circleci.com/gh/jupyter/telemetry) [![codecov](https://codecov.io/gh/jupyter/telemetry/branch/master/graph/badge.svg)](https://codecov.io/gh/jupyter/telemetry) [![Documentation Status](https://readthedocs.org/projects/jupyter-telemetry/badge/?version=latest)](https://jupyter-telemetry.readthedocs.io/en/latest/?badge=latest) *Telemetry for Jupyter Applications and extensions.* > Telemetry (tΙ™ΛˆlemΙ™trΔ“): the process of recording and transmitting the readings of an instrument. [Oxford Dictionaries] Jupyter Telemetry enables Jupyter Applications (e.g. Jupyter Server, Jupyter Notebook, JupyterLab, JupyterHub, etc.) to record **events**β€”i.e. actions by application usersβ€”and transmit them to remote (or local) destinations as **structured** data. It works with Python's standard `logging` library to handle the transmission of events allowing users to send events to local files, over the web, etc. ## Install Jupyter's Telemetry library can be installed from PyPI. ``` pip install jupyter_telemetry ``` ## Basic Usage Telemetry provides a configurable traitlets object, `EventLog`, for structured event-logging in Python. It leverages Python's standard `logging` library for filtering, handling, and recording events. All events are validated (using [jsonschema](https://pypi.org/project/jsonschema/)) against registered [JSON schemas](https://json-schema.org/). Let's look at a basic example of an `EventLog`. ```python import logging from jupyter_telemetry import EventLog eventlog = EventLog( # Use logging handlers to route where events # should be record. handlers=[ logging.FileHandler('events.log') ], # List schemas of events that should be recorded. allowed_schemas=[ 'uri.to.event.schema' ] ) ``` EventLog has two configurable traits: * `handlers`: a list of Python's `logging` handlers. * `allowed_schemas`: a list of event schemas to record. Event schemas must be registered with the `EventLog` for events to be recorded. An event schema looks something like: ```json { "$id": "url.to.event.schema", "title": "My Event", "description": "All events must have a name property.", "type": "object", "properties": { "name": { "title": "Name", "description": "Name of event", "type": "string" } }, "required": ["name"], "version": 1 } ``` 2 fields are required: * `$id`: a valid URI to identify the schema (and possibly fetch it from a remote address). * `version`: the version of the schema. The other fields follow standard JSON schema structure. Schemas can be registered from a Python `dict` object, a file, or a URL. This example loads the above example schema from file. ```python # Register the schema. eventlog.register_schema_file('schema.json') ``` Events are recorded using the `record_event` method. This method validates the event data and routes the JSON string to the Python `logging` handlers listed in the `EventLog`. ```python # Record an example event. event = {'name': 'example event'} eventlog.record_event( schema_id='url.to.event.schema', version=1, event=event ) ``` jupyter_telemetry-0.1.0/dev-requirements.txt0000644€ro.Ÿ€q{Μ0000000004113644061531024553 0ustar stslveANT\Domain Users00000000000000flake8 pytest pytest-cov codecov jupyter_telemetry-0.1.0/jupyter_telemetry/0000755€ro.Ÿ€q{Μ0000000000013644117566024326 5ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/jupyter_telemetry/__init__.py0000644€ro.Ÿ€q{Μ0000000022713644061531026426 0ustar stslveANT\Domain Users00000000000000# Increment this version when the metadata included with each event # changes. TELEMETRY_METADATA_VERSION = 1 from .eventlog import EventLog # noqa jupyter_telemetry-0.1.0/jupyter_telemetry/_version.py0000644€ro.Ÿ€q{Μ0000000015113644062227026512 0ustar stslveANT\Domain Users00000000000000 version_info = (0, 1, 0) __version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:]) jupyter_telemetry-0.1.0/jupyter_telemetry/eventlog.py0000644€ro.Ÿ€q{Μ0000001200713644061531026511 0ustar stslveANT\Domain Users00000000000000""" Emit structured, discrete events when various actions happen. """ import json import logging from datetime import datetime import jsonschema from pythonjsonlogger import jsonlogger from ruamel.yaml import YAML from traitlets import List from traitlets.config import Configurable, Config from .traits import Handlers from . import TELEMETRY_METADATA_VERSION yaml = YAML(typ='safe') def _skip_message(record, **kwargs): """ Remove 'message' from log record. It is always emitted with 'null', and we do not want it, since we are always emitting events only """ del record['message'] return json.dumps(record, **kwargs) class EventLog(Configurable): """ Send structured events to a logging sink """ handlers = Handlers( [], config=True, allow_none=True, help="""A list of logging.Handler instances to send events to. When set to None (the default), events are discarded. """ ) allowed_schemas = List( [], config=True, help=""" Fully qualified names of schemas to record. Each schema you want to record must be manually specified. The default, an empty list, means no events are recorded. """ ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Use a unique name for the logger so that multiple instances of EventLog do not write # to each other's handlers. log_name = __name__ + '.' + str(id(self)) self.log = logging.getLogger(log_name) # We don't want events to show up in the default logs self.log.propagate = False # We will use log.info to emit self.log.setLevel(logging.INFO) if self.handlers: formatter = jsonlogger.JsonFormatter(json_serializer=_skip_message) for handler in self.handlers: handler.setFormatter(formatter) self.log.addHandler(handler) self.schemas = {} def _load_config(self, cfg, section_names=None, traits=None): """Load EventLog traits from a Config object, patching the handlers trait in the Config object to avoid deepcopy errors. """ my_cfg = self._find_my_config(cfg) handlers = my_cfg.pop("handlers", []) # Turn handlers list into a pickeable function def get_handlers(): return handlers my_cfg["handlers"] = get_handlers # Build a new eventlog config object. eventlog_cfg = Config({"EventLog": my_cfg}) super(EventLog, self)._load_config(eventlog_cfg, section_names=None, traits=None) def register_schema_file(self, filename): """ Convenience function for registering a JSON schema from a filepath Supports both JSON & YAML files. """ # Just use YAML loader for everything, since all valid JSON is valid YAML with open(filename) as f: self.register_schema(yaml.load(f)) def register_schema(self, schema): """ Register a given JSON Schema with this event emitter 'version' and '$id' are required fields. """ # Check if our schema itself is valid # This throws an exception if it isn't valid jsonschema.validators.validator_for(schema).check_schema(schema) # Check that the properties we require are present required_schema_fields = {'$id', 'version'} for rsf in required_schema_fields: if rsf not in schema: raise ValueError( '{} is required in schema specification'.format(rsf) ) # Make sure reserved, auto-added fields are not in schema if any([p.startswith('__') for p in schema['properties']]): raise ValueError( 'Schema {} has properties beginning with __, which is not allowed' ) self.schemas[(schema['$id'], schema['version'])] = schema def record_event(self, schema_name, version, event, timestamp_override=None): """ Record given event with schema has occurred. """ if not (self.handlers and schema_name in self.allowed_schemas): # if handler isn't set up or schema is not explicitly whitelisted, # don't do anything return if (schema_name, version) not in self.schemas: raise ValueError('Schema {schema_name} version {version} not registered'.format( schema_name=schema_name, version=version )) schema = self.schemas[(schema_name, version)] jsonschema.validate(event, schema) if timestamp_override is None: timestamp = datetime.utcnow() else: timestamp = timestamp_override capsule = { '__timestamp__': timestamp.isoformat() + 'Z', '__schema__': schema_name, '__schema_version__': version, '__metadata_version__': TELEMETRY_METADATA_VERSION } capsule.update(event) self.log.info(capsule) jupyter_telemetry-0.1.0/jupyter_telemetry/traits.py0000644€ro.Ÿ€q{Μ0000000261613644061531026201 0ustar stslveANT\Domain Users00000000000000import logging from traitlets import TraitType, TraitError class Handlers(TraitType): """A trait that takes a list of logging handlers and converts it to a callable that returns that list (thus, making this trait pickleable). """ info_text = "a list of logging handlers" def validate_elements(self, obj, value): if len(value) > 0: # Check that all elements are logging handlers. for el in value: if isinstance(el, logging.Handler) is False: self.element_error(obj) def element_error(self, obj): raise TraitError( "Elements in the '{}' trait of an {} instance " "must be Python `logging` handler instances." .format(self.name, obj.__class__.__name__) ) def validate(self, obj, value): # If given a callable, call it and set the # value of this trait to the returned list. # Verify that the callable returns a list # of logging handler instances. if callable(value): out = value() self.validate_elements(obj, out) return out # If a list, check it's elements to verify # that each element is a logging handler instance. elif type(value) == list: self.validate_elements(obj, value) return value else: self.error(obj, value) jupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/0000755€ro.Ÿ€q{Μ0000000000013644117566026020 5ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/PKG-INFO0000644€ro.Ÿ€q{Μ0000001072113644117566027116 0ustar stslveANT\Domain Users00000000000000Metadata-Version: 2.1 Name: jupyter-telemetry Version: 0.1.0 Summary: Jupyter telemetry library Home-page: http://jupyter.org Author: Jupyter Development Team Author-email: jupyter@googlegroups.com License: BSD Description: # Telemetry [![CircleCI](https://circleci.com/gh/jupyter/telemetry.svg?style=svg)](https://circleci.com/gh/jupyter/telemetry) [![codecov](https://codecov.io/gh/jupyter/telemetry/branch/master/graph/badge.svg)](https://codecov.io/gh/jupyter/telemetry) [![Documentation Status](https://readthedocs.org/projects/jupyter-telemetry/badge/?version=latest)](https://jupyter-telemetry.readthedocs.io/en/latest/?badge=latest) *Telemetry for Jupyter Applications and extensions.* > Telemetry (tΙ™ΛˆlemΙ™trΔ“): the process of recording and transmitting the readings of an instrument. [Oxford Dictionaries] Jupyter Telemetry enables Jupyter Applications (e.g. Jupyter Server, Jupyter Notebook, JupyterLab, JupyterHub, etc.) to record **events**β€”i.e. actions by application usersβ€”and transmit them to remote (or local) destinations as **structured** data. It works with Python's standard `logging` library to handle the transmission of events allowing users to send events to local files, over the web, etc. ## Install Jupyter's Telemetry library can be installed from PyPI. ``` pip install jupyter_telemetry ``` ## Basic Usage Telemetry provides a configurable traitlets object, `EventLog`, for structured event-logging in Python. It leverages Python's standard `logging` library for filtering, handling, and recording events. All events are validated (using [jsonschema](https://pypi.org/project/jsonschema/)) against registered [JSON schemas](https://json-schema.org/). Let's look at a basic example of an `EventLog`. ```python import logging from jupyter_telemetry import EventLog eventlog = EventLog( # Use logging handlers to route where events # should be record. handlers=[ logging.FileHandler('events.log') ], # List schemas of events that should be recorded. allowed_schemas=[ 'uri.to.event.schema' ] ) ``` EventLog has two configurable traits: * `handlers`: a list of Python's `logging` handlers. * `allowed_schemas`: a list of event schemas to record. Event schemas must be registered with the `EventLog` for events to be recorded. An event schema looks something like: ```json { "$id": "url.to.event.schema", "title": "My Event", "description": "All events must have a name property.", "type": "object", "properties": { "name": { "title": "Name", "description": "Name of event", "type": "string" } }, "required": ["name"], "version": 1 } ``` 2 fields are required: * `$id`: a valid URI to identify the schema (and possibly fetch it from a remote address). * `version`: the version of the schema. The other fields follow standard JSON schema structure. Schemas can be registered from a Python `dict` object, a file, or a URL. This example loads the above example schema from file. ```python # Register the schema. eventlog.register_schema_file('schema.json') ``` Events are recorded using the `record_event` method. This method validates the event data and routes the JSON string to the Python `logging` handlers listed in the `EventLog`. ```python # Record an example event. event = {'name': 'example event'} eventlog.record_event( schema_id='url.to.event.schema', version=1, event=event ) ``` Keywords: Jupyter,JupyterLab Platform: Linux Platform: Mac OS X Platform: Windows Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.5 Description-Content-Type: text/markdown jupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/SOURCES.txt0000644€ro.Ÿ€q{Μ0000000101313644117566027677 0ustar stslveANT\Domain Users00000000000000CHANGELOG.md LICENSE MANIFEST.in README.md dev-requirements.txt package.json requirements.txt setup.cfg setup.py jupyter_telemetry/__init__.py jupyter_telemetry/_version.py jupyter_telemetry/eventlog.py jupyter_telemetry/traits.py jupyter_telemetry.egg-info/PKG-INFO jupyter_telemetry.egg-info/SOURCES.txt jupyter_telemetry.egg-info/dependency_links.txt jupyter_telemetry.egg-info/requires.txt jupyter_telemetry.egg-info/top_level.txt src/handler.ts tests/test_eventlog.py tests/test_register_schema.py tests/test_traits.pyjupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/dependency_links.txt0000644€ro.Ÿ€q{Μ0000000000113644117566032066 0ustar stslveANT\Domain Users00000000000000 jupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/requires.txt0000644€ro.Ÿ€q{Μ0000000006413644117566030420 0ustar stslveANT\Domain Users00000000000000jsonschema python-json-logger traitlets ruamel.yaml jupyter_telemetry-0.1.0/jupyter_telemetry.egg-info/top_level.txt0000644€ro.Ÿ€q{Μ0000000002213644117566030544 0ustar stslveANT\Domain Users00000000000000jupyter_telemetry jupyter_telemetry-0.1.0/package.json0000644€ro.Ÿ€q{Μ0000000156313644061531023013 0ustar stslveANT\Domain Users00000000000000{ "name": "jupyter-telemetry", "version": "0.1.0", "description": "A library for telemetry in Jupyter frontend applications.", "keywords": [ "telemetry", "jupyter", "jupyterlab" ], "homepage": "git@github.com:jupyter/telemetry.git", "bugs": { "url": "git@github.com:jupyter/telemetry.git/issues" }, "license": "BSD-3-Clause", "author": "Project Jupyter", "files": [ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" ], "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", "url": "git@github.com:jupyter/telemetry.git" }, "scripts": { "build": "tsc", "clean": "rimraf lib", "watch": "tsc -w" }, "dependencies": {}, "devDependencies": { "rimraf": "^2.6.1", "typescript": "^3.4.5" } } jupyter_telemetry-0.1.0/requirements.txt0000644€ro.Ÿ€q{Μ0000000000013644061531023772 0ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/setup.cfg0000644€ro.Ÿ€q{Μ0000000010613644117566022350 0ustar stslveANT\Domain Users00000000000000[flake8] max-line-length = 120 [egg_info] tag_build = tag_date = 0 jupyter_telemetry-0.1.0/setup.py0000644€ro.Ÿ€q{Μ0000000260113644063500022227 0ustar stslveANT\Domain Users00000000000000""" Setup module for the jupyterlab-telemetry """ import io import os.path as osp from setuptools import setup, find_packages HERE = osp.dirname(osp.realpath(__file__)) name = "jupyter_telemetry" path = osp.realpath("{0}/{1}/_version.py".format(HERE, name)) version_ns = {} with io.open(path, encoding="utf8") as f: exec(f.read(), {}, version_ns) with open(osp.join(HERE, 'README.md')) as fid: long_description = fid.read() setup( name=name, version=version_ns["__version__"], description='Jupyter telemetry library', long_description=long_description, long_description_content_type='text/markdown', packages=find_packages(), author = 'Jupyter Development Team', author_email = 'jupyter@googlegroups.com', url = 'http://jupyter.org', license = 'BSD', platforms = "Linux, Mac OS X, Windows", keywords = ['Jupyter', 'JupyterLab'], python_requires = '>=3.5', classifiers = [ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', ], install_requires=[ 'jsonschema', 'python-json-logger', 'traitlets', 'ruamel.yaml' ], ) jupyter_telemetry-0.1.0/src/0000755€ro.Ÿ€q{Μ0000000000013644117566021321 5ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/src/handler.ts0000644€ro.Ÿ€q{Μ0000000462713644061531023305 0ustar stslveANT\Domain Users00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { URLExt } from '@jupyterlab/coreutils'; import { ServerConnection } from '@jupyterlab/services'; import { ReadonlyJSONObject } from '@phosphor/coreutils'; /** * The handler for telemetry data. */ export class TelemetryHandler { /** * Create a new telemetry handler. */ constructor(options: TelemetryHandler.IOptions = { }) { this.serverSettings = options.serverSettings || ServerConnection.makeSettings(); } /** * The server settings used to make API requests. */ readonly serverSettings: ServerConnection.ISettings; /** * Emit an event to the server * * This may implement batching and other performance optimizations * in the future. * * @param event - The event being emitted * * @returns A promise that resolves when saving is complete or rejects with * a `ServerConnection.IError`. */ emit(event: Telemetry.ICommandInvocation): Promise { const { serverSettings } = this; const url = URLExt.join(serverSettings.baseUrl, 'eventlog'); const full_event = { 'schema': 'lab.jupyter.org/command-invocations', 'version': 1, 'event': event } const init = { body: JSON.stringify(full_event), method: 'PUT' }; const promise = ServerConnection.makeRequest(url, init, serverSettings); return promise.then(response => { if (response.status !== 204) { throw new ServerConnection.ResponseError(response); } return undefined; }); } } /** * A namespace for `TelemetryHandler` statics. */ export namespace TelemetryHandler { /** * The instantiation options for a telemetry handler. */ export interface IOptions { /** * The server settings used to make API requests. */ serverSettings?: ServerConnection.ISettings; } } /** * A namespace for telemetry API interfaces. */ export namespace Telemetry { /** * An interface describing an executed command. * * FIXME: Automatically generate these from the schema? */ export interface ICommandInvocation { /** * UUID representing current session */ readonly session_id: string; /** * The id of the command. */ readonly command_id: string; /** * The args of the command. */ readonly command_args: ReadonlyJSONObject; } } jupyter_telemetry-0.1.0/tests/0000755€ro.Ÿ€q{Μ0000000000013644117566021674 5ustar stslveANT\Domain Users00000000000000jupyter_telemetry-0.1.0/tests/test_eventlog.py0000644€ro.Ÿ€q{Μ0000000202113644061531025111 0ustar stslveANT\Domain Users00000000000000import pytest import logging from traitlets.config.loader import PyFileConfigLoader from traitlets import TraitError from jupyter_telemetry.eventlog import EventLog GOOD_CONFIG = """ import logging c.EventLog.handlers = [ logging.StreamHandler() ] """ BAD_CONFIG = """ import logging c.EventLog.handlers = [ 0 ] """ def get_config_from_file(path, content): # Write config file filename = 'config.py' config_file = path / filename config_file.write_text(content) # Load written file. loader = PyFileConfigLoader(filename, path=str(path)) cfg = loader.load_config() return cfg def test_good_config_file(tmp_path): cfg = get_config_from_file(tmp_path, GOOD_CONFIG) # Pass config to EventLog e = EventLog(config=cfg) # Assert the assert len(e.handlers) > 0 assert isinstance(e.handlers[0], logging.Handler) def test_bad_config_file(tmp_path): cfg = get_config_from_file(tmp_path, BAD_CONFIG) with pytest.raises(TraitError): e = EventLog(config=cfg) jupyter_telemetry-0.1.0/tests/test_register_schema.py0000644€ro.Ÿ€q{Μ0000001474113644061531026446 0ustar stslveANT\Domain Users00000000000000import io import json import logging import tempfile import jsonschema from datetime import datetime, timedelta import pytest from ruamel.yaml import YAML from jupyter_telemetry.eventlog import EventLog def test_register_invalid_schema(): """ Invalid JSON Schemas should fail registration """ el = EventLog() with pytest.raises(jsonschema.SchemaError): el.register_schema({ # Totally invalid 'properties': True }) def test_missing_required_properties(): """ id and $version are required properties in our schemas. They aren't required by JSON Schema itself """ el = EventLog() with pytest.raises(ValueError): el.register_schema({ 'properties': {} }) with pytest.raises(ValueError): el.register_schema({ '$id': 'something', '$version': 1, # This should been 'version' }) def test_reserved_properties(): """ User schemas can't have properties starting with __ These are reserved """ el = EventLog() with pytest.raises(ValueError): el.register_schema({ '$id': 'test/test', 'version': 1, 'properties': { '__fail__': { 'type': 'string' }, }, }) def test_timestamp_override(): """ Simple test for overriding timestamp """ schema = { '$id': 'test/test', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } output = io.StringIO() handler = logging.StreamHandler(output) el = EventLog(handlers=[handler]) el.register_schema(schema) el.allowed_schemas = ['test/test'] timestamp_override = datetime.utcnow() - timedelta(days=1) el.record_event('test/test', 1, { 'something': 'blah', }, timestamp_override=timestamp_override) handler.flush() event_capsule = json.loads(output.getvalue()) assert event_capsule['__timestamp__'] == timestamp_override.isoformat() + 'Z' def test_record_event(): """ Simple test for emitting valid events """ schema = { '$id': 'test/test', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } output = io.StringIO() handler = logging.StreamHandler(output) el = EventLog(handlers=[handler]) el.register_schema(schema) el.allowed_schemas = ['test/test'] el.record_event('test/test', 1, { 'something': 'blah', }) handler.flush() event_capsule = json.loads(output.getvalue()) assert '__timestamp__' in event_capsule # Remove timestamp from capsule when checking equality, since it is gonna vary del event_capsule['__timestamp__'] assert event_capsule == { '__schema__': 'test/test', '__schema_version__': 1, '__metadata_version__': 1, 'something': 'blah' } def test_register_schema_file(): """ Register schema from a file """ schema = { '$id': 'test/test', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } el = EventLog() yaml = YAML(typ='safe') with tempfile.NamedTemporaryFile(mode='w') as f: yaml.dump(schema, f) f.flush() f.seek(0) el.register_schema_file(f.name) assert schema in el.schemas.values() def test_allowed_schemas(): """ Events should be emitted only if their schemas are allowed """ schema = { '$id': 'test/test', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } output = io.StringIO() handler = logging.StreamHandler(output) el = EventLog(handlers=[handler]) # Just register schema, but do not mark it as allowed el.register_schema(schema) el.record_event('test/test', 1, { 'something': 'blah', }) handler.flush() assert output.getvalue() == '' def test_record_event_badschema(): """ Fail fast when an event doesn't conform to its schema """ schema = { '$id': 'test/test', 'version': 1, 'properties': { 'something': { 'type': 'string' }, 'status': { 'enum': ['success', 'failure'] } } } el = EventLog(handlers=[logging.NullHandler()]) el.register_schema(schema) el.allowed_schemas = ['test/test'] with pytest.raises(jsonschema.ValidationError): el.record_event('test/test', 1, { 'something': 'blah', 'status': 'not-in-enum' }) def test_unique_logger_instances(): schema0 = { '$id': 'test/test0', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } schema1= { '$id': 'test/test1', 'version': 1, 'properties': { 'something': { 'type': 'string' }, }, } output0 = io.StringIO() output1 = io.StringIO() handler0 = logging.StreamHandler(output0) handler1 = logging.StreamHandler(output1) el0 = EventLog(handlers=[handler0]) el0.register_schema(schema0) el0.allowed_schemas = ['test/test0'] el1 = EventLog(handlers=[handler1]) el1.register_schema(schema1) el1.allowed_schemas = ['test/test1'] el0.record_event('test/test0', 1, { 'something': 'blah', }) el1.record_event('test/test1', 1, { 'something': 'blah', }) handler0.flush() handler1.flush() event_capsule0 = json.loads(output0.getvalue()) assert '__timestamp__' in event_capsule0 # Remove timestamp from capsule when checking equality, since it is gonna vary del event_capsule0['__timestamp__'] assert event_capsule0 == { '__schema__': 'test/test0', '__schema_version__': 1, '__metadata_version__': 1, 'something': 'blah' } event_capsule1 = json.loads(output1.getvalue()) assert '__timestamp__' in event_capsule1 # Remove timestamp from capsule when checking equality, since it is gonna vary del event_capsule1['__timestamp__'] assert event_capsule1 == { '__schema__': 'test/test1', '__schema_version__': 1, '__metadata_version__': 1, 'something': 'blah' } jupyter_telemetry-0.1.0/tests/test_traits.py0000644€ro.Ÿ€q{Μ0000000141613644061531024603 0ustar stslveANT\Domain Users00000000000000import logging import pytest from traitlets import HasTraits, TraitError from jupyter_telemetry.traits import Handlers class HasHandlers(HasTraits): handlers = Handlers( None, allow_none=True ) def test_good_handlers_value(): handlers = [ logging.NullHandler(), logging.NullHandler() ] obj = HasHandlers( handlers=handlers ) assert obj.handlers == handlers def test_bad_handlers_values(): handlers = [0, 1] with pytest.raises(TraitError): HasHandlers( handlers=handlers ) def test_mixed_handlers_values(): handlers = [ logging.NullHandler(), 1 ] with pytest.raises(TraitError): HasHandlers( handlers=handlers )