Skip to content

Commit 20ba223

Browse files
authored
@W-21785596 logging refactor (#314)
* reorganization of logger.ts and log levels * adding logging * minor change bump * remove logs in test * version bumped * rename logInvocation to notifyInvocation * added optional param to log error * major version bump * update log calls * fix logging mistake * using info instead of error * adding log error into its field * remove auth log * changing log message field type to string * consolidating to use LogEntry * updated version * conforming logging output * added logLevel * address comments * change log levdels going up * making notificationLevel and logLevel the sme set: * address nit
1 parent eee9c8c commit 20ba223

37 files changed

Lines changed: 573 additions & 159 deletions

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"CONNECTED_APP_SECRET_VALUE": "${config:tableau.mcp.CONNECTED_APP_SECRET_VALUE}",
2525
"JWT_ADDITIONAL_PAYLOAD": "${config:tableau.mcp.JWT_ADDITIONAL_PAYLOAD}",
2626
"DATASOURCE_CREDENTIALS": "${config:tableau.mcp.DATASOURCE_CREDENTIALS}",
27-
"DEFAULT_LOG_LEVEL": "${config:tableau.mcp.DEFAULT_LOG_LEVEL}",
27+
"DEFAULT_NOTIFICATION_LEVEL": "${config:tableau.mcp.DEFAULT_NOTIFICATION_LEVEL}",
2828
"INCLUDE_TOOLS": "${config:tableau.mcp.INCLUDE_TOOLS}",
2929
"EXCLUDE_TOOLS": "${config:tableau.mcp.EXCLUDE_TOOLS}",
3030
"MAX_RESULT_LIMIT": "${config:tableau.mcp.MAX_RESULT_LIMIT}",

.vscode/settings.example.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"tableau.mcp.CONNECTED_APP_SECRET_VALUE": "",
1212
"tableau.mcp.JWT_ADDITIONAL_PAYLOAD": "",
1313
"tableau.mcp.DATASOURCE_CREDENTIALS": "",
14-
"tableau.mcp.DEFAULT_LOG_LEVEL": "debug",
14+
"tableau.mcp.DEFAULT_NOTIFICATION_LEVEL": "debug",
1515
"tableau.mcp.INCLUDE_TOOLS": "",
1616
"tableau.mcp.EXCLUDE_TOOLS": "",
1717
"tableau.mcp.DISABLE_LOG_MASKING": "false",

config.stdio.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"PAT_NAME": "",
1111
"PAT_VALUE": "",
1212
"DATASOURCE_CREDENTIALS": "",
13-
"DEFAULT_LOG_LEVEL": "debug",
13+
"DEFAULT_NOTIFICATION_LEVEL": "debug",
1414
"INCLUDE_TOOLS": "",
1515
"EXCLUDE_TOOLS": "",
1616
"MAX_RESULT_LIMIT": "",

docs/docs/configuration/mcp-config/env-vars.md

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ A comma-separated list of loggers to enable.
7171
`tableau-mcp` for tool calls.
7272
- `message`: The log message itself. This may be a string or a JSON object.
7373

74-
- All notifications are written to the local log files regardless of the server's currently
75-
configured minimum logging level, since that only applies to notifications sent to MCP clients.
76-
See [`DEFAULT_LOG_LEVEL`](#default_log_level) for more information.
74+
- All notifications are written to the local log files regardless of the notification level, since
75+
[`DEFAULT_NOTIFICATION_LEVEL`](#default_notification_level) only applies to notifications sent to
76+
MCP clients. Server log output (stderr/console) is controlled separately by
77+
[`LOG_LEVEL`](#log_level).
7778
- Secrets are masked by default in the log files. To reveal them for debugging purposes, set the
7879
[`DISABLE_LOG_MASKING`](#disable_log_masking) environment variable to `true`.
7980

@@ -90,9 +91,9 @@ The directory server logs are written to when [`ENABLED_LOGGERS`](#enabled_logge
9091

9192
<hr />
9293

93-
## `DEFAULT_LOG_LEVEL`
94+
## `DEFAULT_NOTIFICATION_LEVEL`
9495

95-
The default logging level of the server.
96+
The default minimum level for sending notifications to MCP clients.
9697

9798
- Default: `debug`
9899
- Possible values:
@@ -105,13 +106,35 @@ The default logging level of the server.
105106
- `alert`
106107
- `emergency`
107108

108-
This value determines the minimum log level in which to send notifications to MCP clients. That is,
109-
if the server's currently configured minimum logging level is `debug`, all log messages will be sent
110-
to MCP clients. If the level is set to `error`, only log messages with a level of `error` or higher
111-
will be sent. Note that MCP clients can
112-
[change the minimum log level](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#setting-log-level)
109+
This value determines the minimum level at which to send notifications to MCP clients. That is,
110+
if set to `debug`, all notifications will be sent. If set to `error`, only notifications with a
111+
level of `error` or higher will be sent. Note that MCP clients can
112+
[change the minimum level](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging#setting-log-level)
113113
any time they want.
114114

115+
Note: this variable was named DEFAULT_LOG_LEVEL until version 2.0.0
116+
117+
<hr />
118+
119+
## `LOG_LEVEL`
120+
121+
The minimum severity level for server log output (stderr on stdio transport, console on http
122+
transport, and file logger).
123+
124+
- Default: `info`
125+
- Possible values:
126+
- `debug` — all log entries
127+
- `info`
128+
- `notice`
129+
- `warning`
130+
- `error`
131+
- `critical`
132+
- `alert`
133+
- `emergency`
134+
135+
Log entries with a level below the configured value are silently dropped. This is independent of
136+
[`DEFAULT_NOTIFICATION_LEVEL`](#default_notification_level), which controls MCP client notifications.
137+
115138
<hr />
116139

117140
## `DISABLE_LOG_MASKING`
@@ -289,7 +312,7 @@ This variable is site overridable, see [Site Settings](site-settings.md).
289312

290313
When `false` (the default) and using the Streamable HTTP transport, the MCP server will create and
291314
manage sessions as per the
292-
[Session Management](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#session-management)
315+
[Session Management](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management)
293316
section of the MCP spec. The only state persisted in the session from one request to another is
294317
information about the client's identity, capabilities, and protocol version compatibility.
295318

env.example.list

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ SITE_NAME=
44
PAT_NAME=
55
PAT_VALUE=
66
DATASOURCE_CREDENTIALS=
7-
DEFAULT_LOG_LEVEL=debug
7+
DEFAULT_NOTIFICATION_LEVEL=debug
88
INCLUDE_TOOLS=
99
EXCLUDE_TOOLS=
1010
MAX_RESULT_LIMIT=

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@tableau/mcp-server",
33
"description": "Helping agents see and understand data.",
4-
"version": "1.18.9",
4+
"version": "2.0.0",
55
"repository": {
66
"type": "git",
77
"url": "git+https://github.com/tableau/tableau-mcp.git"

src/config.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,16 @@ describe('Config', () => {
6969
expect(config.siteName).toBe('tc25');
7070
});
7171

72-
it('should set default log level to debug when not specified', () => {
72+
it('should set default notification level to debug when not specified', () => {
7373
const config = new Config();
74-
expect(config.defaultLogLevel).toBe('debug');
74+
expect(config.defaultNotificationLevel).toBe('debug');
7575
});
7676

77-
it('should set custom log level when specified', () => {
78-
vi.stubEnv('DEFAULT_LOG_LEVEL', 'info');
77+
it('should set custom notification level when specified', () => {
78+
vi.stubEnv('DEFAULT_NOTIFICATION_LEVEL', 'info');
7979

8080
const config = new Config();
81-
expect(config.defaultLogLevel).toBe('info');
81+
expect(config.defaultNotificationLevel).toBe('info');
8282
});
8383

8484
it('should set disableLogMasking to false by default', () => {

src/config.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { CorsOptions } from 'cors';
22
import { existsSync, readFileSync } from 'fs';
33
import { join } from 'path';
44

5-
import { LoggerType, parseLoggerTypes } from './logging/logger.js';
5+
import { LoggerType, parseLoggerTypes, parseLogLevel } from './logging/logger.js';
6+
import type { LogLevel } from './logging/types.js';
67
import { isTelemetryProvider, providerConfigSchema, TelemetryConfig } from './telemetry/types.js';
78
import { isTransport, TransportName } from './transports.js';
89
import { getDirname } from './utils/getDirname.js';
@@ -46,7 +47,8 @@ export class Config {
4647
uatKeyId: string;
4748
jwtAdditionalPayload: string;
4849
datasourceCredentials: string;
49-
defaultLogLevel: string;
50+
defaultNotificationLevel: string;
51+
logLevel: LogLevel;
5052
disableLogMasking: boolean;
5153
maxRequestTimeoutMs: number;
5254
disableSessionManagement: boolean;
@@ -108,7 +110,8 @@ export class Config {
108110
UAT_KEY_ID: uatKeyId,
109111
JWT_ADDITIONAL_PAYLOAD: jwtAdditionalPayload,
110112
DATASOURCE_CREDENTIALS: datasourceCredentials,
111-
DEFAULT_LOG_LEVEL: defaultLogLevel,
113+
DEFAULT_NOTIFICATION_LEVEL: defaultNotificationLevel,
114+
LOG_LEVEL: logLevel,
112115
DISABLE_LOG_MASKING: disableLogMasking,
113116
MAX_REQUEST_TIMEOUT_MS: maxRequestTimeoutMs,
114117
DISABLE_SESSION_MANAGEMENT: disableSessionManagement,
@@ -158,7 +161,8 @@ export class Config {
158161
});
159162
this.corsOriginConfig = getCorsOriginConfig(corsOriginConfig?.trim() ?? '');
160163
this.datasourceCredentials = datasourceCredentials ?? '';
161-
this.defaultLogLevel = defaultLogLevel ?? 'debug';
164+
this.defaultNotificationLevel = defaultNotificationLevel ?? 'debug';
165+
this.logLevel = parseLogLevel(logLevel);
162166
this.disableLogMasking = disableLogMasking === 'true';
163167
this.disableSessionManagement = disableSessionManagement === 'true';
164168
this.loggers = parseLoggerTypes(logging);

src/index.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import dotenv from 'dotenv';
55
import { getConfig } from './config.js';
66
import { getTableauServerInfo } from './getTableauServerInfo.js';
77
import { FileLogger, setFileLogger } from './logging/fileLogger.js';
8-
import { writeToStderr } from './logging/logger.js';
8+
import { log } from './logging/logger.js';
99
import { isNotificationLevel, notifier, setNotificationLevel } from './logging/notification.js';
1010
import { RestApi } from './sdks/tableau/restApi.js';
1111
import { Server, serverName, serverVersion } from './server.js';
1212
import { startExpressServer } from './server/express.js';
13-
import { getExceptionMessage } from './utils/getExceptionMessage.js';
1413

1514
async function startServer(): Promise<void> {
1615
dotenv.config();
@@ -24,11 +23,24 @@ async function startServer(): Promise<void> {
2423
// then we await this before declaring the server ready.
2524
// For stdio transport, there are no health checks, but we still await before serving.
2625
const serverInfoReady = getTableauServerInfo(config.server).catch((error) => {
27-
writeToStderr(`Fatal error initializing server info: ${getExceptionMessage(error)}`);
26+
log({
27+
message: 'Fatal error initializing server info',
28+
level: 'error',
29+
logger: 'startup',
30+
error,
31+
});
2832
process.exit(1);
2933
});
3034

31-
const logLevel = isNotificationLevel(config.defaultLogLevel) ? config.defaultLogLevel : 'debug';
35+
log({
36+
message: `Config resolved: transport=${config.transport}, auth=${config.auth}, server=${config.server}`,
37+
level: 'info',
38+
logger: 'startup',
39+
});
40+
41+
const notificationLevel = isNotificationLevel(config.defaultNotificationLevel)
42+
? config.defaultNotificationLevel
43+
: 'debug';
3244
if (config.loggers.has('fileLogger')) {
3345
setFileLogger(new FileLogger({ logDirectory: config.fileLoggerDirectory }));
3446
}
@@ -44,42 +56,58 @@ async function startServer(): Promise<void> {
4456
const transport = new StdioServerTransport();
4557
await server.connect(transport);
4658

47-
setNotificationLevel(server, logLevel);
59+
setNotificationLevel(server, notificationLevel);
4860
notifier.info(server, `${server.name} v${server.version} running on stdio`);
4961
break;
5062
}
5163
case 'http': {
52-
const { url } = await startExpressServer({ basePath: serverName, config, logLevel });
64+
const { url } = await startExpressServer({
65+
basePath: serverName,
66+
config,
67+
logLevel: notificationLevel,
68+
});
5369

5470
// Port is now open. Wait for server info before logging the ready message.
5571
await serverInfoReady;
5672

5773
if (!config.oauth.enabled) {
58-
console.warn(
59-
'⚠️ TRANSPORT is "http" but OAuth is disabled! Your MCP server may not be protected from unauthorized access! By having explicitly disabled OAuth by setting the DANGEROUSLY_DISABLE_OAUTH environment variable to "true", you accept any and all risks associated with this decision.',
60-
);
74+
log({
75+
message:
76+
'⚠️ TRANSPORT is "http" but OAuth is disabled! Your MCP server may not be protected from unauthorized access! By having explicitly disabled OAuth by setting the DANGEROUSLY_DISABLE_OAUTH environment variable to "true", you accept any and all risks associated with this decision.',
77+
level: 'info',
78+
logger: 'startup',
79+
});
6180
}
6281

63-
// eslint-disable-next-line no-console -- console.log is intentional here since the transport is not stdio.
64-
console.log(
65-
`${serverName} v${serverVersion} ${config.disableSessionManagement ? 'stateless ' : ''}streamable HTTP server available at ${url}`,
66-
);
82+
log({
83+
message: `${serverName} v${serverVersion} ${config.disableSessionManagement ? 'stateless ' : ''}streamable HTTP server available at ${url}`,
84+
level: 'info',
85+
logger: 'startup',
86+
});
6787
break;
6888
}
6989
}
7090

7191
if (config.disableLogMasking) {
72-
writeToStderr('⚠️ Log masking is disabled!');
92+
log({ message: '⚠️ Log masking is disabled!', level: 'info', logger: 'startup' });
7393
}
7494

7595
if (config.breakGlassDisableGlobally) {
76-
writeToStderr(
77-
'⚠️ BREAK_GLASS_DISABLE_GLOBALLY is enabled! This means that the MCP server will be disabled globally and will return errors to all users!',
78-
);
96+
log({
97+
message:
98+
'⚠️ BREAK_GLASS_DISABLE_GLOBALLY is enabled! This means that the MCP server will be disabled globally and will return errors to all users!',
99+
level: 'info',
100+
logger: 'startup',
101+
});
79102
}
80103
}
81104

82105
startServer().catch((error) => {
83-
writeToStderr(`Fatal error when starting the server: ${getExceptionMessage(error)}`);
106+
log({
107+
message: 'Fatal error when starting the server',
108+
level: 'error',
109+
logger: 'startup',
110+
error,
111+
});
84112
process.exit(1);
85113
});

0 commit comments

Comments
 (0)