Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Logger package #75

Merged
merged 29 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6b965c0
start logger
t3dotgg Feb 15, 2023
4df178c
add logger package
markflorkowski Feb 16, 2023
25070ee
add web socket server
markflorkowski Feb 16, 2023
377f877
support subscribing to different log levels
markflorkowski Feb 16, 2023
a998a91
add ws to trpc client
markflorkowski Feb 16, 2023
caf1d8e
remove old logger file
markflorkowski Feb 16, 2023
7f16cc8
misc cleanup, start testing
markflorkowski Feb 16, 2023
6d76049
Merge branch 'main' into theo/logger-example
t3dotgg Feb 16, 2023
b1e05f3
aaaaaaaaaaaa
t3dotgg Feb 16, 2023
de64023
add tsconfig
markflorkowski Feb 16, 2023
f97e5b5
add .gitignore
markflorkowski Feb 16, 2023
b27a5eb
lint
markflorkowski Feb 16, 2023
ce4cd24
misc cleanup, close server on SIGTERM
markflorkowski Feb 16, 2023
600f93a
replace a bunch of console.logs
markflorkowski Feb 16, 2023
4d50fff
replace more logs
markflorkowski Feb 16, 2023
327513a
more cleanup
markflorkowski Feb 16, 2023
1ff4193
a few more console logs swapped
markflorkowski Feb 16, 2023
3b7fabd
no-console lint rule
markflorkowski Feb 16, 2023
9d7f146
disable no-console on docs and marketing site
markflorkowski Feb 16, 2023
7808c9e
add missing dep
markflorkowski Feb 16, 2023
ce5e4f9
pnpm i
markflorkowski Feb 16, 2023
e40d319
fix vscode side
t3dotgg Feb 16, 2023
ddc3050
Fix logger in cli
t3dotgg Feb 16, 2023
4069899
allow passing Error objects to logger
markflorkowski Feb 17, 2023
38eb801
missing file
markflorkowski Feb 17, 2023
81cdfe1
Merge branch 'main' into theo/logger-example
markflorkowski Feb 17, 2023
aa5ab62
combine dev checks
markflorkowski Feb 20, 2023
1e4af44
disable multiple eslint rules in one line
markflorkowski Feb 20, 2023
b5c8614
don't store ReactNodes in state
markflorkowski Feb 20, 2023
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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"apps/cli/cli-core",
"apps/cli/cli-web",
"apps/docs",
"apps/marketing"
"apps/marketing",
"packages/logger"
]
}
1 change: 1 addition & 0 deletions apps/cli/cli-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"format:check": "prettier --check --plugin-search-dir=. src/**/*.{cjs,mjs,ts,tsx,md,json} --ignore-path ../.gitignore"
},
"dependencies": {
"@captain/logger": "workspace:*",
"@trpc/client": "10.9.0",
"@trpc/server": "10.9.0",
"node-fetch": "^3.3.0",
Expand Down
14 changes: 7 additions & 7 deletions apps/cli/cli-core/src/get-sample-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import path from "path";
import fs from "fs";
import fsPromise from "fs/promises";
import fetch from "node-fetch";

import { HOOK_PATH } from "./constants";
import logger from "@captain/logger";

export async function getSampleHooks() {
// Create the directory if it doesn't exist
if (!fs.existsSync(HOOK_PATH)) {
console.log(
`\x1b[33m[WARNING] Could not find .thing directory, creating it now!\x1b[0m`
);
logger.warn(`Could not find .thing directory, creating it now!`);
fs.mkdirSync(HOOK_PATH, { recursive: true });
}

Expand All @@ -20,10 +20,10 @@ export async function getSampleHooks() {
download_url: string;
}[];

console.log(`[INFO] Downloading ${files.length} sample hooks.`);
logger.info(`Downloading ${files.length} sample hooks.`);

const promiseMap = files.map(async (file) => {
console.log(`[INFO] Downloading ${file.name}`);
logger.info(`Downloading ${file.name}`);
const fileContent = await fetch(file.download_url).then((res) =>
res.text()
);
Expand All @@ -32,8 +32,8 @@ export async function getSampleHooks() {
try {
return await fsPromise.writeFile(newFilePath, fileContent);
} catch (e) {
console.log(`[ERROR] Could not write file ${file.name}`);
console.log(e);
logger.error(`Could not write file ${file.name}`);
logger.error(e);
throw e;
}
});
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/cli-core/src/open-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { promisify } from "util";
import os from "os";
import fs from "fs";

import logger from "@captain/logger";

const promisifiedExecFile = promisify(childProcess.execFile);

const getCommand = () => {
Expand All @@ -23,9 +25,7 @@ const getCommand = () => {
export async function openInExplorer(path: string) {
// Create the directory if it doesn't exist
if (!fs.existsSync(path)) {
console.log(
`\x1b[33m[WARNING] Could not find .thing directory, creating it now!\x1b[0m`
);
logger.warn(`Could not find .thing directory, creating it now!`);
fs.mkdirSync(path, { recursive: true });
}

Expand Down
8 changes: 4 additions & 4 deletions apps/cli/cli-core/src/templateSubstitution.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logger from "@captain/logger";

export const substituteTemplate = (input: {
template: string;
sanitize?: boolean;
Expand All @@ -13,17 +15,15 @@ export const substituteTemplate = (input: {
const variable = match[1]?.trim();

if (!variable) {
console.log(`\u001b[31m[ERROR] Invalid template configuration`);
logger.error(`Invalid template configuration`);
throw new Error(`Invalid template configuration`);
}
const sanitizedString = `%%${variable}%%`;

const fromEnv = process.env[variable];

if (!fromEnv) {
console.log(
`\u001b[31m[ERROR] Environment variable ${variable} not found`
);
logger.error(`Environment variable ${variable} not found`);
throw new Error(`Environment variable ${variable} not found`);
}

Expand Down
51 changes: 34 additions & 17 deletions apps/cli/cli-core/src/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,29 @@ import { substituteTemplate } from "./templateSubstitution";

export type { ConfigValidatorType } from "./update-config";

import logger from "@captain/logger";
import { observable } from "@trpc/server/observable";

import type { LogLevels } from "@captain/logger";

export const t = initTRPC.create({
transformer: superjson,
});
export const cliApiRouter = t.router({
onLog: t.procedure.subscription(() => {
return observable<{ message: string; level: LogLevels }>((emit) => {
const onLog = (m: { message: string; level: LogLevels }) => {
emit.next(m);
};

logger.subscribe(onLog);

return () => {
logger.unsubscribe(onLog);
};
});
}),

getBlobs: t.procedure.query(async () => {
if (!fs.existsSync(HOOK_PATH)) {
// TODO: this should probably be an error, and the frontend should handle it
Expand Down Expand Up @@ -87,8 +106,8 @@ export const cliApiRouter = t.router({
try {
await openInExplorer(path.join(HOOK_PATH, input.path));
} catch (e) {
console.log(
"[ERROR] Failed to open folder (unless you're on Windows, then this just happens)",
logger.error(
"Failed to open folder (unless you're on Windows, then this just happens)",
e
);
}
Expand All @@ -108,7 +127,7 @@ export const cliApiRouter = t.router({
.mutation(async ({ input }) => {
const { file, url } = input;
let hasCustomConfig = false;
console.log(`[INFO] Reading file ${file}`);
logger.info(`Reading file ${file}`);

let config = {
url,
Expand All @@ -128,7 +147,7 @@ export const cliApiRouter = t.router({

if (fs.existsSync(path.join(HOOK_PATH, configName))) {
hasCustomConfig = true;
console.log(`[INFO] Found ${configName}, reading it`);
logger.info(`Found ${configName}, reading it`);
const configFileContents = await fsPromises.readFile(
path.join(HOOK_PATH, configName)
);
Expand All @@ -154,8 +173,8 @@ export const cliApiRouter = t.router({
const data = await fsPromises.readFile(path.join(HOOK_PATH, file));

try {
console.log(
`[INFO] Sending to ${config.url} ${
logger.info(
`Sending to ${config.url} ${
hasCustomConfig ? `with custom config from ${configName}` : ""
}\n`
);
Expand All @@ -166,18 +185,16 @@ export const cliApiRouter = t.router({
body: config.method !== "GET" ? data.toString() : undefined,
}).then((res) => res.json());

console.log(
`[INFO] Got response: \n\n${JSON.stringify(fetchedResult, null, 2)}\n`
logger.info(
`Got response: \n\n${JSON.stringify(fetchedResult, null, 2)}\n`
);
return fetchedResult;
} catch (e) {
console.log("\u001b[31m[ERROR] FAILED TO SEND");
logger.error("FAILED TO SEND");
if ((e as { code: string }).code === "ECONNREFUSED") {
console.log(
"\u001b[31m[ERROR] Connection refused. Is the server running?"
);
logger.error("Connection refused. Is the server running?");
} else {
console.log("\u001b[31m[ERROR] Unknown error", e);
logger.error("Unknown error", e);
}
throw new Error("Connection refused. Is the server running?");
}
Expand All @@ -193,11 +210,11 @@ export const cliApiRouter = t.router({
)
.mutation(async ({ input }) => {
const { name, body, config } = input;
console.log(`[INFO] Creating ${name}.json`);
logger.info(`Creating ${name}.json`);

await fsPromises.writeFile(path.join(HOOK_PATH, `${name}.json`), body);
if (config?.url || config?.query || config?.headers) {
console.log(`[INFO] Config specified, creating ${name}.config.json`);
logger.info(`Config specified, creating ${name}.config.json`);
return await updateConfig({ name, config });
}
}),
Expand All @@ -216,7 +233,7 @@ export const cliApiRouter = t.router({

if (!name) throw new Error("No name");

console.log(`[INFO] updating ${name}.json`);
logger.info(`Updating ${name}.json`);

const existingBody = await fsPromises.readFile(
path.join(HOOK_PATH, `${name}.json`),
Expand Down Expand Up @@ -244,7 +261,7 @@ export const cliApiRouter = t.router({
config?.headers ||
fs.existsSync(path.join(HOOK_PATH, `${name}.config.json`))
) {
console.log(`[INFO] Config specified, updating ${name}.config.json`);
logger.info(`Config specified, updating ${name}.config.json`);
return await updateConfig({ name, config });
}
}),
Expand Down
1 change: 1 addition & 0 deletions apps/cli/cli-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"format:check": "prettier --check --plugin-search-dir=. src/**/*.{cjs,mjs,ts,tsx,md,json} --ignore-path ../.gitignore"
},
"dependencies": {
"@captain/logger": "workspace:*",
"@headlessui/react": "^1.7.8",
"@heroicons/react": "^2.0.12",
"@hookform/resolvers": "^2.9.10",
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/cli-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Toaster } from "react-hot-toast";
import { JsonBlobs } from "./components/jsonblobs";
import { EndpointSetting } from "./components/endpointsetting";
import { useConnectionStateToasts } from "./utils/useConnectionStateToasts";
import { Logs } from "./components/logs";

const SubscriptionsHelper = () => {
useConnectionStateToasts();
Expand Down Expand Up @@ -52,6 +53,7 @@ export default function Example() {
<div className="flex flex-col gap-4 divide-y divide-gray-200 rounded-lg border-gray-200">
<EndpointSetting />
<JsonBlobs />
<Logs />
</div>
</div>
</div>
Expand Down
47 changes: 47 additions & 0 deletions apps/cli/cli-web/src/components/logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ReactNode, useState } from "react";
import { cliApi } from "../utils/api";

import type { LogLevels } from "@captain/logger";

const colorMap = {
trace: "text-gray-600", // gray
debug: `text-cyan-600`, // cyan
info: `text-white`, // white
warn: `text-yellow-600`, // yellow
error: `text-red-600`, // red
} as const;

const webFormat = (input: { level: LogLevels; message: string }) => {
const { level, message } = input;
return (
<span className={colorMap[level]}>
<span className="font-semibold">{`[${level.toUpperCase()}]`}</span>{" "}
{message}
</span>
);
};

export const Logs = () => {
const [messages, setMessages] = useState<
{ level: LogLevels; message: string }[]
>([]);
cliApi.onLog.useSubscription(undefined, {
onData: (data) => {
setMessages((messages) => {
return [...messages, data];
});
},
});

return (
<div className="flex h-96 flex-col overflow-y-auto rounded-md bg-gray-900 p-4">
{messages.map((message, index) => {
return (
<div key={index} className="text-sm">
{webFormat(message)}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is just a component, should probably be mounted as such so it can be keyed and diffed accordingly

Copy link
Member Author

Choose a reason for hiding this comment

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

Also worth memoizing

</div>
);
})}
</div>
);
};
22 changes: 21 additions & 1 deletion apps/cli/cli-web/src/utils/api-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { httpBatchLink } from "@trpc/client";
import { createWSClient, httpBatchLink, splitLink, wsLink } from "@trpc/client";
import superjson from "superjson";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { PropsWithChildren, useState } from "react";

const WS_PORT = 2034;

// create persistent WebSocket connection
const wsClient = createWSClient({
url: `ws://localhost:${WS_PORT}`,
});

import { cliApi } from "./api";

export const ApiTRPCProvider = (props: PropsWithChildren) => {
Expand All @@ -11,6 +19,18 @@ export const ApiTRPCProvider = (props: PropsWithChildren) => {
cliApi.createClient({
transformer: superjson,
links: [
splitLink({
// subscriptions are handled by WebSocket, everything else is handled by HTTP
condition(op) {
return op.type === "subscription";
},
true: wsLink({
client: wsClient,
}),
false: httpBatchLink({
url: "http://localhost:2033/trpc",
}),
}),
httpBatchLink({
url: "http://localhost:2033/trpc",
}),
Expand Down
5 changes: 4 additions & 1 deletion apps/cli/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@captain/cli-core": "*",
"@captain/logger": "workspace:*",
"@fastify/cors": "^8.2.0",
"@fastify/http-proxy": "^8.4.0",
"@fastify/static": "^6.6.0",
Expand All @@ -34,7 +35,8 @@
"fs-extra": "^11.1.0",
"graceful-fs": "^4.2.10",
"gradient-string": "^2.0.2",
"open": "^8.4.0"
"open": "^8.4.0",
"ws": "^8.12.1"
},
"devDependencies": {
"@captain/cli-web": "*",
Expand All @@ -43,6 +45,7 @@
"@types/gradient-string": "^1.1.2",
"@types/inquirer": "^9.0.2",
"@types/node": "^18.8.0",
"@types/ws": "^8.5.4",
"@vercel/ncc": "^0.36.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
Expand Down
12 changes: 6 additions & 6 deletions apps/cli/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#!/usr/bin/env node
import { startServer } from "./server";
import { startServer, startSocketServer } from "./server";
import { renderTitle } from "./utils/renderTitle";

const DOCS_LINK = "https://docs.webhookthing.com";

const main = async () => {
renderTitle();

// link to docs
console.log(
`\x1b[36m%s\x1b[0m`,
`Questions? Check out the docs: ${DOCS_LINK}`
);
// eslint-disable-next-line no-console
console.log(`\x1b[36mQuestions? Check out the docs: ${DOCS_LINK}\x1b[0m`);

startSocketServer();

await startServer();
};

main().catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(1);
});
Loading