Skip to content

Commit 2605b63

Browse files
authored
Merge pull request #765 from MasterKale/feat/improved-metadataservice-logging
Improve MetadataService logging
2 parents da5601f + 1bb6912 commit 2605b63

5 files changed

Lines changed: 123 additions & 26 deletions

File tree

‎packages/server/src/helpers/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export * from './verifySignature.ts';
1515
export * from './iso/index.ts';
1616
export * from '../metadata/verifyMDSBlob.ts';
1717
export * as cose from './cose.ts';
18+
export { type SimpleWebAuthnLogger } from './logging.ts';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { assertEquals } from '@std/assert';
2+
import { assertSpyCallArg, spy } from '@std/testing/mock';
3+
4+
import { buildLoggerAllMethods } from './logging.ts';
5+
6+
Deno.test('should define default methods for undefined methods', () => {
7+
const logger = buildLoggerAllMethods({});
8+
9+
assertEquals(typeof logger.debug, 'function');
10+
assertEquals(typeof logger.info, 'function');
11+
assertEquals(typeof logger.warn, 'function');
12+
assertEquals(typeof logger.error, 'function');
13+
});
14+
15+
Deno.test('should use provided logger methods', () => {
16+
const _debugSpy = spy();
17+
const logger = buildLoggerAllMethods({ debug: _debugSpy });
18+
19+
logger.debug('SimpleWebAuthn');
20+
21+
assertEquals(logger.debug, _debugSpy);
22+
assertSpyCallArg(_debugSpy, 0, 0, 'SimpleWebAuthn');
23+
});
Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,62 @@
1-
// const defaultLogger = debug('SimpleWebAuthn');
2-
31
/**
4-
* Generate an instance of a `debug` logger that extends off of the "simplewebauthn" namespace for
5-
* consistent naming.
6-
*
7-
* See https://www.npmjs.com/package/debug for information on how to control logging output when
8-
* using @simplewebauthn/server
2+
* A basic logging interface that enables projects to capture logging output from SimpleWebAuthn
3+
* using whatever logging method is appropriate for the project. Logging levels can be defined
4+
* independently to only capture desired levels.
95
*
10-
* Example:
6+
* For example, a project using `console` statements to capture logs can use the following
7+
* implementation of this interface:
118
*
9+
* ```ts
10+
* const ConsoleLogger: SimpleWebAuthnLogger = {
11+
* // debug(message: string, ...args: unknown[]) { console.debug(message, ...args); },
12+
* info(message: string, ...args: unknown[]) { console.info(message, ...args); },
13+
* warn(message: string, ...args: unknown[]) { console.warn(message, ...args); },
14+
* error(message: string, ...args: unknown[]) { console.error(message, ...args); },
15+
* };
1216
* ```
13-
* const log = getLogger('mds');
14-
* log('hello'); // simplewebauthn:mds hello +0ms
15-
* ```
1617
*/
17-
export function getLogger(_name: string): (message: string, ..._rest: unknown[]) => void {
18-
// This is a noop for now while I search for a better debug logger technique
19-
return (_message, ..._rest) => {};
18+
export interface SimpleWebAuthnLogger {
19+
debug?: (message: string, ...args: unknown[]) => void;
20+
info?: (message: string, ...args: unknown[]) => void;
21+
warn?: (message: string, ...args: unknown[]) => void;
22+
error?: (message: string, ...args: unknown[]) => void;
23+
}
24+
25+
/**
26+
* A logger instance that doesn't do anything. Useful as a default argument when no custom instance
27+
* of the `SimpleWebAuthnLogger` interface is specified.
28+
*/
29+
export const DefaultNoopLogger: Required<SimpleWebAuthnLogger> = {
30+
debug() {},
31+
info() {},
32+
warn() {},
33+
error() {},
34+
};
35+
36+
/**
37+
* Generate an instance of SimpleWebAuthnLogger that defines all methods. Any logging method not
38+
* defined on `logger` will be a no-op.
39+
*/
40+
export function buildLoggerAllMethods(
41+
logger: SimpleWebAuthnLogger,
42+
): Required<SimpleWebAuthnLogger> {
43+
const toReturn: Required<SimpleWebAuthnLogger> = { ...DefaultNoopLogger };
44+
45+
if (logger.debug) {
46+
toReturn.debug = logger.debug;
47+
}
48+
49+
if (logger.info) {
50+
toReturn.info = logger.info;
51+
}
52+
53+
if (logger.warn) {
54+
toReturn.warn = logger.warn;
55+
}
56+
57+
if (logger.error) {
58+
toReturn.error = logger.error;
59+
}
60+
61+
return toReturn;
2062
}

‎packages/server/src/services/metadataService.test.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { assertEquals, assertRejects } from '@std/assert';
22
import { afterEach, beforeEach, describe, it } from '@std/testing/bdd';
3-
import { assertSpyCallArg, assertSpyCalls, type Stub, stub } from '@std/testing/mock';
3+
import { assertSpyCallArg, assertSpyCalls, spy, type Stub, stub } from '@std/testing/mock';
44

55
import { _fetchInternals } from '../helpers/fetch.ts';
66

@@ -95,6 +95,25 @@ describe('Method: getStatement()', () => {
9595
});
9696
});
9797

