Skip to content

Commit e3507e5

Browse files
committed
chore: allow logging unredacted messages
1 parent cf6a446 commit e3507e5

File tree

16 files changed

+316
-174
lines changed

16 files changed

+316
-174
lines changed

src/common/atlas/accessListUtils.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,25 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI
3030
params: { path: { groupId: projectId } },
3131
body: [entry],
3232
});
33-
logger.debug(
34-
LogId.atlasIpAccessListAdded,
35-
"accessListUtils",
36-
`IP access list created: ${JSON.stringify(entry)}`
37-
);
33+
logger.debug({
34+
id: LogId.atlasIpAccessListAdded,
35+
context: "accessListUtils",
36+
message: `IP access list created: ${JSON.stringify(entry)}`,
37+
});
3838
} catch (err) {
3939
if (err instanceof ApiClientError && err.response?.status === 409) {
4040
// 409 Conflict: entry already exists, log info
41-
logger.debug(
42-
LogId.atlasIpAccessListAdded,
43-
"accessListUtils",
44-
`IP address ${entry.ipAddress} is already present in the access list for project ${projectId}.`
45-
);
41+
logger.debug({
42+
id: LogId.atlasIpAccessListAdded,
43+
context: "accessListUtils",
44+
message: `IP address ${entry.ipAddress} is already present in the access list for project ${projectId}.`,
45+
});
4646
return;
4747
}
48-
logger.warning(
49-
LogId.atlasIpAccessListAddFailure,
50-
"accessListUtils",
51-
`Error adding IP access list: ${err instanceof Error ? err.message : String(err)}`
52-
);
48+
logger.warning({
49+
id: LogId.atlasIpAccessListAddFailure,
50+
context: "accessListUtils",
51+
message: `Error adding IP access list: ${err instanceof Error ? err.message : String(err)}`,
52+
});
5353
}
5454
}

src/common/atlas/apiClient.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ export class ApiClient {
179179
};
180180
} catch (error: unknown) {
181181
const err = error instanceof Error ? error : new Error(String(error));
182-
logger.error(LogId.atlasConnectFailure, "apiClient", `Failed to request access token: ${err.message}`);
182+
logger.error({
183+
id: LogId.atlasConnectFailure,
184+
context: "apiClient",
185+
message: `Failed to request access token: ${err.message}`,
186+
});
183187
}
184188
return this.accessToken;
185189
}
@@ -197,7 +201,11 @@ export class ApiClient {
197201
}
198202
} catch (error: unknown) {
199203
const err = error instanceof Error ? error : new Error(String(error));
200-
logger.error(LogId.atlasApiRevokeFailure, "apiClient", `Failed to revoke access token: ${err.message}`);
204+
logger.error({
205+
id: LogId.atlasApiRevokeFailure,
206+
context: "apiClient",
207+
message: `Failed to revoke access token: ${err.message}`,
208+
});
201209
}
202210
this.accessToken = undefined;
203211
}

src/common/atlas/cluster.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ export async function inspectCluster(apiClient: ApiClient, projectId: string, cl
8787
return formatFlexCluster(cluster);
8888
} catch (flexError) {
8989
const err = flexError instanceof Error ? flexError : new Error(String(flexError));
90-
logger.error(LogId.atlasInspectFailure, "inspect-cluster", `error inspecting cluster: ${err.message}`);
90+
logger.error({
91+
id: LogId.atlasInspectFailure,
92+
context: "inspect-cluster",
93+
message: `error inspecting cluster: ${err.message}`,
94+
});
9195
throw error;
9296
}
9397
}

src/common/logger.ts

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,44 +50,95 @@ export const LogId = {
5050
streamableHttpTransportCloseFailure: mongoLogId(1_006_006),
5151
} as const;
5252

