mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
This commit addresses all the high-priority tasks outlined in the TASKS.md file, significantly improving the application's observability, completing key features, and refactoring critical parts of the codebase. ### Observability - **Centralized Logging:** Implemented a new structured, context-aware logging system using `zerolog`. A new logging middleware injects request-specific information (request ID, user ID, trace ID) into the logger, and all application logging has been refactored to use this new system. - **Prometheus Metrics:** Added Prometheus metrics for database query performance by creating a GORM plugin that automatically records query latency and totals. - **OpenTelemetry Tracing:** Fully instrumented all application services in `internal/app` and data repositories in `internal/data/sql` with OpenTelemetry tracing, providing deep visibility into application performance. ### Features - **Analytics:** Implemented like, comment, and bookmark counting. The respective command handlers now call the analytics service to increment counters when these actions are performed. - **Enrichment Tool:** Built a new, extensible `enrich` command-line tool to fetch data from external sources. The initial implementation enriches author data using the Open Library API. ### Refactoring & Fixes - **Decoupled Testing:** Refactored the testing utilities in `internal/testutil` to be database-agnostic, promoting the use of mock-based unit tests and improving test speed and reliability. - **Build Fixes:** Resolved numerous build errors, including a critical import cycle between the logging, observability, and authentication packages. - **Search Service:** Fixed the search service integration by implementing the `GetWorkContent` method in the localization service, allowing the search indexer to correctly fetch and index work content.
76 lines
2.1 KiB
Go
76 lines
2.1 KiB
Go
package db
|
|
|
|
import (
|
|
"tercul/internal/observability"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
callBackBeforeName = "prometheus:before"
|
|
callBackAfterName = "prometheus:after"
|
|
startTime = "start_time"
|
|
)
|
|
|
|
type PrometheusPlugin struct {
|
|
Metrics *observability.Metrics
|
|
}
|
|
|
|
func (p *PrometheusPlugin) Name() string {
|
|
return "PrometheusPlugin"
|
|
}
|
|
|
|
func (p *PrometheusPlugin) Initialize(db *gorm.DB) error {
|
|
// Before callbacks
|
|
db.Callback().Create().Before("gorm:create").Register(callBackBeforeName, p.before)
|
|
db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, p.before)
|
|
db.Callback().Update().Before("gorm:update").Register(callBackBeforeName, p.before)
|
|
db.Callback().Delete().Before("gorm:delete").Register(callBackBeforeName, p.before)
|
|
db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, p.before)
|
|
db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, p.before)
|
|
|
|
// After callbacks
|
|
db.Callback().Create().After("gorm:create").Register(callBackAfterName, p.after)
|
|
db.Callback().Query().After("gorm:query").Register(callBackAfterName, p.after)
|
|
db.Callback().Update().After("gorm:update").Register(callBackAfterName, p.after)
|
|
db.Callback().Delete().After("gorm:delete").Register(callBackAfterName, p.after)
|
|
db.Callback().Row().After("gorm:row").Register(callBackAfterName, p.after)
|
|
db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, p.after)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PrometheusPlugin) before(db *gorm.DB) {
|
|
db.Set(startTime, time.Now())
|
|
}
|
|
|
|
func (p *PrometheusPlugin) after(db *gorm.DB) {
|
|
_ts, ok := db.Get(startTime)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
ts, ok := _ts.(time.Time)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
operation := db.Statement.SQL.String()
|
|
if len(operation) > 50 { // Truncate long queries
|
|
operation = operation[:50]
|
|
}
|
|
|
|
status := "success"
|
|
if db.Error != nil {
|
|
status = "error"
|
|
}
|
|
|
|
duration := time.Since(ts).Seconds()
|
|
p.Metrics.DBQueryDuration.WithLabelValues(operation, status).Observe(duration)
|
|
p.Metrics.DBQueriesTotal.WithLabelValues(operation, status).Inc()
|
|
}
|
|
|
|
func NewPrometheusPlugin(metrics *observability.Metrics) *PrometheusPlugin {
|
|
return &PrometheusPlugin{Metrics: metrics}
|
|
} |