Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,13 @@ Optional runtime controls: `blockPollingIntervalMs` (default `200`), `confirmati

The Solidity entrypoint must be importable by Bun. At startup, typewriter runs `forge build`, reads the compiled ABI/storage layout/AST, and derives app mutation IDs, params, and typed storage from the contract.

**Logging.** Two environment variables control runtime logs:

- `TYPEWRITER_LOG_LEVEL` — `"trace" | "debug" | "info" | "warn" | "error"`. Default `"info"`.
- `TYPEWRITER_LOG_FORMAT` — `"pretty" | "json"`. Default `"json"` when `NODE_ENV` is `"production"`, else `"pretty"`.

An invalid value throws when the server module loads.

#### `typewriter.manifest`

```ts
Expand Down
7 changes: 7 additions & 0 deletions packages/typewriter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,13 @@ Optional runtime controls: `blockPollingIntervalMs` (default `200`), `confirmati

The Solidity entrypoint must be importable by Bun. At startup, typewriter runs `forge build`, reads the compiled ABI/storage layout/AST, and derives app mutation IDs, params, and typed storage from the contract.

**Logging.** Two environment variables control runtime logs:

- `TYPEWRITER_LOG_LEVEL` — `"trace" | "debug" | "info" | "warn" | "error"`. Default `"info"`.
- `TYPEWRITER_LOG_FORMAT` — `"pretty" | "json"`. Default `"json"` when `NODE_ENV` is `"production"`, else `"pretty"`.

An invalid value throws when the server module loads.

#### `typewriter.manifest`

```ts
Expand Down
46 changes: 43 additions & 3 deletions packages/typewriter/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Layer, Logger, References } from "effect";
import { Layer, Logger, type LogLevel, References } from "effect";

const stringify: typeof JSON.stringify = (value, replacer, space) =>
JSON.stringify(
Expand All @@ -14,12 +14,52 @@ const consoleJson = Logger.withConsoleLog(
Logger.map(Logger.formatStructured, stringify),
);

const LOG_LEVELS = {
trace: "Trace",
debug: "Debug",
info: "Info",
warn: "Warn",
error: "Error",
} as const satisfies Record<string, LogLevel.LogLevel>;

const LOG_FORMATS = ["pretty", "json"] as const;

function readEnv<const T extends string>(
name: string,
values: readonly T[],
fallback: T,
): T {
const value = process.env[name];
if (value === undefined || value === "") return fallback;
if (!(values as readonly string[]).includes(value)) {
throw new Error(
`${name} must be one of ${values.map((v) => `"${v}"`).join(", ")}, got "${value}"`,
);
}
return value as T;
Comment on lines +27 to +39

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Logging settings lack tests

The new environment settings have no automated tests. A later change could silently hide logs or stop server startup without failing the suite.

Add tests that import logger.ts in a fresh process for each case: unset and empty values, supported levels and formats, production defaults, and invalid values. Fresh processes matter because the module reads these settings only once.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/typewriter/src/logger.ts
Line: 27-39

Comment:
**Logging settings lack tests**

The new environment settings have no automated tests. A later change could silently hide logs or stop server startup without failing the suite.

Add tests that import `logger.ts` in a fresh process for each case: unset and empty values, supported levels and formats, production defaults, and invalid values. Fresh processes matter because the module reads these settings only once.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

}

const logLevel =
LOG_LEVELS[
readEnv(
"TYPEWRITER_LOG_LEVEL",
Object.keys(LOG_LEVELS) as (keyof typeof LOG_LEVELS)[],
"info",
)
];

const logFormat = readEnv(
"TYPEWRITER_LOG_FORMAT",
LOG_FORMATS,
process.env.NODE_ENV === "production" ? "json" : "pretty",
);

export const logger =
process.env.NODE_ENV === "production" ? consoleJson : Logger.consolePretty();
logFormat === "json" ? consoleJson : Logger.consolePretty();

export const loggerLayer = Layer.merge(
Logger.layer([logger]),
Layer.succeed(References.MinimumLogLevel, "Debug"),
Layer.succeed(References.MinimumLogLevel, logLevel),
);

export function startTimer(): number {
Expand Down
39 changes: 15 additions & 24 deletions packages/typewriter/test/harness.benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {
secp256k1PublicKey,
} from "./utils";

process.env.NODE_ENV = "production";
Object.assign(process.env, {
NODE_ENV: "production",
TYPEWRITER_LOG_LEVEL: "info",
});

const LATENCY_MUTATION_COUNT = 1_000;
const THROUGHPUT_MUTATION_COUNT = 10_000;
Expand Down Expand Up @@ -61,34 +64,22 @@ test("harness benchmark", async () => {
const latencyMutations = mutations.slice(0, LATENCY_MUTATION_COUNT);
const throughputMutations = mutations.slice(LATENCY_MUTATION_COUNT);

// Keep the cost of Debug log formatting, but do not print the lines.
const consoleLog = console.log;
const consoleDebug = console.debug;
console.log = () => {};
console.debug = () => {};
const typewriter = await createTypewriter(Harness, config);
await typewriter.execute(createAccount);

const latenciesMs: number[] = [];
let throughputDurationMs: number;
try {
const typewriter = await createTypewriter(Harness, config);
await typewriter.execute(createAccount);

for (const mutation of latencyMutations) {
const start = performance.now();
await typewriter.execute(mutation);
latenciesMs.push(performance.now() - start);
}

for (const mutation of latencyMutations) {
const start = performance.now();
await Promise.all(
throughputMutations.map((mutation) => typewriter.execute(mutation)),
);
throughputDurationMs = performance.now() - start;
} finally {
console.log = consoleLog;
console.debug = consoleDebug;
await typewriter.execute(mutation);
latenciesMs.push(performance.now() - start);
}

const start = performance.now();
await Promise.all(
throughputMutations.map((mutation) => typewriter.execute(mutation)),
);
const throughputDurationMs = performance.now() - start;

latenciesMs.sort((a, b) => a - b);
const averageMs =
latenciesMs.reduce((total, latency) => total + latency, 0) /
Expand Down
Loading