53+
interface LogPayload {
54+
id: MongoLogId;
55+
context: string;
56+
message: string;
57+
noRedaction?: boolean | LoggerType | LoggerType[];
58+
}
59+
60+
export type LoggerType = "console" | "disk" | "mcp";
61+
5362
export abstract class LoggerBase {
54-
abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
63+
abstract log(level: LogLevel, payload: Omit<LogPayload, "noRedaction">): void;
64+
65+
abstract type: LoggerType | null;
66+
67+
private logCore(level: LogLevel, payload: LogPayload) {
68+
// Default to not redacting mcp logs, redact everything else
69+
const noRedaction = payload.noRedaction !== undefined ? payload.noRedaction : "mcp";
70+
71+
this.log(level, {
72+
id: payload.id,
73+
context: payload.context,
74+
message: this.redactIfNecessary(payload.message, noRedaction),
75+
});
76+
}
77+
78+
private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
79+
if (typeof noRedaction === "boolean" && noRedaction) {
80+
// If the consumer has supplied noRedaction: true, we don't redact the log message
81+
// regardless of the logger type
82+
return message;
83+
}
5584

56-
info(id: MongoLogId, context: string, message: string): void {
57-
this.log("info", id, context, message);
85+
if (typeof noRedaction === "string" && noRedaction === this.type) {
86+
// If the consumer has supplied noRedaction: logger-type, we skip redacting if
87+
// our logger type is the same as what the consumer requested
88+
return message;
89+
}
90+
91+
if (
92+
typeof noRedaction === "object" &&
93+
Array.isArray(noRedaction) &&
94+
this.type !== null &&
95+
noRedaction.indexOf(this.type) !== -1
96+
) {
97+
// If the consumer has supplied noRedaction: array, we skip redacting if our logger
98+
// type is included in that array
99+
return message;
100+
}
101+
102+
return redact(message);
103+
}
104+
105+
info(payload: LogPayload): void {
106+
this.log("info", payload);
58107
}
59108

60-
error(id: MongoLogId, context: string, message: string): void {
61-
this.log("error", id, context, message);
109+
error(payload: LogPayload): void {
110+
this.log("error", payload);
62111
}
63-
debug(id: MongoLogId, context: string, message: string): void {
64-
this.log("debug", id, context, message);
112+
debug(payload: LogPayload): void {
113+
this.log("debug", payload);
65114
}
66115

67-
notice(id: MongoLogId, context: string, message: string): void {
68-
this.log("notice", id, context, message);
116+
notice(payload: LogPayload): void {
117+
this.log("notice", payload);
69118
}
70119

71-
warning(id: MongoLogId, context: string, message: string): void {
72-
this.log("warning", id, context, message);
120+
warning(payload: LogPayload): void {
121+
this.log("warning", payload);
73122
}
74123

75-
critical(id: MongoLogId, context: string, message: string): void {
76-
this.log("critical", id, context, message);
124+
critical(payload: LogPayload): void {
125+
this.log("critical", payload);
77126
}
78127

79-
alert(id: MongoLogId, context: string, message: string): void {
80-
this.log("alert", id, context, message);
128+
alert(payload: LogPayload): void {
129+
this.log("alert", payload);
81130
}
82131

83-
emergency(id: MongoLogId, context: string, message: string): void {
84-
this.log("emergency", id, context, message);
132+
emergency(payload: LogPayload): void {
133+
this.log("emergency", payload);
85134
}
86135
}
87136

88137
export class ConsoleLogger extends LoggerBase {
89-
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
90-
message = redact(message);
138+
type: LoggerType = "console";
139+
140+
log(level: LogLevel, payload: LogPayload): void {
141+
const { id, context, message } = payload;
91142
console.error(`[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid})`);
92143
}
93144
}
@@ -97,6 +148,8 @@ export class DiskLogger extends LoggerBase {
97148
super();
98149
}
99150

151+
type: LoggerType = "disk";
152+
100153
static async fromPath(logPath: string): Promise<DiskLogger> {
101154
await fs.mkdir(logPath, { recursive: true });
102155

@@ -116,8 +169,8 @@ export class DiskLogger extends LoggerBase {
116169
return new DiskLogger(logWriter);
117170
}
118171

119-
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
120-
message = redact(message);
172+
log(level: LogLevel, payload: LogPayload): void {
173+
const { id, context, message } = payload;
121174
const mongoDBLevel = this.mapToMongoDBLogLevel(level);
122175

123176
this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message);
@@ -149,20 +202,24 @@ export class McpLogger extends LoggerBase {
149202
super();
150203
}
151204

152-
log(level: LogLevel, _: MongoLogId, context: string, message: string): void {
205+
type: LoggerType = "mcp";
206+
207+
log(level: LogLevel, payload: LogPayload): void {
153208
// Only log if the server is connected
154209
if (!this.server?.isConnected()) {
155210
return;
156211
}
157212

158213
void this.server.server.sendLoggingMessage({
159214
level,
160-
data: `[${context}]: ${message}`,
215+
data: `[${payload.context}]: ${payload.message}`,
161216
});
162217
}
163218
}
164219

165220
class CompositeLogger extends LoggerBase {
221+
type: LoggerType | null = null;
222+
166223
private loggers: LoggerBase[] = [];
167224

168225
constructor(...loggers: LoggerBase[]) {
@@ -178,9 +235,9 @@ class CompositeLogger extends LoggerBase {
178235
this.loggers = [...loggers];
179236
}
180237

181-
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
238+
log(level: LogLevel, payload: LogPayload): void {
182239
for (const logger of this.loggers) {
183-
logger.log(level, id, context, message);
240+
logger.log(level, payload);
184241
}
185242
}
186243
}

src/common/session.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ export class Session extends EventEmitter<SessionEvents> {
6767
await this.serviceProvider.close(true);
6868
} catch (err: unknown) {
6969
const error = err instanceof Error ? err : new Error(String(err));
70-
logger.error(LogId.mongodbDisconnectFailure, "Error closing service provider:", error.message);
70+
logger.error({
71+
id: LogId.mongodbDisconnectFailure,
72+
context: "session",
73+
message: `Error closing service provider: ${error.message}`,
74+
});
7175
}
7276
this.serviceProvider = undefined;
7377
}
@@ -84,11 +88,11 @@ export class Session extends EventEmitter<SessionEvents> {
8488
})
8589
.catch((err: unknown) => {
8690
const error = err instanceof Error ? err : new Error(String(err));
87-
logger.error(
88-
LogId.atlasDeleteDatabaseUserFailure,
89-
"atlas-connect-cluster",
90-
`Error deleting previous database user: ${error.message}`
91-
);
91+
logger.error({
92+
id: LogId.atlasDeleteDatabaseUserFailure,
93+
context: "session",
94+
message: `Error deleting previous database user: ${error.message}`,
95+
});
9296
});
9397
this.connectedAtlasCluster = undefined;
9498
}

src/common/sessionStore.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ export class SessionStore {
4747
private sendNotification(sessionId: string): void {
4848
const session = this.sessions[sessionId];
4949
if (!session) {
50-
logger.warning(
51-
LogId.streamableHttpTransportSessionCloseNotificationFailure,
52-
"sessionStore",
53-
`session ${sessionId} not found, no notification delivered`
54-
);
50+
logger.warning({
51+
id: LogId.streamableHttpTransportSessionCloseNotificationFailure,
52+
context: "sessionStore",
53+
message: `session ${sessionId} not found, no notification delivered`,
54+
});
5555
return;
5656
}
57-
session.logger.info(
58-
LogId.streamableHttpTransportSessionCloseNotification,
59-
"sessionStore",
60-
"Session is about to be closed due to inactivity"
61-
);
57+
session.logger.info({
58+
id: LogId.streamableHttpTransportSessionCloseNotification,
59+
context: "sessionStore",
60+
message: "Session is about to be closed due to inactivity",
61+
});
6262
}
6363

6464
setSession(sessionId: string, transport: StreamableHTTPServerTransport, mcpServer: McpServer): void {
@@ -68,11 +68,11 @@ export class SessionStore {
6868
}
6969
const abortTimeout = setManagedTimeout(async () => {
7070
if (this.sessions[sessionId]) {
71-
this.sessions[sessionId].logger.info(
72-
LogId.streamableHttpTransportSessionCloseNotification,
73-
"sessionStore",
74-
"Session closed due to inactivity"
75-
);
71+
this.sessions[sessionId].logger.info({
72+
id: LogId.streamableHttpTransportSessionCloseNotification,
73+
context: "sessionStore",
74+
message: "Session closed due to inactivity",
75+
});
7676

7777
await this.closeSession(sessionId);
7878
}
@@ -95,11 +95,11 @@ export class SessionStore {
9595
try {
9696
await session.transport.close();
9797
} catch (error) {
98-
logger.error(
99-
LogId.streamableHttpTransportSessionCloseFailure,
100-
"streamableHttpTransport",
101-
`Error closing transport ${sessionId}: ${error instanceof Error ? error.message : String(error)}`
102-
);
98+
logger.error({
99+
id: LogId.streamableHttpTransportSessionCloseFailure,
100+
context: "streamableHttpTransport",
101+
message: `Error closing transport ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
102+
});
103103
}
104104
}
105105
delete this.sessions[sessionId];

0 commit comments

Comments
 (0)