Skip to content

Instantly share code, notes, and snippets.

@howbizarre
Created October 1, 2025 13:55
Show Gist options
  • Save howbizarre/2643b54a2af7c9494f8befe1fd1dd8ba to your computer and use it in GitHub Desktop.
Save howbizarre/2643b54a2af7c9494f8befe1fd1dd8ba to your computer and use it in GitHub Desktop.
Cloudflare Tail Worker
// hows-logger - Tail Worker за събиране на логове
import type { ProcessedLogEntry, TraceItem } from './types';
type ConsoleMethod = 'log' | 'error';
const toIsoString = (timestamp?: number | null): string => {
if (!timestamp) {
return new Date().toISOString();
}
try {
return new Date(timestamp).toISOString();
} catch {
return new Date().toISOString();
}
};
const safeData = (value: unknown) => {
try {
return JSON.parse(JSON.stringify(value));
} catch {
return '[unserializable]';
}
};
const emitEntry = (entry: ProcessedLogEntry, method: ConsoleMethod = 'log') => {
const prefix = entry.type.toUpperCase();
console[method](`[${prefix}] ${JSON.stringify(entry)}`);
};
const handleTraceItem = (trace: TraceItem) => {
const {
logs = [],
outcome,
scriptName,
eventTimestamp,
exceptions = [],
diagnosticsChannelEvents = [],
event
} = trace;
const summary = {
scriptName,
outcome,
eventTimestamp: toIsoString(eventTimestamp),
logsCount: logs.length,
exceptionsCount: exceptions.length,
diagnosticsCount: diagnosticsChannelEvents.length
};
console.log(`[EVENT] ${JSON.stringify(summary)}`);
logs.forEach(({ timestamp, level, message }) => {
emitEntry({
type: 'log',
timestamp: toIsoString(timestamp),
level,
message,
scriptName,
outcome
});
});
exceptions.forEach(({ timestamp, name, message }) => {
emitEntry({
type: 'exception',
timestamp: toIsoString(timestamp),
scriptName,
name,
message
}, 'error');
});
diagnosticsChannelEvents.forEach(({ timestamp, channel, message }) => {
emitEntry({
type: 'diagnostic',
timestamp: toIsoString(timestamp),
scriptName,
channel,
message
});
});
if (event) {
emitEntry({
type: 'event_info',
timestamp: toIsoString(eventTimestamp),
scriptName,
message: safeData(event),
eventType: typeof event
});
}
};
export default {
async tail(events: TraceItem[], _env: unknown, _ctx: unknown) {
if (!events || events.length === 0) {
return;
}
console.log(`[TAIL] Получени ${events.length} събития`);
for (const trace of events) {
try {
handleTraceItem(trace);
} catch (error) {
console.error(`[TAIL_ERROR] Грешка при обработка на събитие: ${error instanceof Error ? error.message : String(error)}`);
}
}
console.log(`[TAIL] Завършена обработка на ${events.length} събития`);
}
} satisfies ExportedHandler;
// types.ts - Документация на типовете за tail worker
// Тези типове са вградени в @cloudflare/workers-types, но ги документираме тук за справка
/**
* Основен интерфейс за tail worker handler
*/
export interface TailHandler {
tail(events: TraceItem[], env: Env, ctx: ExecutionContext): Promise<void>;
}
/**
* Основно събитие в tail worker-а
*/
export interface TraceItem {
/** Име на worker-а, който генерира събитието */
scriptName: string | null;
/** Резултат от изпълнението: "ok", "exception", "canceled" */
outcome: string;
/** Timestamp на събитието */
eventTimestamp: number | null;
/** Информация за HTTP заявка или друго събитие */
event: any; // Реалният тип е по-сложен union type
/** Масив от логове (console.log, console.error и т.н.) */
logs: TraceLog[];
/** Масив от изключения и грешки */
exceptions: TraceException[];
/** Масив от diagnostic channel events */
diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];
}
/**
* Лог запис от console.log, console.error и т.н.
*/
export interface TraceLog {
/** Timestamp на лога */
timestamp: number;
/** Ниво на лога: "log", "error", "warn", "info" */
level: string;
/** Съдържание на лога (може да е всякакъв тип) */
message: any;
}
/**
* JavaScript изключение или грешка
*/
export interface TraceException {
/** Timestamp на грешката */
timestamp: number;
/** Име на грешката (напр. "Error", "TypeError") */
name: string;
/** Съобщение за грешката */
message: string;
}
/**
* Diagnostic channel event
*/
export interface TraceDiagnosticChannelEvent {
/** Timestamp на събитието */
timestamp: number;
/** Име на diagnostic channel-а */
channel: string;
/** Данни от diagnostic event */
message: any;
}
/**
* Формат на обработените логове в нашия worker
*/
export interface ProcessedLogEntry {
type: 'log' | 'exception' | 'diagnostic' | 'event_info';
timestamp: string; // ISO string
scriptName: string | null;
level?: string;
message: any;
outcome?: string;
channel?: string; // само за diagnostic events
name?: string; // само за exceptions
eventType?: string; // само за event_info
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment