monitor-nodejs
Version:
Code Execution Monitoring for Node.js
223 lines (167 loc) • 4.47 kB
JavaScript
'use strict'
const Transaction = require('./models/transaction')
const Segment = require('./models/segment')
const { Handler, AxiosHandler } = require('./libs/handler')
const stacktrace = require('./libs/stacktrace')
async function parseErrorData(e, handled) {
const rawStack = e.stack.split('\n')
let firstLine = rawStack.shift().split(':')
let data = {
class: firstLine[0],
message: firstLine[1].trim(),
file: undefined,
line: undefined,
code: 1,
stack: [],
handled,
}
for (const [index, line] of rawStack.entries()) {
let lineObj = stacktrace.stackLineParser(line)
lineObj.code = await stacktrace.getCodeOfStackElement(lineObj)
if (index === 0) {
data.file = lineObj.file
data.line = lineObj.line
}
data.stack.push(lineObj)
}
return data
}
function Monitor() {
this._recording = true
this._handler = undefined
this._transaction = undefined
this._segments = []
this.isRecording = () => {
return this._recording
}
this.startRecording = () => {
this._recording = true
return this
}
this.stopRecording = () => {
this._recording = false
return this
}
this.getHandler = () => {
return this._handler
}
this.setHandler = (handler) => {
this._handler = handler
return this
}
this.getSegments = () => {
return this._segments
}
this.transaction = () => {
return this._transaction
}
this.hasTransaction = () => {
return this._transaction instanceof Transaction
}
this.needTransaction = () => {
return this.isRecording() && !this.transaction()
}
this.canAddSegments = () => {
return this.isRecording() && this.transaction()
}
this.startTransaction = (name) => {
if (!this.isRecording()) {
throw new Error('You must turn on recording to start a transaction.')
}
this._transaction = new Transaction(name)
this._transaction.start()
// TODO: Catch exception
return this._transaction
}
this.startSegment = (type, label) => {
if (!this.hasTransaction()) {
const transaction = new Transaction('dummy')
transaction.start()
const segment = new Segment(transaction, type, label)
segment.start()
return segment
}
const segment = new Segment(this._transaction, type, label)
segment.start()
this._segments.push(segment)
return segment
}
this.addSegment = async (callback, type, label, throwE = false) => {
if (!this.canAddSegments()) {
return callback()
}
let segment
try {
segment = this.startSegment(type, label)
const result = await callback()
segment.end()
return result
}
catch (error) {
if (segment) {
segment.end()
}
if (throwE) {
throw error
}
return this.report(error)
}
}
this.report = async function (e, handled = false) {
if (this.needTransaction()) {
this.startTransaction(e.name)
.setType(Transaction.TYPE_UNEXPECTED)
.markAsFailed()
}
const segment = this.startSegment(Segment.TYPE_ERROR, e.message)
segment.addContext('_monitor', {
error: await parseErrorData(e, handled),
})
segment.end()
return segment
}
this.flush = async () => {
const entries = [
this._transaction,
...this._segments,
].filter(entry => entry)
for (const entry of entries) {
if (!entry.isEnded()) {
entry.end()
}
}
if (typeof this._handler === 'function') {
await this._handler(entries)
}
else if (this._handler instanceof Handler) {
await this._handler.handle(entries)
}
this._transaction = null
this.clear()
return this
}
this.clear = () => {
this._transaction = undefined
this._segments = []
return this
}
}
const packageData = require('../package.json')
Monitor.VERSION = packageData['version']
Monitor.create = (key, options = {}) => {
const base = options.url || 'https://ingest.dailydesk.app'
// TODO: trim / at the end of base
const url = base + '/entries'
const version = options.version || Monitor.VERSION
// const maxItems = options.maxItems || 1000
const handler = new AxiosHandler({
url: url,
ingestionKey: key,
version: version,
// maxItems: maxItems,
})
const monitor = new Monitor()
monitor.setHandler(handler)
return monitor
}
module.exports = Monitor