Skip to content

Commit 375e1a0

Browse files
authored
fix: re-use helia's logger (#283)
Do not create top-level loggers for verified fetch components, instead re-use the existing logger and create new scopes from it. This lets us configure helia and verified fetch together.
1 parent 6c1e66f commit 375e1a0

10 files changed

Lines changed: 30 additions & 41 deletions

packages/verified-fetch/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@
177177
"@ipld/dag-pb": "^4.1.5",
178178
"@libp2p/interface": "^3.0.0",
179179
"@libp2p/kad-dht": "^16.0.0",
180-
"@libp2p/logger": "^6.0.0",
181180
"@libp2p/peer-id": "^6.0.0",
182181
"@libp2p/utils": "^7.0.5",
183182
"@libp2p/webrtc": "^6.0.0",
@@ -206,6 +205,7 @@
206205
"@helia/json": "^5.0.0",
207206
"@ipld/car": "^5.4.2",
208207
"@libp2p/crypto": "^5.1.3",
208+
"@libp2p/logger": "^6.0.0",
209209
"@types/sinon": "^17.0.4",
210210
"aegir": "^47.0.24",
211211
"browser-readablestream-to-it": "^2.0.9",

packages/verified-fetch/src/plugins/plugin-base.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export abstract class BasePlugin implements VerifiedFetchPlugin {
1717
protected _log?: Logger
1818

1919
get log (): Logger {
20-
// instantiate the logger lazily because it depends on the id, which is not set until after the constructor is called
20+
// instantiate the logger lazily because it depends on the id, which is not
21+
// set until after the constructor is called
2122
if (this._log == null) {
22-
this._log = this.pluginOptions.logger.forComponent(this.id)
23+
this._log = this.pluginOptions.logger.newScope(this.id)
2324
}
2425
return this._log
2526
}

packages/verified-fetch/src/plugins/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { AcceptHeader } from '../utils/select-output-type.ts'
44
import type { ServerTiming } from '../utils/server-timing.ts'
55
import type { PathWalkerResponse } from '../utils/walk-path.js'
66
import type { IPNSResolver } from '@helia/ipns'
7-
import type { AbortOptions, ComponentLogger, Logger } from '@libp2p/interface'
7+
import type { AbortOptions, Logger } from '@libp2p/interface'
88
import type { Helia } from 'helia'
99
import type { Blockstore } from 'interface-blockstore'
1010
import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
@@ -17,7 +17,7 @@ import type { CustomProgressEvent } from 'progress-events'
1717
* - Persistent: Relevant even after the request completes (e.g., logging or metrics).
1818
*/
1919
export interface PluginOptions {
20-
logger: ComponentLogger
20+
logger: Logger
2121
getBlockstore(cid: CID, resource: string | CID, useSession?: boolean, options?: AbortOptions): Blockstore
2222
contentTypeParser?: ContentTypeParser
2323
helia: Helia

packages/verified-fetch/src/utils/byte-range-context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InvalidRangeError } from '../errors.js'
33
import { calculateByteRangeIndexes, getHeader } from './request-headers.js'
44
import { getContentRangeHeader } from './response-headers.js'
55
import type { SupportedBodyTypes } from '../index.js'
6-
import type { ComponentLogger, Logger } from '@libp2p/interface'
6+
import type { Logger } from '@libp2p/interface'
77

88
type SliceableBody = Exclude<SupportedBodyTypes, ReadableStream<Uint8Array> | null>
99

@@ -90,8 +90,8 @@ export class ByteRangeContext {
9090
// to be set by isValidRangeRequest so that we don't need to re-check the byteRanges
9191
private _isValidRangeRequest: boolean = false
9292

93-
constructor (logger: ComponentLogger, private readonly headers?: HeadersInit) {
94-
this.log = logger.forComponent('helia:verified-fetch:byte-range-context')
93+
constructor (logger: Logger, private readonly headers?: HeadersInit) {
94+
this.log = logger.newScope('byte-range-context')
9595
this.rangeRequestHeader = getHeader(this.headers, 'Range')
9696

9797
if (this.rangeRequestHeader != null) {

packages/verified-fetch/src/utils/content-type-parser.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
1-
import { logger } from '@libp2p/logger'
21
import { fileTypeFromBuffer } from 'file-type'
32

4-
const log = logger('helia:verified-fetch:content-type-parser')
5-
63
export const defaultMimeType = 'application/octet-stream'
74
function checkForSvg (text: string): boolean {
8-
log('checking for svg')
95
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(text)
106
}
117

128
async function checkForJson (text: string): Promise<boolean> {
13-
log('checking for json')
149
try {
1510
JSON.parse(text)
1611
return true
1712
} catch (err) {
18-
log('failed to parse as json', err)
1913
return false
2014
}
2115
}
2216

2317
function getText (bytes: Uint8Array): string | null {
24-
log('checking for text')
2518
const decoder = new TextDecoder('utf-8', { fatal: true })
19+
2620
try {
2721
return decoder.decode(bytes)
2822
} catch (err) {
@@ -31,25 +25,24 @@ function getText (bytes: Uint8Array): string | null {
3125
}
3226

3327
async function checkForHtml (text: string): Promise<boolean> {
34-
log('checking for html')
3528
return /^\s*<(?:!doctype\s+html|html|head|body)\b/i.test(text)
3629
}
3730

3831
export async function contentTypeParser (bytes: Uint8Array, fileName?: string): Promise<string> {
39-
log('contentTypeParser called for fileName: %s, byte size=%s', fileName, bytes.length)
4032
const detectedType = (await fileTypeFromBuffer(bytes))?.mime
33+
4134
if (detectedType != null) {
42-
log('detectedType: %s', detectedType)
4335
if (detectedType === 'application/xml' && fileName?.toLowerCase().endsWith('.svg')) {
4436
return 'image/svg+xml'
4537
}
38+
4639
return detectedType
4740
}
48-
log('no detectedType')
4941

5042
if (fileName == null) {
5143
// it's likely text... no other way to determine file-type.
5244
const text = getText(bytes)
45+
5346
if (text != null) {
5447
// check for svg, json, html, or it's plain text.
5548
if (checkForSvg(text)) {
@@ -62,6 +55,7 @@ export async function contentTypeParser (bytes: Uint8Array, fileName?: string):
6255
return 'text/plain; charset=utf-8'
6356
}
6457
}
58+
6559
return defaultMimeType
6660
}
6761

packages/verified-fetch/src/utils/get-stream-from-async-iterable.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,18 @@ import { AbortError } from '@libp2p/interface'
22
import { CustomProgressEvent } from 'progress-events'
33
import { NoContentError } from '../errors.js'
44
import type { VerifiedFetchInit } from '../index.js'
5-
import type { ComponentLogger } from '@libp2p/interface'
5+
import type { Logger } from '@libp2p/interface'
66

77
/**
88
* Converts an async iterator of Uint8Array bytes to a stream and returns the first chunk of bytes
99
*/
10-
export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8Array>, path: string, logger: ComponentLogger, options?: Pick<VerifiedFetchInit, 'onProgress' | 'signal'>): Promise<{ stream: ReadableStream<Uint8Array>, firstChunk: Uint8Array }> {
11-
const log = logger.forComponent('helia:verified-fetch:get-stream-from-async-iterable')
10+
export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8Array>, path: string, logger: Logger, options?: Pick<VerifiedFetchInit, 'onProgress' | 'signal'>): Promise<{ stream: ReadableStream<Uint8Array>, firstChunk: Uint8Array }> {
11+
const log = logger.newScope('get-stream-from-async-iterable')
1212
const reader = iterator[Symbol.asyncIterator]()
1313
const { value: firstChunk, done } = await reader.next()
1414

1515
if (done === true) {
1616
log.error('no content found for path "%s"', path)
17-
/*
18-
return {
19-
stream: new ReadableStream({
20-
start (controller) {
21-
controller.close()
22-
}
23-
}),
24-
firstChunk: new Uint8Array()
25-
}
26-
*/
2717
throw new NoContentError()
2818
}
2919

packages/verified-fetch/src/utils/walk-path.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function isObjectNode (node: UnixFSEntry): node is ObjectNode {
5252
*/
5353
export async function handlePathWalking ({ cid, path, resource, options, blockstore, log }: PluginContext & { blockstore: Blockstore, log: Logger }): Promise<PathWalkerResponse | Response> {
5454
try {
55-
return await walkPath(blockstore, `${cid.toString()}/${path}`, options)
55+
return await walkPath(blockstore, `${cid}/${path}`, options)
5656
} catch (err: any) {
5757
if (options?.signal?.aborted) {
5858
throw new AbortError(options?.signal?.reason)

packages/verified-fetch/src/verified-fetch.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { dnsLink } from '@helia/dnslink'
22
import { ipnsResolver } from '@helia/ipns'
33
import { AbortError } from '@libp2p/interface'
4-
import { prefixLogger } from '@libp2p/logger'
54
import { CustomProgressEvent } from 'progress-events'
65
import QuickLRU from 'quick-lru'
76
import { ByteRangeContextPlugin } from './plugins/plugin-handle-byte-range-context.js'
@@ -84,7 +83,7 @@ export class VerifiedFetch {
8483

8584
const pluginOptions: PluginOptions = {
8685
...init,
87-
logger: prefixLogger('helia:verified-fetch'),
86+
logger: helia.logger.forComponent('verified-fetch'),
8887
getBlockstore: (cid, resource, useSession, options) => this.getBlockstore(cid, resource, useSession, options),
8988
helia,
9089
contentTypeParser: this.contentTypeParser,
@@ -276,6 +275,8 @@ export class VerifiedFetch {
276275

277276
const maybeResponse = await plugin.handle(context)
278277

278+
this.log('plugin response %s %o', plugin.id, maybeResponse)
279+
279280
if (maybeResponse != null) {
280281
// if a plugin returns a final Response, short-circuit
281282
finalResponse = maybeResponse

packages/verified-fetch/test/get-stream-from-async-iterable.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@ import { defaultLogger } from '@libp2p/logger'
22
import { expect } from 'aegir/chai'
33
import sinon from 'sinon'
44
import { getStreamFromAsyncIterable } from '../src/utils/get-stream-from-async-iterable.js'
5+
import type { Logger } from '@libp2p/logger'
56

67
describe('getStreamFromAsyncIterable', () => {
78
let onProgressSpy: sinon.SinonSpy
9+
let log: Logger
810

911
beforeEach(() => {
1012
onProgressSpy = sinon.spy()
13+
log = defaultLogger().forComponent('test')
1114
})
1215

1316
it('should throw an error if no content is found', async () => {
1417
const iterator = (async function * () { })()
15-
await expect(getStreamFromAsyncIterable(iterator, 'test', defaultLogger())).to.be.rejectedWith('No content found')
18+
await expect(getStreamFromAsyncIterable(iterator, 'test', log)).to.be.rejectedWith('No content found')
1619
})
1720

1821
it('should return the correct content type and a readable stream', async () => {
1922
const chunks = new TextEncoder().encode('Hello, world!')
2023
const iterator = (async function * () { yield chunks })()
21-
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', defaultLogger(), { onProgress: onProgressSpy })
24+
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', log, { onProgress: onProgressSpy })
2225
expect(firstChunk).to.equal(chunks)
2326
const reader = stream.getReader()
2427
const { value } = await reader.read()
@@ -30,7 +33,7 @@ describe('getStreamFromAsyncIterable', () => {
3033
const textEncoder = new TextEncoder()
3134
const chunks = ['Hello,', ' world!'].map((txt) => textEncoder.encode(txt))
3235
const iterator = (async function * () { yield chunks[0]; yield chunks[1] })()
33-
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', defaultLogger(), { onProgress: onProgressSpy })
36+
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', log, { onProgress: onProgressSpy })
3437
expect(firstChunk).to.equal(chunks[0])
3538
const reader = stream.getReader()
3639
let result = ''
@@ -59,7 +62,7 @@ describe('getStreamFromAsyncIterable', () => {
5962
}
6063
}
6164
}
62-
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', defaultLogger(), { onProgress: onProgressSpy })
65+
const { firstChunk, stream } = await getStreamFromAsyncIterable(iterator, 'test.txt', log, { onProgress: onProgressSpy })
6366
// @ts-expect-error - actualFirstChunk is not used before set, because the await above.
6467
expect(firstChunk).to.equal(actualFirstChunk)
6568
const reader = stream.getReader()

packages/verified-fetch/test/utils/byte-range-context.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { Helia } from 'helia'
1212
import type { CID } from 'multiformats/cid'
1313

1414
describe('ByteRangeContext', () => {
15-
const logger = prefixLogger('test')
15+
const logger = prefixLogger('test').forComponent('test')
1616

1717
it('should correctly detect range request', () => {
1818
const context = new ByteRangeContext(logger, { Range: 'bytes=0-2' })
@@ -166,7 +166,7 @@ describe('ByteRangeContext', () => {
166166
let cid: CID
167167
const getBodyStream = async (offset?: number, length?: number): Promise<ReadableStream<Uint8Array>> => {
168168
const iter = fs.cat(cid, { offset, length })
169-
const { stream } = await getStreamFromAsyncIterable(iter, 'test.txt', defaultLogger())
169+
const { stream } = await getStreamFromAsyncIterable(iter, 'test.txt', defaultLogger().forComponent('test'))
170170
return stream
171171
}
172172

0 commit comments

Comments
 (0)