Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/wicked-turkeys-shine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'bentocache': minor
---

Add experimental support for Node.js Diagnostic Channels (TracingChannel) for cache operations instrumentation.

This enables APM tools and OpenTelemetry to trace cache operations with timing information:

```ts
import { tracingChannels } from 'bentocache'

tracingChannels.cacheOperation.start.subscribe((message) => {
console.log(`Starting ${message.operation} on ${message.key}`)
})

tracingChannels.cacheOperation.asyncEnd.subscribe((message) => {
console.log(`Completed with hit=${message.hit}, tier=${message.tier}`)
})
```

Traced operations: `get`, `set`, `delete`, `deleteMany`, `clear`, `expire`, and `getOrSet` (as get + set on miss).
123 changes: 115 additions & 8 deletions docs/content/docs/digging_deeper/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,73 +65,180 @@ export interface Emitter {

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


## List of Events

### `cache:hit`

Emitted when a key is found in the cache.
Emitted when a key is found in the cache.

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

- `key`: The key that was found in the cache
- `value`: The value that was found in the cache
- `store`: The name of the store that was used to retrieve the value
- `graced`: `true` when the value was retrieved from a grace period

---

### `cache:miss`

Emitted when a key is not found in the cache.
Emitted when a key is not found in the cache.

Payload: `{ key, store }`

- `key`: The key that was not found in the cache
- `store`: The name of the store that was used to retrieve the value

---

### `cache:written`

Emitted when a key is written to the cache.
Emitted when a key is written to the cache.

Payload: `{ key, value, store }`

- `key`: The key that was written to the cache
- `value`: The value that was written to the cache

---

### `cache:deleted`

Emitted when the key is removed from the cache.
Emitted when the key is removed from the cache.

Payload: `{ key, store }`

- `key`: The key that was removed from the cache
- `store`: The name of the store that was used to remove the value

---

### `cache:cleared`

Emitted when the cache is emptied.
Emitted when the cache is emptied.

Payload: `{ store }`

- `store`: The name of the store that was emptied

---

### `bus:message:published`

Emitted when the bus publishes a message to other applications.
Emitted when the bus publishes a message to other applications.

Payload: `{ message }`

- `message`: The message that was published

---

### `bus:message:received`

Emitted when the application receives a message instructing it to update its cache.
Emitted when the application receives a message instructing it to update its cache.

Payload: `{ message }`

- `message`: The message that was received

---

## Tracing Channels (Experimental)

:::warning
This API is experimental and may change in future versions.
:::

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.

### Why Tracing Channels?

- **Built-in**: No external dependencies, uses Node.js native `diagnostics_channel`
- **Zero overhead**: When no subscribers are attached, there's virtually no performance impact
- **Timing information**: Automatically tracks start/end of async operations
- **APM integration**: Works seamlessly with OpenTelemetry and other APM tools

### Available Channels

#### `bentocache.cache.operation`

Traces all cache operations with timing information.

```ts
import { tracingChannels } from 'bentocache'

tracingChannels.cacheOperation.subscribe({
start(message) {
// Called when operation starts
console.log(`Starting ${message.operation} on ${message.key}`)
},
asyncEnd(message) {
// Called when async operation completes
console.log(`Completed ${message.operation}`, {
key: message.key,
store: message.store,
hit: message.hit,
tier: message.tier,
graced: message.graced,
})
},
error({ error, ...message }) {
// Called when operation fails
console.error(`Failed ${message.operation}:`, error)
},
})
```

### Message Properties

| Property | Type | Description |
| ----------- | ------------------------------------------------------------------- | ----------------------------------------------------- |
| `operation` | `'get' \| 'set' \| 'delete' \| 'deleteMany' \| 'clear' \| 'expire'` | The operation type |
| `key` | `string` | Cache key with full prefix (e.g., `'users:123'`) |
| `keys` | `string[]` | Multiple keys for `deleteMany` operation |
| `store` | `string` | Store name |
| `hit` | `boolean` | Whether the key was found (only for `get`) |
| `tier` | `'l1' \| 'l2'` | Which tier served the value (only for `get` hits) |
| `graced` | `boolean` | Whether value came from grace period (only for `get`) |

### OpenTelemetry Integration Example

```ts
import { tracingChannels, type CacheOperationMessage } from 'bentocache'
import { trace } from '@opentelemetry/api'

const tracer = trace.getTracer('bentocache')
const spans = new WeakMap()

tracingChannels.cacheOperation.subscribe({
start(message: CacheOperationMessage) {
const span = tracer.startSpan(`cache.${message.operation}`)
span.setAttribute('cache.key', message.key ?? '')
span.setAttribute('cache.store', message.store)
spans.set(message, span)
},
asyncEnd(message: CacheOperationMessage) {
const span = spans.get(message)
if (!span) return

if (message.hit !== undefined) {
span.setAttribute('cache.hit', message.hit)
}
if (message.tier) {
span.setAttribute('cache.tier', message.tier)
}
if (message.graced) {
span.setAttribute('cache.graced', message.graced)
}

span.end()
},
error({ error, ...message }) {
const span = spans.get(message)
if (!span) return

span.recordException(error)
span.end()
},
})
```
2 changes: 2 additions & 0 deletions packages/bentocache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { errors } from './src/errors.js'
export { BentoCache } from './src/bento_cache.js'
export { bentostore } from './src/bento_store.js'
export { BentoStore } from './src/bento_store.js'
export * as tracingChannels from './src/tracing_channels.js'
export type { CacheOperationMessage } from './src/types/tracing_channels.js'
Loading