Logger in opencode codebase.
In this article, we review logger in opencode codebase. We will look at:
-
Log namespace
-
Log usage
I study patterns used in an open source project found on Github Trending. For this week, I reviewed some parts of opencode codebase and wrote this article.

Log namespace
Logger in Opencode is defined at sst/opencode/packages/opencode/src/util/log.ts as a namespace.
export namespace Log { export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) export type Level = z.infer<typeof Level>
In TypeScript, a namespace serves as a mechanism to organize code and prevent naming conflicts, particularly in larger applications or when integrating with older JavaScript libraries. It acts as a container for logically related code, such as classes, interfaces, functions, and variables, effectively encapsulating them within a dedicated scope.
Learn more about Namespaces in TypeScript.
Logger type
Logger type is defined as shown below:
export type Logger = { debug(message?: any, extra?: Record<string, any>): void info(message?: any, extra?: Record<string, any>): void error(message?: any, extra?: Record<string, any>): void warn(message?: any, extra?: Record<string, any>): void tag(key: string, value: string): Logger clone(): Logger time( message: string, extra?: Record<string, any>, ): { stop(): void [Symbol.dispose](): void } }

This image shows a list of symbols in log.ts file, we are particularly interested in create method as this helps in understanding how this logger is used/invoked in other parts of codebase.
create function
create function has the following definition:
export function create(tags?: Record<string, any>) { tags = tags || {} const service = tags["service"] if (service && typeof service === "string") { const cached = loggers.get(service) if (cached) { return cached } } function build(message: any, extra?: Record<string, any>) { ... } const result: Logger = { debug(message?: any, extra?: Record<string, any>) { }, info(message?: any, extra?: Record<string, any>) { }, error(message?: any, extra?: Record<string, any>) { }, warn(message?: any, extra?: Record<string, any>) { }, tag(key: string, value: string) { if (tags) tags[key] = value return result }, clone() { return Log.create({ ...tags }) }, time(message: string, extra?: Record<string, any>) { }, } if (service && typeof service === "string") { loggers.set(service, result) } return result }
This create method accepts tags as a parameter. Below is the cache mechanism it has in place:
const service = tags["service"] if (service && typeof service === "string") { const cached = loggers.get(service) if (cached) { return cached } }
result is assigned an object with some functions defined. For example, following code snippet shows the debug function:
const result: Logger = { debug(message?: any, extra?: Record<string, any>) { if (shouldLog("DEBUG")) { process.stderr.write("DEBUG " + build(message, extra)) } },
Another example is the tags:
tag(key: string, value: string) { if (tags) tags[key] = value return result },
Log usage
At sst/opencode/packages/console/core/src/actor.ts#L40, you will find the following code:
import { Log } from "./util/log" ... const log = Log.create().tag("namespace", "actor") ...
But this Log is imported from util/log file and it contains the below code:
import { Context } from "../context" export namespace Log { const ctx = Context.create<{ tags: Record<string, any> }>() export function create(tags?: Record<string, any>) { tags = tags || {} const result = { info(message?: any, extra?: Record<string, any>) { const prefix = Object.entries({ ...use().tags, ...tags, ...extra, }) .map(([key, value]) => `${key}=${value}`) .join(" ") console.log(prefix, message) return result }, tag(key: string, value: string) { if (tags) tags[key] = value return result }, clone() { return Log.create({ ...tags }) }, } return result } export function provide<R>(tags: Record<string, any>, cb: () => R) { const existing = use() return ctx.provide( { tags: { ...existing.tags, ...tags, }, }, cb, ) } function use() { try { return ctx.use() } catch (e) { return { tags: {} } } } }
Wait, does this mean this code is duplicated? I will leave that to you to find out ;)
About me:
Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.
Email: ramu.narasinga@gmail.com
Want to learn from open-source? Solve challenges inspired by open-source projects.