agentsclimarketplace

Code logging

Skill jjmendezrodriguez/jm-claude-plugin/skills/code-logging

Production logging best practices for TypeScript applications with pino (server) and PostHog (browser analytics + session replay). Use this when adding logs, choosing log levels, deciding what to record or redact, structuring log payloads, correlating requests, or setting up observability for Edge Functions. Trigger on: "how should I log this?", "console.log is fine, right?", "add observability", any new service / API endpoint / error handler, any debugging session that needs traces. Apply proactively — logs are the production debugger.From its SKILL.md

Install
npx -y skills add jjmendezrodriguez/jm-claude-plugin --skill code-logging

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.0 KB, as published. Nobody here has run it

Logging — TypeScript / pino / PostHog

Staff Engineer standard: logs are your production debugger — structured, contextual, and never leaking secrets.


1. Golden Rule

// ❌ NEVER in production code
console.log("User logged in:", user);

// ✅ ALWAYS use a structured logger
logger.info("user_logged_in", { userId: user.id, method: "email" });

console.log lacks levels, structure, redaction, and shipping. It only works on your laptop.


2. Recommended Stack

RuntimeLibraryWhy
Node / BunpinoFastest structured JSON, low overhead, async transports
BrowserPostHogAnalytics + session replay + error tracking in one
Edge Functionsconsole + PostHogDeno has no pino-pretty; ship via PostHog HTTP API
AggregationBetter Stack / Datadog / LokiQuery, alert, retain

3. Log Levels

LevelWhenExample
traceHyper-detailed, dev only"entering function parseForm"
debugDev diagnostics"parsing form data"
infoSignificant business events"user_signed_up", "payment_processed"
warnAttention needed, not broken"rate_limit_approaching"
errorOperation failed"supabase_query_failed"
fatalProcess may crash / restart"database_connection_lost"

Rule: if you wouldn't page someone on it, it's info or lower. If you would, it's error+.


4. What to Log / Never Log

✅ ALWAYS LOG:                    ❌ NEVER LOG:
- User ID (uuid, not email)        - Passwords, tokens, API keys
- Action / event name              - Credit card numbers, SSN, PII
- Timestamp (ISO 8601)             - Full request bodies w/ secrets
- Request ID (trace correlation)   - Session tokens / JWTs
- Error code + message             - Raw user input (sanitize first)
- Duration (perf tracking)         - Authorization headers
- Feature flag state               - Email addresses (use userId)

If unsure, don't log it. PII leaks compound — every log sink amplifies the breach.


5. Logger Setup (pino)

// src/lib/logger.ts
import pino from "pino";

export const logger = pino({
  level: process.env.LOG_LEVEL ?? (import.meta.env.DEV ? "debug" : "info"),

  // Redact sensitive paths automatically — even if someone passes them by accident
  redact: {
    paths: [
      "password",
      "*.password",
      "token",
      "*.token",
      "authorization",
      "*.authorization",
      "req.headers.cookie",
    ],
    censor: "[REDACTED]",
  },

  // Pretty in dev, JSON in prod
  transport: import.meta.env.DEV
    ? { target: "pino-pretty", options: { colorize: true } }
    : undefined,

  // Standard base fields on every log
  base: {
    service: "auth-api",
    env: process.env.NODE_ENV,
  },
});

6. Structured Logging Pattern

Convention: event name first as string, context object second. Matches pino's signature and reads naturally.

// ✅ Pattern: logger.<level>(event_name, context)
logger.info("profile_updated", { userId, durationMs: 42 });

logger.error("supabase_query_failed", {
  err,                  // pino auto-serializes Error.stack
  userId,
  query: "users.select",
  retries: 3,
});

// ❌ String concatenation — unsearchable, no fields to filter on
logger.info(`User ${userId} updated profile in ${duration}ms`);

// ❌ Object first then message — works in pino but inconsistent style
logger.info({ userId }, "User logged in");

Why event-name-first:

  • Searchable via event:"profile_updated" filter in any log tool
  • Stable identifier even when context changes
  • Reads like a domain event, not a sentence

7. Request Correlation

Every request through your system should carry the same requestId. Without it, debugging a distributed flow is guessing.

// Generate at the edge (or trust X-Request-Id if upstream sends it)
const requestId = req.headers.get("x-request-id") ?? crypto.randomUUID();

const log = logger.child({ requestId, userId: session?.user.id });

log.info("request_started", { method: req.method, path: url.pathname });
// ... handler logic
log.info("request_completed", { status: 200, durationMs: 124 });

logger.child({...}) returns a logger where every line includes the parent fields. Use it per-request.


8. Edge Functions (Deno / Supabase)

// supabase/functions/_shared/log.ts
const POSTHOG_KEY = Deno.env.get("POSTHOG_API_KEY")!;

export async function logEvent(event: string, props: Record<string, unknown>) {
  // Always echo to console for Supabase log drain
  console.log(JSON.stringify({ event, ...props, ts: new Date().toISOString() }));

  // Ship to PostHog for analytics
  await fetch("https://app.posthog.com/capture/", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: POSTHOG_KEY,
      event,
      properties: props,
      timestamp: new Date().toISOString(),
    }),
  }).catch(() => {});  // never let logging take down the request
}

Rule: logging failures must never break the request. Wrap shipping calls in try/catch or .catch(() => {}).


9. Browser (PostHog)

// src/lib/analytics.ts
import posthog from "posthog-js";

posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
  api_host: "https://app.posthog.com",
  capture_pageview: true,
  session_recording: { maskAllInputs: true },  // mask form fields by default
});

// Usage — same event-name + props shape as server
posthog.capture("checkout_completed", { orderId, valueCents });

Rules:

  • maskAllInputs: true for session recording — never replay passwords
  • Identify users on auth: posthog.identify(userId, { plan: "pro" })
  • Reset on logout: posthog.reset() — prevents cross-account leak

10. Quick Checklist

[ ] No console.log in production code (lint rule: no-console)
[ ] Logger configured with redact paths for secrets
[ ] Structured JSON in prod, pretty in dev
[ ] Event name first, context object second
[ ] requestId on every request, propagated via logger.child()
[ ] No PII (passwords, emails, tokens, SSN) in logs
[ ] Errors logged with .err (auto-serialized stack)
[ ] PostHog inputs masked for session replay
[ ] Logging failures swallowed — never break the request

Keep looking

Skills are one crate of 626,474. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.