11// src/index.ts
2+ // Initialize OpenTelemetry first, before any other imports
3+ import { initTelemetry } from "./telemetry" ;
4+ initTelemetry ( ) ;
5+
26import postgres from "postgres" ;
37import { getSwaggerHtml , getRootPageHtml } from "./html" ;
48import { openapi } from "./openapi" ;
9+ import { trace , context , SpanStatusCode } from "@opentelemetry/api" ;
510
611type Item = { id : number ; name : string } ;
712
@@ -31,19 +36,27 @@ const sql = postgres({
3136
3237// Initialize database schema
3338async function initDatabase ( ) {
34- try {
35- await sql `
36- CREATE TABLE IF NOT EXISTS items (
37- id SERIAL PRIMARY KEY,
38- name VARCHAR(255) NOT NULL,
39- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
40- )
41- ` ;
42- console . log ( "✅ Database initialized successfully" ) ;
43- } catch ( error ) {
44- console . error ( "❌ Failed to initialize database:" , error ) ;
45- throw error ;
46- }
39+ const tracer = trace . getTracer ( "items-service" ) ;
40+ return await tracer . startActiveSpan ( "initDatabase" , async ( span ) => {
41+ try {
42+ await sql `
43+ CREATE TABLE IF NOT EXISTS items (
44+ id SERIAL PRIMARY KEY,
45+ name VARCHAR(255) NOT NULL,
46+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47+ )
48+ ` ;
49+ console . log ( "✅ Database initialized successfully" ) ;
50+ span . setStatus ( { code : SpanStatusCode . OK } ) ;
51+ } catch ( error ) {
52+ console . error ( "❌ Failed to initialize database:" , error ) ;
53+ span . recordException ( error as Error ) ;
54+ span . setStatus ( { code : SpanStatusCode . ERROR , message : String ( error ) } ) ;
55+ throw error ;
56+ } finally {
57+ span . end ( ) ;
58+ }
59+ } ) ;
4760}
4861
4962function json ( data : unknown , init : ResponseInit = { } ) {
@@ -58,63 +71,109 @@ function notFound() {
5871}
5972
6073async function handle ( req : Request ) : Promise < Response > {
74+ const tracer = trace . getTracer ( "items-service" ) ;
6175 const url = new URL ( req . url ) ;
6276 let path = url . pathname ;
6377
64- // Root page
65- if ( path === "/" && req . method === "GET" ) return new Response ( getRootPageHtml ( API_PREFIX ) , { headers : { "content-type" : "text/html; charset=utf-8" } } ) ;
66-
67- // Swagger UI & OpenAPI JSON
68- if ( path === "/docs" && req . method === "GET" ) return new Response ( getSwaggerHtml ( APP_PREFIX ) , { headers : { "content-type" : "text/html; charset=utf-8" } } ) ;
69- if ( path === `${ API_PREFIX } /openapi.json` && req . method === "GET" ) return json ( openapi ) ;
70-
71- // if (!path.startsWith(API_PREFIX)) return notFound();
72- path = path . slice ( API_PREFIX . length ) || "/" ;
73-
74- if ( path === "/health" && req . method === "GET" ) {
75- try {
76- // Check database connection
77- await sql `SELECT 1` ;
78- return json ( { status : "ok" , commit : COMMIT_SHA , database : "connected" } ) ;
79- } catch ( error ) {
80- return json ( { status : "degraded" , commit : COMMIT_SHA , database : "disconnected" , error : String ( error ) } , { status : 503 } ) ;
81- }
82- }
83-
84- if ( path === "/items" && req . method === "GET" ) {
78+ // Create a span for the HTTP request
79+ return await tracer . startActiveSpan ( `${ req . method } ${ path } ` , async ( span ) => {
8580 try {
86- const items = await sql < Item [ ] > `SELECT id, name FROM items ORDER BY id` ;
87- return json ( { items } ) ;
88- } catch ( error ) {
89- console . error ( "Error fetching items:" , error ) ;
90- return json ( { error : "Failed to fetch items" } , { status : 500 } ) ;
91- }
92- }
81+ span . setAttributes ( {
82+ "http.method" : req . method ,
83+ "http.url" : url . toString ( ) ,
84+ "http.target" : path ,
85+ "http.scheme" : url . protocol . replace ( ":" , "" ) ,
86+ "http.host" : url . host ,
87+ } ) ;
88+
89+ let response : Response ;
90+
91+ // Root page
92+ if ( path === "/" && req . method === "GET" ) {
9393 response = new Response ( getRootPageHtml ( ) , { headers : { "content-type" : "text/html; charset=utf-8" } } ) ;
94+ }
95+ // Swagger UI & OpenAPI JSON
96+ else if ( path === "/docs" && req . method === "GET" ) {
97+ response = new Response ( getSwaggerHtml ( APP_PREFIX ) , { headers : { "content-type" : "text/html; charset=utf-8" } } ) ;
98+ }
99+ else if ( path === `${ API_PREFIX } /openapi.json` && req . method === "GET" ) {
100+ response = json ( openapi ) ;
101+ }
102+ else {
103+ // if (!path.startsWith(API_PREFIX)) return notFound();
104+ path = path . slice ( API_PREFIX . length ) || "/" ;
105+
106+ if ( path === "/health" && req . method === "GET" ) {
107+ try {
108+ // Check database connection
109+ await sql `SELECT 1` ;
110+ response = json ( { status : "ok" , commit : COMMIT_SHA , database : "connected" } ) ;
111+ } catch ( error ) {
112+ span . recordException ( error as Error ) ;
113+ span . setStatus ( { code : SpanStatusCode . ERROR , message : String ( error ) } ) ;
114+ response = json ( { status : "degraded" , commit : COMMIT_SHA , database : "disconnected" , error : String ( error ) } , { status : 503 } ) ;
115+ }
116+ }
117+ else if ( path === "/items" && req . method === "GET" ) {
118+ try {
119+ const items = await sql < Item [ ] > `SELECT id, name FROM items ORDER BY id` ;
120+ span . setAttribute ( "items.count" , items . length ) ;
121+ response = json ( { items } ) ;
122+ } catch ( error ) {
123+ console . error ( "Error fetching items:" , error ) ;
124+ span . recordException ( error as Error ) ;
125+ span . setStatus ( { code : SpanStatusCode . ERROR , message : String ( error ) } ) ;
126+ response = json ( { error : "Failed to fetch items" } , { status : 500 } ) ;
127+ }
128+ }
129+ else if ( path === "/items" && req . method === "POST" ) {
130+ try {
131+ const body = ( await req . json ( ) ) as Partial < Item > ;
132+ if ( ! body ?. name || typeof body . name !== "string" ) {
133+ response = json ( { error : "name required" } , { status : 400 } ) ;
134+ } else {
135+ const [ item ] = await sql < Item [ ] > `
136+ INSERT INTO items (name)
137+ VALUES (${ body . name } )
138+ RETURNING id, name
139+ ` ;
140+ span . setAttribute ( "item.id" , item . id ) ;
141+ span . setAttribute ( "item.name" , item . name ) ;
142+ response = json ( item , { status : 201 } ) ;
143+ }
144+ } catch ( error ) {
145+ console . error ( "Error creating item:" , error ) ;
146+ span . recordException ( error as Error ) ;
147+ span . setStatus ( { code : SpanStatusCode . ERROR , message : String ( error ) } ) ;
148+ if ( error instanceof SyntaxError ) {
149+ response = json ( { error : "invalid json" } , { status : 400 } ) ;
150+ } else {
151+ response = json ( { error : "Failed to create item" } , { status : 500 } ) ;
152+ }
153+ }
154+ }
155+ else {
156+ response = notFound ( ) ;
157+ }
158+ }
94159
95- if ( path === "/items" && req . method === "POST" ) {
96- try {
97- const body = ( await req . json ( ) ) as Partial < Item > ;
98- if ( ! body ?. name || typeof body . name !== "string" ) return json ( { error : "name required" } , { status : 400 } ) ;
99- response = new Response ( getRootPageHtml ( APP_PREFIX , API_PREFIX ) , { headers : { "content-type" : "text/html; charset=utf-8" } } ) ;
100-
101- const [ item ] = await sql < Item [ ] > `
102- INSERT INTO items (name)
103- VALUES (${ body . name } )
104- RETURNING id, name
105- ` ;
160+ // Set response status on span
161+ span . setAttribute ( "http.status_code" , response . status ) ;
162+ if ( response . status >= 400 ) {
163+ span . setStatus ( { code : SpanStatusCode . ERROR , message : `HTTP ${ response . status } ` } ) ;
164+ } else {
165+ span . setStatus ( { code : SpanStatusCode . OK } ) ;
166+ }
106167
107- return json ( item , { status : 201 } ) ;
168+ return response ;
108169 } catch ( error ) {
109- console . error ( "Error creating item:" , error ) ;
110- if ( error instanceof SyntaxError ) {
111- return json ( { error : "invalid json" } , { status : 400 } ) ;
112- }
113- return json ( { error : "Failed to create item" } , { status : 500 } ) ;
170+ span . recordException ( error as Error ) ;
171+ span . setStatus ( { code : SpanStatusCode . ERROR , message : String ( error ) } ) ;
172+ throw error ;
173+ } finally {
174+ span . end ( ) ;
114175 }
115- }
116-
117- return notFound ( ) ;
176+ } ) ;
118177}
119178
120179// Initialize database and start server
0 commit comments