98+
describe('Behavior: logging', () => {
99+
it('should use provided logger', async () => {
100+
const _debugSpy = spy();
101+
102+
await MetadataService.initialize({
103+
mdsServers: [],
104+
statements: [],
105+
logger: { debug: _debugSpy },
106+
});
107+
108+
/**
109+
* NOTE TO FUTURE SELF: As of June 2026 the content of the logging is less important here than
110+
* the fact that the service used the provided logger to communicate internal goings on.
111+
*/
112+
assertSpyCallArg(_debugSpy, 0, 0, 'MetadataService is REFRESHING');
113+
assertSpyCallArg(_debugSpy, 1, 0, 'MetadataService is READY');
114+
});
115+
});
116+
98117
const localStatementAAGUID = '91dfead7-959e-4475-ad26-9b0d482be089';
99118
const localStatement: MetadataStatement = {
100119
legalHeader: 'https://fidoalliance.org/metadata/metadata-statement-legal-header/',

‎packages/server/src/services/metadataService.ts‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString.ts';
22
import type { MetadataBLOBPayloadEntry, MetadataStatement } from '../metadata/mdsTypes.ts';
33
import { verifyMDSBlob } from '../metadata/verifyMDSBlob.ts';
4-
import { getLogger } from '../helpers/logging.ts';
4+
import {
5+
buildLoggerAllMethods,
6+
DefaultNoopLogger,
7+
type SimpleWebAuthnLogger,
8+
} from '../helpers/logging.ts';
59
import { fetch } from '../helpers/fetch.ts';
610
import type { Uint8Array_ } from '../types/index.ts';
711

@@ -44,8 +48,6 @@ enum SERVICE_STATE {
4448
*/
4549
export type VerificationMode = 'permissive' | 'strict';
4650

47-
const log = getLogger('MetadataService');
48-
4951
interface MetadataService {
5052
/**
5153
* Prepare the service to handle remote MDS servers and/or cache local metadata statements.
@@ -65,6 +67,7 @@ interface MetadataService {
6567
mdsServers?: string[];
6668
statements?: MetadataStatement[];
6769
verificationMode?: VerificationMode;
70+
logger?: SimpleWebAuthnLogger;
6871
}): Promise<void>;
6972
/**
7073
* Get a metadata statement for a given AAGUID.
@@ -86,18 +89,27 @@ export class BaseMetadataService implements MetadataService {
8689
private statementCache: { [aaguid: string]: CachedBLOBEntry } = {};
8790
private state: SERVICE_STATE = SERVICE_STATE.DISABLED;
8891
private verificationMode: VerificationMode = 'strict';
92+
private logger: Required<SimpleWebAuthnLogger> = DefaultNoopLogger;
8993

9094
async initialize(
9195
opts: {
9296
mdsServers?: string[];
9397
statements?: MetadataStatement[];
9498
verificationMode?: VerificationMode;
99+
logger?: SimpleWebAuthnLogger;
95100
} = {},
96101
): Promise<void> {
97102
// Reset statement cache
98103
this.statementCache = {};
99104

100-
const { mdsServers = [defaultURLMDS], statements, verificationMode } = opts;
105+
const {
106+
mdsServers = [defaultURLMDS],
107+
statements,
108+
verificationMode,
109+
logger = DefaultNoopLogger,
110+
} = opts;
111+
112+
this.logger = buildLoggerAllMethods(logger);
101113

102114
this.setState(SERVICE_STATE.REFRESHING);
103115

@@ -124,7 +136,7 @@ export class BaseMetadataService implements MetadataService {
124136
}
125137
});
126138

127-
log(`Cached ${statementsAdded} local statements`);
139+
this.logger.info(`Cached ${statementsAdded} local statements`);
128140
}
129141

130142
/**
@@ -149,15 +161,15 @@ export class BaseMetadataService implements MetadataService {
149161
await this.verifyBlob(blob, cachedMDS);
150162
} catch (err) {
151163
// Notify of the error and move on
152-
log(`Could not download BLOB from ${url}:`, err);
164+
this.logger.error(`Could not download BLOB from ${url}:`, err);
153165
numServers -= 1;
154166
}
155167
}
156168

157169
// Calculate the difference to get the total number of new statements we successfully added
158170
const newCacheCount = Object.keys(this.statementCache).length;
159171
const cacheDiff = newCacheCount - currentCacheCount;
160-
log(
172+
this.logger.info(
161173
`Cached ${cacheDiff} statements from ${numServers} metadata server(s)`,
162174
);
163175
}
@@ -288,7 +300,7 @@ export class BaseMetadataService implements MetadataService {
288300
// TODO (Feb 2026): It'd be more actionable for devs if a specific error was raised here,
289301
// then this message was logged higher up when it can include the array index of the stale
290302
// blob.
291-
log(
303+
this.logger.warn(
292304
`⚠️ This MDS blob (serial: ${payload.no}) contains stale data as of ${parsedNextUpdate.toISOString()}. Please consider re-initializing MetadataService with a newer MDS blob.`,
293305
);
294306
}
@@ -337,11 +349,11 @@ export class BaseMetadataService implements MetadataService {
337349
this.state = newState;
338350

339351
if (newState === SERVICE_STATE.DISABLED) {
340-
log('MetadataService is DISABLED');
352+
this.logger.debug('MetadataService is DISABLED');
341353
} else if (newState === SERVICE_STATE.REFRESHING) {
342-
log('MetadataService is REFRESHING');
354+
this.logger.debug('MetadataService is REFRESHING');
343355
} else if (newState === SERVICE_STATE.READY) {
344-
log('MetadataService is READY');
356+
this.logger.debug('MetadataService is READY');
345357
}
346358
}
347359
}

0 commit comments

Comments
 (0)