A no-dependency library defining a framework for sending analytics and observability events in a standardized format. Stratum is a plugin-based framework that allows you to create your own custom plugins to define, validate, and publish events to your observability stack. We also offer community-driven plugins for popular integrations.
Common problems that Stratum helps solve:
- Standardized data for clean queries, clear ownership, and strongest possible signals for alerting/reporting
- Being the first to know when your app is down, up, or sideways (let alone determining what any of those mean to you)
- Clear cataloging of what your product is capable of and who's using what
A typical Stratum implementation consists of:
- Stratum Catalog - single source of truth for the voice of your application
- Shared StratumService instance to load plugins and publish events from
- Functional code aka your application code! Any files containing logic you'd like to observe
import { StratumService } from '@capitalone/stratum-observability';
import { NewRelicPluginFactory } from '@capitalone/stratum-observability/plugins/new-relic';
/**
* Using enumerated keys afford the best chance of consistent references throughout your application.
* This replaces the need to hard code string values as reference.
*/
export const EventKey = {
LOADED: 'app-loaded'
};
/**
* Initialize Stratum with required arguments, again
* typically within a shared file for re-use.
*
* The StratumService is the engine for publishing.
*/
export const stratumService = new StratumService({
/**
* 1+ plugins defining your standardized event schemas and how to
* map these schemas when published to your data collectors
*
* The NewRelicPlugin is available from the @capitalone/stratum-observability library
*/
plugins: [NewRelicPluginFactory()],
/**
* The canonical name for referring to this capability
*/
productName: 'stratumExampleApp',
/**
* Typically, this references the version of the application you're
* publishing observability events from
*/
productVersion: 'REPLACE_WITH_PRODUCT_VERSION',
/**
* Your "catalog" or dictionary serves as the source-of-truth of events
* published by an application. This provides the structure for standardization.
*
* Custom plugins allow you to define your own catalog events, attributes,
* and custom validation rules.
*
* We've added an example event for the sake of getting started.
*/
catalog: {
items: {
[EventKey.LOADED]: {
eventType: 'base', // The base event type for Stratum events
description: 'This application has loaded for the first time',
id: 1 // Very important reference identifier -- the key to simple queries
}
}
}
});
/**
* Publish your event via Stratum. In this example, Stratum will send your event
* to New Relic's Browser Agent, if available on the webpage.
*
* (see: https://docs.newrelic.com/docs/browser/browser-monitoring/getting-started/introduction-browser-monitoring/)
*/
stratumService.publish(EventKey.LOADED);
Via npm:
npm install @capitalone/stratum-observability
Via yarn:
yarn add @capitalone/stratum-observability
Plugins are composed of 3 pieces of data:
- Name as a string identifier (required)
- Map of eventTypes to the correspond EventModel class (optional)
- One or more instantiated publishers to handle specific event type(s) (optional)
import { BasePlugin } from '@capitalone/stratum-observability';
/**
* For TypeScript support, BasePlugin accepts types for
*/
export class SimplePlugin extends BasePlugin<never, never> {
name: 'mySimplePlugin',
eventTypes: {
// Map catalog events with { eventType: 'simple' } to an instance of SimpleEventModel
simple: SimpleEventModel
},
publishers: [ new SimplePublisher() ]
}
Each of the concepts above are explained in further detail in the following sections.
If you have a configurable plugin that can be customized based on context, define your plugin as a PluginFactory instead of a static object. Plugin factories are a function that (optionally) take in arguments and return a new plugin instance on each execution.
Plugin factory options are good way to dynamically define plugins or pass initialization data to publishers at run-time. Use this in cases where your plugin needs to behave differently based on what application or environment is using it.
import type { PluginFactory } from '@capitalone/stratum-observability';
const SimplePluginFactory: PluginFactory<SimplePlugin> = () => new SimplePlugin();
const myPluginInstance = SimplePluginFactory();
import type { PluginFactory } from '@capitalone/stratum-observability';
interface MyOptions {
isLoggedIn: boolean
}
const SimplePluginFactoryWithOptions: PluginFactory<SimplePlugin> = (options) => {
return options?.isLoggedIn ? new SimplePluginWAuthentication() : new SimplePlugin();
}
/**
* In the above, options is optional so both of the
* following implementations are allowed by the compiler.
*/
const myPluginInstance = SimplePluginFactoryWithOptions(); // Valid
const myPluginInstanceWithOptions = SimplePluginFactoryWithOptions({ isLoggedIn: true }); // Also valid
Through plugins, you may define custom event type which are referenced by the eventType
property on your catalog items. Any events loaded into your StratumService must have a corresponding eventType to EventModel relation defined via a plugin.
import { StratumCatalog, CatalogEvent } from '@capitalone/stratum-observability';
const SimpleEventType = 'my-simple-event';
/**
* Creates a new event type with the base stratum event fields
* and an additional "simpleValue" number attribute
*/
interface SimpleEvent extends StratumEvent<SimpleEventType> {
simpleValue: number;
}
/**
* A catalog composed of SimpleEvents can then
* be defined.
*
* If you do not provide your custom event type interface as a generic
* to the StratumCatalog type, ype-hinting for required properties
* will not be available.
*
* Multiple custom event type interfaces can be added as a union:
* `StratumCatalog<SimpleEvent | ComplexEvent>`
*/
const catalog: StratumCatalog<SimpleEvent> = {
{
eventType: SimpleEventType,
// Implement the required base properties required by Stratum
description: 'My simple event that fires from the "mySimplePlugin" plugin',
id: 1,
// Additional fields defined by SimpleEvent
simpleValue: 12345
}
}
import { BaseEventModel } from '@capitalone/stratum-observability';
// Pass your SimpleEvent interface into the base EventModel
export class SimpleEventModel extends BaseEventModel<SimpleEvent> {
// Override to include any custom run-time validation rules
protected checkValidity(): boolean {
let isValid = super.checkValidity();
if(!this.item.simpleValue || typeof this.item.simpleValue === 'number') {
this.addValidationError('The "simpleValue" value provided is in an invalid format');
isValid = false;
}
return isValid;
}
}
import { BasePublisher, EventOptions } from '@capitalone/stratum-observability';
interface ExternalEventSchema {
id: number,
simpleValue: number
}
export class SimplePublisher extends BasePublisher<ExternalEventSchema> {
// Required
name = 'SimplePublisher';
/**
* Required
* Check if your publisher source is available (aka scripts installed, environment
* is set up, etc.)
*
* In this case, we make sure that console.log() is accessible.
*/
async isAvailable(_model: SimpleEventModel) {
return typeof console !== 'undefined';
}
/**
* Required
* Map the contents of your event model instance to your event schema
*/
getModelOutput(model: SimpleEventModel, options?: Partial<EventOptions>): ExternalEventSchema {
const data = model.getData(options);
return {
id: data.id,
simpleValue: data.simpleValue
};
}
/**
* Required
* Send your simple event content to the external publisher
*
* In this, case we publish the event to the console log
*/
async publish(event: ExternalEventSchema) {
console.log('publishing simple event!', { id: event.id, simpleValue: event.simpleValue });
}
}
We welcome and appreciate your contributions!
If you have suggestions or find a bug, please open an issue.
If you want to contribute, visit the contributing page.