@fiberplane/hono-otel
Version:
Hono middleware to forward OpenTelemetry traces to a local instance of @fiberplane/studio
78 lines (77 loc) • 2.97 kB
JavaScript
import { trace } from "@opentelemetry/api";
import shimmer from "shimmer";
import { errorToJson, isLikelyNeonDbError, isWrapped, neonDbErrorToJson, safelySerializeJSON, } from "../utils/index.js";
const { wrap } = shimmer;
export function patchConsole() {
patchMethod("debug", "debug");
patchMethod("log", "info");
patchMethod("warn", "warn");
patchMethod("error", "error");
}
function patchMethod(methodName, level) {
// Check if the function is already patched
// If it is, we don't want to patch it again
if (isWrapped(console[methodName])) {
return;
}
wrap(console, methodName, (original) => {
// NOTE - Original message needs to be typed as `string` to be compatible with the original method,
// but in fact it is `unknown`.
// People pass non-string arguments to console.log all the time.
return (originalMessage, ...args) => {
const span = trace.getActiveSpan();
if (span) {
const { message, messageType } = serializeLogMessage(originalMessage);
span.addEvent("log", {
message: message,
messageType,
level,
arguments: safelySerializeJSON(args?.map(transformLogMessage)),
});
}
// IMPORTANT - We need to return the original message, otherwise the console method will not work as expected
return original(originalMessage, ...args);
};
});
}
/**
* Helper that takes the first argument to a log method and transforms it into a string
* that can be logged.
*
* Also returns `messageType` to be used in the log event, as a hint to the UI.
*/
function serializeLogMessage(rawMessage) {
const messageType = rawMessage === null ? "null" : typeof rawMessage;
const transformedMessage = transformLogMessage(rawMessage);
const stringifiedMessage = transformedMessage === "string"
? transformedMessage
: safelySerializeJSON(transformedMessage);
return {
message: stringifiedMessage,
messageType,
};
}
/**
* Helper that takes a log message and transforms it into something that
* hopefully can be stringified (into JSON), we don't lose information on
* what was logged.
*
* The reason this is necessary is because Error objects do not
* serialize well to JSON.
*
* Also NeonDbErrors do not extend the Error class, so they need their own handling.
*/
function transformLogMessage(message) {
if (isLikelyNeonDbError(message)) {
return neonDbErrorToJson(message);
}
if (message instanceof Error) {
return errorToJson(message);
}
// NOTE - Functions are not serializable, so we stringify them.
// Otherwise, we could end up with a `null` value in the attributes!
if (typeof message === "function") {
return message?.toString() ?? "";
}
return message;
}