Skip to content

Commit b9db3b5

Browse files
authored
feat: rework logging system (#55)
* feat: prepare better logging + add new logs * feat: rework logging system * chore: add changeset * chore: update lockfile
1 parent 346a808 commit b9db3b5

19 files changed

+379
-187
lines changed

.changeset/tasty-shirts-judge.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'bentocache': minor
3+
---
4+
5+
Rework the logs issued by bentocache to make them much cleaner, more consistent and make debug easier

benchmarks/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"keywords": [],
1010
"main": "index.js",
1111
"scripts": {
12-
"test": "echo \"Error: no test specified\" && exit 1"
12+
"bench:all": "pnpm tsx all.ts"
1313
},
1414
"devDependencies": {
1515
"@keyv/redis": "^4.2.0",

packages/bentocache/src/bento_cache_options.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@ import lodash from '@poppinss/utils/lodash'
33
import string from '@poppinss/utils/string'
44
import { noopLogger } from '@julr/utils/logger'
55

6+
import { Logger } from './logger.js'
67
import { resolveTtl } from './helpers.js'
78
import type { FactoryError } from './errors.js'
89
import { JsonSerializer } from './serializers/json.js'
9-
import type {
10-
CacheSerializer,
11-
Duration,
12-
Emitter,
13-
Logger,
14-
RawBentoCacheOptions,
15-
} from './types/main.js'
10+
import type { CacheSerializer, Duration, Emitter, RawBentoCacheOptions } from './types/main.js'
1611

1712
const defaultSerializer = new JsonSerializer()
1813

@@ -57,7 +52,7 @@ export class BentoCacheOptions {
5752
/**
5853
* The logger used throughout the library
5954
*/
60-
logger: Logger = noopLogger()
55+
logger: Logger
6156

6257
/**
6358
* The emitter used throughout the library
@@ -102,7 +97,7 @@ export class BentoCacheOptions {
10297
this.serializer = this.#options.serializer ?? defaultSerializer
10398
this.l2CircuitBreakerDuration = resolveTtl(this.#options.l2CircuitBreakerDuration, null)
10499

105-
this.logger = this.#options.logger!.child({ pkg: 'bentocache' })
100+
this.logger = new Logger(this.#options.logger ?? noopLogger())
106101
this.onFactoryError = this.#options.onFactoryError
107102
}
108103

packages/bentocache/src/bus/bus.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Bus as BoringBus } from '@boringnode/bus'
22
import type { Transport } from '@boringnode/bus/types/main'
33

4+
import type { Logger } from '../logger.js'
45
import { busEvents } from '../events/bus_events.js'
56
import { CacheBusMessageType } from '../types/bus.js'
67
import type { LocalCache } from '../cache/facades/local_cache.js'
7-
import type { BusOptions, CacheBusMessage, Emitter, Logger } from '../types/main.js'
8+
import type { BusOptions, CacheBusMessage, Emitter } from '../types/main.js'
89

910
/**
1011
* The bus is used to notify other processes about cache changes.

packages/bentocache/src/cache/cache.ts

+82-51
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CacheBusMessageType } from '../types/main.js'
55
import { cacheEvents } from '../events/cache_events.js'
66
import type { CacheProvider } from '../types/provider.js'
77
import { GetSetHandler } from './get_set/get_set_handler.js'
8+
import type { BentoCacheOptions } from '../bento_cache_options.js'
89
import type {
910
Factory,
1011
ClearOptions,
@@ -25,11 +26,13 @@ export class Cache implements CacheProvider {
2526

2627
#getSetHandler: GetSetHandler
2728
#stack: CacheStack
29+
#options: BentoCacheOptions
2830

2931
constructor(name: string, stack: CacheStack) {
3032
this.name = name
3133

3234
this.#stack = stack
35+
this.#options = stack.options
3336
this.#getSetHandler = new GetSetHandler(this.#stack)
3437
}
3538

@@ -45,54 +48,61 @@ export class Cache implements CacheProvider {
4548
}
4649

4750
get<T = any>(options: GetOptions<T>): Promise<T>
48-
async get<T = any>(keyOrOptions: GetOptions<T>): Promise<T | undefined | null> {
49-
const key = keyOrOptions.key
50-
const providedOptions = keyOrOptions
51-
const defaultValueFn = this.#resolveDefaultValue(keyOrOptions.defaultValue)
51+
async get<T = any>(rawOptions: GetOptions<T>): Promise<T | undefined | null> {
52+
const key = rawOptions.key
53+
const defaultValueFn = this.#resolveDefaultValue(rawOptions.defaultValue)
5254

53-
const options = this.#stack.defaultOptions.cloneWith(providedOptions)
54-
const localItem = this.#stack.l1?.get(key, options)
55+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
56+
this.#options.logger.logMethod({ method: 'get', key, options, cacheName: this.name })
5557

56-
if (localItem !== undefined && !localItem.isLogicallyExpired()) {
57-
this.#stack.emit(cacheEvents.hit(key, localItem.getValue(), this.name))
58-
return localItem.getValue()
58+
const localItem = this.#stack.l1?.get(key, options)
59+
if (localItem?.isGraced === false) {
60+
this.#stack.emit(cacheEvents.hit(key, localItem.entry.getValue(), this.name))
61+
this.#options.logger.logL1Hit({ cacheName: this.name, key, options })
62+
return localItem.entry.getValue()
5963
}
6064

6165
const remoteItem = await this.#stack.l2?.get(key, options)
6266

63-
if (remoteItem !== undefined && !remoteItem.isLogicallyExpired()) {
64-
this.#stack.l1?.set(key, remoteItem.serialize(), options)
65-
this.#stack.emit(cacheEvents.hit(key, remoteItem.getValue(), this.name))
66-
return remoteItem.getValue()
67-
}
68-
69-
if (!options.isGraceEnabled()) {
70-
this.#stack.emit(cacheEvents.miss(key, this.name))
71-
return this.#resolveDefaultValue(defaultValueFn)
67+
if (remoteItem?.isGraced === false) {
68+
this.#stack.l1?.set(key, remoteItem.entry.serialize(), options)
69+
this.#stack.emit(cacheEvents.hit(key, remoteItem.entry.getValue(), this.name))
70+
this.#options.logger.logL2Hit({ cacheName: this.name, key, options })
71+
return remoteItem.entry.getValue()
7272
}
7373

74-
if (remoteItem) {
75-
this.#stack.l1?.set(key, remoteItem.serialize(), options)
76-
this.#stack.emit(cacheEvents.hit(key, remoteItem.serialize(), this.name, true))
77-
return remoteItem.getValue()
74+
if (remoteItem && options.isGraceEnabled()) {
75+
this.#stack.l1?.set(key, remoteItem.entry.serialize(), options)
76+
this.#stack.emit(cacheEvents.hit(key, remoteItem.entry.serialize(), this.name, true))
77+
this.#options.logger.logL2Hit({ cacheName: this.name, key, options, graced: true })
78+
return remoteItem.entry.getValue()
7879
}
7980

80-
if (localItem) {
81-
this.#stack.emit(cacheEvents.hit(key, localItem.serialize(), this.name, true))
82-
return localItem.getValue()
81+
if (localItem && options.isGraceEnabled()) {
82+
this.#stack.emit(cacheEvents.hit(key, localItem.entry.serialize(), this.name, true))
83+
this.#options.logger.logL1Hit({ cacheName: this.name, key, options, graced: true })
84+
return localItem.entry.getValue()
8385
}
8486

8587
this.#stack.emit(cacheEvents.miss(key, this.name))
88+
this.#options.logger.debug({ key, cacheName: this.name }, 'cache miss. using default value')
8689
return this.#resolveDefaultValue(defaultValueFn)
8790
}
8891

8992
/**
9093
* Set a value in the cache
9194
* Returns true if the value was set, false otherwise
9295
*/
93-
set(options: SetOptions) {
94-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
95-
return this.#stack.set(options.key, options.value, cacheOptions)
96+
set(rawOptions: SetOptions) {
97+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
98+
this.#options.logger.logMethod({
99+
method: 'set',
100+
options,
101+
key: rawOptions.key,
102+
cacheName: this.name,
103+
})
104+
105+
return this.#stack.set(rawOptions.key, rawOptions.value, options)
96106
}
97107

98108
/**
@@ -107,28 +117,41 @@ export class Cache implements CacheProvider {
107117
* Retrieve an item from the cache if it exists, otherwise store the value
108118
* provided by the factory and return it
109119
*/
110-
getOrSet<T>(options: GetOrSetOptions<T>): Promise<T> {
111-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
112-
return this.#getSetHandler.handle(options.key, options.factory, cacheOptions)
120+
getOrSet<T>(rawOptions: GetOrSetOptions<T>): Promise<T> {
121+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
122+
this.#options.logger.logMethod({
123+
method: 'getOrSet',
124+
key: rawOptions.key,
125+
cacheName: this.name,
126+
options,
127+
})
128+
129+
return this.#getSetHandler.handle(rawOptions.key, rawOptions.factory, options)
113130
}
114131

115132
/**
116133
* Retrieve an item from the cache if it exists, otherwise store the value
117134
* provided by the factory forever and return it
118135
*/
119-
getOrSetForever<T>(options: GetOrSetForeverOptions<T>): Promise<T> {
120-
const cacheOptions = this.#stack.defaultOptions.cloneWith({ ttl: null, ...options })
121-
return this.#getSetHandler.handle(options.key, options.factory, cacheOptions)
136+
getOrSetForever<T>(rawOptions: GetOrSetForeverOptions<T>): Promise<T> {
137+
const options = this.#stack.defaultOptions.cloneWith({ ttl: null, ...rawOptions })
138+
return this.#getSetHandler.handle(rawOptions.key, rawOptions.factory, options)
122139
}
123140

124141
/**
125142
* Check if a key exists in the cache
126143
*/
127144
async has(options: HasOptions) {
128145
const key = options.key
129-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
130-
131-
const inRemote = await this.#stack.l2?.has(key, cacheOptions)
146+
const entryOptions = this.#stack.defaultOptions.cloneWith(options)
147+
this.#options.logger.logMethod({
148+
method: 'has',
149+
key,
150+
cacheName: this.name,
151+
options: entryOptions,
152+
})
153+
154+
const inRemote = await this.#stack.l2?.has(key, entryOptions)
132155
const inLocal = this.#stack.l1?.has(key)
133156

134157
return !!(inRemote || inLocal)
@@ -155,12 +178,13 @@ export class Cache implements CacheProvider {
155178
* Delete a key from the cache, emit cache:deleted event and
156179
* publish invalidation through the bus
157180
*/
158-
async delete(options: DeleteOptions): Promise<boolean> {
159-
const key = options.key
160-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
181+
async delete(rawOptions: DeleteOptions): Promise<boolean> {
182+
const key = rawOptions.key
183+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
184+
this.#options.logger.logMethod({ method: 'delete', key, cacheName: this.name, options })
161185

162-
this.#stack.l1?.delete(key, cacheOptions)
163-
await this.#stack.l2?.delete(key, cacheOptions)
186+
this.#stack.l1?.delete(key, options)
187+
await this.#stack.l2?.delete(key, options)
164188

165189
this.#stack.emit(cacheEvents.deleted(key, this.name))
166190
await this.#stack.publish({ type: CacheBusMessageType.Delete, keys: [key] })
@@ -173,12 +197,18 @@ export class Cache implements CacheProvider {
173197
* Then emit cache:deleted events for each key
174198
* And finally publish invalidation through the bus
175199
*/
176-
async deleteMany(options: DeleteManyOptions): Promise<boolean> {
177-
const keys = options.keys
178-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
179-
180-
this.#stack.l1?.deleteMany(keys, cacheOptions)
181-
await this.#stack.l2?.deleteMany(keys, cacheOptions)
200+
async deleteMany(rawOptions: DeleteManyOptions): Promise<boolean> {
201+
const keys = rawOptions.keys
202+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
203+
this.#options.logger.logMethod({
204+
method: 'deleteMany',
205+
key: keys,
206+
cacheName: this.name,
207+
options,
208+
})
209+
210+
this.#stack.l1?.deleteMany(keys, options)
211+
await this.#stack.l2?.deleteMany(keys, options)
182212

183213
keys.forEach((key) => this.#stack.emit(cacheEvents.deleted(key, this.name)))
184214
await this.#stack.publish({ type: CacheBusMessageType.Delete, keys })
@@ -189,12 +219,13 @@ export class Cache implements CacheProvider {
189219
/**
190220
* Remove all items from the cache
191221
*/
192-
async clear(options?: ClearOptions) {
193-
const cacheOptions = this.#stack.defaultOptions.cloneWith(options)
222+
async clear(rawOptions?: ClearOptions) {
223+
const options = this.#stack.defaultOptions.cloneWith(rawOptions)
224+
this.#options.logger.logMethod({ method: 'clear', cacheName: this.name, options })
194225

195226
await Promise.all([
196227
this.#stack.l1?.clear(),
197-
this.#stack.l2?.clear(cacheOptions),
228+
this.#stack.l2?.clear(options),
198229
this.#stack.publish({ type: CacheBusMessageType.Clear, keys: [] }),
199230
])
200231

packages/bentocache/src/cache/cache_stack.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { is } from '@julr/utils/is'
22
import lodash from '@poppinss/utils/lodash'
33

44
import { Bus } from '../bus/bus.js'
5+
import type { Logger } from '../logger.js'
56
import { UndefinedValueError } from '../errors.js'
67
import { LocalCache } from './facades/local_cache.js'
78
import { BaseDriver } from '../drivers/base_driver.js'
@@ -15,7 +16,6 @@ import {
1516
type CacheEvent,
1617
type CacheStackDrivers,
1718
type CacheBusMessage,
18-
type Logger,
1919
CacheBusMessageType,
2020
} from '../types/main.js'
2121

@@ -65,9 +65,8 @@ export class CacheStack extends BaseDriver {
6565
{ retryQueue: { enabled: true, maxSize: undefined } },
6666
busOptions,
6767
)
68-
const newBus = new Bus(this.name, this.#busDriver, this.logger, this.emitter, this.#busOptions)
6968

70-
return newBus
69+
return new Bus(this.name, this.#busDriver, this.logger, this.emitter, this.#busOptions)
7170
}
7271

7372
namespace(namespace: string): CacheStack {

packages/bentocache/src/cache/facades/local_cache.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { Logger } from '../../logger.js'
12
import { CacheEntry } from '../cache_entry/cache_entry.js'
3+
import type { L1CacheDriver, CacheSerializer } from '../../types/main.js'
24
import type { CacheEntryOptions } from '../cache_entry/cache_entry_options.js'
3-
import type { Logger, L1CacheDriver, CacheSerializer } from '../../types/main.js'
45

56
/**
67
* LocalCache is a wrapper around a CacheDriver that provides a
@@ -14,7 +15,7 @@ export class LocalCache {
1415
constructor(driver: L1CacheDriver, logger: Logger, serializer: CacheSerializer | undefined) {
1516
this.#driver = driver
1617
this.#serializer = serializer
17-
this.#logger = logger.child({ context: 'bentocache.localCache' })
18+
this.#logger = logger.child({ layer: 'l1' })
1819
}
1920

2021
/**
@@ -24,18 +25,26 @@ export class LocalCache {
2425
/**
2526
* Try to get the item from the local cache
2627
*/
27-
this.#logger.trace({ key, opId: options.id }, 'try getting local cache item')
28+
this.#logger.trace({ key, opId: options.id }, 'try getting from l1 cache')
2829
const value = this.#driver.get(key)
2930

3031
/**
3132
* If the item is not found, return undefined
3233
*/
3334
if (value === undefined) {
34-
this.#logger.trace({ key, opId: options.id }, 'local cache item not found')
35+
this.#logger.debug({ key, opId: options.id }, 'cache miss')
3536
return
3637
}
3738

38-
return CacheEntry.fromDriver(key, value, this.#serializer)
39+
const entry = CacheEntry.fromDriver(key, value, this.#serializer)
40+
const isGraced = entry.isLogicallyExpired()
41+
if (isGraced) {
42+
this.#logger.debug({ key, opId: options.id }, 'cache hit (graced)')
43+
} else {
44+
this.#logger.debug({ key, opId: options.id }, 'cache hit')
45+
}
46+
47+
return { entry, isGraced }
3948
}
4049

4150
/**
@@ -53,15 +62,15 @@ export class LocalCache {
5362
/**
5463
* Save the item to the local cache
5564
*/
56-
this.#logger.trace({ key, value, opId: options.id }, 'saving local cache item')
65+
this.#logger.debug({ key, opId: options.id }, 'saving item')
5766
this.#driver.set(key, value, physicalTtl)
5867
}
5968

6069
/**
6170
* Delete an item from the local cache
6271
*/
6372
delete(key: string, options?: CacheEntryOptions) {
64-
this.#logger.trace({ key, opId: options?.id }, 'deleting local cache item')
73+
this.#logger.debug({ key, opId: options?.id }, 'deleting item')
6574
return this.#driver.delete(key)
6675
}
6776

@@ -73,7 +82,7 @@ export class LocalCache {
7382
* is down and the grace period is enabled
7483
*/
7584
logicallyExpire(key: string) {
76-
this.#logger.trace({ key }, 'logically expiring local cache item')
85+
this.#logger.debug({ key }, 'logically expiring item')
7786

7887
const value = this.#driver.get(key)
7988
if (value === undefined) return
@@ -86,7 +95,7 @@ export class LocalCache {
8695
* Delete many item from the local cache
8796
*/
8897
deleteMany(keys: string[], options: CacheEntryOptions) {
89-
this.#logger.trace({ keys, options, opId: options.id }, 'deleting local cache items')
98+
this.#logger.debug({ keys, options, opId: options.id }, 'deleting items')
9099
this.#driver.deleteMany(keys)
91100
}
92101

0 commit comments

Comments
 (0)