Skip to content

Commit 63a2c28

Browse files
committed
Deploy configs
1 parent 2ce8623 commit 63a2c28

File tree

16 files changed

+314
-90
lines changed

16 files changed

+314
-90
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55
branches: [main, develop]
66
push:
7-
branches: [develop]
7+
branches: [main, develop]
88

99
concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref }}

.github/workflows/deploy.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "apps/orchestrator-worker/**"
8+
- "packages/**"
9+
- ".github/workflows/deploy.yml"
10+
11+
concurrency:
12+
group: deploy-${{ github.ref }}
13+
cancel-in-progress: false
14+
15+
jobs:
16+
deploy-production:
17+
name: Deploy to Production
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 15
20+
environment:
21+
name: production
22+
url: https://poppy-orchestrator-production.caelinsutch.workers.dev
23+
24+
steps:
25+
- name: Checkout code
26+
uses: actions/checkout@v4
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: "22"
32+
33+
- name: Setup pnpm
34+
uses: pnpm/action-setup@v4
35+
with:
36+
version: 10
37+
38+
- name: Get pnpm store directory
39+
shell: bash
40+
run: |
41+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
42+
43+
- name: Setup pnpm cache
44+
uses: actions/cache@v4
45+
with:
46+
path: ${{ env.STORE_PATH }}
47+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
48+
restore-keys: |
49+
${{ runner.os }}-pnpm-store-
50+
51+
- name: Install dependencies
52+
run: pnpm install --frozen-lockfile
53+
54+
- name: Deploy to Cloudflare Workers (Production)
55+
uses: cloudflare/wrangler-action@v3
56+
with:
57+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
58+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
59+
command: deploy --env production
60+
workingDirectory: apps/orchestrator-worker

apps/orchestrator-worker/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2-
"name": "@poppy/worker",
2+
"name": "@poppy/orchestrator-worker",
33
"private": true,
44
"version": "0.0.0",
55
"sideEffects": false,
66
"type": "module",
77
"scripts": {
88
"dev": "run-wrangler-dev --env staging",
99
"dev:production": "run-wrangler-dev --env production",
10-
"deploy": "run-wrangler-deploy --env production",
11-
"deploy:staging": "run-wrangler-deploy --env staging",
10+
"deploy": "wrangler deploy --env production",
11+
"deploy:staging": "wrangler deploy --env staging",
1212
"typecheck": "run-tsc",
1313
"cf-typegen": "run-wrangler-types",
1414
"test": "run-vitest run",

apps/orchestrator-worker/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {
2+
useWorkersLogger,
23
withDefaultCors,
34
withNotFound,
45
withOnError,
56
} from "@poppy/hono-helpers";
67
import { Hono } from "hono";
7-
import { logger } from "hono/logger";
88
import type { App } from "./context";
99
import { loopMessageRoutes } from "./routes/loop-message";
1010

@@ -14,7 +14,7 @@ export { MessageDebouncer } from "./durable-objects/message-debouncer";
1414
const app = new Hono<App>();
1515

1616
// Middleware
17-
app.use("*", logger());
17+
app.use("*", useWorkersLogger());
1818
app.use("*", withDefaultCors());
1919

2020
// Health check routes

apps/orchestrator-worker/wrangler.jsonc

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,28 @@
33
"main": "src/index.ts",
44
"compatibility_date": "2025-01-10",
55
"compatibility_flags": ["nodejs_compat"],
6+
"logpush": true,
7+
"upload_source_maps": true,
8+
"observability": {
9+
"logs": {
10+
"enabled": true,
11+
"head_sampling_rate": 1 // 100%
12+
}
13+
},
14+
"migrations": [
15+
{
16+
"tag": "v1",
17+
"new_sqlite_classes": ["MessageDebouncer"]
18+
}
19+
],
620
"env": {
721
"production": {
822
"name": "poppy-orchestrator-production",
923
"durable_objects": {
1024
"bindings": [
1125
{
1226
"name": "MESSAGE_DEBOUNCER",
13-
"class_name": "MessageDebouncer",
14-
"script_name": "poppy-orchestrator-production"
27+
"class_name": "MessageDebouncer"
1528
}
1629
]
1730
},
@@ -23,7 +36,8 @@
2336
}
2437
],
2538
"vars": {
26-
"NODE_ENV": "production"
39+
"NODE_ENV": "production",
40+
"NAME": "poppy-orchestrator"
2741
}
2842
},
2943
"staging": {
@@ -32,8 +46,7 @@
3246
"bindings": [
3347
{
3448
"name": "MESSAGE_DEBOUNCER",
35-
"class_name": "MessageDebouncer",
36-
"script_name": "poppy-orchestrator-staging"
49+
"class_name": "MessageDebouncer"
3750
}
3851
]
3952
},
@@ -45,7 +58,8 @@
4558
}
4659
],
4760
"vars": {
48-
"NODE_ENV": "staging"
61+
"NODE_ENV": "staging",
62+
"NAME": "poppy-orchestrator"
4963
}
5064
}
5165
}
Lines changed: 14 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,16 @@
1-
/**
2-
* Simple structured logger for Cloudflare Workers
3-
*/
4-
export interface LogContext {
5-
[key: string]: any;
6-
}
1+
import { WorkersLogger } from "workers-tagged-logger";
72

