seq-logging
Version:
Sends structured log events to the Seq HTTP ingestion API
183 lines (160 loc) • 6.29 kB
JavaScript
;
import assert from 'assert';
import http from 'http';
import { Logger as SeqLogger } from '../index.js';
describe('SeqLogger', () => {
describe('constructor()', () => {
it('defaults missing configuration arguments', () => {
let logger = new SeqLogger();
assert.strictEqual(logger._endpoint, 'http://localhost:5341/api/events/raw');
assert.strictEqual(logger._apiKey, null);
assert.strictEqual(logger._maxRetries, 5);
assert.strictEqual(logger._retryDelay, 5000);
});
it('uses configuration arguments that are provided', () => {
let logger = new SeqLogger({ serverUrl: 'https://my-seq/prd', apiKey: '12345', maxRetries: 10, retryDelay: 10000 });
assert.strictEqual(logger._endpoint, 'https://my-seq/prd/api/events/raw');
assert.strictEqual(logger._apiKey, '12345');
assert.strictEqual(logger._maxRetries, 10);
assert.strictEqual(logger._retryDelay, 10000);
});
});
describe('emit()', () => {
it('detects missing event', () => {
let logger = new SeqLogger();
try {
logger.emit();
} catch (e) {
return;
}
assert.fail(null, null, 'Expected an error');
});
it('enqueues events', () => {
let logger = new SeqLogger();
logger.emit(makeTestEvent());
logger._clearTimer();
assert.strictEqual(1, logger._queue.length);
});
it('ignores calls afer the logger is closed', () => {
let logger = new SeqLogger();
return logger.close().then(() => {
logger.emit(makeTestEvent());
assert.strictEqual(0, logger._queue.length);
});
});
it('converts events to the wire format', () => {
let logger = new SeqLogger();
let event = makeTestEvent();
logger.emit(event);
logger._clearTimer();
let wire = logger._queue[0];
assert.strictEqual(event.messageTemplate, wire.MessageTemplate);
assert.strictEqual(event.timestamp, wire.Timestamp);
assert.strictEqual(event.level, wire.Level);
assert.strictEqual(event.exception, wire.Exception);
assert.strictEqual(event.properties.a, wire.Properties.a);
});
it('handles missing data in wire format', () => {
let logger = new SeqLogger();
let event = {}
logger.emit(event);
logger._clearTimer();
let wire = logger._queue[0];
assert(wire.Timestamp instanceof Date);
assert.strictEqual("(No message provided)", wire.MessageTemplate);
assert.strictEqual("undefined", typeof wire.Exception);
assert.strictEqual("undefined", typeof wire.Level);
assert.strictEqual("undefined", typeof wire.Properties);
});
it('handles invalid data in wire format', () => {
let logger = new SeqLogger();
let event = {
timestamp: 'helo',
level: 3,
messageTemplate: {},
exception: new Error('broken'),
properties: 5
}
logger.emit(event);
logger._clearTimer();
let wire = logger._queue[0];
assert(wire.Timestamp instanceof Date);
assert.strictEqual("[object Object]", wire.MessageTemplate);
assert.strictEqual("Error: broken", wire.Exception);
assert.strictEqual("undefined", typeof wire.Level);
assert.strictEqual("undefined", typeof wire.Properties);
});
});
describe("_post()", function () {
it("retries 5 times after 5xx response from seq server", async () => {
const mockSeq = new MockSeq(3000);
try {
await mockSeq.ready;
const logger = new SeqLogger({ serverUrl: 'http://localhost:3000', maxBatchingTime: 1, retryDelay: 100 });
const event = makeTestEvent();
mockSeq.status = 500;
logger.emit(event);
await logger.flush();
assert.strictEqual(mockSeq.requestCount, 5);
await logger.close();
} finally {
mockSeq.close();
}
});
it("does not retry on 4xx responses", async () => {
const mockSeq = new MockSeq(3001);
try {
await mockSeq.ready;
const logger = new SeqLogger({ serverUrl: 'http://localhost:3001', maxBatchingTime: 1, retryDelay: 100 });
const event = makeTestEvent();
mockSeq.status = 400;
logger.emit(event);
await logger.flush();
assert.strictEqual(mockSeq.requestCount, 1);
await logger.close();
} finally {
mockSeq.close();
}
});
it("retries the amount of times set in configuration", async () => {
const mockSeq = new MockSeq(3002);
try {
await mockSeq.ready;
const logger = new SeqLogger({ serverUrl: 'http://localhost:3002', maxBatchingTime: 1, retryDelay: 100, maxRetries: 7 });
const event = makeTestEvent();
mockSeq.status = 503;
logger.emit(event);
await logger.flush();
assert.strictEqual(mockSeq.requestCount, 7);
await logger.close()
} finally {
mockSeq.close();
}
});
});
});
class MockSeq extends http.Server {
constructor(port) {
super((_, res) => {
res.statusCode = this.status;
this.requestCount++;
res.end()
});
this.status = 200;
this.requestCount = 0;
this.ready = new Promise((resolve, reject) => {
this.listen(port, "localhost")
.once('listening', resolve)
.once('error', reject);
});
}
}
function makeTestEvent() {
return {
level: "Error",
timestamp: new Date(),
messageTemplate: 'Hello!',
exception: "Some error at some file on some line",
properties: { "a": 1 }
};
}