diff --git a/.gitignore b/.gitignore index b3222bb..bca800c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build dist .DS_Store coverage +.idea/ diff --git a/package-lock.json b/package-lock.json index 5e3fcf8..a8cda7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@logdna/browser", - "version": "1.0.2", + "version": "1.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7a1b0c4..0d61f9e 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "version": "1.1.2", "module": "dist/index.esm.js", "license": "MIT", - "types": "./dist/types/index.d.ts", + "types": "./dist/types/types.d.ts", "scripts": { "clean": "rm -rf dist", "build": "npm run clean && rollup -c rollup.config.ts", "start": "rollup -c rollup.config.ts -w", + "lint": "prettier --write --config .prettierrc.json ./src/*.ts", "test": "jest", "test:watch": "jest --watch", "test:coverage": "npm run test -- --coverage", diff --git a/rollup.config.ts b/rollup.config.ts index 84ef15d..73f17aa 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -1,10 +1,9 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import typescript from 'rollup-plugin-typescript2'; import json from '@rollup/plugin-json'; -import { terser } from 'rollup-plugin-terser'; +import resolve from '@rollup/plugin-node-resolve'; import replace from '@rollup/plugin-replace'; -import copy from 'rollup-plugin-copy'; +import { terser } from 'rollup-plugin-terser'; +import typescript from 'rollup-plugin-typescript2'; const OUTPUT_DATA = [ { @@ -48,9 +47,6 @@ const config = OUTPUT_DATA.map(({ file, format, input }) => ({ comments: false, }, }), - copy({ - targets: [{ src: 'src/logdna.d.ts', dest: 'dist/types' }], - }), ], })); diff --git a/src/constants.ts b/src/constants.ts index 87d11ad..4f24108 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,25 +1,15 @@ -const DEFAULT_INGESTION_URL = 'https://logs.logdna.com/logs/ingest'; -const LOG_LINE_FLUSH_TIMEOUT = 250; // ms -const FLUSH_BYTE_LIMIT = 60 * 1024; // Max chrome allows with fetch and keep alive is 64kb, we are making it smaller to account for headers and unknowns -const SAMPLE_RATE = 100; +export const DEFAULT_INGESTION_URL: string = + 'https://logs.logdna.com/logs/ingest'; +export const LOG_LINE_FLUSH_TIMEOUT: number = 250; // ms +export const FLUSH_BYTE_LIMIT: number = 60 * 1024; // Max chrome allows with fetch and keep alive is 64kb, we are making it smaller to account for headers and unknowns +export const SAMPLE_RATE: number = 100; -const STARTING_BACK_OFF = 1000; // 1 sec -const MAX_BACK_OFF = 60000; // 60 sec +export const STARTING_BACK_OFF: number = 1000; // 1 sec +export const MAX_BACK_OFF: number = 60000; // 60 sec -const MAX_FETCH_ERROR_RETRY = 30; +export const MAX_FETCH_ERROR_RETRY: number = 30; -const HOSTNAME_CHECK = new RegExp( +export const HOSTNAME_CHECK: RegExp = new RegExp( '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\\.)*' + '([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$', ); - -export { - DEFAULT_INGESTION_URL, - LOG_LINE_FLUSH_TIMEOUT, - FLUSH_BYTE_LIMIT, - HOSTNAME_CHECK, - SAMPLE_RATE, - MAX_FETCH_ERROR_RETRY, - STARTING_BACK_OFF, - MAX_BACK_OFF, -}; diff --git a/src/exceptions.ts b/src/exceptions.ts new file mode 100644 index 0000000..5035815 --- /dev/null +++ b/src/exceptions.ts @@ -0,0 +1,69 @@ +export class InvalidConsoleIntegrationError extends Error { + constructor() { + super(`LogDNA Browser Logger console integration types must be an array`); + } +} + +export class InvalidConsoleMethodError extends Error { + constructor() { + super( + `LogDNA Browser Logger console plugin was passed an invalid console methods`, + ); + } +} + +export class UndefinedIngestionKeyError extends Error { + constructor() { + super(`Ingestion key can not be undefined when calling init`); + } +} + +export class InvalidHostnameError extends Error { + constructor(public readonly hostname: string) { + super( + `LogDNA Browser Logger: \`${hostname}\` is not a valid hostname, see documentation for the \`hostname\` configuration option for details.`, + ); + } +} + +export class InvalidSampleRateError extends Error { + constructor() { + super( + `LogDNA Browser Logger: \`sampleRate\` option must be a number between 0 and 100`, + ); + } +} + +export class DuplicatePluginMethodError extends Error { + constructor() { + super( + 'A LogDNA Browser Logger plugin is attempting to register a method that already exists.', + ); + } +} + +export class MissingPluginNameError extends Error { + constructor() { + super(`A LogDNA Browser Logger plugin must contain a name property`); + } +} + +export class MissingPluginInitError extends Error { + constructor() { + super(`A LogDNA Browser Logger plugin must contain an init function`); + } +} + +export class DuplicatePluginRegistrationError extends Error { + constructor(public readonly pluginName: string) { + super(`The plugin ${pluginName} is already registered with LogDNA Browser`); + } +} + +export class PrematureLogLineError extends Error { + constructor() { + super( + `LogDNA Browser Logger: Attempting send to log lines before calling "init()"`, + ); + } +} diff --git a/src/index.ts b/src/index.ts index 4249a77..8a311de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,33 @@ import { detect } from 'detect-browser'; -import { - ContextT, - LogDNABrowserOptionsT, - StaticContext, - ILogDNABrowserLogger, - Plugin, -} from './logdna.d'; -import utils from './utils'; import { DEFAULT_INGESTION_URL, LOG_LINE_FLUSH_TIMEOUT, SAMPLE_RATE, } from './constants'; +import { + DuplicatePluginMethodError, + DuplicatePluginRegistrationError, + InvalidHostnameError, + InvalidSampleRateError, + MissingPluginInitError, + MissingPluginNameError, + PrematureLogLineError, + UndefinedIngestionKeyError, +} from './exceptions'; import Logger from './logger'; -import SessionManager from './session-manager'; +import allPlugins from './plugins'; import ConsolePlugin from './plugins/console'; import GlobalErrorHandlerPlugin from './plugins/global-handlers'; -import allPlugins from './plugins'; +import SessionManager from './session-manager'; +import { + ContextT, + ILogDNABrowserLogger, + LogDNABrowserOptionsT, + Plugin, + StaticContext, +} from './types'; +import utils from './utils'; class LogDNABrowserLogger implements ILogDNABrowserLogger { Plugins = allPlugins; @@ -53,16 +63,14 @@ class LogDNABrowserLogger implements ILogDNABrowserLogger { this.options = { ...this.options, ...options }; if (ingestionKey == null) { - throw new Error('Ingestion key can not be undefined when calling init'); + throw new UndefinedIngestionKeyError(); } if ( this.options.hostname && !utils.validateHostname(this.options.hostname) ) { - throw new Error( - `LogDNA Browser Logger: \`${this.options.hostname}\` is not a valid hostname, see documentation for the \`hostname\` configuration option for details.`, - ); + throw new InvalidHostnameError(this.options.hostname); } if ( @@ -71,9 +79,7 @@ class LogDNABrowserLogger implements ILogDNABrowserLogger { this.options.sampleRate > 100 || isNaN(this.options.sampleRate) ) { - throw new Error( - `LogDNA Browser Logger: \`sampleRate\` option must be a number between 0 and 100`, - ); + throw new InvalidSampleRateError(); } this.staticContext = this.getStaticContext(); @@ -184,9 +190,7 @@ class LogDNABrowserLogger implements ILogDNABrowserLogger { registerMethod(name: string, fn: Function) { // @ts-ignore if (this[name]) { - throw Error( - 'A LogDNA Browser Logger plugin is attempting to register a method that already exists.', - ); + throw new DuplicatePluginMethodError(); } // @ts-ignore this[name] = fn; @@ -228,21 +232,15 @@ class LogDNABrowserLogger implements ILogDNABrowserLogger { private registerPlugin(plugin: Plugin) { if (!plugin.name) { - throw new Error( - 'A LogDNA Browser Logger plugin must contain a name property', - ); + throw new MissingPluginNameError(); } if (typeof plugin.init !== 'function') { - throw new Error( - 'A LogDNA Browser Logger plugin must contain an init function', - ); + throw new MissingPluginInitError(); } if (this.plugins.includes(plugin.name)) { - throw Error( - `The plugin ${plugin.name} is already registered with LogDNA Browser`, - ); + throw new DuplicatePluginRegistrationError(plugin.name); } this.plugins.push(plugin.name); @@ -257,9 +255,7 @@ class LogDNABrowserLogger implements ILogDNABrowserLogger { private logLines(level: string, message: any, lineContext?: any) { if (this.logger == null) { - throw new Error( - 'LogDNA Browser Logger: Attempting send to log lines before calling `.init()`', - ); + throw new PrematureLogLineError(); } if ( diff --git a/src/logger.ts b/src/logger.ts index 5a990d5..6b031c3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,4 +1,4 @@ -import { LoggerOptionsT, LogDNALogLine } from './logdna.d'; +import { LoggerOptionsT, LogDNALogLine } from './types'; import { LOG_LINE_FLUSH_TIMEOUT, FLUSH_BYTE_LIMIT, diff --git a/src/plugins/console.ts b/src/plugins/console.ts index c9db57e..51785ae 100644 --- a/src/plugins/console.ts +++ b/src/plugins/console.ts @@ -1,13 +1,15 @@ -import { Plugin, ILogDNABrowserLogger } from '../logdna.d'; +import { + InvalidConsoleIntegrationError, + InvalidConsoleMethodError, +} from '../exceptions'; +import { + ConsoleOptions, + ILogDNABrowserLogger, + ConsoleLogType, + Plugin, +} from '../types'; -type LogType = 'log' | 'debug' | 'error' | 'warn' | 'info' | 'assert'; - -export type Options = { - integrations?: LogType[]; - enable?: boolean; -}; - -export const DEFAULT_CONSOLE_METHODS: LogType[] = [ +export const DEFAULT_CONSOLE_METHODS: ConsoleLogType[] = [ 'log', 'debug', 'error', @@ -21,7 +23,7 @@ class ConsolePlugin implements Plugin { options; constructor( - options: Options = { + options: ConsoleOptions = { integrations: DEFAULT_CONSOLE_METHODS, }, ) { @@ -32,9 +34,7 @@ class ConsolePlugin implements Plugin { const { integrations = DEFAULT_CONSOLE_METHODS } = this.options; if (!Array.isArray(integrations)) { - throw new Error( - 'LogDNA Browser Logger console integration types must be an array', - ); + throw new InvalidConsoleIntegrationError(); } const { log, debug, error, warn, info, assert } = window.console; @@ -44,12 +44,13 @@ class ConsolePlugin implements Plugin { window.__LogDNA__.console = _windowConsole; (integrations || []) - .map((method: LogType): LogType => method.toLowerCase() as LogType) - .forEach((method: LogType) => { + .map( + (method: ConsoleLogType): ConsoleLogType => + method.toLowerCase() as ConsoleLogType, + ) + .forEach((method: ConsoleLogType) => { if (!DEFAULT_CONSOLE_METHODS.includes(method)) { - throw Error( - 'LogDNA Browser Logger console plugin was passed an invalid console methods', - ); + throw new InvalidConsoleMethodError(); } window.console[method] = (...args: any[]) => { diff --git a/src/plugins/global-handlers.ts b/src/plugins/global-handlers.ts index 9f3f964..6ca4663 100644 --- a/src/plugins/global-handlers.ts +++ b/src/plugins/global-handlers.ts @@ -1,9 +1,8 @@ -import { Plugin, ILogDNABrowserLogger } from '../logdna.d'; - -export type Options = { - enableErrorHandler?: boolean; - enableUnhandledPromiseRejection?: boolean; -}; +import { + GlobalErrorHandlerOptions, + ILogDNABrowserLogger, + Plugin, +} from '../types'; class GlobalErrorHandlerPlugin implements Plugin { name = 'GlobalErrorHandlerPlugin'; @@ -11,7 +10,7 @@ class GlobalErrorHandlerPlugin implements Plugin { options; constructor( - options: Options = { + options: GlobalErrorHandlerOptions = { enableErrorHandler: true, enableUnhandledPromiseRejection: true, }, diff --git a/src/plugins/performance-measure.ts b/src/plugins/performance-measure.ts index 7f8b065..20c3fd5 100644 --- a/src/plugins/performance-measure.ts +++ b/src/plugins/performance-measure.ts @@ -1,19 +1,19 @@ -import { Plugin, ILogDNABrowserLogger, LogType } from '../logdna.d'; -export type Options = { - prefix?: String; - logLevel?: LogType; -}; +import { + Plugin, + ILogDNABrowserLogger, + PerformanceMeasureOptions, +} from '../types'; -const NAMESPACE = 'logdna:'; +const NAMESPACE: string = 'logdna:'; /* istanbul ignore next */ class PerformanceMeasurePlugin implements Plugin { name = 'PerformanceMeasurePlugin'; logdna: any; - options: Options; + options: PerformanceMeasureOptions; - constructor(options: Options = {}) { - const defaultOptions: Options = { + constructor(options: PerformanceMeasureOptions = {}) { + const defaultOptions: PerformanceMeasureOptions = { prefix: 'Performance Measurement', logLevel: 'debug', }; diff --git a/src/session-manager.ts b/src/session-manager.ts index ed8c596..48777f4 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -1,5 +1,6 @@ import utils from './utils'; -const SESSION_KEY = 'logdna::browser::sessionid'; + +const SESSION_KEY: string = 'logdna::browser::sessionid'; class SessionManager { sessionId?: string; @@ -38,9 +39,9 @@ class SessionManager { // taken from https://gist.github.com/jed/982883 private generate() { - return ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/1|0/g, function() { - return (0 | (Math.random() * 16)).toString(16); - }); + return ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/1|0/g, () => + (0 | (Math.random() * 16)).toString(16), + ); } } diff --git a/src/logdna.d.ts b/src/types.ts similarity index 73% rename from src/logdna.d.ts rename to src/types.ts index 77b85c8..70366fb 100644 --- a/src/logdna.d.ts +++ b/src/types.ts @@ -1,8 +1,25 @@ -import { Options as ConsoleOptions } from './plugins/console'; -import { Options as GlobalErrorHandlerOptions } from './plugins/global-handlers'; +export type LogType = 'log' | 'debug' | 'error' | 'warn' | 'info'; + +export type ConsoleLogType = LogType | 'assert'; export type ContextT = { [key: string]: string }; -export type LogDNABrowserOptionsT = { + +export interface GlobalErrorHandlerOptions { + enableErrorHandler?: boolean; + enableUnhandledPromiseRejection?: boolean; +} + +export interface ConsoleOptions { + integrations?: ConsoleLogType[]; + enable?: boolean; +} + +export interface PerformanceMeasureOptions { + prefix?: String; + logLevel?: LogType; +} + +export interface LogDNABrowserOptionsT { hostname?: string; url?: string; app?: string; @@ -17,32 +34,33 @@ export type LogDNABrowserOptionsT = { globalErrorHandlers?: GlobalErrorHandlerOptions | boolean; debug?: boolean; disabled?: boolean; -}; +} -export type LoggerOptionsT = { +export interface LoggerOptionsT { url?: string; app?: string; hostname: string; flushInterval?: number; tags: string; log: Function; -}; -export type LogDNALogLine = { +} + +export interface LogDNALogLine { line: string; timestamp?: number; level?: string; meta?: any; env?: string; app?: string; -}; -export type StaticContext = { - browser?: object; -}; +} -export type LogType = 'log' | 'debug' | 'error' | 'warn' | 'info'; +export interface StaticContext { + browser?: object; +} export interface Plugin { readonly name: string; + init(logdna: any): void; } @@ -51,14 +69,24 @@ export interface ILogDNABrowserLogger { ingestionKey: string, options?: LogDNABrowserOptionsT, ): ILogDNABrowserLogger; + addContext(context: ContextT): ILogDNABrowserLogger; + setSessionId(sessionId: string): void; + clearContext(): void; + error(message: any, lineContext?: object): void; + log(message: any, lineContext?: object): void; + warn(message: any, lineContext?: object): void; + debug(message: any, lineContext?: object): void; + info(message: any, lineContext?: object): void; + captureError(error: ErrorEvent | Error, lineContext?: object): void; + registerMethod(name: string, fn: Function): void; } diff --git a/src/utils.ts b/src/utils.ts index dbab5ab..1ab588e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import safeStringify from 'fast-safe-stringify'; -import { ContextT, LogDNABrowserOptionsT } from './logdna.d'; -import { HOSTNAME_CHECK } from './constants'; import { version } from '../package.json'; +import { HOSTNAME_CHECK } from './constants'; +import { ContextT, LogDNABrowserOptionsT } from './types'; const hasSessionStorage = !!window.sessionStorage; const SESSION_SCORE_KEY = 'logdna::browser::sessionscore'; @@ -91,7 +91,7 @@ const getIpAddress = async (): Promise => { // This doesnt validate if its a correct ip address const regex = /ip=([\d\.a-fA-f:]+)/; - var ip = regex.exec(text); + let ip = regex.exec(text); return ip ? ip[1] : undefined; } catch (error) { console.warn('LogDNA Browser Logger is unable to retrieve the ip address'); @@ -114,8 +114,7 @@ const getIpAddress = async (): Promise => { * @returns {Number} calculated sleep time */ function backOffWithJitter(base: number, cap: number, lastSleep: number) { - const sleep = Math.min(cap, _randomBetween(base, lastSleep * 3)); - return sleep; + return Math.min(cap, _randomBetween(base, lastSleep * 3)); } function _randomBetween(min: number, max: number) {