Socket
Socket
Sign inDemoInstall

seq-logging

Package Overview
Dependencies
9
Maintainers
2
Versions
25
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.2 to 2.0.0

1

example/browser/package.json

@@ -19,3 +19,2 @@ {

"html-webpack-plugin": "^5.3",
"node-polyfill-webpack-plugin": "^1.1",
"webpack": "^5.38",

@@ -22,0 +21,0 @@ "webpack-cli": "^4.7",

7

example/browser/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
module.exports = {
entry: './src/main.js',
resolve: {
fallback: {
"buffer": false
}
},
plugins: [

@@ -12,3 +16,2 @@ new HtmlWebpackPlugin({

}),
new NodePolyfillPlugin(),
],

@@ -15,0 +18,0 @@ module: {

{
"name": "seq-logging",
"version": "1.1.2",
"version": "2.0.0",
"description": "Sends structured log events to the Seq HTTP ingestion API",

@@ -25,3 +25,2 @@ "keywords": [

"mocha": "^9.2.2",
"node-polyfill-webpack-plugin": "^1.1.4",
"nyc": "^15.1.0",

@@ -32,3 +31,7 @@ "simple-mock": "^0.8.0",

"uuid": "^8.3.2"
},
"dependencies": {
"abort-controller": "^3.0.0",
"node-fetch": "^2.6.9"
}
}
# Seq Logging for JavaScript ![Build](https://github.com/datalust/seq-logging/workflows/Test/badge.svg) ![Publish](https://github.com/datalust/seq-logging/workflows/Publish/badge.svg) [![NPM](https://img.shields.io/npm/v/seq-logging.svg)](https://www.npmjs.com/package/seq-logging)
> This library makes it easy to support Seq from Node.js logging libraries, including [Pino](https://github.com/pinojs/pino) via [`pino-seq`](https://github.com/datalust/pino-seq), [Bunyan](https://github.com/trentm/node-bunyan) via [`bunyan-seq`](https://github.com/continuousit/bunyan-seq), and [Ts.ED logger](https://logger.tsed.io) via [@tsed/logger-seq](https://logger.tsed.io/appenders/seq.html). It is not expected that applications will interact directly with this package.
> This library makes it easy to support Seq from Node.js logging libraries, including [Winston](https://github.com/winstonjs/winston) via [winston-seq](https://github.com/datalust/winston-seq), [Pino](https://github.com/pinojs/pino) via [`pino-seq`](https://github.com/datalust/pino-seq), [Bunyan](https://github.com/trentm/node-bunyan) via [`bunyan-seq`](https://github.com/continuousit/bunyan-seq), and [Ts.ED logger](https://logger.tsed.io) via [@tsed/logger-seq](https://logger.tsed.io/appenders/seq.html). It is not expected that applications will interact directly with this package.

@@ -5,0 +5,0 @@ ### Usage

"use strict";
let http = require('http');
let https = require('https');
let url = require('url');
const SafeGlobalBlob = typeof Blob !== 'undefined' ? Blob : require('buffer').Blob;
const safeGlobalFetch = typeof fetch !== 'undefined' ? fetch : require('node-fetch');
const SafeGlobalAbortController = typeof AbortController !== 'undefined' ? AbortController : require('abort-controller');
const HEADER = '{"Events":[';
const FOOTER = "]}";
const HEADER_FOOTER_BYTES = Buffer.byteLength(HEADER, 'utf8') + Buffer.byteLength(FOOTER, 'utf8');
const HEADER_FOOTER_BYTES = (new SafeGlobalBlob([HEADER])).size + (new SafeGlobalBlob([FOOTER])).size;
class SeqLogger {

@@ -30,3 +29,3 @@ constructor(config) {

}
this._endpoint = url.parse(serverUrl + 'api/events/raw');
this._endpoint = serverUrl + 'api/events/raw';
this._apiKey = cfg.apiKey || dflt.apiKey;

@@ -47,8 +46,2 @@ this._maxBatchingTime = cfg.maxBatchingTime || dflt.maxBatchingTime;

this._lastRemoteConfig = null;
this._httpModule = this._endpoint.protocol === "https:" ? https : http
this._httpAgent = new this._httpModule.Agent({
keepAlive: true,
maxTotalSockets: 25, // recommendation from https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-configuring-maxsockets.html
});
}

@@ -65,33 +58,2 @@

/**
* * A browser only function that queues events for sending using the navigator.sendBeacon() API.
* * This may work in an unload or pagehide event handler when a normal flush() would not.
* * Events over 63K in length are discarded (with a warning sent in its place) and the total size batch will be no more than 63K in length.
* @returns {boolean}
*/
flushToBeacon () {
if (this._queue.length === 0) {
return false;
}
if (typeof navigator === 'undefined' || !navigator.sendBeacon || typeof Blob === 'undefined') {
return false;
}
const currentBatchSizeLimit = this._batchSizeLimit;
const currentEventSizeLimit = this._eventSizeLimit;
this._batchSizeLimit = Math.min(63 * 1024, this._batchSizeLimit);
this._eventSizeLimit = Math.min(63 * 1024, this._eventSizeLimit);
const dequeued = this._dequeBatch();
this._batchSizeLimit = currentBatchSizeLimit;
this._eventSizeLimit = currentEventSizeLimit;
const { dataParts, options, beaconUrl, size } = this._prepForBeacon(dequeued);
const data = new Blob(dataParts, options);
return navigator.sendBeacon(beaconUrl, data);
}
/**
* Flush then destroy connections, close the logger, destroying timers and other resources.

@@ -107,5 +69,3 @@ * @returns {Promise<void>}

this._clearTimer();
return this.flush().then(() => {
this._httpAgent.destroy();
});
return this.flush();
}

@@ -257,3 +217,3 @@

}
var jsonLen = Buffer.byteLength(json, 'utf8');
var jsonLen = new SafeGlobalBlob([json]).size;
if (jsonLen > this._eventSizeLimit) {

@@ -263,3 +223,3 @@ this._onError("[seq] Event body is larger than " + this._eventSizeLimit + " bytes: " + json);

json = JSON.stringify(next);
jsonLen = Buffer.byteLength(json, 'utf8');
jsonLen = new SafeGlobalBlob([json]).size;
}

@@ -285,3 +245,3 @@

const networkErrors = ['ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN', 'EBUSY'];
return networkErrors.includes(res) || 500 <= res.statusCode && res.statusCode < 600;
return networkErrors.includes(res) || 500 <= res.status && res.status < 600;
}

@@ -294,74 +254,40 @@

const sendRequest = (batch, bytes) => {
const controller = new SafeGlobalAbortController();
attempts++;
let req = this._httpModule.request({
host: this._endpoint.hostname,
port: this._endpoint.port,
path: this._endpoint.path,
protocol: this._endpoint.protocol,
agent: this._httpAgent,
headers: {
"Content-Type": "application/json",
"X-Seq-ApiKey": this._apiKey ? this._apiKey : null,
"Content-Length": bytes,
},
method: "POST",
timeout: this._requestTimeout
});
const timerId = setTimeout(() => {
controller.abort();
if (attempts > this._maxRetries) {
reject('HTTP log shipping failed, reached timeout (' + this._requestTimeout + ' ms)');
} else {
setTimeout(() => sendRequest(batch, bytes), this._retryDelay);
}
}, this._requestTimeout);
req.on("socket", (socket) => {
if (socket.listeners("timeout").length == 0) {
socket.on("timeout", () => {
req.destroy();
if (attempts > this._maxRetries) {
return reject('HTTP log shipping failed, reached timeout (' + this._requestTimeout + ' ms)')
} else {
return setTimeout(() => sendRequest(batch, bytes), this._retryDelay);
}
});
safeGlobalFetch(this._endpoint, {
keepalive: true,
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Seq-ApiKey": this._apiKey ? this._apiKey : null,
"Content-Length": bytes,
},
body: `${HEADER}${batch.join(',')}${FOOTER}`,
signal: controller.signal,
})
.then((res) => {
clearTimeout(timerId);
let httpErr = null;
if (res.status !== 200 && res.status !== 201) {
httpErr = 'HTTP log shipping failed: ' + res.status;
if (this._httpOrNetworkError(res) && attempts < this._maxRetries) {
return setTimeout(() => sendRequest(batch, bytes), this._retryDelay);
}
return reject(httpErr);
}
});
req.on('response', res => {
var httpErr = null;
if (res.statusCode !== 200 && res.statusCode !== 201) {
httpErr = 'HTTP log shipping failed: ' + res.statusCode;
}
res.on('data', (buffer) => {
let dataRaw = buffer.toString();
if (this._onRemoteConfigChange && this._lastRemoteConfig !== dataRaw) {
this._lastRemoteConfig = dataRaw;
this._onRemoteConfigChange(JSON.parse(dataRaw));
}
});
res.on('error', e => {
return reject(e);
});
res.on('end', () => {
if (httpErr !== null) {
if (this._httpOrNetworkError(res) && attempts < this._maxRetries) {
return setTimeout(() => sendRequest(batch, bytes), this._retryDelay);
}
return reject(httpErr);
} else {
return resolve(true);
}
});
});
req.on('error', e => {
return reject(e);
});
req.write(HEADER);
var delim = "";
for (var b = 0; b < batch.length; b++) {
req.write(delim);
delim = ",";
req.write(batch[b]);
}
req.write(FOOTER);
req.end();
return resolve(true);
})
.catch((err) => {
clearTimeout(timerId);
reject(err);
})
}

@@ -372,20 +298,2 @@

}
_prepForBeacon (dequeued) {
const { batch, bytes } = dequeued;
const dataParts = [HEADER, batch.join(','), FOOTER];
// CORS-safelisted for the Content-Type request header
const options = { type: 'text/plain' };
const endpointWithKey = Object.assign({}, this._endpoint, { query: { 'apiKey': this._apiKey } });
return {
dataParts,
options,
beaconUrl: url.format(endpointWithKey),
size: bytes,
};
}
}

@@ -392,0 +300,0 @@

"use strict";
let assert = require('assert');
let simple = require('simple-mock');
const http = require("http");

@@ -13,6 +12,3 @@ let SeqLogger = require('../seq_logger');

let logger = new SeqLogger();
assert.strictEqual(logger._endpoint.hostname, 'localhost');
assert.strictEqual(logger._endpoint.port, '5341');
assert.strictEqual(logger._endpoint.protocol, 'http:');
assert.strictEqual(logger._endpoint.path, '/api/events/raw');
assert.strictEqual(logger._endpoint, 'http://localhost:5341/api/events/raw');
assert.strictEqual(logger._apiKey, null);

@@ -25,6 +21,3 @@ assert.strictEqual(logger._maxRetries, 5);

let logger = new SeqLogger({ serverUrl: 'https://my-seq/prd', apiKey: '12345', maxRetries: 10, retryDelay: 10000 });
assert.strictEqual(logger._endpoint.hostname, 'my-seq');
assert.strictEqual(logger._endpoint.port, null);
assert.strictEqual(logger._endpoint.protocol, 'https:');
assert.strictEqual(logger._endpoint.path, '/prd/api/events/raw');
assert.strictEqual(logger._endpoint, 'https://my-seq/prd/api/events/raw');
assert.strictEqual(logger._apiKey, '12345');

@@ -35,6 +28,2 @@ assert.strictEqual(logger._maxRetries, 10);

it('correctly formats slashed paths', () => {
let logger = new SeqLogger({serverUrl: 'https://my-seq/prd/'});
assert.strictEqual(logger._endpoint.path, '/prd/api/events/raw');
});
});

@@ -114,57 +103,5 @@

describe('flushToBeacon()', function() {
const sendBeacon = simple.stub().returnWith(true);
const MockBlob = function MockBlob(blobParts, options) {
this.size = blobParts.join('').length;
this.type = (options && options.type) || ''
}
beforeEach(function() {
simple.mock(global, 'navigator', {sendBeacon});
simple.mock(global, 'Blob', MockBlob);
});
it('return false with no events', function() {
let logger = new SeqLogger();
const result = logger.flushToBeacon();
assert.strictEqual(result, false);
});
it('formats url to include api key', function() {
let logger = new SeqLogger({serverUrl: 'https://my-seq/prd', apiKey: '12345'});
let event = makeTestEvent();
logger.emit(event);
logger._clearTimer();
const {dataParts, options, beaconUrl, size} = logger._prepForBeacon({batch: [], bytes: 11});
assert.strictEqual(beaconUrl, 'https://my-seq/prd/api/events/raw?apiKey=12345');
});
it('queues beacon', function() {
let logger = new SeqLogger({serverUrl: 'https://my-seq/prd', apiKey: '12345'});
let event = makeTestEvent();
logger.emit(event);
logger._clearTimer();
const result = logger.flushToBeacon();
assert(result);
assert.strictEqual(sendBeacon.callCount, 1);
assert.strictEqual(sendBeacon.lastCall.args[0], 'https://my-seq/prd/api/events/raw?apiKey=12345');
assert.strictEqual(sendBeacon.lastCall.args[1].type, 'text/plain');
assert.strictEqual(sendBeacon.lastCall.args[1].size, 168);
});
it('does handle event properties with circular structures', () => {
let logger = new SeqLogger({serverUrl: 'https://my-seq/prd', apiKey: '12345'});
const event = makeCircularTestEvent();
logger.emit(event);
logger.flushToBeacon();
});
afterEach(function() {
sendBeacon.reset();
simple.restore();
});
});
describe("_post()", function () {
it("retries 5 times after 5xx response from seq server", async () => {
const mockSeq = new MockSeq();
const mockSeq = new MockSeq(3000);
try {

@@ -186,6 +123,6 @@ await mockSeq.ready;

it("does not retry on 4xx responses", async () => {
const mockSeq = new MockSeq();
const mockSeq = new MockSeq(3001);
try {
await mockSeq.ready;
const logger = new SeqLogger({ serverUrl: 'http://localhost:3000', maxBatchingTime: 1, retryDelay: 100 });
const logger = new SeqLogger({ serverUrl: 'http://localhost:3001', maxBatchingTime: 1, retryDelay: 100 });
const event = makeTestEvent();

@@ -204,6 +141,6 @@

it("retries the amount of times set in configuration", async () => {
const mockSeq = new MockSeq();
const mockSeq = new MockSeq(3002);
try {
await mockSeq.ready;
const logger = new SeqLogger({ serverUrl: 'http://localhost:3000', maxBatchingTime: 1, retryDelay: 100, maxRetries: 7 });
const logger = new SeqLogger({ serverUrl: 'http://localhost:3002', maxBatchingTime: 1, retryDelay: 100, maxRetries: 7 });
const event = makeTestEvent();

@@ -226,3 +163,3 @@

class MockSeq extends http.Server {
constructor() {
constructor(port) {
super((_, res) => {

@@ -236,3 +173,3 @@ res.statusCode = this.status;

this.ready = new Promise((resolve, reject) => {
this.listen(3000, "localhost")
this.listen(port, "localhost")
.once('listening', resolve)

@@ -254,13 +191,1 @@ .once('error', reject);

function makeCircularTestEvent() {
const a = {};
a.a = a;
return {
level: "Error",
timestamp: new Date(),
messageTemplate: 'Circular structure issue!',
exception: "Some error at some file on some line",
properties: { a }
};
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc