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) {
// Clean up or copy `value` so our logger or error reporting system
// can record it.
//
// Because our logger `pino` uses JSON.stringify, we need to do
// the following here:
//
// 1. Remove all cycles. JSON.stringify throws an error when you pass
// a value with cyclical references.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
// 2. Because JSON.stringify only serializes enumerable properties, we
// need to copy interesting, but non-enumerable properties like
// value.name and value.message for errors:
// JSON.stringify(new Error('nothing serialized')) returns '{}'
//
// Implementing this correctly is beyond the scope of my example.
return value
}
setUpDatadogTracing()
setUpDOMParser()
setUpLogging()
@arihantverma
Copy link

arihantverma commented Jul 15, 2021

Hi @justjake!

I have general logging question on top of your write up. How do we grab hold of incoming request, for unhandled promise rejections or unhandled errors events, that say happen somewhere in our getServerSideProps code, because of an incoming request? Wouldn't it be a good idea to be able to associate request meta data with those unhandled promise rejections or unhandled errors?

I've never implemented logging in any application before and am trying to wrap my head around what data logged where helps in tracing something having gone wrong. I couldn't find any production code or resources which would help me find some of these answers. If you could direct me to some that you might know that'd be of help.

@justjake
Copy link
Author

justjake commented Aug 6, 2021

  1. You can read the Nextjs source code to find out how Nextjs handles requests
  2. You can read the dd-trace source code to find out how Datadog hooks libraries to make them inspectable. For example: https://github.com/DataDog/dd-trace-js/blob/master/packages/datadog-plugin-http/src/server.js

@spikebrehm
Copy link

spikebrehm commented Dec 22, 2021

thanks jake

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment