Skip to content

Commit ed9e4a0

Browse files
authored
Merge pull request #2663 from ankaboot-source/fix/standardize-edge-function-logging
refactor: standardize Edge Function logging with structured JSON logger
2 parents fd292d3 + eb541c8 commit ed9e4a0

File tree

4 files changed

+244
-128
lines changed

4 files changed

+244
-128
lines changed

AGENTS.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,47 @@ try {
185185
- Include context (user ID, operation) in log messages
186186
- Use appropriate levels: `error`, `warn`, `info`, `debug`
187187

188+
### Edge Function Logging (Deno)
189+
190+
**Use the shared structured logger instead of raw console.log:**
191+
192+
```typescript
193+
import { createLogger } from "../_shared/logger.ts";
194+
195+
const logger = createLogger("service-name");
196+
197+
// Structured JSON logging with context
198+
logger.debug("Debug information", { userId: "123" });
199+
logger.info("Operation completed", { userId: "123", duration: 150 });
200+
logger.warn("Unexpected state", { details: context });
201+
logger.error("Operation failed", { error: error.message });
202+
```
203+
204+
**Best Practices:**
205+
206+
- Output structured JSON for log aggregators (CloudWatch, Datadog)
207+
- Control verbosity with `LOG_LEVEL` environment variable
208+
- Use English only for developer/operator logs (not user-facing)
209+
- Always include relevant context (user ID, operation, IDs)
210+
- Never log sensitive data (passwords, tokens, PII)
211+
- Use appropriate levels:
212+
- `debug`: Development-only, detailed troubleshooting
213+
- `info`: Operational events (token refresh, email sent)
214+
- `warn`: Recoverable issues, unexpected states
215+
- `error`: Failures requiring attention
216+
217+
**Example Output:**
218+
219+
```json
220+
{
221+
"timestamp": "2026-03-02T14:30:00.000Z",
222+
"service": "email-campaigns",
223+
"level": "info",
224+
"message": "OAuth token refreshed",
225+
"email": "user@example.com"
226+
}
227+
```
228+
188229
### Testing
189230

190231
- **Jest** for backend and emails-fetcher
@@ -223,3 +264,24 @@ try {
223264
- Node.js >= 20.0.0 required
224265
- Bun >= 1.1.0 required for emails-fetcher
225266
- Docker and Docker Compose required for local dev
267+
268+
## Planning Documents
269+
270+
### docs/plans Directory
271+
272+
The `docs/plans/` directory contains local planning documents and implementation notes created during development. These files:
273+
274+
- **Should NOT be pushed to the remote repository**
275+
- Are meant for local development reference only
276+
- Help track implementation details and decisions
277+
278+
If you create plan documents in `docs/plans/`, they should remain local to your machine and not be shared with the team through git.
279+
280+
### Creating Plan Documents
281+
282+
When creating implementation plans:
283+
284+
1. Save them to `docs/plans/YYYY-MM-DD-<feature-name>.md`
285+
2. Keep them local - do not commit to git
286+
3. Use them for local reference during development
287+
4. Delete them once the feature is complete if desired
Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,85 @@
1-
const getCurrentDate = () => {
2-
const date = new Date();
3-
const day = date.getDate();
1+
/**
2+
* Structured logger for Supabase Edge Functions
3+
*
4+
* Deno best practices:
5+
* - Output structured JSON for log aggregators (CloudWatch, Datadog, etc.)
6+
* - Use LOG_LEVEL environment variable to control verbosity
7+
* - Consistent ISO 8601 timestamps (UTC)
8+
* - English only for developer/operator logs
9+
* - Include service name for tracing
10+
*/
411

5-
const month = date.getMonth() + 1;
12+
export type LogLevel = "debug" | "info" | "warn" | "error";
613

7-
const year = date.getFullYear();
14+
export interface LogContext {
15+
[key: string]: unknown;
16+
}
817

9-
return `[${year}-${month}-${day} ${new Date(date).toLocaleTimeString()}]`;
18+
const LOG_LEVELS: Record<LogLevel, number> = {
19+
debug: 0,
20+
info: 1,
21+
warn: 2,
22+
error: 3,
1023
};
1124

12-
const Logger = {
13-
error: (message: string) => {
14-
console.error(
15-
`${getCurrentDate()} %c${message}`,
16-
"color: red",
17-
);
18-
},
19-
info: (message: string) => {
20-
console.log(
21-
`${getCurrentDate()} ${message}`,
22-
);
23-
},
24-
};
25+
function getLogLevel(): LogLevel {
26+
const envLevel = Deno.env.get("LOG_LEVEL")?.toLowerCase() as LogLevel;
27+
if (envLevel && envLevel in LOG_LEVELS) {
28+
return envLevel;
29+
}
30+
// Default to info in production, debug in development
31+
return Deno.env.get("DENO_ENV") === "development" ? "debug" : "info";
32+
}
33+
34+
function shouldLog(level: LogLevel): boolean {
35+
return LOG_LEVELS[level] >= LOG_LEVELS[getLogLevel()];
36+
}
37+
38+
export function createLogger(service: string) {
39+
function log(level: LogLevel, message: string, context?: LogContext): void {
40+
if (!shouldLog(level)) {
41+
return;
42+
}
43+
44+
const entry = {
45+
timestamp: new Date().toISOString(),
46+
service,
47+
level,
48+
message,
49+
...context,
50+
};
51+
52+
const output = JSON.stringify(entry);
53+
54+
switch (level) {
55+
case "error":
56+
console.error(output);
57+
break;
58+
case "warn":
59+
console.warn(output);
60+
break;
61+
case "debug":
62+
console.debug(output);
63+
break;
64+
default:
65+
console.log(output);
66+
}
67+
}
68+
69+
return {
70+
debug: (message: string, context?: LogContext) =>
71+
log("debug", message, context),
72+
info: (message: string, context?: LogContext) =>
73+
log("info", message, context),
74+
warn: (message: string, context?: LogContext) =>
75+
log("warn", message, context),
76+
error: (message: string, context?: LogContext) =>
77+
log("error", message, context),
78+
};
79+
}
80+
81+
export type Logger = ReturnType<typeof createLogger>;
2582

26-
export default Logger;
83+
// Default logger instance for simple imports
84+
const defaultLogger = createLogger("edge-function");
85+
export default defaultLogger;

0 commit comments

Comments
 (0)