summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/effect/logger.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/effect/logger.ts')
-rw-r--r--packages/core/src/effect/logger.ts73
1 files changed, 73 insertions, 0 deletions
diff --git a/packages/core/src/effect/logger.ts b/packages/core/src/effect/logger.ts
new file mode 100644
index 000000000..69f9631e0
--- /dev/null
+++ b/packages/core/src/effect/logger.ts
@@ -0,0 +1,73 @@
+import { Cause, Effect, Logger, References } from "effect"
+import * as Log from "../util/log"
+
+type Fields = Record<string, unknown>
+
+const normalizeKey = (key: string) => (key === "sessionID" ? "session.id" : key)
+
+export interface Handle {
+ readonly debug: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
+ readonly info: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
+ readonly warn: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
+ readonly error: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
+ readonly with: (extra: Fields) => Handle
+}
+
+const clean = (input?: Fields): Fields =>
+ Object.fromEntries(
+ Object.entries(input ?? {})
+ .filter((entry) => entry[1] !== undefined && entry[1] !== null)
+ .map(([key, value]) => [normalizeKey(key), value]),
+ )
+
+const text = (input: unknown): string => {
+ // oxlint-disable-next-line no-base-to-string
+ if (Array.isArray(input)) return input.map((item) => String(item)).join(" ")
+ // oxlint-disable-next-line no-base-to-string
+ return input === undefined ? "" : String(input)
+}
+
+const call = (run: (msg?: unknown) => Effect.Effect<void>, base: Fields, msg?: unknown, extra?: Fields) => {
+ const ann = clean({ ...base, ...extra })
+ const fx = run(msg)
+ return Object.keys(ann).length ? Effect.annotateLogs(fx, ann) : fx
+}
+
+export const logger = Logger.make((opts) => {
+ const extra = clean(opts.fiber.getRef(References.CurrentLogAnnotations))
+ const now = opts.date.getTime()
+ for (const [key, start] of opts.fiber.getRef(References.CurrentLogSpans)) {
+ extra[`logSpan.${key}`] = `${now - start}ms`
+ }
+ if (opts.cause.reasons.length > 0) {
+ extra.cause = Cause.pretty(opts.cause)
+ }
+
+ const svc = typeof extra.service === "string" ? extra.service : undefined
+ if (svc) delete extra.service
+ const log = svc ? Log.create({ service: svc }) : Log.Default
+ const msg = text(opts.message)
+
+ switch (opts.logLevel) {
+ case "Trace":
+ case "Debug":
+ return log.debug(msg, extra)
+ case "Warn":
+ return log.warn(msg, extra)
+ case "Error":
+ case "Fatal":
+ return log.error(msg, extra)
+ default:
+ return log.info(msg, extra)
+ }
+})
+
+export const layer = Logger.layer([logger], { mergeWithExisting: false })
+
+export const create = (base: Fields = {}): Handle => ({
+ debug: (msg, extra) => call((item) => Effect.logDebug(item), base, msg, extra),
+ info: (msg, extra) => call((item) => Effect.logInfo(item), base, msg, extra),
+ warn: (msg, extra) => call((item) => Effect.logWarning(item), base, msg, extra),
+ error: (msg, extra) => call((item) => Effect.logError(item), base, msg, extra),
+ with: (extra) => create({ ...base, ...extra }),
+})