Skip to content

Commit 18bfe2c

Browse files
Merge pull request #11 from nextinterfaces/adds-logger
Adds logger
2 parents 552af09 + 3e53a38 commit 18bfe2c

File tree

11 files changed

+402
-36
lines changed

11 files changed

+402
-36
lines changed

apps/items-service/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Items Service
2+
3+
A simple REST API service for managing items, built with Bun and PostgreSQL.
4+
5+
## Features
6+
7+
- **RESTful API** for CRUD operations on items
8+
- **PostgreSQL** database integration
9+
- **Health check** endpoint with database connectivity status
10+
- **OpenAPI/Swagger** documentation
11+
- **OpenTelemetry** distributed tracing support
12+
- **Structured Logging** with Pino for high-performance logging
13+
- **Hot reload** in development mode
14+
- **Docker** support with development and production configurations
15+
16+
## API Endpoints
17+
18+
### Health Check
19+
- `GET /v1/health` - Check service and database health
20+
- `GET /v1/items` - List all items
21+
- `POST /v1/items` - Create a new item
22+
- `GET /docs` - Swagger UI for interactive API documentation
23+
- `GET /v1/openapi.json` - OpenAPI specification
24+
25+
## Local Development
26+
27+
### Using Tilt
28+
```bash
29+
task local:setup
30+
task local:start
31+
```
32+
33+
Access the service at:
34+
- Service: http://localhost:8081
35+
- Health Check: http://localhost:8081/v1/health
36+
- API Docs: http://localhost:8081/docs
37+
38+
## Technology Stack
39+
40+
- **Runtime**: [Bun](https://bun.sh/) - Fast JavaScript runtime
41+
- **Database**: PostgreSQL with [postgres.js](https://github.com/porsager/postgres)
42+
- **Logging**: [Pino](https://getpino.io/) - High-performance structured logging
43+
- **Observability**: [OpenTelemetry](https://opentelemetry.io/) - Distributed tracing
44+
- **Container**: Docker with Alpine Linux
45+
- **Orchestration**: Kubernetes (k3s)
46+
- **Local Development**: Task and Tilt with k3d
47+

apps/items-service/bun.lock

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"@opentelemetry/resources": "^2.2.0",
1111
"@opentelemetry/sdk-node": "^0.207.0",
1212
"@opentelemetry/semantic-conventions": "^1.37.0",
13+
"pino": "^10.1.0",
14+
"pino-pretty": "^13.1.2",
1315
"postgres": "^3.4.4",
1416
},
1517
"devDependencies": {
@@ -178,6 +180,8 @@
178180

179181
"@opentelemetry/sql-common": ["@opentelemetry/[email protected]", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="],
180182

183+
"@pinojs/redact": ["@pinojs/[email protected]", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
184+
181185
"@protobufjs/aspromise": ["@protobufjs/[email protected]", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
182186

183187
"@protobufjs/base64": ["@protobufjs/[email protected]", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
@@ -230,6 +234,8 @@
230234

231235
"ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
232236

237+
"atomic-sleep": ["[email protected]", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
238+
233239
"bignumber.js": ["[email protected]", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
234240

235241
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
@@ -242,16 +248,26 @@
242248

243249
"color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
244250

251+
"colorette": ["[email protected]", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
252+
245253
"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
246254

255+
"dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
256+
247257
"debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
248258

249259
"emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
250260

261+
"end-of-stream": ["[email protected]", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
262+
251263
"escalade": ["[email protected]", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
252264

253265
"extend": ["[email protected]", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
254266

267+
"fast-copy": ["[email protected]", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="],
268+
269+
"fast-safe-stringify": ["[email protected]", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
270+
255271
"forwarded-parse": ["[email protected]", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
256272

257273
"gaxios": ["[email protected]", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="],
@@ -262,6 +278,8 @@
262278

263279
"google-logging-utils": ["[email protected]", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
264280

281+
"help-me": ["[email protected]", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
282+
265283
"https-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
266284

267285
"import-in-the-middle": ["[email protected]", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A=="],
@@ -270,24 +288,40 @@
270288

271289
"is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
272290

291+
"joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
292+
273293
"json-bigint": ["[email protected]", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
274294

275295
"lodash.camelcase": ["[email protected]", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
276296

277297
"long": ["[email protected]", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
278298

299+
"minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
300+
279301
"module-details-from-path": ["[email protected]", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
280302

281303
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
282304

283305
"node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
284306

307+
"on-exit-leak-free": ["[email protected]", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
308+
309+
"once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
310+
285311
"pg-int8": ["[email protected]", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
286312

287313
"pg-protocol": ["[email protected]", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
288314

289315
"pg-types": ["[email protected]", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
290316

317+
"pino": ["[email protected]", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w=="],
318+
319+
"pino-abstract-transport": ["[email protected]", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
320+
321+
"pino-pretty": ["[email protected]", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^3.0.2", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ=="],
322+
323+
"pino-std-serializers": ["[email protected]", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="],
324+
291325
"postgres": ["[email protected]", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
292326

293327
"postgres-array": ["[email protected]", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
@@ -298,16 +332,36 @@
298332

299333
"postgres-interval": ["[email protected]", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
300334

335+
"process-warning": ["[email protected]", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
336+
301337
"protobufjs": ["[email protected]", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
302338

339+
"pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
340+
341+
"quick-format-unescaped": ["[email protected]", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
342+
343+
"real-require": ["[email protected]", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
344+
303345
"require-directory": ["[email protected]", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
304346

305347
"require-in-the-middle": ["[email protected]", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
306348

349+
"safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
350+
351+
"secure-json-parse": ["[email protected]", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
352+
353+
"sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
354+
355+
"split2": ["[email protected]", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
356+
307357
"string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
308358

309359
"strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
310360

361+
"strip-json-comments": ["[email protected]", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],
362+
363+
"thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
364+
311365
"tr46": ["[email protected]", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
312366

313367
"undici-types": ["[email protected]", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@@ -320,6 +374,8 @@
320374

321375
"wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
322376

377+
"wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
378+
323379
"xtend": ["[email protected]", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
324380

325381
"y18n": ["[email protected]", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],

apps/items-service/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"@opentelemetry/resources": "^2.2.0",
1515
"@opentelemetry/sdk-node": "^0.207.0",
1616
"@opentelemetry/semantic-conventions": "^1.37.0",
17+
"pino": "^10.1.0",
18+
"pino-pretty": "^13.1.2",
1719
"postgres": "^3.4.4"
1820
},
1921
"devDependencies": {

apps/items-service/src/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ export interface ServerConfig {
2121
commitSha: string;
2222
}
2323

24+
export interface LoggingConfig {
25+
level: string;
26+
pretty: boolean;
27+
}
28+
2429
export interface Config {
2530
server: ServerConfig;
2631
database: DatabaseConfig;
32+
logging: LoggingConfig;
2733
}
2834

2935
export function loadConfig(): Config {
36+
const nodeEnv = process.env.NODE_ENV || "development";
37+
3038
return {
3139
server: {
3240
port: Number(process.env.PORT || 8080),
@@ -44,6 +52,10 @@ export function loadConfig(): Config {
4452
idleTimeout: 20,
4553
connectTimeout: 10,
4654
},
55+
logging: {
56+
level: process.env.LOG_LEVEL || "info",
57+
pretty: nodeEnv === "development" || process.env.LOG_PRETTY === "true",
58+
},
4759
};
4860
}
4961

apps/items-service/src/controllers.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { trace, SpanStatusCode } from "@opentelemetry/api";
77
import { ItemsRepository } from "./database.js";
88
import type { CreateItemDto, HealthResponse, ItemsListResponse } from "./models.js";
99
import { json, badRequest, internalServerError, serviceUnavailable } from "./http-utils.js";
10+
import { createLoggerWithTrace, logError } from "./logger.js";
1011

1112
export class HealthController {
1213
constructor(
@@ -19,7 +20,7 @@ export class HealthController {
1920
return await tracer.startActiveSpan("healthCheck", async (span) => {
2021
try {
2122
const isHealthy = await this.repository.healthCheck();
22-
23+
2324
if (isHealthy) {
2425
const response: HealthResponse = {
2526
status: "ok",
@@ -29,6 +30,9 @@ export class HealthController {
2930
span.setStatus({ code: SpanStatusCode.OK });
3031
return json(response);
3132
} else {
33+
const log = createLoggerWithTrace();
34+
log.warn("Health check failed: database disconnected");
35+
3236
const response: HealthResponse = {
3337
status: "degraded",
3438
commit: this.commitSha,
@@ -38,9 +42,10 @@ export class HealthController {
3842
return serviceUnavailable(response);
3943
}
4044
} catch (error) {
45+
logError(error, "Health check failed with exception");
4146
span.recordException(error as Error);
4247
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
43-
48+
4449
const response: HealthResponse = {
4550
status: "degraded",
4651
commit: this.commitSha,
@@ -65,11 +70,14 @@ export class ItemsController {
6570
const items = await this.repository.findAll();
6671
span.setAttribute("items.count", items.length);
6772
span.setStatus({ code: SpanStatusCode.OK });
68-
73+
74+
const log = createLoggerWithTrace({ itemCount: items.length });
75+
log.debug("Items fetched successfully");
76+
6977
const response: ItemsListResponse = { items };
7078
return json(response);
7179
} catch (error) {
72-
console.error("Error fetching items:", error);
80+
logError(error, "Error fetching items");
7381
span.recordException(error as Error);
7482
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
7583
return internalServerError("Failed to fetch items");
@@ -87,27 +95,34 @@ export class ItemsController {
8795
try {
8896
body = (await req.json()) as Partial<CreateItemDto>;
8997
} catch (error) {
98+
const log = createLoggerWithTrace();
99+
log.warn({ error: String(error) }, "Invalid JSON in create item request");
90100
span.recordException(error as Error);
91101
span.setStatus({ code: SpanStatusCode.ERROR, message: "Invalid JSON" });
92102
span.end();
93103
return badRequest("invalid json");
94104
}
95105

96106
if (!body?.name || typeof body.name !== "string") {
107+
const log = createLoggerWithTrace();
108+
log.warn("Create item request missing required field: name");
97109
span.setStatus({ code: SpanStatusCode.ERROR, message: "Name required" });
98110
span.end();
99111
return badRequest("name required");
100112
}
101113

102114
const item = await this.repository.create({ name: body.name });
103-
115+
104116
span.setAttribute("item.id", item.id);
105117
span.setAttribute("item.name", item.name);
106118
span.setStatus({ code: SpanStatusCode.OK });
107-
119+
120+
const log = createLoggerWithTrace({ itemId: item.id, itemName: item.name });
121+
log.info("Item created successfully");
122+
108123
return json(item, { status: 201 });
109124
} catch (error) {
110-
console.error("Error creating item:", error);
125+
logError(error, "Error creating item");
111126
span.recordException(error as Error);
112127
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
113128
return internalServerError("Failed to create item");

0 commit comments

Comments
 (0)