8-
export function createLogger(workerName: string, environment?: string) {
9-
const baseContext = {
10-
worker: workerName,
11-
environment: environment || "unknown",
12-
};
3+
export type LogTagHints = {
4+
// add common tags here so that they show up as hints
5+
// in `logger.setTags()` and `logger.withTags()`
6+
url: string;
7+
method: string;
8+
path: string;
9+
routePath: string;
10+
searchParams: string;
11+
headers: string;
12+
ip?: string;
13+
timestamp: string;
14+
};
1315

14-
return {
15-
info: (message: string, context?: LogContext) => {
16-
console.log(
17-
JSON.stringify({
18-
level: "info",
19-
message,
20-
...baseContext,
21-
...context,
22-
timestamp: new Date().toISOString(),
23-
}),
24-
);
25-
},
26-
27-
error: (message: string, error?: Error, context?: LogContext) => {
28-
console.error(
29-
JSON.stringify({
30-
level: "error",
31-
message,
32-
error: error?.message,
33-
stack: error?.stack,
34-
...baseContext,
35-
...context,
36-
timestamp: new Date().toISOString(),
37-
}),
38-
);
39-
},
40-
41-
warn: (message: string, context?: LogContext) => {
42-
console.warn(
43-
JSON.stringify({
44-
level: "warn",
45-
message,
46-
...baseContext,
47-
...context,
48-
timestamp: new Date().toISOString(),
49-
}),
50-
);
51-
},
52-
53-
debug: (message: string, context?: LogContext) => {
54-
console.debug(
55-
JSON.stringify({
56-
level: "debug",
57-
message,
58-
...baseContext,
59-
...context,
60-
timestamp: new Date().toISOString(),
61-
}),
62-
);
63-
},
64-
};
65-
}
66-
67-
export type Logger = ReturnType<typeof createLogger>;
16+
export const logger = new WorkersLogger<LogTagHints>();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Context } from "hono";
2+
import type { HonoApp } from "../types";
3+
import { redactUrl } from "./url";
4+
5+
export interface LogDataRequest {
6+
url: string;
7+
method: string;
8+
path: string;
9+
/** Hono route for the request */
10+
routePath: string;
11+
/* URL search params */
12+
searchParams: string;
13+
headers: string;
14+
/** Eyeball IP address of the request */
15+
ip?: string;
16+
timestamp: string;
17+
}
18+
19+
/**
20+
* Get logdata from request
21+
*/
22+
export function getRequestLogData<T extends HonoApp>(
23+
c: Context<T>,
24+
requestStartTimestamp: number,
25+
): LogDataRequest {
26+
const redactedUrl = redactUrl(c.req.url);
27+
return {
28+
url: redactedUrl.toString(),
29+
method: c.req.method,
30+
path: c.req.path,
31+
routePath: c.req.routePath,
32+
searchParams: redactedUrl.searchParams.toString(),
33+
headers: JSON.stringify(Array.from(c.req.raw.headers)),
34+
ip:
35+
c.req.header("cf-connecting-ip") ||
36+
c.req.header("x-real-ip") ||
37+
c.req.header("x-forwarded-for"),
38+
timestamp: new Date(requestStartTimestamp).toISOString(),
39+
};
40+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** Redacts keys from a url */
2+
export function redactUrl(_url: URL | string): URL {
3+
let url: URL;
4+
if (typeof _url === "string") {
5+
url = new URL(_url);
6+
} else {
7+
url = new URL(_url.toString()); // clone
8+
}
9+
for (const [key, _] of Array.from(url.searchParams)) {
10+
if (["key", "apikey", "api_key", "token"].includes(key.toLowerCase())) {
11+
url.searchParams.set(key, "REDACTED");
12+
}
13+
}
14+
return url;
15+
}
16+
17+
/**
18+
* Converts a URLSearchParams object into an array of "key=value" strings.
19+
*
20+
* @param searchParams - The URLSearchParams object to convert.
21+
* @returns An array of strings, where each string is in the format "key=value".
22+
*/
23+
export function searchParamsToArray(searchParams: URLSearchParams): string[] {
24+
const result: string[] = [];
25+
for (const [key, value] of searchParams.entries()) {
26+
result.push(`${key}=${value}`);
27+
}
28+
return result;
29+
}

packages/hono-helpers/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Export types
22

33
export * from "./helpers/errors";
4-
// Export helpers
4+
export * from "./helpers/errors";
55
export * from "./helpers/logger";
6+
export { logger } from "./helpers/logger";
7+
export { getRequestLogData, type LogDataRequest } from "./helpers/request";
8+
export * from "./helpers/url";
69
export * from "./middleware/withDefaultCors";
10+
export * from "./middleware/withLogger";
711
export * from "./middleware/withNotFound";
812
// Export middleware
913
export * from "./middleware/withOnError";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Context, MiddlewareHandler } from "hono";
2+
import { useWorkersLogger as useWorkersLoggerBase } from "workers-tagged-logger";
3+
import type { HonoApp } from "../types";
4+
import { logger } from "../helpers/logger";
5+
import { getRequestLogData } from "../helpers/request";
6+
7+
/**
8+
* Middleware to set up workers-tagged-logger for request logging
9+
*/
10+
export function useWorkersLogger<T extends HonoApp>(): MiddlewareHandler<T> {
11+
return async (c: Context<T>, next) => {
12+
const requestStartTimestamp = Date.now();
13+
14+
// Set up the tagged logger middleware from workers-tagged-logger
15+
await useWorkersLoggerBase(c.env.NAME, {
16+
environment: c.env.NODE_ENV,
17+
release: c.env.SENTRY_RELEASE,
18+
})(c, next);
19+
20+
// After the request is processed, log request data
21+
const logData = getRequestLogData(c, requestStartTimestamp);
22+
const duration = Date.now() - requestStartTimestamp;
23+
24+
logger
25+
.withTags({
26+
url: logData.url,
27+
method: logData.method,
28+
path: logData.path,
29+
routePath: logData.routePath,
30+
})
31+
.info("Request processed", {
32+
...logData,
33+
duration,
34+
status: c.res.status,
35+
});
36+
};
37+
}

0 commit comments

Comments
 (0)