Skip to content

Commit

Permalink
wrangler tail tty-awareness and json-respecting (#810)
Browse files Browse the repository at this point in the history
2 quick fixes:

- Check `process.stdout.isTTY` at runtime to determine whether to default to "pretty" or "json" output for tailing.
- Only print messages like "Connected to {worker}" if in "pretty" mode (errors still throw strings)
  • Loading branch information
Cass Fridkin committed Apr 15, 2022
1 parent 7e560e1 commit 0ce47a5
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 85 deletions.
12 changes: 12 additions & 0 deletions .changeset/unlucky-terms-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"wrangler": patch
---

Make `wrangler tail` TTY-aware, and stop printing non-JSON in JSON mode

Closes #493

2 quick fixes:

- Check `process.stdout.isTTY` at runtime to determine whether to default to "pretty" or "json" output for tailing.
- Only print messages like "Connected to {worker}" if in "pretty" mode (errors still throw strings)
23 changes: 23 additions & 0 deletions packages/wrangler/src/__tests__/helpers/mock-istty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const ORIGINAL_ISTTY = process.stdout.isTTY;

/**
* Mock `process.stdout.isTTY`
*/
export function useMockIsTTY() {
/**
* Explicitly set `process.stdout.isTTY` to a given value
*/
const setIsTTY = (isTTY: boolean) => {
process.stdout.isTTY = isTTY;
};

beforeEach(() => {
process.stdout.isTTY = ORIGINAL_ISTTY;
});

afterEach(() => {
process.stdout.isTTY = ORIGINAL_ISTTY;
});

return { setIsTTY };
}
74 changes: 46 additions & 28 deletions packages/wrangler/src/__tests__/tail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Headers, Request } from "undici";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
import { mockConsoleMethods } from "./helpers/mock-console";
import { useMockIsTTY } from "./helpers/mock-istty";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import type { TailEventMessage, RequestEvent, ScheduledEvent } from "../tail";
Expand Down Expand Up @@ -216,6 +217,8 @@ describe("tail", () => {
});

