Skip to content

Commit e368e08

Browse files
authored
feat: improve structured logging with environment-based formatting and verbosity levels (#534)
# Improved Structured Logging for Better Developer Experience This PR enhances the logging system in Edge Worker with significant developer experience improvements: - Added a new `verbose` log level between `info` and `debug` for task outcomes - Implemented environment detection to automatically set appropriate defaults: - Local development: Colorful "fancy" format at `verbose` level - Hosted environments: Structured key=value format at `info` level - Created two formatter types: - Fancy formatter with colored icons, worker-prefixed lines, and flow/step paths - Simple formatter with structured key=value output optimized for log aggregators - Added task duration display and retry information with exponential backoff delay calculation - Added support for the NO_COLOR standard - Introduced environment variable controls: - `EDGE_WORKER_LOG_LEVEL` to set verbosity - `EDGE_WORKER_LOG_FORMAT` to choose output style The documentation has been updated with comprehensive logging configuration details, examples of both output formats, and a clearer explanation of the log level hierarchy.
1 parent 622cf2e commit e368e08

File tree

10 files changed

+119
-170
lines changed

10 files changed

+119
-170
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@pgflow/edge-worker': patch
3+
---
4+
5+
Major developer experience improvements with structured logging:
6+
7+
- Add verbose log level between info and debug for task outcomes
8+
- Auto-detect local vs hosted environment for log format and level defaults
9+
- Fancy formatter for local dev with colored icons, worker-prefixed lines, and flow/step paths
10+
- Simple formatter for production with structured key=value output for log aggregators
11+
- Display task duration, retry information with exponential backoff delay calculation
12+
- Support NO_COLOR standard and EDGE_WORKER_LOG_LEVEL/EDGE_WORKER_LOG_FORMAT env vars

pkgs/cli/scripts/assert-pgflow-installed

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ if [ ! -d "${SUPABASE_PATH}/migrations" ] || [ -z "$(ls -A ${SUPABASE_PATH}/migr
1919
error "No SQL migration files found in ${SUPABASE_PATH}/migrations/"
2020
fi
2121

22-
# Verify environment variables in .env file
23-
if ! grep -q 'EDGE_WORKER_LOG_LEVEL' "${SUPABASE_PATH}/functions/.env"; then
24-
error "EDGE_WORKER_LOG_LEVEL not found in ${SUPABASE_PATH}/functions/.env"
25-
fi
26-
2722
# Verify per_worker policy in config.toml
2823
if ! grep -q 'policy = "per_worker"' "${SUPABASE_PATH}/config.toml"; then
2924
error "policy = \"per_worker\" not found in ${SUPABASE_PATH}/config.toml"

pkgs/cli/src/commands/install/index.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { intro, log, confirm, cancel, outro } from '@clack/prompts';
33
import chalk from 'chalk';
44
import { copyMigrations } from './copy-migrations.js';
55
import { updateConfigToml } from './update-config-toml.js';
6-
import { updateEnvFile } from './update-env-file.js';
76
import { createEdgeFunction } from './create-edge-function.js';
87
import { createFlowsDirectory } from './create-flows-directory.js';
98
import { createExampleWorker } from './create-example-worker.js';
@@ -39,7 +38,6 @@ export default (program: Command) => {
3938
` • Create ${chalk.cyan('supabase/flows/')} ${chalk.dim('(flow definitions with GreetUser example)')}`,
4039
` • Create Control Plane in ${chalk.cyan('supabase/functions/pgflow/')}`,
4140
` • Create ${chalk.cyan('supabase/functions/greet-user-worker/')} ${chalk.dim('(example worker)')}`,
42-
` • Configure ${chalk.cyan('supabase/functions/.env')}`,
4341
'',
4442
` ${chalk.green('✓ Safe to re-run - completed steps will be skipped')}`,
4543
].join('\n');
@@ -92,15 +90,10 @@ export default (program: Command) => {
9290
autoConfirm: true,
9391
});
9492

95-
const envFile = await updateEnvFile({
96-
supabasePath,
97-
autoConfirm: true,
98-
});
99-
10093
// Step 4: Show completion message
10194
const outroMessages: string[] = [];
10295

103-
if (migrations || configUpdate || flowsDirectory || edgeFunction || exampleWorker || envFile) {
96+
if (migrations || configUpdate || flowsDirectory || edgeFunction || exampleWorker) {
10497
outroMessages.push(chalk.green.bold('✓ Installation complete!'));
10598
} else {
10699
outroMessages.push(
@@ -114,7 +107,7 @@ export default (program: Command) => {
114107

115108
let stepNumber = 1;
116109

117-
if (configUpdate || envFile) {
110+
if (configUpdate) {
118111
outroMessages.push(
119112
` ${stepNumber}. Restart Supabase: ${chalk.cyan('supabase stop && supabase start')}`
120113
);

pkgs/cli/src/commands/install/update-env-file.ts

Lines changed: 0 additions & 108 deletions
This file was deleted.

pkgs/edge-worker/src/platform/logging.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export type LoggingEnv = Record<string, string | undefined>;
1515
// ANSI Color Codes (16-color safe palette)
1616
// ============================================================
1717

18+
/**
19+
* Default base delay in seconds for retry exponential backoff.
20+
* Matches pgflow SQL default in create_flow/add_step functions.
21+
*/
22+
const DEFAULT_BASE_DELAY_SECONDS = 5;
23+
1824
const ANSI = {
1925
// Colors
2026
blue: '\x1b[34m',
@@ -117,8 +123,7 @@ class FancyFormatter {
117123
const retryIcon = colorize('↻', ANSI.yellow, this.colorsEnabled);
118124
// Exponential backoff: baseDelay * 2^(attempt-1)
119125
// Example with baseDelay=5: attempt 1 -> 5s, attempt 2 -> 10s, attempt 3 -> 20s
120-
// Default 5s matches pgflow SQL default in create_flow/add_step
121-
const baseDelay = ctx.baseDelay ?? 5;
126+
const baseDelay = ctx.baseDelay ?? DEFAULT_BASE_DELAY_SECONDS;
122127
const delay = baseDelay * Math.pow(2, ctx.retryAttempt - 1);
123128
const retryInfo = colorize(`retry ${ctx.retryAttempt}/${ctx.maxRetries} in ${delay}s`, ANSI.yellow, this.colorsEnabled);
124129
result += `\n${prefix} ${retryIcon} ${retryInfo}`;
@@ -205,9 +210,8 @@ class SimpleFormatter {
205210
let result = `[VERBOSE] worker=${ctx.workerName} queue=${ctx.queueName} flow=${ctx.flowSlug} step=${ctx.stepSlug} status=failed error="${error.message}" run_id=${ctx.runId} msg_id=${ctx.msgId} worker_id=${ctx.workerId}`;
206211

207212
// Add retry info if there are retries remaining (see FancyFormatter for detailed comments)
208-
// Default 5s matches pgflow SQL default in create_flow/add_step
209213
if (ctx.retryAttempt !== undefined && ctx.maxRetries !== undefined && ctx.retryAttempt <= ctx.maxRetries) {
210-
const baseDelay = ctx.baseDelay ?? 5;
214+
const baseDelay = ctx.baseDelay ?? DEFAULT_BASE_DELAY_SECONDS;
211215
const delay = baseDelay * Math.pow(2, ctx.retryAttempt - 1);
212216
result += ` retry=${ctx.retryAttempt}/${ctx.maxRetries} retry_delay_s=${delay}`;
213217
}
@@ -301,7 +305,6 @@ export function createLoggingFactory(env?: LoggingEnv) {
301305
const levelValue =
302306
levels[logLevel as keyof typeof levels] ?? levels.info;
303307
if (levelValue >= levels.debug) {
304-
// Use console.log for debug messages since console.debug isn't available in Supabase
305308
console.debug(
306309
`worker_id=${sharedWorkerId} module=${module} ${message}`,
307310
...args
@@ -312,7 +315,6 @@ export function createLoggingFactory(env?: LoggingEnv) {
312315
const levelValue =
313316
levels[logLevel as keyof typeof levels] ?? levels.info;
314317
if (levelValue >= levels.info) {
315-
// Use console.log for info messages since console.info isn't available in Supabase
316318
console.info(
317319
`worker_id=${sharedWorkerId} module=${module} ${message}`,
318320
...args
@@ -397,7 +399,8 @@ export function createLoggingFactory(env?: LoggingEnv) {
397399
levels[logLevel as keyof typeof levels] ?? levels.info;
398400
if (levelValue >= levels.info) {
399401
const lines = formatter.startupBanner(ctx);
400-
lines.forEach(line => console.info(line));
402+
// Join lines to reduce blank log entries in Supabase Edge Functions
403+
console.info(lines.join('\n'));
401404
}
402405
},
403406

pkgs/website/src/content/docs/deploy/monitor-workers-health.mdx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,28 @@ sidebar:
77

88
## Logging
99

10-
Edge Worker logs various events to the console. You can change the log level
11-
by setting `EDGE_WORKER_LOG_LEVEL` environment variable:
10+
Edge Worker automatically configures logging based on your environment:
11+
- **Local development**: Colorful "fancy" format at `verbose` level
12+
- **Hosted Supabase**: Structured key=value format at `info` level
13+
14+
You can override the log level by setting `EDGE_WORKER_LOG_LEVEL`:
1215

1316
```bash
14-
// supabase/functions/.env
17+
# supabase/functions/.env
1518
EDGE_WORKER_LOG_LEVEL=debug
1619
```
1720

18-
Available log levels are:
21+
Available log levels (from least to most verbose):
1922

20-
- `debug`
21-
- `info`
22-
- `error`
23-
- (more will come)
23+
| Level | Description |
24+
|-------|-------------|
25+
| `error` | Critical failures only |
26+
| `warn` | Deprecation notices, auth failures |
27+
| `info` | Startup banner, shutdown events |
28+
| `verbose` | Task outcomes, polling feedback |
29+
| `debug` | Task started events, detailed identifiers |
2430

25-
By default, Edge Worker's log level is `info`.
31+
For complete logging configuration including output format options and examples, see [Worker Logging Configuration](/reference/configuration/worker/#logging-configuration).
2632

2733
## Heartbeats
2834

pkgs/website/src/content/docs/reference/configuration/worker.mdx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,63 @@ How frequently (in milliseconds) to check the database for new tasks during the
117117

118118
Your flow step handlers can access the resolved worker configuration through the context object. For detailed information about available configuration options and usage patterns, see [`workerConfig`](/reference/context/#workerconfig).
119119

120+
## Logging Configuration
121+
122+
Edge Worker automatically configures logging based on your environment, but you can override the defaults with environment variables.
123+
124+
### Environment Variables
125+
126+
#### `EDGE_WORKER_LOG_LEVEL`
127+
**Type:** `'error' | 'warn' | 'info' | 'verbose' | 'debug'`
128+
129+
**Default:** `'verbose'` (local), `'info'` (hosted)
130+
131+
Controls log verbosity. The hierarchy is: error < warn < info < verbose < debug.
132+
133+
| Level | Description |
134+
|-------|-------------|
135+
| error | Critical failures only |
136+
| warn | Deprecation notices, auth failures |
137+
| info | Startup banner, shutdown (hosted default) |
138+
| verbose | Task outcomes, polling feedback (local default) |
139+
| debug | Task started events, identifiers (run_id, msg_id, worker_id) |
140+
141+
#### `EDGE_WORKER_LOG_FORMAT`
142+
**Type:** `'fancy' | 'simple'`
143+
144+
**Default:** Auto-detected (`'fancy'` for local, `'simple'` for hosted)
145+
146+
- **fancy**: Colored output with icons, worker-prefixed lines, and human-readable formatting for local development
147+
- **simple**: Structured `key=value` pairs for log aggregators in production
148+
149+
#### `NO_COLOR`
150+
**Type:** `string` (any value)
151+
152+
**Default:** `undefined`
153+
154+
Set to any value to disable ANSI colors in fancy mode. Follows the [NO_COLOR standard](https://no-color.org/).
155+
156+
### Log Output Examples
157+
158+
**Fancy format (local development):**
159+
```
160+
➜ my-worker [abc123]
161+
Queue: my_flow
162+
Flows: ✓ myFlow (compiled)
163+
164+
my-worker: ✓ myFlow/fetchData 234ms
165+
my-worker: ✗ myFlow/sendEmail
166+
my-worker: ValidationError: invalid email
167+
my-worker: ↻ retry 1/3 in 5s
168+
```
169+
170+
**Simple format (production):**
171+
```
172+
[INFO] worker=my-worker queue=my_flow flow=myFlow status=compiled worker_id=abc123
173+
[VERBOSE] worker=my-worker flow=myFlow step=fetchData status=completed duration_ms=234
174+
[VERBOSE] worker=my-worker flow=myFlow step=sendEmail status=failed error="ValidationError: invalid email" retry=1/3 retry_delay_s=5
175+
```
176+
120177
## Background Jobs Mode Differences
121178

122179
When using Edge Worker in [Background Jobs Mode](/get-started/faq/#what-are-the-two-edge-worker-modes) (without pgflow orchestration), there are a few key differences:

0 commit comments

Comments
 (0)