@gravito/monitor 3.0.1 โ 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -220
- package/README.zh-TW.md +170 -0
- package/dist/index.cjs +70 -2
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +70 -2
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
# @gravito/monitor
|
|
1
|
+
# @gravito/monitor ๐ฐ๏ธ
|
|
2
2
|
|
|
3
|
-
Lightweight observability module for Gravito - Health Checks, Metrics, and Tracing.
|
|
3
|
+
Lightweight observability module for Gravito - Health Checks, Metrics, and Tracing. Built on top of the **Galaxy Architecture**, this Orbit provides essential infrastructure for monitoring your planet's health.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
5
|
+
## ๐ Features
|
|
6
6
|
|
|
7
|
-
- ๐ฅ **Health Checks** - Kubernetes-ready `/health`, `/ready`, `/live` endpoints
|
|
8
|
-
- ๐ **Metrics** - Prometheus-compatible `/metrics` endpoint
|
|
9
|
-
- ๐ **Tracing** - OpenTelemetry OTLP support
|
|
7
|
+
- ๐ฅ **Health Checks** - Kubernetes-ready `/health`, `/ready`, `/live` endpoints with custom check support.
|
|
8
|
+
- ๐ **Metrics** - Prometheus-compatible `/metrics` endpoint with built-in Node.js runtime and HTTP metrics.
|
|
9
|
+
- ๐ **Tracing** - OpenTelemetry OTLP support for distributed tracing across services.
|
|
10
|
+
- ๐ก๏ธ **Kubernetes Native** - Seamless integration with probe configurations and Prometheus ServiceMonitors.
|
|
10
11
|
|
|
11
|
-
## Installation
|
|
12
|
+
## ๐ฆ Installation
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
15
|
bun add @gravito/monitor
|
|
@@ -20,7 +21,9 @@ For OpenTelemetry tracing (optional):
|
|
|
20
21
|
bun add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
## Quick Start
|
|
24
|
+
## ๐ Quick Start
|
|
25
|
+
|
|
26
|
+
Enable observability by adding the `MonitorOrbit` to your `PlanetCore`.
|
|
24
27
|
|
|
25
28
|
```typescript
|
|
26
29
|
import { PlanetCore } from '@gravito/core'
|
|
@@ -32,274 +35,136 @@ core.orbit(new MonitorOrbit({
|
|
|
32
35
|
health: {
|
|
33
36
|
enabled: true,
|
|
34
37
|
path: '/health',
|
|
35
|
-
readyPath: '/ready',
|
|
36
|
-
livePath: '/live',
|
|
37
38
|
},
|
|
38
39
|
metrics: {
|
|
39
40
|
enabled: true,
|
|
40
|
-
path: '/metrics',
|
|
41
41
|
prefix: 'myapp_',
|
|
42
42
|
},
|
|
43
43
|
tracing: {
|
|
44
|
-
enabled:
|
|
45
|
-
serviceName: '
|
|
46
|
-
endpoint: 'http://localhost:4318/v1/traces',
|
|
44
|
+
enabled: process.env.NODE_ENV === 'production',
|
|
45
|
+
serviceName: 'order-service',
|
|
47
46
|
},
|
|
48
47
|
}))
|
|
49
48
|
|
|
50
49
|
await core.liftoff()
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
## Health Checks
|
|
52
|
+
## ๐ฅ Health Checks
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
The health system provides three distinct probes following Kubernetes best practices:
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
| `GET /ready` | Kubernetes readiness probe |
|
|
61
|
-
| `GET /live` | Kubernetes liveness probe |
|
|
56
|
+
- **Liveness (`/live`)**: Indicates if the process is running.
|
|
57
|
+
- **Readiness (`/ready`)**: Indicates if the app is ready to serve traffic (waits for all checks to pass).
|
|
58
|
+
- **Health (`/health`)**: Full aggregated report of all registered checks.
|
|
62
59
|
|
|
63
60
|
### Registering Custom Checks
|
|
64
61
|
|
|
62
|
+
You can register custom health checks via the `monitor` service.
|
|
63
|
+
|
|
65
64
|
```typescript
|
|
66
65
|
const monitor = core.services.get('monitor')
|
|
67
66
|
|
|
68
|
-
//
|
|
67
|
+
// Simple check
|
|
69
68
|
monitor.health.register('database', async () => {
|
|
70
|
-
const
|
|
71
|
-
return
|
|
72
|
-
? { status: 'healthy' }
|
|
73
|
-
: { status: 'unhealthy', message: 'Database disconnected' }
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Register a Redis check
|
|
77
|
-
monitor.health.register('redis', async () => {
|
|
78
|
-
const result = await redis.ping()
|
|
79
|
-
return { status: result === 'PONG' ? 'healthy' : 'unhealthy' }
|
|
69
|
+
const isOk = await db.ping()
|
|
70
|
+
return isOk ? { status: 'healthy' } : { status: 'unhealthy', message: 'DB down' }
|
|
80
71
|
})
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Built-in Check Factories
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import {
|
|
87
|
-
createDatabaseCheck,
|
|
88
|
-
createRedisCheck,
|
|
89
|
-
createMemoryCheck,
|
|
90
|
-
createHttpCheck
|
|
91
|
-
} from '@gravito/monitor'
|
|
92
72
|
|
|
93
|
-
//
|
|
94
|
-
monitor.health.register('
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// External service check
|
|
100
|
-
monitor.health.register('api', createHttpCheck('https://api.example.com/health'))
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Health Response Format
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"status": "healthy",
|
|
108
|
-
"timestamp": "2024-12-25T12:00:00Z",
|
|
109
|
-
"uptime": 3600,
|
|
110
|
-
"checks": {
|
|
111
|
-
"database": { "status": "healthy", "latency": 5 },
|
|
112
|
-
"redis": { "status": "healthy", "latency": 2 },
|
|
113
|
-
"memory": {
|
|
114
|
-
"status": "healthy",
|
|
115
|
-
"details": { "heapUsedPercent": "45.2" }
|
|
116
|
-
}
|
|
73
|
+
// Detailed check
|
|
74
|
+
monitor.health.register('disk_space', () => {
|
|
75
|
+
const usage = getDiskUsage()
|
|
76
|
+
return {
|
|
77
|
+
status: usage < 90 ? 'healthy' : 'degraded',
|
|
78
|
+
details: { usage: `${usage}%` }
|
|
117
79
|
}
|
|
118
|
-
}
|
|
80
|
+
})
|
|
119
81
|
```
|
|
120
82
|
|
|
121
|
-
## Metrics
|
|
122
|
-
|
|
123
|
-
### Prometheus Endpoint
|
|
83
|
+
## ๐ Metrics
|
|
124
84
|
|
|
125
|
-
|
|
85
|
+
Metrics are exposed in Prometheus text format at `/metrics`.
|
|
126
86
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
myapp_http_requests_total{method="GET",path="/api/users",status="200"} 150
|
|
131
|
-
|
|
132
|
-
# HELP myapp_http_request_duration_seconds HTTP request duration
|
|
133
|
-
# TYPE myapp_http_request_duration_seconds histogram
|
|
134
|
-
myapp_http_request_duration_seconds_bucket{le="0.01"} 50
|
|
135
|
-
myapp_http_request_duration_seconds_bucket{le="0.1"} 120
|
|
136
|
-
myapp_http_request_duration_seconds_sum 12.5
|
|
137
|
-
myapp_http_request_duration_seconds_count 150
|
|
138
|
-
```
|
|
87
|
+
### Built-in Metrics
|
|
88
|
+
- **Runtime**: Heap usage, uptime, active handles.
|
|
89
|
+
- **HTTP**: Request total (`http_requests_total`), duration histogram (`http_request_duration_seconds`).
|
|
139
90
|
|
|
140
91
|
### Custom Metrics
|
|
141
92
|
|
|
142
93
|
```typescript
|
|
143
94
|
const monitor = core.services.get('monitor')
|
|
144
95
|
|
|
145
|
-
// Counter
|
|
146
|
-
const
|
|
147
|
-
name: '
|
|
148
|
-
help: 'Total
|
|
149
|
-
labels: ['
|
|
96
|
+
// 1. Counter (Monotonically increasing)
|
|
97
|
+
const orders = monitor.metrics.counter({
|
|
98
|
+
name: 'orders_total',
|
|
99
|
+
help: 'Total orders processed',
|
|
100
|
+
labels: ['status']
|
|
150
101
|
})
|
|
151
|
-
|
|
102
|
+
orders.inc({ status: 'completed' })
|
|
152
103
|
|
|
153
|
-
// Gauge
|
|
154
|
-
const
|
|
155
|
-
name: '
|
|
156
|
-
help: 'Current active
|
|
104
|
+
// 2. Gauge (Can go up and down)
|
|
105
|
+
const activeUsers = monitor.metrics.gauge({
|
|
106
|
+
name: 'active_users',
|
|
107
|
+
help: 'Current active users'
|
|
157
108
|
})
|
|
158
|
-
|
|
159
|
-
activeConnections.inc()
|
|
160
|
-
activeConnections.dec()
|
|
161
|
-
|
|
162
|
-
// Histogram
|
|
163
|
-
const responseTime = monitor.metrics.histogram({
|
|
164
|
-
name: 'response_time_seconds',
|
|
165
|
-
help: 'Response time in seconds',
|
|
166
|
-
labels: ['endpoint'],
|
|
167
|
-
buckets: [0.01, 0.05, 0.1, 0.5, 1],
|
|
168
|
-
})
|
|
169
|
-
responseTime.observe(0.125, { endpoint: '/users' })
|
|
109
|
+
activeUsers.set(42)
|
|
170
110
|
|
|
171
|
-
//
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
111
|
+
// 3. Histogram (Value distribution)
|
|
112
|
+
const processTime = monitor.metrics.histogram({
|
|
113
|
+
name: 'order_processing_seconds',
|
|
114
|
+
help: 'Time to process orders',
|
|
115
|
+
buckets: [0.1, 0.5, 1, 2, 5]
|
|
116
|
+
})
|
|
117
|
+
const stop = processTime.startTimer()
|
|
118
|
+
// ... logic ...
|
|
119
|
+
stop()
|
|
175
120
|
```
|
|
176
121
|
|
|
177
|
-
## Tracing
|
|
178
|
-
|
|
179
|
-
### OpenTelemetry Integration
|
|
122
|
+
## ๐ Tracing
|
|
180
123
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
| Backend | Method |
|
|
184
|
-
|---------|--------|
|
|
185
|
-
| **Jaeger** | OTLP Collector โ Jaeger |
|
|
186
|
-
| **Zipkin** | OTLP Collector โ Zipkin |
|
|
187
|
-
| **AWS X-Ray** | AWS ADOT Collector |
|
|
188
|
-
| **Google Cloud Trace** | GCP OTLP Collector |
|
|
189
|
-
| **Datadog** | Datadog Agent (OTLP) |
|
|
124
|
+
Distributed tracing is powered by OpenTelemetry (OTLP). It automatically propagates trace context via W3C `traceparent` headers.
|
|
190
125
|
|
|
191
126
|
### Configuration
|
|
192
|
-
|
|
193
127
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
})
|
|
128
|
+
tracing: {
|
|
129
|
+
enabled: true,
|
|
130
|
+
serviceName: 'gateway',
|
|
131
|
+
endpoint: 'http://otel-collector:4318/v1/traces',
|
|
132
|
+
sampleRate: 0.1, // Sample 10% of requests
|
|
133
|
+
resourceAttributes: {
|
|
134
|
+
env: 'production'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
206
137
|
```
|
|
207
138
|
|
|
208
139
|
### Manual Spans
|
|
209
|
-
|
|
210
140
|
```typescript
|
|
211
141
|
const tracer = core.services.get('tracing')
|
|
212
142
|
|
|
213
|
-
|
|
214
|
-
const span = tracer.startSpan('process-order', {
|
|
215
|
-
attributes: { 'order.id': '12345' },
|
|
216
|
-
})
|
|
217
|
-
|
|
143
|
+
const span = tracer.startSpan('compute_heavy_logic')
|
|
218
144
|
try {
|
|
219
|
-
//
|
|
220
|
-
tracer.
|
|
221
|
-
tracer.setAttribute(span, 'order.total', 99.99)
|
|
145
|
+
// ... work ...
|
|
146
|
+
tracer.setAttribute(span, 'items_count', 100)
|
|
222
147
|
tracer.endSpan(span, 'ok')
|
|
223
|
-
} catch (
|
|
148
|
+
} catch (e) {
|
|
224
149
|
tracer.endSpan(span, 'error')
|
|
225
|
-
throw error
|
|
226
150
|
}
|
|
227
151
|
```
|
|
228
152
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
The tracing middleware automatically:
|
|
232
|
-
- Extracts `traceparent` header from incoming requests
|
|
233
|
-
- Injects trace context into outgoing requests
|
|
234
|
-
- Records HTTP method, path, status code
|
|
235
|
-
|
|
236
|
-
## Kubernetes Integration
|
|
237
|
-
|
|
238
|
-
### Deployment Example
|
|
239
|
-
|
|
240
|
-
```yaml
|
|
241
|
-
apiVersion: apps/v1
|
|
242
|
-
kind: Deployment
|
|
243
|
-
metadata:
|
|
244
|
-
name: my-gravito-app
|
|
245
|
-
spec:
|
|
246
|
-
template:
|
|
247
|
-
spec:
|
|
248
|
-
containers:
|
|
249
|
-
- name: app
|
|
250
|
-
image: my-app:latest
|
|
251
|
-
ports:
|
|
252
|
-
- containerPort: 3000
|
|
253
|
-
livenessProbe:
|
|
254
|
-
httpGet:
|
|
255
|
-
path: /live
|
|
256
|
-
port: 3000
|
|
257
|
-
initialDelaySeconds: 5
|
|
258
|
-
periodSeconds: 10
|
|
259
|
-
readinessProbe:
|
|
260
|
-
httpGet:
|
|
261
|
-
path: /ready
|
|
262
|
-
port: 3000
|
|
263
|
-
initialDelaySeconds: 5
|
|
264
|
-
periodSeconds: 5
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### ServiceMonitor for Prometheus
|
|
268
|
-
|
|
269
|
-
```yaml
|
|
270
|
-
apiVersion: monitoring.coreos.com/v1
|
|
271
|
-
kind: ServiceMonitor
|
|
272
|
-
metadata:
|
|
273
|
-
name: my-gravito-app
|
|
274
|
-
spec:
|
|
275
|
-
selector:
|
|
276
|
-
matchLabels:
|
|
277
|
-
app: my-gravito-app
|
|
278
|
-
endpoints:
|
|
279
|
-
- port: http
|
|
280
|
-
path: /metrics
|
|
281
|
-
interval: 15s
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
## Configuration Reference
|
|
153
|
+
## โ๏ธ Configuration Reference
|
|
285
154
|
|
|
286
155
|
| Option | Type | Default | Description |
|
|
287
156
|
|--------|------|---------|-------------|
|
|
288
|
-
| `health.enabled` | boolean | `true` | Enable health endpoints |
|
|
289
|
-
| `health.path` | string | `/health` |
|
|
290
|
-
| `health.
|
|
291
|
-
| `health.
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `metrics.
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
## License
|
|
304
|
-
|
|
305
|
-
MIT
|
|
157
|
+
| `health.enabled` | `boolean` | `true` | Enable health endpoints |
|
|
158
|
+
| `health.path` | `string` | `/health` | Path for aggregated health check |
|
|
159
|
+
| `health.timeout` | `number` | `5000` | Timeout for checks in ms |
|
|
160
|
+
| `health.cacheTtl` | `number` | `0` | Cache results in ms (0 = disabled) |
|
|
161
|
+
| `metrics.enabled` | `boolean` | `true` | Enable Prometheus endpoint |
|
|
162
|
+
| `metrics.prefix` | `string` | `gravito_` | Metric name prefix |
|
|
163
|
+
| `metrics.defaultMetrics` | `boolean` | `true` | Collect Node.js runtime metrics |
|
|
164
|
+
| `tracing.enabled` | `boolean` | `false` | Enable OpenTelemetry tracing |
|
|
165
|
+
| `tracing.endpoint` | `string` | `http://localhost:4318/v1/traces` | OTLP Collector URL |
|
|
166
|
+
| `tracing.sampleRate` | `number` | `1.0` | Probability sampling (0.0 - 1.0) |
|
|
167
|
+
|
|
168
|
+
## ๐ License
|
|
169
|
+
|
|
170
|
+
MIT ยฉ Carl Lee
|
package/README.zh-TW.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @gravito/monitor ๐ฐ๏ธ
|
|
2
|
+
|
|
3
|
+
่ผ้็ด็ Gravito ๅฏ่งๆธฌๆงๆจก็ต (Observability) - ๅ
ๅซๅฅๅบทๆชขๆฅ (Health Checks)ใๆๆจ็ฃๆง (Metrics) ่้่ทฏ่ฟฝ่นค (Tracing)ใๅบๆผ **Galaxy Architecture** ่จญ่จ๏ผๆญค Orbit ็บๆจ็ๆ็ๆไพๅฟ
ๅ็็ฃๆงๅบ็ค่จญๆฝใ
|
|
4
|
+
|
|
5
|
+
## ๐ ๆ ธๅฟ็นๆง
|
|
6
|
+
|
|
7
|
+
- ๐ฅ **ๅฅๅบทๆชขๆฅ (Health Checks)** - ๆฏๆด Kubernetes ๆจๆบ็ `/health`ใ`/ready`ใ`/live` ็ซฏ้ป๏ผไธฆๅฏ่ชๅฎ็พฉๆชขๆฅ้
ใ
|
|
8
|
+
- ๐ **ๆๆจ็ฃๆง (Metrics)** - ็ธๅฎน Prometheus ๆ ผๅผ็ `/metrics` ็ซฏ้ป๏ผๅ
งๅปบ Node.js ๅท่ก้ๆฎต่ HTTP ็ฃๆงใ
|
|
9
|
+
- ๐ **้่ทฏ่ฟฝ่นค (Tracing)** - ๆฏๆด OpenTelemetry OTLP ๆจๆบ๏ผๅฏฆ็พ่ทจๆๅ็ๅๆฃๅผ่ฟฝ่นคใ
|
|
10
|
+
- ๐ก๏ธ **้ฒๅ็ๆดๅ** - ๅฎ็พ้ฉ้
Kubernetes Probe ่จญๅฎ่ Prometheus ServiceMonitorใ
|
|
11
|
+
|
|
12
|
+
## ๐ฆ ๅฎ่ฃ
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @gravito/monitor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
ๅฆ้ไฝฟ็จ OpenTelemetry ่ฟฝ่นคๅ่ฝ๏ผ้ธ็จ๏ผ๏ผ
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## ๐ ๅฟซ้ไธๆ
|
|
25
|
+
|
|
26
|
+
ๅช้ๅฐ `MonitorOrbit` ๅ ๅ
ฅๆจ็ `PlanetCore` ๅณๅฏๅ็จใ
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { PlanetCore } from '@gravito/core'
|
|
30
|
+
import { MonitorOrbit } from '@gravito/monitor'
|
|
31
|
+
|
|
32
|
+
const core = new PlanetCore()
|
|
33
|
+
|
|
34
|
+
core.orbit(new MonitorOrbit({
|
|
35
|
+
health: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
path: '/health',
|
|
38
|
+
},
|
|
39
|
+
metrics: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
prefix: 'myapp_',
|
|
42
|
+
},
|
|
43
|
+
tracing: {
|
|
44
|
+
enabled: process.env.NODE_ENV === 'production',
|
|
45
|
+
serviceName: 'order-service',
|
|
46
|
+
},
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
await core.liftoff()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## ๐ฅ ๅฅๅบทๆชขๆฅ (Health Checks)
|
|
53
|
+
|
|
54
|
+
ๅฅๅบทๆชขๆฅ็ณป็ตฑๆไพไธๅ็ฌฆๅ Kubernetes ๆไฝณๅฏฆ่ธ็็ซฏ้ป๏ผ
|
|
55
|
+
|
|
56
|
+
- **Liveness (`/live`)**: ๆ็คบ็จๅบๆฏๅฆๆญฃๅธธ้่กใ
|
|
57
|
+
- **Readiness (`/ready`)**: ๆ็คบๆ็จๆฏๅฆๆบๅๅฅฝๆฅๆถๆต้๏ผ้้้ๆๆ่จปๅ็ๆชขๆฅ้
๏ผใ
|
|
58
|
+
- **Health (`/health`)**: ๅฎๆด็ๅฅๅบทๅ ฑๅ๏ผๅ
ๅซๆๆ่จปๅ็็ดฐ็ฏใ
|
|
59
|
+
|
|
60
|
+
### ่จปๅ่ชๅฎ็พฉๆชขๆฅ้
|
|
61
|
+
|
|
62
|
+
ๆจๅฏไปฅ้้ `monitor` ๆๅ่จปๅ่ชๅฎ็พฉๆชขๆฅ้่ผฏใ
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const monitor = core.services.get('monitor')
|
|
66
|
+
|
|
67
|
+
// ็ฐกๅฎๆชขๆฅ
|
|
68
|
+
monitor.health.register('database', async () => {
|
|
69
|
+
const isOk = await db.ping()
|
|
70
|
+
return isOk ? { status: 'healthy' } : { status: 'unhealthy', message: '่ณๆๅบซ้ฃ็ทไธญๆท' }
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// ่ฉณ็ดฐๅ ฑๅ
|
|
74
|
+
monitor.health.register('disk_space', () => {
|
|
75
|
+
const usage = getDiskUsage()
|
|
76
|
+
return {
|
|
77
|
+
status: usage < 90 ? 'healthy' : 'degraded',
|
|
78
|
+
details: { usage: `${usage}%` }
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## ๐ ๆๆจ็ฃๆง (Metrics)
|
|
84
|
+
|
|
85
|
+
ๆๆจๆไปฅ Prometheus ๆๅญๆ ผๅผๆด้ฒๆผ `/metrics` ็ซฏ้ปใ
|
|
86
|
+
|
|
87
|
+
### ๅ
งๅปบๆๆจ
|
|
88
|
+
- **ๅท่ก้ๆฎต (Runtime)**: ๅ ็ฉ่จๆถ้ซ (Heap usage)ใ้่กๆ้ (Uptime)ใๆดป่บๆงๅถไปฃ็ขผ (Active handles)ใ
|
|
89
|
+
- **HTTP**: ่ซๆฑ็ธฝๆธ (`http_requests_total`)ใ่ซๆฑ่ๆๅไฝ (`http_request_duration_seconds`)ใ
|
|
90
|
+
|
|
91
|
+
### ่ชๅฎ็พฉๆๆจ
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const monitor = core.services.get('monitor')
|
|
95
|
+
|
|
96
|
+
// 1. Counter (ๅฎ่ชฟ้ๅข็่จๆธๅจ)
|
|
97
|
+
const orders = monitor.metrics.counter({
|
|
98
|
+
name: 'orders_total',
|
|
99
|
+
help: '่็็่จๅฎ็ธฝๆธ',
|
|
100
|
+
labels: ['status']
|
|
101
|
+
})
|
|
102
|
+
orders.inc({ status: 'completed' })
|
|
103
|
+
|
|
104
|
+
// 2. Gauge (ๅฏๅขๅฏๆธ็้่กจ)
|
|
105
|
+
const activeUsers = monitor.metrics.gauge({
|
|
106
|
+
name: 'active_users',
|
|
107
|
+
help: '็ถๅๆดป่บไฝฟ็จ่
ๆธ'
|
|
108
|
+
})
|
|
109
|
+
activeUsers.set(42)
|
|
110
|
+
|
|
111
|
+
// 3. Histogram (ๆธๅผๅไฝ็ตฑ่จ)
|
|
112
|
+
const processTime = monitor.metrics.histogram({
|
|
113
|
+
name: 'order_processing_seconds',
|
|
114
|
+
help: '่จๅฎ่็่ๆ',
|
|
115
|
+
buckets: [0.1, 0.5, 1, 2, 5]
|
|
116
|
+
})
|
|
117
|
+
const stop = processTime.startTimer()
|
|
118
|
+
// ... ๅท่ก้่ผฏ ...
|
|
119
|
+
stop()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## ๐ ้่ทฏ่ฟฝ่นค (Tracing)
|
|
123
|
+
|
|
124
|
+
้่ทฏ่ฟฝ่นค็ฑ OpenTelemetry (OTLP) ้ฉ
ๅ๏ผๆ่ชๅ้้ W3C `traceparent` Header ๅณ้่ฟฝ่นคไธไธๆใ
|
|
125
|
+
|
|
126
|
+
### ้
็ฝฎ็ฏไพ
|
|
127
|
+
```typescript
|
|
128
|
+
tracing: {
|
|
129
|
+
enabled: true,
|
|
130
|
+
serviceName: 'gateway',
|
|
131
|
+
endpoint: 'http://otel-collector:4318/v1/traces',
|
|
132
|
+
sampleRate: 0.1, // ๅๆจฃ 10% ็่ซๆฑ
|
|
133
|
+
resourceAttributes: {
|
|
134
|
+
env: 'production'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### ๆๅๅปบ็ซ Span
|
|
140
|
+
```typescript
|
|
141
|
+
const tracer = core.services.get('tracing')
|
|
142
|
+
|
|
143
|
+
const span = tracer.startSpan('compute_heavy_logic')
|
|
144
|
+
try {
|
|
145
|
+
// ... ๅท่ก่ค้้็ฎ ...
|
|
146
|
+
tracer.setAttribute(span, 'items_count', 100)
|
|
147
|
+
tracer.endSpan(span, 'ok')
|
|
148
|
+
} catch (e) {
|
|
149
|
+
tracer.endSpan(span, 'error')
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## โ๏ธ ้
็ฝฎๅๆธๅ่
|
|
154
|
+
|
|
155
|
+
| ๅๆธ | ้กๅ | ้ ่จญๅผ | ่ชชๆ |
|
|
156
|
+
|--------|------|---------|-------------|
|
|
157
|
+
| `health.enabled` | `boolean` | `true` | ๆฏๅฆๅ็จๅฅๅบทๆชขๆฅ็ซฏ้ป |
|
|
158
|
+
| `health.path` | `string` | `/health` | ๅฎๆดๅฅๅบทๅ ฑๅ่ทฏๅพ |
|
|
159
|
+
| `health.timeout` | `number` | `5000` | ๆชขๆฅ้
้พๆๆ้ (ms) |
|
|
160
|
+
| `health.cacheTtl` | `number` | `0` | ็ตๆๅฟซๅๆ้ (ms, 0 ไปฃ่กจไธๅฟซๅ) |
|
|
161
|
+
| `metrics.enabled` | `boolean` | `true` | ๆฏๅฆๅ็จ Prometheus ๆๆจ็ซฏ้ป |
|
|
162
|
+
| `metrics.prefix` | `string` | `gravito_` | ๆๆจๅ็จฑๅ็ถด |
|
|
163
|
+
| `metrics.defaultMetrics` | `boolean` | `true` | ๆฏๅฆๆถ้ Node.js ๅท่ก้ๆฎตๆๆจ |
|
|
164
|
+
| `tracing.enabled` | `boolean` | `false` | ๆฏๅฆๅ็จ OpenTelemetry ่ฟฝ่นค |
|
|
165
|
+
| `tracing.endpoint` | `string` | `http://localhost:4318/v1/traces` | OTLP Collector ่ทฏๅพ |
|
|
166
|
+
| `tracing.sampleRate` | `number` | `1.0` | ๅๆจฃ็ (0.0 - 1.0) |
|
|
167
|
+
|
|
168
|
+
## ๐ ๆๆฌๅ่ญฐ
|
|
169
|
+
|
|
170
|
+
MIT ยฉ Carl Lee
|
package/dist/index.cjs
CHANGED
|
@@ -65,8 +65,15 @@ var HealthController = class {
|
|
|
65
65
|
*/
|
|
66
66
|
async health(c) {
|
|
67
67
|
const report = await this.registry.check();
|
|
68
|
+
const cacheStats = this.registry.getCacheStats();
|
|
68
69
|
const status = report.status === "healthy" ? 200 : report.status === "degraded" ? 200 : 503;
|
|
69
|
-
return c.json(
|
|
70
|
+
return c.json(
|
|
71
|
+
{
|
|
72
|
+
...report,
|
|
73
|
+
cache: cacheStats
|
|
74
|
+
},
|
|
75
|
+
status
|
|
76
|
+
);
|
|
70
77
|
}
|
|
71
78
|
/**
|
|
72
79
|
* GET /ready - Kubernetes readiness probe
|
|
@@ -101,6 +108,8 @@ var HealthRegistry = class {
|
|
|
101
108
|
cacheExpiry = 0;
|
|
102
109
|
timeout;
|
|
103
110
|
cacheTtl;
|
|
111
|
+
cacheHits = 0;
|
|
112
|
+
cacheMisses = 0;
|
|
104
113
|
constructor(config = {}) {
|
|
105
114
|
this.timeout = config.timeout ?? DEFAULTS.timeout;
|
|
106
115
|
this.cacheTtl = config.cacheTtl ?? DEFAULTS.cacheTtl;
|
|
@@ -158,8 +167,10 @@ var HealthRegistry = class {
|
|
|
158
167
|
*/
|
|
159
168
|
async check() {
|
|
160
169
|
if (this.cacheTtl > 0 && this.cachedReport && Date.now() < this.cacheExpiry) {
|
|
170
|
+
this.cacheHits++;
|
|
161
171
|
return this.cachedReport;
|
|
162
172
|
}
|
|
173
|
+
this.cacheMisses++;
|
|
163
174
|
const results = await Promise.all(
|
|
164
175
|
Array.from(this.checks.entries()).map(([name, check]) => this.executeCheck(name, check))
|
|
165
176
|
);
|
|
@@ -209,6 +220,19 @@ var HealthRegistry = class {
|
|
|
209
220
|
}
|
|
210
221
|
return { status: "healthy" };
|
|
211
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Get cache statistics
|
|
225
|
+
*
|
|
226
|
+
* Useful for monitoring cache effectiveness and tuning cacheTtl
|
|
227
|
+
*/
|
|
228
|
+
getCacheStats() {
|
|
229
|
+
const total = this.cacheHits + this.cacheMisses;
|
|
230
|
+
return {
|
|
231
|
+
hits: this.cacheHits,
|
|
232
|
+
misses: this.cacheMisses,
|
|
233
|
+
hitRate: total > 0 ? this.cacheHits / total : 0
|
|
234
|
+
};
|
|
235
|
+
}
|
|
212
236
|
};
|
|
213
237
|
|
|
214
238
|
// src/health/index.ts
|
|
@@ -342,6 +366,7 @@ var MetricsController = class {
|
|
|
342
366
|
* GET /metrics - Prometheus metrics endpoint
|
|
343
367
|
*/
|
|
344
368
|
async metrics(_c) {
|
|
369
|
+
this.updateHealthCacheMetrics();
|
|
345
370
|
const prometheusFormat = this.registry.toPrometheus();
|
|
346
371
|
return new Response(prometheusFormat, {
|
|
347
372
|
status: 200,
|
|
@@ -350,6 +375,21 @@ var MetricsController = class {
|
|
|
350
375
|
}
|
|
351
376
|
});
|
|
352
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* ๆดๆฐ health cache metrics
|
|
380
|
+
*
|
|
381
|
+
* ๅพ HealthRegistry ่ฎๅๆๆฐ็ cache ็ตฑ่จไธฆๆดๆฐ gauges
|
|
382
|
+
*/
|
|
383
|
+
updateHealthCacheMetrics() {
|
|
384
|
+
const healthMetrics = this.registry._healthCacheMetrics;
|
|
385
|
+
if (!healthMetrics) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const stats = healthMetrics.registry.getCacheStats();
|
|
389
|
+
healthMetrics.hits.set(stats.hits);
|
|
390
|
+
healthMetrics.misses.set(stats.misses);
|
|
391
|
+
healthMetrics.hitRate.set(stats.hitRate);
|
|
392
|
+
}
|
|
353
393
|
};
|
|
354
394
|
|
|
355
395
|
// src/metrics/MetricsRegistry.ts
|
|
@@ -705,7 +745,7 @@ function createHttpMetricsMiddleware(registry) {
|
|
|
705
745
|
});
|
|
706
746
|
return async (c, next) => {
|
|
707
747
|
const method = c.req.method;
|
|
708
|
-
const path = normalizePath(c.req.path);
|
|
748
|
+
const path = c.req.routePattern ?? normalizePath(c.req.path);
|
|
709
749
|
const start = performance.now();
|
|
710
750
|
await next();
|
|
711
751
|
const duration = (performance.now() - start) / 1e3;
|
|
@@ -1002,9 +1042,37 @@ var MonitorOrbit = class {
|
|
|
1002
1042
|
const metricsController = new MetricsController(this.metricsRegistry);
|
|
1003
1043
|
router.get(metricsPath, (c) => metricsController.metrics(c));
|
|
1004
1044
|
console.log(`[Monitor] Metrics endpoint: ${metricsPath}`);
|
|
1045
|
+
if (healthEnabled && this.healthRegistry) {
|
|
1046
|
+
this.registerHealthCacheMetrics(this.metricsRegistry, this.healthRegistry);
|
|
1047
|
+
}
|
|
1005
1048
|
}
|
|
1006
1049
|
console.log("[Monitor] Observability services initialized");
|
|
1007
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* ่จปๅ health cache metrics
|
|
1053
|
+
*
|
|
1054
|
+
* ๅปบ็ซ metrics ไพ่ฟฝ่นค health check cache ็ๆ่ฝ
|
|
1055
|
+
*/
|
|
1056
|
+
registerHealthCacheMetrics(metricsRegistry, healthRegistry) {
|
|
1057
|
+
const cacheHitsGauge = metricsRegistry.gauge({
|
|
1058
|
+
name: "health_cache_hits_total",
|
|
1059
|
+
help: "Total number of health check cache hits"
|
|
1060
|
+
});
|
|
1061
|
+
const cacheMissesGauge = metricsRegistry.gauge({
|
|
1062
|
+
name: "health_cache_misses_total",
|
|
1063
|
+
help: "Total number of health check cache misses"
|
|
1064
|
+
});
|
|
1065
|
+
const cacheHitRateGauge = metricsRegistry.gauge({
|
|
1066
|
+
name: "health_cache_hit_rate",
|
|
1067
|
+
help: "Health check cache hit rate (0.0 to 1.0)"
|
|
1068
|
+
});
|
|
1069
|
+
metricsRegistry._healthCacheMetrics = {
|
|
1070
|
+
hits: cacheHitsGauge,
|
|
1071
|
+
misses: cacheMissesGauge,
|
|
1072
|
+
hitRate: cacheHitRateGauge,
|
|
1073
|
+
registry: healthRegistry
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1008
1076
|
/**
|
|
1009
1077
|
* Shutdown hook
|
|
1010
1078
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -113,6 +113,14 @@ interface HealthReport {
|
|
|
113
113
|
name: string;
|
|
114
114
|
}>;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Cache statistics
|
|
118
|
+
*/
|
|
119
|
+
interface CacheStats {
|
|
120
|
+
hits: number;
|
|
121
|
+
misses: number;
|
|
122
|
+
hitRate: number;
|
|
123
|
+
}
|
|
116
124
|
/**
|
|
117
125
|
* HealthRegistry manages all health checks
|
|
118
126
|
*/
|
|
@@ -123,6 +131,8 @@ declare class HealthRegistry {
|
|
|
123
131
|
private cacheExpiry;
|
|
124
132
|
private timeout;
|
|
125
133
|
private cacheTtl;
|
|
134
|
+
private cacheHits;
|
|
135
|
+
private cacheMisses;
|
|
126
136
|
constructor(config?: HealthConfig);
|
|
127
137
|
/**
|
|
128
138
|
* Register a health check
|
|
@@ -158,6 +168,12 @@ declare class HealthRegistry {
|
|
|
158
168
|
status: 'healthy' | 'unhealthy';
|
|
159
169
|
reason?: string;
|
|
160
170
|
}>;
|
|
171
|
+
/**
|
|
172
|
+
* Get cache statistics
|
|
173
|
+
*
|
|
174
|
+
* Useful for monitoring cache effectiveness and tuning cacheTtl
|
|
175
|
+
*/
|
|
176
|
+
getCacheStats(): CacheStats;
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
/**
|
|
@@ -373,6 +389,12 @@ declare class MetricsController {
|
|
|
373
389
|
* GET /metrics - Prometheus metrics endpoint
|
|
374
390
|
*/
|
|
375
391
|
metrics(_c: GravitoContext): Promise<Response>;
|
|
392
|
+
/**
|
|
393
|
+
* ๆดๆฐ health cache metrics
|
|
394
|
+
*
|
|
395
|
+
* ๅพ HealthRegistry ่ฎๅๆๆฐ็ cache ็ตฑ่จไธฆๆดๆฐ gauges
|
|
396
|
+
*/
|
|
397
|
+
private updateHealthCacheMetrics;
|
|
376
398
|
}
|
|
377
399
|
|
|
378
400
|
/**
|
|
@@ -519,6 +541,12 @@ declare class MonitorOrbit implements GravitoOrbit {
|
|
|
519
541
|
* Install the orbit (required by GravitoOrbit interface)
|
|
520
542
|
*/
|
|
521
543
|
install(core: PlanetCore): Promise<void>;
|
|
544
|
+
/**
|
|
545
|
+
* ่จปๅ health cache metrics
|
|
546
|
+
*
|
|
547
|
+
* ๅปบ็ซ metrics ไพ่ฟฝ่นค health check cache ็ๆ่ฝ
|
|
548
|
+
*/
|
|
549
|
+
private registerHealthCacheMetrics;
|
|
522
550
|
/**
|
|
523
551
|
* Shutdown hook
|
|
524
552
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -113,6 +113,14 @@ interface HealthReport {
|
|
|
113
113
|
name: string;
|
|
114
114
|
}>;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Cache statistics
|
|
118
|
+
*/
|
|
119
|
+
interface CacheStats {
|
|
120
|
+
hits: number;
|
|
121
|
+
misses: number;
|
|
122
|
+
hitRate: number;
|
|
123
|
+
}
|
|
116
124
|
/**
|
|
117
125
|
* HealthRegistry manages all health checks
|
|
118
126
|
*/
|
|
@@ -123,6 +131,8 @@ declare class HealthRegistry {
|
|
|
123
131
|
private cacheExpiry;
|
|
124
132
|
private timeout;
|
|
125
133
|
private cacheTtl;
|
|
134
|
+
private cacheHits;
|
|
135
|
+
private cacheMisses;
|
|
126
136
|
constructor(config?: HealthConfig);
|
|
127
137
|
/**
|
|
128
138
|
* Register a health check
|
|
@@ -158,6 +168,12 @@ declare class HealthRegistry {
|
|
|
158
168
|
status: 'healthy' | 'unhealthy';
|
|
159
169
|
reason?: string;
|
|
160
170
|
}>;
|
|
171
|
+
/**
|
|
172
|
+
* Get cache statistics
|
|
173
|
+
*
|
|
174
|
+
* Useful for monitoring cache effectiveness and tuning cacheTtl
|
|
175
|
+
*/
|
|
176
|
+
getCacheStats(): CacheStats;
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
/**
|
|
@@ -373,6 +389,12 @@ declare class MetricsController {
|
|
|
373
389
|
* GET /metrics - Prometheus metrics endpoint
|
|
374
390
|
*/
|
|
375
391
|
metrics(_c: GravitoContext): Promise<Response>;
|
|
392
|
+
/**
|
|
393
|
+
* ๆดๆฐ health cache metrics
|
|
394
|
+
*
|
|
395
|
+
* ๅพ HealthRegistry ่ฎๅๆๆฐ็ cache ็ตฑ่จไธฆๆดๆฐ gauges
|
|
396
|
+
*/
|
|
397
|
+
private updateHealthCacheMetrics;
|
|
376
398
|
}
|
|
377
399
|
|
|
378
400
|
/**
|
|
@@ -519,6 +541,12 @@ declare class MonitorOrbit implements GravitoOrbit {
|
|
|
519
541
|
* Install the orbit (required by GravitoOrbit interface)
|
|
520
542
|
*/
|
|
521
543
|
install(core: PlanetCore): Promise<void>;
|
|
544
|
+
/**
|
|
545
|
+
* ่จปๅ health cache metrics
|
|
546
|
+
*
|
|
547
|
+
* ๅปบ็ซ metrics ไพ่ฟฝ่นค health check cache ็ๆ่ฝ
|
|
548
|
+
*/
|
|
549
|
+
private registerHealthCacheMetrics;
|
|
522
550
|
/**
|
|
523
551
|
* Shutdown hook
|
|
524
552
|
*/
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,15 @@ var HealthController = class {
|
|
|
13
13
|
*/
|
|
14
14
|
async health(c) {
|
|
15
15
|
const report = await this.registry.check();
|
|
16
|
+
const cacheStats = this.registry.getCacheStats();
|
|
16
17
|
const status = report.status === "healthy" ? 200 : report.status === "degraded" ? 200 : 503;
|
|
17
|
-
return c.json(
|
|
18
|
+
return c.json(
|
|
19
|
+
{
|
|
20
|
+
...report,
|
|
21
|
+
cache: cacheStats
|
|
22
|
+
},
|
|
23
|
+
status
|
|
24
|
+
);
|
|
18
25
|
}
|
|
19
26
|
/**
|
|
20
27
|
* GET /ready - Kubernetes readiness probe
|
|
@@ -49,6 +56,8 @@ var HealthRegistry = class {
|
|
|
49
56
|
cacheExpiry = 0;
|
|
50
57
|
timeout;
|
|
51
58
|
cacheTtl;
|
|
59
|
+
cacheHits = 0;
|
|
60
|
+
cacheMisses = 0;
|
|
52
61
|
constructor(config = {}) {
|
|
53
62
|
this.timeout = config.timeout ?? DEFAULTS.timeout;
|
|
54
63
|
this.cacheTtl = config.cacheTtl ?? DEFAULTS.cacheTtl;
|
|
@@ -106,8 +115,10 @@ var HealthRegistry = class {
|
|
|
106
115
|
*/
|
|
107
116
|
async check() {
|
|
108
117
|
if (this.cacheTtl > 0 && this.cachedReport && Date.now() < this.cacheExpiry) {
|
|
118
|
+
this.cacheHits++;
|
|
109
119
|
return this.cachedReport;
|
|
110
120
|
}
|
|
121
|
+
this.cacheMisses++;
|
|
111
122
|
const results = await Promise.all(
|
|
112
123
|
Array.from(this.checks.entries()).map(([name, check]) => this.executeCheck(name, check))
|
|
113
124
|
);
|
|
@@ -157,6 +168,19 @@ var HealthRegistry = class {
|
|
|
157
168
|
}
|
|
158
169
|
return { status: "healthy" };
|
|
159
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Get cache statistics
|
|
173
|
+
*
|
|
174
|
+
* Useful for monitoring cache effectiveness and tuning cacheTtl
|
|
175
|
+
*/
|
|
176
|
+
getCacheStats() {
|
|
177
|
+
const total = this.cacheHits + this.cacheMisses;
|
|
178
|
+
return {
|
|
179
|
+
hits: this.cacheHits,
|
|
180
|
+
misses: this.cacheMisses,
|
|
181
|
+
hitRate: total > 0 ? this.cacheHits / total : 0
|
|
182
|
+
};
|
|
183
|
+
}
|
|
160
184
|
};
|
|
161
185
|
|
|
162
186
|
// src/health/index.ts
|
|
@@ -290,6 +314,7 @@ var MetricsController = class {
|
|
|
290
314
|
* GET /metrics - Prometheus metrics endpoint
|
|
291
315
|
*/
|
|
292
316
|
async metrics(_c) {
|
|
317
|
+
this.updateHealthCacheMetrics();
|
|
293
318
|
const prometheusFormat = this.registry.toPrometheus();
|
|
294
319
|
return new Response(prometheusFormat, {
|
|
295
320
|
status: 200,
|
|
@@ -298,6 +323,21 @@ var MetricsController = class {
|
|
|
298
323
|
}
|
|
299
324
|
});
|
|
300
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* ๆดๆฐ health cache metrics
|
|
328
|
+
*
|
|
329
|
+
* ๅพ HealthRegistry ่ฎๅๆๆฐ็ cache ็ตฑ่จไธฆๆดๆฐ gauges
|
|
330
|
+
*/
|
|
331
|
+
updateHealthCacheMetrics() {
|
|
332
|
+
const healthMetrics = this.registry._healthCacheMetrics;
|
|
333
|
+
if (!healthMetrics) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const stats = healthMetrics.registry.getCacheStats();
|
|
337
|
+
healthMetrics.hits.set(stats.hits);
|
|
338
|
+
healthMetrics.misses.set(stats.misses);
|
|
339
|
+
healthMetrics.hitRate.set(stats.hitRate);
|
|
340
|
+
}
|
|
301
341
|
};
|
|
302
342
|
|
|
303
343
|
// src/metrics/MetricsRegistry.ts
|
|
@@ -653,7 +693,7 @@ function createHttpMetricsMiddleware(registry) {
|
|
|
653
693
|
});
|
|
654
694
|
return async (c, next) => {
|
|
655
695
|
const method = c.req.method;
|
|
656
|
-
const path = normalizePath(c.req.path);
|
|
696
|
+
const path = c.req.routePattern ?? normalizePath(c.req.path);
|
|
657
697
|
const start = performance.now();
|
|
658
698
|
await next();
|
|
659
699
|
const duration = (performance.now() - start) / 1e3;
|
|
@@ -950,9 +990,37 @@ var MonitorOrbit = class {
|
|
|
950
990
|
const metricsController = new MetricsController(this.metricsRegistry);
|
|
951
991
|
router.get(metricsPath, (c) => metricsController.metrics(c));
|
|
952
992
|
console.log(`[Monitor] Metrics endpoint: ${metricsPath}`);
|
|
993
|
+
if (healthEnabled && this.healthRegistry) {
|
|
994
|
+
this.registerHealthCacheMetrics(this.metricsRegistry, this.healthRegistry);
|
|
995
|
+
}
|
|
953
996
|
}
|
|
954
997
|
console.log("[Monitor] Observability services initialized");
|
|
955
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* ่จปๅ health cache metrics
|
|
1001
|
+
*
|
|
1002
|
+
* ๅปบ็ซ metrics ไพ่ฟฝ่นค health check cache ็ๆ่ฝ
|
|
1003
|
+
*/
|
|
1004
|
+
registerHealthCacheMetrics(metricsRegistry, healthRegistry) {
|
|
1005
|
+
const cacheHitsGauge = metricsRegistry.gauge({
|
|
1006
|
+
name: "health_cache_hits_total",
|
|
1007
|
+
help: "Total number of health check cache hits"
|
|
1008
|
+
});
|
|
1009
|
+
const cacheMissesGauge = metricsRegistry.gauge({
|
|
1010
|
+
name: "health_cache_misses_total",
|
|
1011
|
+
help: "Total number of health check cache misses"
|
|
1012
|
+
});
|
|
1013
|
+
const cacheHitRateGauge = metricsRegistry.gauge({
|
|
1014
|
+
name: "health_cache_hit_rate",
|
|
1015
|
+
help: "Health check cache hit rate (0.0 to 1.0)"
|
|
1016
|
+
});
|
|
1017
|
+
metricsRegistry._healthCacheMetrics = {
|
|
1018
|
+
hits: cacheHitsGauge,
|
|
1019
|
+
misses: cacheMissesGauge,
|
|
1020
|
+
hitRate: cacheHitRateGauge,
|
|
1021
|
+
registry: healthRegistry
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
956
1024
|
/**
|
|
957
1025
|
* Shutdown hook
|
|
958
1026
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/monitor",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Observability module for Gravito - Health checks, Metrics, and Tracing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,13 +25,15 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "bun run build.ts",
|
|
27
27
|
"typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
28
|
-
"test": "bun test",
|
|
29
|
-
"test:coverage": "bun test --coverage --coverage-
|
|
30
|
-
"test:ci": "bun test --coverage --coverage-
|
|
28
|
+
"test": "bun test --timeout=10000",
|
|
29
|
+
"test:coverage": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
30
|
+
"test:ci": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
31
|
+
"test:unit": "bun test tests/ --timeout=10000",
|
|
32
|
+
"test:integration": "test $(find tests -name '*.integration.test.ts' 2>/dev/null | wc -l) -gt 0 && find tests -name '*.integration.test.ts' -print0 | xargs -0 bun test --timeout=10000 || echo 'No integration tests found'"
|
|
31
33
|
},
|
|
32
34
|
"peerDependencies": {
|
|
33
|
-
"@gravito/core": "
|
|
34
|
-
"@gravito/photon": "
|
|
35
|
+
"@gravito/core": "^1.6.1",
|
|
36
|
+
"@gravito/photon": "^1.0.1"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
39
|
"@types/bun": "latest",
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
|
|
41
43
|
"@opentelemetry/resources": "^1.29.0",
|
|
42
44
|
"@opentelemetry/semantic-conventions": "^1.28.0",
|
|
43
|
-
"@gravito/core": "
|
|
45
|
+
"@gravito/core": "^1.6.1",
|
|
44
46
|
"tsup": "^8.2.4",
|
|
45
47
|
"typescript": "^5.9.3"
|
|
46
48
|
},
|