My first attempt to setup error logging was in a global state. Put it at the top, call it a day. But, this wasnât really getting the logs that I wanted. I was missing errors I cared about and getting library errors that I didnât care about.
Sending Messages
I have started using more manual capturing of error logs, rather than depending entirely on the automatic error capturing. I wanted two things
- More breadcrumbs, as well as being more relevant
- More error logging for specific issues I consider errors, such as logical errors.
Sentry.captureMessage(message)This worked better, but it was still very limited in the amount of information it would carry. I thought this would work, but it didnât provide any useful debug information.
Sentry.captureMessage(message, { level: 'warning', data: { a: 1, b: 2,}})I found this strange, because why didnât it include the extra information? Searching around for why this wasnât working, I stumbled on this Github issue
What I needed was scopes. With this, I could attach information about a specific event in its own scope(?)
So now I call sentry withScope, then set it as an extra debugData data field, and the error one (since it didnât seem to show what I needed)
For good measure, I still capture the message and place the exception in there too.
Sentry.withScope((scope) => { scope.setTag("file", "handleError.ts") Sentry.captureMessage(message); if(data) { Sentry.setExtra("debugData", data) } if(error) { Sentry.captureException(error); Sentry.setExtra("errorInfo", { type: error.type, message: error.message, stack: error.stack, }) }})Breadcrumbs
After discovering that Sentry also offers breadcrumbs, I started placing them in there too. Essentially, these allow you to see things that were just run before the error occurred. It doesnât mean the error is related, but it allows you to see what was going on around it at that moment.
Sentry.addBreadcrumb({ a: 1, b: 2,});Serverless
While building this for a serverless environment, I discovered that Sentry would capture the events, but not log them. There was no error messages or anything, it just wouldnât send it.
The debug logs looked something like this
Sentry Logger [log]: Initializing Sentry: process: 314650, thread: main.Sentry Logger [log]: Integration installed: InboundFilters[...]Sentry Logger [log]: Integration installed: LangChainSentry Logger [log]: SDK initialized from ESMSentry Logger [log]: @opentelemetry/api: Registered a global for diag v1.9.0.Sentry Logger [log]: @opentelemetry/api: Registered a global for trace v1.9.0.Sentry Logger [log]: @opentelemetry/api: Registered a global for propagation v1.9.0.Sentry Logger [log]: @opentelemetry/api: Registered a global for context v1.9.0.Sentry Logger [log]: Captured error event `Hello`Sentry Logger [log]: Captured error event `abc is not defined`Hereâs the code too:
sentry.captureMessage("Hello")5 collapsed lines
try { abc() } catch(e) { sentry.captureException(e) }Youâll notice that it a captures the error events. Unfortunately, it didnât show up in my logs! After trying several iterations of this and confirming the config, I stumbled across this issue.
You need to flush the event in lambda handlers, otherwise, it wonât get delivered, as return âerrorâ you call at the end closes the handler and effectively freezes its execution.
So, to resolve I need to include a flush. I put one in with a 1000ms timeout and it started working.
sentry.captureMessage("Hello")sentry.flush(1000)Template
Hereâs a template for functions to get things caught. Itâs with bun, so you may need to change it around a little. Config for bun.
import * as Sentry from "@sentry/bun";
export default async function getSentry(name: string): Promise<typeof Sentry> { const sentryConfig = getSentryConfig(); Sentry.init({ dsn: sentryConfig.config.dsn, }) Sentry.setTag("function", name); console.log("Returning inited Sentry") return Sentry}
export type SentryT = typeof Sentry;import getSentry from "@code/getSentry";import type { SentryT } from "@code/getSentry";
export async function main(data: dataT) { const sentry = await getSentry("amazingFunction") as SentryT; let response;
try { response = await codeLogic(data); } catch(e) { sentry.captureException(e); await sentry.flush(1000); throw e; } return response;}Troubleshooting
This is the original version I had out. It caused a number of issues that I had to debug through. First of all, scope doesnât
have a method setExtra. You have to use Sentry.
If you call Sentry.captureException and there is no exception, it will create an error log called <anonymous>.
Notice errorInfo only is set if error exists. However, I noticed while debugging that it is always sent, even if it doesnât exist.
Iâm not sure why.
Sentry.withScope((scope: ScopeT) => { if(data) { scope.setExtra("debugData", data) } if(error) { Sentry.setExtra("errorInfo", { type: error.type, message: error.message, stack: error.stack, }) } Sentry.captureMessage(message); Sentry.captureException(error);})