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 docs/plan-reorganization 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}