Skip to content

Commit f54e5e0

Browse files
authored
feat: add diagnostic channels support (#107)
1 parent abd73a9 commit f54e5e0

File tree

10 files changed

+715
-138
lines changed

10 files changed

+715
-138
lines changed

.changeset/wicked-turkeys-shine.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'bentocache': minor
3+
---
4+
5+
Add experimental support for Node.js Diagnostic Channels (TracingChannel) for cache operations instrumentation.
6+
7+
This enables APM tools and OpenTelemetry to trace cache operations with timing information:
8+
9+
```ts
10+
import { tracingChannels } from 'bentocache'
11+
12+
tracingChannels.cacheOperation.start.subscribe((message) => {
13+
console.log(`Starting ${message.operation} on ${message.key}`)
14+
})
15+
16+
tracingChannels.cacheOperation.asyncEnd.subscribe((message) => {
17+
console.log(`Completed with hit=${message.hit}, tier=${message.tier}`)
18+
})
19+
```
20+
21+
Traced operations: `get`, `set`, `delete`, `deleteMany`, `clear`, `expire`, and `getOrSet` (as get + set on miss).

docs/content/docs/digging_deeper/events.md

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,73 +65,180 @@ export interface Emitter {
6565

6666
[Emittery](https://github.com/sindresorhus/emittery) is notably a good alternative to Node.js's `EventEmitter` that is compatible with this interface.
6767

68-
6968
## List of Events
7069

7170
### `cache:hit`
7271

73-
Emitted when a key is found in the cache.
72+
Emitted when a key is found in the cache.
7473

7574
Payload: `{ key, value, store, graced }`
7675

7776
- `key`: The key that was found in the cache
7877
- `value`: The value that was found in the cache
7978
- `store`: The name of the store that was used to retrieve the value
8079
- `graced`: `true` when the value was retrieved from a grace period
80+
8181
---
8282

8383
### `cache:miss`
8484

85-
Emitted when a key is not found in the cache.
85+
Emitted when a key is not found in the cache.
8686

8787
Payload: `{ key, store }`
8888

8989
- `key`: The key that was not found in the cache
9090
- `store`: The name of the store that was used to retrieve the value
91+
9192
---
9293

9394
### `cache:written`
9495

95-
Emitted when a key is written to the cache.
96+
Emitted when a key is written to the cache.
9697

9798
Payload: `{ key, value, store }`
9899

99100
- `key`: The key that was written to the cache
100101
- `value`: The value that was written to the cache
102+
101103
---
102104

103105
### `cache:deleted`
104106

105-
Emitted when the key is removed from the cache.
107+
Emitted when the key is removed from the cache.
106108

107109
Payload: `{ key, store }`
108110

109111
- `key`: The key that was removed from the cache
110112
- `store`: The name of the store that was used to remove the value
113+
111114
---
112115

113116
### `cache:cleared`
114117

115-
Emitted when the cache is emptied.
118+
Emitted when the cache is emptied.
116119

117120
Payload: `{ store }`
118121

119122
- `store`: The name of the store that was emptied
123+
120124
---
121125

122126
### `bus:message:published`
123127

124-
Emitted when the bus publishes a message to other applications.
128+
Emitted when the bus publishes a message to other applications.
125129

126130
Payload: `{ message }`
127131

128132
- `message`: The message that was published
133+
129134
---
130135

131136
### `bus:message:received`
132137

133-
Emitted when the application receives a message instructing it to update its cache.
138+
Emitted when the application receives a message instructing it to update its cache.
134139

135140
Payload: `{ message }`
136141

137142
- `message`: The message that was received
143+
144+
---
145+
146+
## Tracing Channels (Experimental)
147+
148+
:::warning
149+
This API is experimental and may change in future versions.
150+
:::
151+
152+
Bentocache also exposes [Node.js Diagnostic Channels](https://nodejs.org/api/diagnostics_channel.html#tracingchannel) for more advanced instrumentation needs. Unlike events, tracing channels provide timing information and are designed for APM tools and OpenTelemetry integration.
153+
154+
### Why Tracing Channels?
155+
156+
- **Built-in**: No external dependencies, uses Node.js native `diagnostics_channel`
157+
- **Zero overhead**: When no subscribers are attached, there's virtually no performance impact
158+
- **Timing information**: Automatically tracks start/end of async operations
159+
- **APM integration**: Works seamlessly with OpenTelemetry and other APM tools
160+
161+
### Available Channels
162+
163+
#### `bentocache.cache.operation`
164+
165+
Traces all cache operations with timing information.
166+
167+
```ts
168+
import { tracingChannels } from 'bentocache'
169+
170+
tracingChannels.cacheOperation.subscribe({
171+
start(message) {
172+
// Called when operation starts
173+
console.log(`Starting ${message.operation} on ${message.key}`)
174+
},
175+
asyncEnd(message) {
176+
// Called when async operation completes
177+
console.log(`Completed ${message.operation}`, {
178+
key: message.key,
179+
store: message.store,
180+
hit: message.hit,
181+
tier: message.tier,
182+
graced: message.graced,
183+
})
184+
},
185+
error({ error, ...message }) {
186+
// Called when operation fails
187+
console.error(`Failed ${message.operation}:`, error)
188+
},
189+
})
190+
```
191+
192+
### Message Properties
193+
194+
| Property | Type | Description |
195+
| ----------- | ------------------------------------------------------------------- | ----------------------------------------------------- |
196+
| `operation` | `'get' \| 'set' \| 'delete' \| 'deleteMany' \| 'clear' \| 'expire'` | The operation type |
197+
| `key` | `string` | Cache key with full prefix (e.g., `'users:123'`) |
198+
| `keys` | `string[]` | Multiple keys for `deleteMany` operation |
199+
| `store` | `string` | Store name |
200+
| `hit` | `boolean` | Whether the key was found (only for `get`) |
201+
| `tier` | `'l1' \| 'l2'` | Which tier served the value (only for `get` hits) |
202+
| `graced` | `boolean` | Whether value came from grace period (only for `get`) |
203+
204+
### OpenTelemetry Integration Example
205+
206+
```ts
207+
import { tracingChannels, type CacheOperationMessage } from 'bentocache'
208+
import { trace } from '@opentelemetry/api'
209+
210+
const tracer = trace.getTracer('bentocache')
211+
const spans = new WeakMap()
212+
213+
tracingChannels.cacheOperation.subscribe({
214+
start(message: CacheOperationMessage) {
215+
const span = tracer.startSpan(`cache.${message.operation}`)
216+
span.setAttribute('cache.key', message.key ?? '')
217+
span.setAttribute('cache.store', message.store)
218+
spans.set(message, span)
219+
},
220+
asyncEnd(message: CacheOperationMessage) {
221+
const span = spans.get(message)
222+
if (!span) return
223+
224+
if (message.hit !== undefined) {
225+
span.setAttribute('cache.hit', message.hit)
226+
}
227+
if (message.tier) {
228+
span.setAttribute('cache.tier', message.tier)
229+
}
230+
if (message.graced) {
231+
span.setAttribute('cache.graced', message.graced)
232+
}
233+
234+
span.end()
235+
},
236+
error({ error, ...message }) {
237+
const span = spans.get(message)
238+
if (!span) return
239+
240+
span.recordException(error)
241+
span.end()
242+
},
243+
})
244+
```

packages/bentocache/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export { errors } from './src/errors.js'
22
export { BentoCache } from './src/bento_cache.js'
33
export { bentostore } from './src/bento_store.js'
44
export { BentoStore } from './src/bento_store.js'
5+
export * as tracingChannels from './src/tracing_channels.js'
6+
export type { CacheOperationMessage } from './src/types/tracing_channels.js'

0 commit comments

Comments
 (0)