Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Customizing NextJS for error reporting and Datadog APM (dd-trace) integration. See https://jake.tl/notes/2021-04-04-nextjs-preload-hack
// @ts-check
"use strict"
/**
* Set up datadog tracing. This should be called first, so Datadog can hook
* all the other dependencies like `http`.
*/
function setUpDatadogTracing() {
const { tracer: Tracer } = require('dd-trace')
const tracer = Tracer.init({
// Your options here.
runtimeMetrics: true,
logInjection: true,
})
}
/**
* Polyfill DOMParser for react-intl
* Otherwise react-intl spews errors related to formatting
* messages with <xml>in them</xml>
*/
function setUpDOMParser() {
const xmldom = require("xmldom")
global["DOMParser"] = xmldom.DOMParser
}
/**
* Set up logging. Monkey patches a bunch of stuff.
*/
function setUpLogging() {
// pino is a simple JSON logger with Datadog integration.
// By default it logs to STDOUT.
const pino = require('pino')
const logger = pino({
// Your options here.
})
function getLoggingFunction(/** @type {string} */ levelName) {
const baseLogFn = (logger[levelName] || logger.info).bind(logger)
return function patchedLog(/** @type {any[]} */ ...parts) {
/** @type {object | undefined} */
let data = undefined
/** @type {object | undefined} */
let error = undefined
/** @type {object | undefined} */
const nativeError = parts.find(
it =>
(it && it instanceof Error) ||
(it && typeof it === "object" && "name" in it && "message" in it)
)
if (nativeError) {
error = cleanObjectForSerialization(nativeError)
// If you use Sentry, Rollbar, etc, you could capture the error here.
// ErrorThingy.report(nativeError)
}
// If next is trying to log funky stuff, put it into the data object.
if (parts.length > 1) {
data = data || {}
data.parts = parts.map(part => cleanObjectForSerialization(part))
}
const messages =
nativeError && parts.length === 1 ? [nativeError.toString()] : parts
baseLogFn({ data, error, type: levelName }, ...messages)
}
}
// Monkey-patch Next.js logger.
// See https://github.com/atkinchris/next-logger/blob/main/index.js
// See https://github.com/vercel/next.js/blob/canary/packages/next/build/output/log.ts
const nextBuiltInLogger = require("next/dist/build/output/log")
for (const [property, value] of Object.entries(nextBuiltInLogger)) {
if (typeof value !== "function") {
continue
}
nextBuiltInLogger[property] = getLoggingFunction(property)
}
/**
* Monkey-patch global console.log logger. Yes. Sigh.
* @type {Array<keyof typeof console>}
*/
const loggingProperties = ["log", "debug", "info", "warn", "error"]
for (const property of loggingProperties) {
console[property] = getLoggingFunction(property)
}
// Add general error logging.
process.on("unhandledRejection", (error, promise) => {
logger.error(
{
type: "unhandledRejection",
error: cleanObjectForSerialization(error),
data: { promise: cleanObjectForSerialization(promise) },
},
`${error}`
)
})
process.on("uncaughtException", error => {
logger.error(
{ type: "uncaughtException", error: cleanObjectForSerialization(error) },
`${error}`
)
})
}
function cleanObjectForSerialization(value) {
// This function should recursively cleans `value` of cycles and copy
// interesting non-enumerable properties like `name` and `message`.
return value
}
setUpDatadogTracing()
setUpDOMParser()
setUpLogging()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment