Skip to content

Commit 273dbc7

Browse files
committed
proper logging of http
1 parent 2f8520e commit 273dbc7

6 files changed

Lines changed: 140 additions & 42 deletions

File tree

packages/b2c-tooling/src/auth/api-key.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import type {AuthStrategy} from './types.js';
2+
import {getLogger} from '../logging/logger.js';
23

34
export class ApiKeyStrategy implements AuthStrategy {
45
constructor(
56
private key: string,
67
private headerName = 'x-api-key',
7-
) {}
8+
) {
9+
const logger = getLogger();
10+
// Show partial key for identification (first 8 chars)
11+
const keyPreview = key.length > 8 ? `${key.slice(0, 8)}...` : key;
12+
logger.debug({headerName}, `[Auth] Using API Key authentication (${headerName}): ${keyPreview}`);
13+
}
814

915
async fetch(url: string, init: RequestInit = {}): Promise<Response> {
1016
const headers = new Headers(init.headers);

packages/b2c-tooling/src/auth/basic.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import type {AuthStrategy} from './types.js';
2+
import {getLogger} from '../logging/logger.js';
23

34
export class BasicAuthStrategy implements AuthStrategy {
45
private encoded: string;
56

6-
constructor(user: string, pass: string) {
7+
constructor(
8+
private user: string,
9+
pass: string,
10+
) {
711
this.encoded = Buffer.from(`${user}:${pass}`).toString('base64');
12+
13+
const logger = getLogger();
14+
logger.debug({username: user}, `[Auth] Using Basic authentication for user: ${user}`);
815
}
916

1017
async fetch(url: string, init: RequestInit = {}): Promise<Response> {

packages/b2c-tooling/src/auth/oauth.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {AuthStrategy, AccessTokenResponse, DecodedJWT} from './types.js';
2-
import {getLogger} from '../logger.js';
2+
import {getLogger} from '../logging/logger.js';
33

44
const DEFAULT_ACCOUNT_MANAGER_HOST = 'account.demandware.com';
55

@@ -109,7 +109,8 @@ export class OAuthStrategy implements AuthStrategy {
109109
*/
110110
private async clientCredentialsGrant(): Promise<AccessTokenResponse> {
111111
const logger = getLogger();
112-
logger.debug('Getting access token from client credentials');
112+
const url = `https://${this.accountManagerHost}/dwsso/oauth2/access_token`;
113+
const method = 'POST';
113114

114115
const params = new URLSearchParams({
115116
grant_type: 'client_credentials',
@@ -120,18 +121,44 @@ export class OAuthStrategy implements AuthStrategy {
120121
}
121122

122123
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
124+
const requestHeaders = {
125+
Authorization: `Basic ${credentials}`,
126+
'Content-Type': 'application/x-www-form-urlencoded',
127+
};
128+
129+
logger.debug(
130+
{clientId: this.config.clientId},
131+
`[Auth] Using OAuth client_credentials grant for client: ${this.config.clientId}`,
132+
);
133+
// Debug: Log request start
134+
logger.debug({method, url}, `[Auth REQ] ${method} ${url}`);
135+
136+
// Trace: Log request details
137+
logger.trace({headers: requestHeaders, body: params.toString()}, `[Auth REQ BODY] ${method} ${url}`);
123138

124-
const response = await fetch(`https://${this.accountManagerHost}/dwsso/oauth2/access_token`, {
125-
method: 'POST',
126-
headers: {
127-
Authorization: `Basic ${credentials}`,
128-
'Content-Type': 'application/x-www-form-urlencoded',
129-
},
139+
const startTime = Date.now();
140+
const response = await fetch(url, {
141+
method,
142+
headers: requestHeaders,
130143
body: params.toString(),
131144
});
145+
const duration = Date.now() - startTime;
146+
147+
// Debug: Log response summary
148+
logger.debug(
149+
{method, url, status: response.status, duration},
150+
`[Auth RESP] ${method} ${url} ${response.status} ${duration}ms`,
151+
);
152+
153+
// Get response headers
154+
const responseHeaders: Record<string, string> = {};
155+
response.headers.forEach((value, key) => {
156+
responseHeaders[key] = value;
157+
});
132158

133159
if (!response.ok) {
134160
const errorText = await response.text();
161+
logger.trace({headers: responseHeaders, body: errorText}, `[Auth RESP BODY] ${method} ${url}`);
135162
throw new Error(`Failed to get access token: ${response.status} ${response.statusText} - ${errorText}`);
136163
}
137164

@@ -141,8 +168,11 @@ export class OAuthStrategy implements AuthStrategy {
141168
scope?: string;
142169
};
143170

171+
// Trace: Log response details
172+
logger.trace({headers: responseHeaders, body: data}, `[Auth RESP BODY] ${method} ${url}`);
173+
144174
const jwt = decodeJWT(data.access_token);
145-
logger.debug(`JWT payload: ${JSON.stringify(jwt.payload, null, 2)}`);
175+
logger.trace({jwt: jwt.payload}, '[Auth] JWT payload');
146176

147177
const now = new Date();
148178
const expiration = new Date(now.getTime() + data.expires_in * 1000);

packages/b2c-tooling/src/clients/ocapi.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,30 @@ export function createAuthMiddleware(auth: AuthStrategy): Middleware {
6262
}
6363

6464
/**
65-
* Creates logging middleware for openapi-fetch.
66-
*
67-
* Logs HTTP requests at debug level (summary) and trace level (full details).
68-
*
69-
* @returns Middleware that logs requests and responses
65+
* Converts Headers to a plain object for logging.
7066
*/
67+
function headersToObject(headers: Headers): Record<string, string> {
68+
const result: Record<string, string> = {};
69+
headers.forEach((value, key) => {
70+
result[key] = value;
71+
});
72+
return result;
73+
}
74+
7175
export function createLoggingMiddleware(): Middleware {
7276
return {
7377
async onRequest({request, options}) {
7478
const logger = getLogger();
75-
const url = new URL(request.url);
76-
const path = url.pathname;
79+
const url = request.url;
7780

7881
// Debug: Log request start
79-
logger.debug({method: request.method, path}, `[OCAPI REQ] ${request.method} ${path}`);
82+
logger.debug({method: request.method, url}, `[OCAPI REQ] ${request.method} ${url}`);
8083

81-
// Trace: Log request body
82-
if (options.body) {
83-
logger.trace({body: options.body}, `[OCAPI REQ BODY] ${request.method} ${path}`);
84-
}
84+
// Trace: Log request details
85+
logger.trace(
86+
{headers: headersToObject(request.headers), body: options.body},
87+
`[OCAPI REQ BODY] ${request.method} ${url}`,
88+
);
8589

8690
// Store start time for duration calculation
8791
(request as Request & {_startTime?: number})._startTime = Date.now();
@@ -93,17 +97,15 @@ export function createLoggingMiddleware(): Middleware {
9397
const logger = getLogger();
9498
const startTime = (request as Request & {_startTime?: number})._startTime ?? Date.now();
9599
const duration = Date.now() - startTime;
96-
97-
const url = new URL(request.url);
98-
const path = url.pathname;
100+
const url = request.url;
99101

100102
// Debug: Log response summary
101103
logger.debug(
102-
{method: request.method, path, status: response.status, duration},
103-
`[OCAPI RESP] ${request.method} ${path} ${response.status} ${duration}ms`,
104+
{method: request.method, url, status: response.status, duration},
105+
`[OCAPI RESP] ${request.method} ${url} ${response.status} ${duration}ms`,
104106
);
105107

106-
// Trace: Log response body
108+
// Trace: Log response details
107109
const clonedResponse = response.clone();
108110
let responseBody: unknown;
109111
try {
@@ -112,7 +114,10 @@ export function createLoggingMiddleware(): Middleware {
112114
responseBody = await clonedResponse.text();
113115
}
114116

115-
logger.trace({body: responseBody}, `[OCAPI RESP BODY] ${request.method} ${path}`);
117+
logger.trace(
118+
{headers: headersToObject(response.headers), body: responseBody},
119+
`[OCAPI RESP BODY] ${request.method} ${url}`,
120+
);
116121

117122
return response;
118123
},

packages/b2c-tooling/src/clients/webdav.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,33 +69,57 @@ export class WebDavClient {
6969
const method = init?.method ?? 'GET';
7070

7171
// Debug: Log request start
72-
logger.debug({method, path}, `[WebDAV REQ] ${method} ${path}`);
72+
logger.debug({method, url}, `[WebDAV REQ] ${method} ${url}`);
7373

74-
// Trace: Log request body
75-
if (init?.body) {
76-
logger.trace({body: this.formatBody(init.body)}, `[WebDAV REQ BODY] ${method} ${path}`);
77-
}
74+
// Trace: Log request details
75+
logger.trace(
76+
{headers: this.headersToObject(init?.headers), body: this.formatBody(init?.body)},
77+
`[WebDAV REQ BODY] ${method} ${url}`,
78+
);
7879

7980
const startTime = Date.now();
8081
const response = await this.auth.fetch(url, init);
8182
const duration = Date.now() - startTime;
8283

8384
// Debug: Log response summary
8485
logger.debug(
85-
{method, path, status: response.status, duration},
86-
`[WebDAV RESP] ${method} ${path} ${response.status} ${duration}ms`,
86+
{method, url, status: response.status, duration},
87+
`[WebDAV RESP] ${method} ${url} ${response.status} ${duration}ms`,
8788
);
8889

89-
// Trace: Log response body (only for non-binary responses)
90+
// Trace: Log response details
91+
const responseHeaders = this.headersToObject(response.headers);
92+
let responseBody: string | undefined;
9093
if (response.headers.get('content-type')?.includes('xml')) {
9194
const clonedResponse = response.clone();
92-
const responseBody = await clonedResponse.text();
93-
logger.trace({body: responseBody}, `[WebDAV RESP BODY] ${method} ${path}`);
95+
responseBody = await clonedResponse.text();
9496
}
97+
logger.trace({headers: responseHeaders, body: responseBody}, `[WebDAV RESP BODY] ${method} ${url}`);
9598

9699
return response;
97100
}
98101

102+
/**
103+
* Converts Headers to a plain object for logging.
104+
*/
105+
private headersToObject(headers?: HeadersInit | Headers): Record<string, string> | undefined {
106+
if (!headers) return undefined;
107+
108+
const result: Record<string, string> = {};
109+
if (headers instanceof Headers) {
110+
headers.forEach((value, key) => {
111+
result[key] = value;
112+
});
113+
} else if (Array.isArray(headers)) {
114+
for (const [key, value] of headers) {
115+
result[key] = value;
116+
}
117+
} else {
118+
Object.assign(result, headers);
119+
}
120+
return result;
121+
}
122+
99123
/**
100124
* Formats body for logging, describing binary data.
101125
*/

packages/b2c-tooling/src/logging/logger.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,39 @@ const REDACT_FIELDS = [
1919
'token',
2020
'secret',
2121
'authorization',
22+
'Authorization',
2223
];
2324

2425
const REDACT_PATHS = REDACT_FIELDS.flatMap((field) => [field, `*.${field}`]);
2526

26-
function censor(value: unknown): string {
27+
function censor(value: unknown, path: string[]): string {
28+
// Special handling for authorization headers
29+
if (path[path.length - 1].toLowerCase() === 'authorization' && typeof value === 'string') {
30+
const parts = value.split(' ');
31+
if (parts.length === 2) {
32+
const [scheme, credentials] = parts;
33+
34+
// For Basic auth, decode, redact password, and re-encode
35+
if (scheme.toLowerCase() === 'basic') {
36+
try {
37+
const decoded = Buffer.from(credentials, 'base64').toString('utf-8');
38+
const colonIndex = decoded.indexOf(':');
39+
if (colonIndex !== -1) {
40+
const username = decoded.slice(0, colonIndex);
41+
const redacted = Buffer.from(`${username}:REDACTED`).toString('base64');
42+
return `Basic ${redacted}`;
43+
}
44+
} catch {
45+
// If decoding fails, fall through to default behavior
46+
}
47+
}
48+
49+
// For other schemes (Bearer, etc.), show scheme and partial token
50+
return `${scheme} ${credentials.slice(0, 6)}...REDACTED`;
51+
}
52+
}
2753
if (typeof value === 'string' && value.length > 10) {
28-
return `${value.slice(0, 4)}REDACTED`;
54+
return `${value.slice(0, 4)}...REDACTED`;
2955
}
3056
return 'REDACTED';
3157
}

0 commit comments

Comments
 (0)