WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
at main 100 lines 3.2 kB view raw
1import type { ExportResult } from "@opentelemetry/core"; 2import { ExportResultCode } from "@opentelemetry/core"; 3import type { LogRecordExporter, ReadableLogRecord } from "@opentelemetry/sdk-logs"; 4import { SeverityNumber } from "@opentelemetry/api-logs"; 5import { 6 ATTR_SERVICE_NAME, 7 ATTR_SERVICE_VERSION, 8} from "@opentelemetry/semantic-conventions"; 9 10const SEVERITY_TEXT: Record<number, string> = { 11 [SeverityNumber.DEBUG]: "debug", 12 [SeverityNumber.DEBUG2]: "debug", 13 [SeverityNumber.DEBUG3]: "debug", 14 [SeverityNumber.DEBUG4]: "debug", 15 [SeverityNumber.INFO]: "info", 16 [SeverityNumber.INFO2]: "info", 17 [SeverityNumber.INFO3]: "info", 18 [SeverityNumber.INFO4]: "info", 19 [SeverityNumber.WARN]: "warn", 20 [SeverityNumber.WARN2]: "warn", 21 [SeverityNumber.WARN3]: "warn", 22 [SeverityNumber.WARN4]: "warn", 23 [SeverityNumber.ERROR]: "error", 24 [SeverityNumber.ERROR2]: "error", 25 [SeverityNumber.ERROR3]: "error", 26 [SeverityNumber.ERROR4]: "error", 27 [SeverityNumber.FATAL]: "fatal", 28 [SeverityNumber.FATAL2]: "fatal", 29 [SeverityNumber.FATAL3]: "fatal", 30 [SeverityNumber.FATAL4]: "fatal", 31}; 32 33/** 34 * Exports OTel log records as newline-delimited JSON to stdout. 35 * 36 * Output format: 37 * {"timestamp":"2026-02-23T12:00:00.000Z","level":"info","message":"Server started","service":"atbb-appview","port":3000} 38 * 39 * Compatible with standard log aggregation tools (ELK, Grafana Loki, Datadog, etc.). 40 */ 41export class StructuredLogExporter implements LogRecordExporter { 42 export( 43 records: ReadableLogRecord[], 44 resultCallback: (result: ExportResult) => void 45 ): void { 46 try { 47 for (const record of records) { 48 const entry = this.formatRecord(record); 49 process.stdout.write(JSON.stringify(entry) + "\n"); 50 } 51 resultCallback({ code: ExportResultCode.SUCCESS }); 52 } catch { 53 resultCallback({ code: ExportResultCode.FAILED }); 54 } 55 } 56 57 shutdown(): Promise<void> { 58 return Promise.resolve(); 59 } 60 61 private formatRecord(record: ReadableLogRecord): Record<string, unknown> { 62 const resourceAttrs = record.resource?.attributes ?? {}; 63 const logAttrs = record.attributes ?? {}; 64 65 // Build the base log entry 66 const entry: Record<string, unknown> = { 67 timestamp: this.hrTimeToISO(record.hrTime), 68 level: SEVERITY_TEXT[record.severityNumber ?? SeverityNumber.INFO] ?? "info", 69 message: record.body ?? "", 70 }; 71 72 // Add service metadata from resource 73 const service = resourceAttrs[ATTR_SERVICE_NAME]; 74 if (service) { 75 entry.service = service; 76 } 77 const version = resourceAttrs[ATTR_SERVICE_VERSION]; 78 if (version) { 79 entry.version = version; 80 } 81 const environment = resourceAttrs["deployment.environment.name"]; 82 if (environment) { 83 entry.environment = environment; 84 } 85 86 // Spread user-provided attributes into the top level 87 for (const [key, value] of Object.entries(logAttrs)) { 88 if (value !== undefined && value !== null) { 89 entry[key] = value; 90 } 91 } 92 93 return entry; 94 } 95 96 private hrTimeToISO(hrTime: [number, number]): string { 97 const ms = hrTime[0] * 1000 + hrTime[1] / 1_000_000; 98 return new Date(ms).toISOString(); 99 } 100}