describe("printing", () => {
const { setIsTTY } = useMockIsTTY();

it("logs request messages in JSON format", async () => {
const api = mockWebsocketAPIs();
await runWrangler("tail test-worker --format json");
Expand Down Expand Up @@ -266,8 +269,34 @@ describe("tail", () => {
`);
});

it("defaults to logging in pretty format", async () => {
// the same test as the one before, but without the --format flag
it("logs scheduled messages in pretty format", async () => {
const api = mockWebsocketAPIs();
await runWrangler("tail test-worker --format pretty");

const event = generateMockScheduledEvent();
const message = generateMockEventMessage({ event });
const serializedMessage = serialize(message);

api.ws.send(serializedMessage);
expect(
std.out
.replace(
new Date(mockEventTimestamp).toLocaleString(),
"[mock timestamp string]"
)
.replace(
mockTailExpiration.toLocaleString(),
"[mock expiration date]"
)
).toMatchInlineSnapshot(`
"successfully created tail, expires at [mock expiration date]
Connected to test-worker, waiting for logs...
\\"* * * * *\\" @ [mock timestamp string] - Ok"
`);
});

it("defaults to logging in pretty format when the output is a TTY", async () => {
setIsTTY(true);
const api = mockWebsocketAPIs();
await runWrangler("tail test-worker");

Expand All @@ -293,7 +322,22 @@ describe("tail", () => {
`);
});

it("defaults to logging in json format when the output is not a TTY", async () => {
setIsTTY(false);

const api = mockWebsocketAPIs();
await runWrangler("tail test-worker");

const event = generateMockRequestEvent();
const message = generateMockEventMessage({ event });
const serializedMessage = serialize(message);

api.ws.send(serializedMessage);
expect(std.out).toMatch(deserializeToJson(serializedMessage));
});

it("logs console messages and exceptions", async () => {
setIsTTY(true);
const api = mockWebsocketAPIs();
await runWrangler("tail test-worker");

Expand Down Expand Up @@ -341,32 +385,6 @@ describe("tail", () => {
`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});

it("logs scheduled messages in pretty format", async () => {
const api = mockWebsocketAPIs();
await runWrangler("tail test-worker --format pretty");

const event = generateMockScheduledEvent();
const message = generateMockEventMessage({ event });
const serializedMessage = serialize(message);

api.ws.send(serializedMessage);
expect(
std.out
.replace(
new Date(mockEventTimestamp).toLocaleString(),
"[mock timestamp string]"
)
.replace(
mockTailExpiration.toLocaleString(),
"[mock expiration date]"
)
).toMatchInlineSnapshot(`
"successfully created tail, expires at [mock expiration date]
Connected to test-worker, waiting for logs...
\\"* * * * *\\" @ [mock timestamp string] - Ok"
`);
});
});
});

Expand Down
115 changes: 58 additions & 57 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1071,59 +1071,56 @@ export async function main(argv: string[]): Promise<void> {
"tail [name]",
"🦚 Starts a log tailing session for a published Worker.",
(yargs) => {
return (
yargs
.positional("name", {
describe: "Name of the worker",
type: "string",
})
// TODO: auto-detect if this should be json or pretty based on atty
.option("format", {
default: "pretty",
choices: ["json", "pretty"],
describe: "The format of log entries",
})
.option("status", {
choices: ["ok", "error", "canceled"],
describe: "Filter by invocation status",
array: true,
})
.option("header", {
type: "string",
describe: "Filter by HTTP header",
})
.option("method", {
type: "string",
describe: "Filter by HTTP method",
array: true,
})
.option("sampling-rate", {
type: "number",
describe: "Adds a percentage of requests to log sampling rate",
})
.option("search", {
type: "string",
describe: "Filter by a text match in console.log messages",
})
.option("ip", {
type: "string",
describe:
'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
array: true,
})
.option("env", {
type: "string",
describe: "Perform on a specific environment",
alias: "e",
})
.option("debug", {
type: "boolean",
hidden: true,
default: false,
describe:
"If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
})
);
return yargs
.positional("name", {
describe: "Name of the worker",
type: "string",
})
.option("format", {
default: process.stdout.isTTY ? "pretty" : "json",
choices: ["json", "pretty"],
describe: "The format of log entries",
})
.option("status", {
choices: ["ok", "error", "canceled"],
describe: "Filter by invocation status",
array: true,
})
.option("header", {
type: "string",
describe: "Filter by HTTP header",
})
.option("method", {
type: "string",
describe: "Filter by HTTP method",
array: true,
})
.option("sampling-rate", {
type: "number",
describe: "Adds a percentage of requests to log sampling rate",
})
.option("search", {
type: "string",
describe: "Filter by a text match in console.log messages",
})
.option("ip", {
type: "string",
describe:
'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
array: true,
})
.option("env", {
type: "string",
describe: "Perform on a specific environment",
alias: "e",
})
.option("debug", {
type: "boolean",
hidden: true,
default: false,
describe:
"If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
});
},
async (args) => {
if (args.format === "pretty") {
Expand Down Expand Up @@ -1164,9 +1161,11 @@ export async function main(argv: string[]): Promise<void> {
args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
}`;

console.log(
`successfully created tail, expires at ${expiration.toLocaleString()}`
);
if (args.format === "pretty") {
console.log(
`successfully created tail, expires at ${expiration.toLocaleString()}`
);
}

onExit(async () => {
tail.terminate();
Expand All @@ -1193,7 +1192,9 @@ export async function main(argv: string[]): Promise<void> {
}
}

console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
if (args.format === "pretty") {
console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
}

tail.on("close", async () => {
tail.terminate();
Expand Down

0 comments on commit 0ce47a5

Please sign in to comment.