Skip to content

Commit 6e5268e

Browse files
committed
feat: add wrappers for route handlers and server handlers
1 parent ab80518 commit 6e5268e

6 files changed

Lines changed: 217 additions & 54 deletions

File tree

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"release": "np"
3636
},
3737
"dependencies": {
38+
"serialize-error": "^11.0.3",
3839
"zod": "^3.22.4"
3940
},
4041
"devDependencies": {

packages/core/src/index.ts

Lines changed: 57 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,7 @@
11
import { z } from "zod"
22
import { baseLogSchema } from "./schemas"
3-
4-
function buildJson<T extends {}>(logs: Array<Partial<T>>) {
5-
const logsObj: Record<string, unknown> = {}
6-
for (let i = 0; i < logs.length; i++) {
7-
Object.entries(logs[i]).forEach(([key, value]) => {
8-
logsObj[key] = value
9-
})
10-
}
11-
return logsObj as T
12-
}
13-
14-
function buildText<T>(logs: Array<Partial<T>>) {
15-
const jsonLine = buildJson(logs)
16-
return Object.entries(jsonLine)
17-
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
18-
.join(" ")
19-
}
3+
import { catchUncaughtAction, catchUncaughtRoute } from "./request-utils"
4+
import { buildJsonLog, buildTextLog } from "./utils"
205

216
export type FlytrapLogsOptions = {
227
/**
@@ -88,46 +73,64 @@ export function createFlytrapLogger<T>({
8873

8974
let logs: Array<Partial<Log>> = [defaultLog as Log]
9075

91-
return {
92-
getContext() {
93-
return logs
94-
},
95-
addContext(context: Partial<Log>) {
96-
logs.push(context)
97-
},
98-
flush(level: FlushLevel = "log") {
99-
try {
100-
const logValue =
101-
logFormat === "text" ? buildText(logs) : buildJson(logs)
76+
// Function defintions
77+
function getContext() {
78+
return logs
79+
}
10280

103-
if (flushMethod === "stdout") {
104-
console[level](
105-
typeof logValue === "string" ? logValue : JSON.stringify(logValue)
106-
)
107-
}
108-
if (flushMethod === "api") {
109-
fetch(logsEndpoint, {
110-
method: "POST",
111-
body: JSON.stringify(logValue),
112-
headers: new Headers({
113-
"Content-Type": "application/json",
114-
...(flytrapPublicKey && {
115-
Authorization: `Bearer ${flytrapPublicKey}`,
116-
}),
81+
function addContext(context: Partial<Log>) {
82+
logs.push(context)
83+
}
84+
85+
function flush(level: FlushLevel = "log") {
86+
try {
87+
const logValue =
88+
logFormat === "text" ? buildTextLog(logs) : buildJsonLog(logs)
89+
90+
if (flushMethod === "stdout") {
91+
console[level](
92+
typeof logValue === "string" ? logValue : JSON.stringify(logValue)
93+
)
94+
}
95+
if (flushMethod === "api") {
96+
fetch(logsEndpoint, {
97+
method: "POST",
98+
body: JSON.stringify(logValue),
99+
headers: new Headers({
100+
"Content-Type": "application/json",
101+
...(flytrapPublicKey && {
102+
Authorization: `Bearer ${flytrapPublicKey}`,
117103
}),
118-
}).then(async (res) => {
119-
if (res.ok === false) {
120-
console.error(
121-
"Flytrap Logs SDK: Failed to save logs to API. Error:"
122-
)
123-
console.error(await res.text())
124-
}
125-
})
126-
}
127-
} catch (error) {
128-
console.error("Flytrap Logs SDK: Error when flushing logs. Error:")
129-
console.error(error)
104+
}),
105+
}).then(async (res) => {
106+
if (res.ok === false) {
107+
console.error(
108+
"Flytrap Logs SDK: Failed to save logs to API. Error:"
109+
)
110+
console.error(await res.text())
111+
}
112+
})
130113
}
114+
} catch (error) {
115+
console.error("Flytrap Logs SDK: Error when flushing logs. Error:")
116+
console.error(error)
117+
}
118+
}
119+
120+
return {
121+
getContext,
122+
addContext,
123+
flush,
124+
catchUncaughtAction<T extends (...args: any[]) => Promise<any>>(
125+
fn: T,
126+
options?: Partial<z.infer<typeof baseLogSchema>>
127+
) {
128+
return catchUncaughtAction(fn, addContext, flush, options)
129+
},
130+
catchUncaughtRoute<T extends { params: Record<string, unknown> }>(
131+
fn: (request: Request, context: T) => Promise<Response> | Response
132+
) {
133+
return catchUncaughtRoute(fn, addContext, flush)
131134
},
132135
}
133136
}

packages/core/src/request-utils.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { createFlytrapLogger } from "./index"
2+
import { serializeError } from "serialize-error"
3+
import { baseLogSchema } from "./schemas"
4+
import { z } from "zod"
5+
import { headersToRecord, parseJsonOrPassthrough } from "./utils"
6+
import { AddContextFn, FlushFn } from "./types"
7+
8+
export function response(
9+
body: BodyInit,
10+
opts: ResponseInit = {},
11+
addContext: ReturnType<typeof createFlytrapLogger>["addContext"]
12+
) {
13+
addContext({
14+
res: parseJsonOrPassthrough(body),
15+
http_status: opts?.status ?? 200,
16+
})
17+
18+
return new Response(body, opts)
19+
}
20+
21+
export function catchUncaughtRoute<
22+
T extends { params: Record<string, unknown> }
23+
>(
24+
fn: (request: Request, context: T) => Promise<Response> | Response,
25+
addContext: AddContextFn<z.infer<typeof baseLogSchema>>,
26+
flush: FlushFn
27+
): (request: Request, context: T) => Promise<Response> {
28+
return async (request: Request, context: T) => {
29+
const t0 = Date.now()
30+
try {
31+
addContext({
32+
req_headers: headersToRecord(request.headers),
33+
})
34+
const res = await fn(request, context)
35+
addContext({
36+
res_headers: headersToRecord(res.headers),
37+
})
38+
addContext({
39+
duration: Date.now() - t0,
40+
})
41+
flush("log")
42+
return res
43+
} catch (error) {
44+
addContext({ error: serializeError(error) })
45+
const res = response(
46+
JSON.stringify({
47+
message: `Internal Server Error. Please try again later.`,
48+
}),
49+
{
50+
status: 500,
51+
headers: new Headers({ "Content-Type": "application/json" }),
52+
},
53+
addContext
54+
)
55+
addContext({
56+
duration: Date.now() - t0,
57+
})
58+
addContext({
59+
res_headers: headersToRecord(res.headers),
60+
})
61+
62+
flush("error")
63+
return res
64+
}
65+
}
66+
}
67+
68+
export function catchUncaughtAction<T extends (...args: any[]) => Promise<any>>(
69+
fn: T,
70+
addContext: AddContextFn<z.infer<typeof baseLogSchema>>,
71+
flush: FlushFn,
72+
options?: Partial<z.infer<typeof baseLogSchema>>
73+
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
74+
return async (...args: Parameters<T>) => {
75+
const t0 = Date.now()
76+
if (options) addContext(options)
77+
try {
78+
addContext({
79+
req: args,
80+
})
81+
addContext({
82+
type: "action",
83+
})
84+
const res = await fn(...args)
85+
addContext({ res })
86+
addContext({ http_status: 200 })
87+
addContext({
88+
duration: Date.now() - t0,
89+
})
90+
flush("log")
91+
return res as ReturnType<T>
92+
} catch (error) {
93+
addContext({ error: serializeError(error) })
94+
addContext({ http_status: 500 })
95+
const res = {
96+
success: false,
97+
message: `Internal Server Error. Please try again later.`,
98+
}
99+
addContext({ res })
100+
addContext({
101+
duration: Date.now() - t0,
102+
})
103+
flush("error")
104+
return res as ReturnType<T>
105+
}
106+
}
107+
}

packages/core/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { FlushLevel } from "./index"
2+
3+
export type AddContextFn<Log> = (context: Partial<Log>) => void
4+
export type FlushFn = (level: FlushLevel) => void

packages/core/src/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export function parseJsonOrPassthrough<T>(input: T) {
2+
try {
3+
return JSON.parse(String(input))
4+
} catch {
5+
return input
6+
}
7+
}
8+
9+
export function headersToRecord(headers: Headers) {
10+
const headersRecord: Record<string, string> = {}
11+
headers.forEach((value, key) => {
12+
headersRecord[key] = value
13+
})
14+
15+
return headersRecord
16+
}
17+
18+
export function buildJsonLog<T extends {}>(logs: Array<Partial<T>>) {
19+
const logsObj: Record<string, unknown> = {}
20+
for (let i = 0; i < logs.length; i++) {
21+
Object.entries(logs[i]).forEach(([key, value]) => {
22+
logsObj[key] = value
23+
})
24+
}
25+
return logsObj as T
26+
}
27+
28+
export function buildTextLog<T>(logs: Array<Partial<T>>) {
29+
const jsonLine = buildJsonLog(logs)
30+
return Object.entries(jsonLine)
31+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
32+
.join(" ")
33+
}

pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)