Skip to content

Commit c208f35

Browse files
authored
feat(logger): add shared logger package with pino integration (#37)
* feat(logger): add @pleaseai/logger package with pino integration Create new logger package for structured logging across the monorepo: - pino-based implementation for high performance - Auto-detects pretty printing via NODE_ENV - Redacts sensitive fields (password, secret, token, apiKey, etc.) - All output to stderr (stdout reserved for protocol data) - Typed API: createLogger(name) factory and default logger singleton Issue: #36 * feat(logger): integrate logger across all packages Replace console.* calls with structured pino logging in: - code: CLI error handling with fatal log - code-format: Format initialization and formatting operations - code-lsp: LSP server lifecycle, diagnostics, and operations - dora: MCP server, port discovery, and ast-grep provider All packages now use @pleaseai/logger for consistent structured logging. * fix(logger): add error notification in fallback and comprehensive tests - Add console.error notification for unexpected errors during pino-pretty initialization - Distinguish expected "module not found" from unexpected errors - Add 22 new tests for redaction functionality covering all sensitive fields - Add tests for environment configuration (LOG_LEVEL, NODE_ENV) - Add tests for child logger context propagation
1 parent 74117d3 commit c208f35

20 files changed

Lines changed: 776 additions & 120 deletions

File tree

bun.lock

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

packages/code/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
},
3333
"dependencies": {
3434
"@pleaseai/code-format": "workspace:*",
35-
"@pleaseai/code-lsp": "workspace:*"
35+
"@pleaseai/code-lsp": "workspace:*",
36+
"@pleaseai/logger": "workspace:*"
3637
},
3738
"devDependencies": {
3839
"@types/bun": "latest",

packages/code/src/cli.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
import { Buffer } from 'node:buffer'
1616
import process from 'node:process'
1717
import { Format } from '@pleaseai/code-format'
18+
import { createLogger } from '@pleaseai/logger'
1819
import pkg from '../package.json'
1920
import { runLSPDiagnostics } from './hooks/lsp'
2021
import { parseArgs } from './utils'
2122

23+
const log = createLogger('code')
24+
2225
const VERSION = pkg.version
2326

2427
interface HookInput {
@@ -203,6 +206,6 @@ async function main(): Promise<void> {
203206
}
204207

205208
main().catch((error) => {
206-
console.error('[code] Error:', error)
209+
log.fatal({ err: error }, 'Unhandled error')
207210
process.exit(1)
208211
})

packages/dora/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"dependencies": {
3131
"@modelcontextprotocol/sdk": "^1.12.0",
3232
"@pleaseai/code-lsp": "workspace:*",
33+
"@pleaseai/logger": "workspace:*",
3334
"zod": "^3.25.0"
3435
},
3536
"devDependencies": {

packages/dora/src/cli.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
import process from 'node:process'
1212
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
13+
import { createLogger } from '@pleaseai/logger'
1314
import pkg from '../package.json'
1415
import { createDoraServer } from './server'
1516

17+
const log = createLogger('dora')
18+
1619
const VERSION = pkg.version
1720

1821
interface ParsedArgs {
@@ -48,13 +51,13 @@ function parseArgs(argv: string[]): ParsedArgs {
4851
}
4952

5053
async function serveCommand(projectPath: string, timeout: number): Promise<void> {
51-
console.error(`[dora] Starting MCP server for project: ${projectPath}`)
54+
log.info({ projectPath }, 'Starting MCP server')
5255

5356
const server = await createDoraServer({ projectPath, timeout })
5457
const transport = new StdioServerTransport()
5558

5659
await server.connect(transport)
57-
console.error('[dora] MCP server connected and ready')
60+
log.info('MCP server connected and ready')
5861
}
5962

6063
function versionCommand(): void {
@@ -122,6 +125,6 @@ async function main(): Promise<void> {
122125
}
123126

124127
main().catch((error) => {
125-
console.error('[dora] Error:', error)
128+
log.fatal({ err: error }, 'Unhandled error')
126129
process.exit(1)
127130
})

packages/dora/src/client/port-discovery.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
* Scans ports 24226-24245 to find the running IDE instance
44
*/
55

6+
import { createLogger } from '@pleaseai/logger'
67
import { ServerNotFoundError } from '../errors'
78
import { transformResponse } from './response-transformer'
89

10+
const log = createLogger('dora')
11+
912
const BASE_PORT = 0x5EA2 // 24226
1013
const PORT_RANGE = 20
1114
const DISCOVERY_TIMEOUT = 1000 // 1 second for port discovery
@@ -74,7 +77,7 @@ export async function discoverPort(projectPath: string): Promise<number> {
7477
if (await checkPort(port, projectPath)) {
7578
cache.port = port
7679
cache.projectPath = projectPath
77-
console.error(`[dora] Found JetBrains IDE service at port ${port}`)
80+
log.debug({ port }, 'Found JetBrains IDE service')
7881
return port
7982
}
8083
}

packages/dora/src/providers/ast-grep/cli.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import type { CliMatch, RunSgOptions, SgResult } from './types'
88
import { existsSync } from 'node:fs'
9+
import { createLogger } from '@pleaseai/logger'
910
import { spawn } from 'bun'
1011
import {
1112
CLI_LANGUAGES,
@@ -15,6 +16,8 @@ import {
1516
} from './constants'
1617
import { ensureAstGrepBinary, getInstallInstructions } from './downloader'
1718

19+
const log = createLogger('ast-grep')
20+
1821
// Cached binary path
1922
let resolvedCliPath: string | null = null
2023

@@ -233,7 +236,7 @@ export async function runSg(options: RunSgOptions, retried = false): Promise<SgR
233236
else {
234237
// Non-truncated output but failed to parse - log and return error
235238
const errorMsg = parseError instanceof Error ? parseError.message : 'unknown'
236-
console.error(`[ast-grep] Failed to parse output: ${errorMsg}`)
239+
log.error({ err: errorMsg }, 'Failed to parse output')
237240
return {
238241
matches: [],
239242
totalMatches: 0,

packages/dora/src/providers/ast-grep/downloader.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import type { PlatformId } from './types'
99
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
1010
import process from 'node:process'
11+
import { createLogger } from '@pleaseai/logger'
1112
import { spawn } from 'bun'
1213
import {
1314
AST_GREP_VERSION,
@@ -18,6 +19,8 @@ import {
1819
PLATFORM_CONFIGS,
1920
} from './constants'
2021

22+
const log = createLogger('ast-grep')
23+
2124
/**
2225
* Verify a binary is actually ast-grep by checking --version output
2326
*/
@@ -34,7 +37,7 @@ async function verifyAstGrepBinary(binaryPath: string): Promise<boolean> {
3437
return stdout.includes('ast-grep') || /^\d+\.\d+\.\d+/.test(stdout.trim())
3538
}
3639
catch (e) {
37-
console.error(`[ast-grep] Binary verification failed for ${binaryPath}: ${e instanceof Error ? e.message : String(e)}`)
40+
log.warn({ binaryPath, err: e }, 'Binary verification failed')
3841
return false
3942
}
4043
}
@@ -81,7 +84,7 @@ export function getCachedBinary(): string | null {
8184
}
8285
catch (e) {
8386
// Log warning but continue - will trigger re-download
84-
console.warn(`[ast-grep] Failed to read version marker at ${versionPath}: ${e instanceof Error ? e.message : String(e)}`)
87+
log.warn({ versionPath, err: e }, 'Failed to read version marker')
8588
return null
8689
}
8790
}
@@ -129,13 +132,13 @@ async function extractZip(archivePath: string, destDir: string): Promise<void> {
129132
export async function downloadAstGrepBinary(platformId?: PlatformId): Promise<string | null> {
130133
const platform = platformId ?? getPlatformId()
131134
if (!platform) {
132-
console.error('[dora] Unsupported platform for ast-grep binary download')
135+
log.error('Unsupported platform for ast-grep binary download')
133136
return null
134137
}
135138

136139
const config = PLATFORM_CONFIGS[platform]
137140
if (!config) {
138-
console.error(`[dora] No binary configuration for platform: ${platform}`)
141+
log.error({ platform }, 'No binary configuration for platform')
139142
return null
140143
}
141144

@@ -153,7 +156,7 @@ export async function downloadAstGrepBinary(platformId?: PlatformId): Promise<st
153156
}
154157
}
155158

156-
console.log(`[dora] Downloading ast-grep binary v${AST_GREP_VERSION}...`)
159+
log.info({ version: AST_GREP_VERSION }, 'Downloading ast-grep binary')
157160

158161
try {
159162
// Create cache directory
@@ -188,12 +191,12 @@ export async function downloadAstGrepBinary(platformId?: PlatformId): Promise<st
188191
// Write version marker
189192
writeFileSync(getVersionMarkerPath(), AST_GREP_VERSION)
190193

191-
console.log(`[dora] ast-grep binary ready at ${binaryPath}`)
194+
log.info({ binaryPath }, 'ast-grep binary ready')
192195

193196
return binaryPath
194197
}
195198
catch (err) {
196-
console.error(`[dora] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`)
199+
log.error({ err }, 'Failed to download ast-grep')
197200
return null
198201
}
199202
}

packages/dora/src/providers/ast-grep/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
import type { Provider, ToolDefinition, ToolResult } from '../provider'
88
import type { RegistryConfig } from '../registry'
99
import type { NapiLanguage } from './types'
10+
import { createLogger } from '@pleaseai/logger'
1011
import { z, ZodError } from 'zod'
1112
import { isCliAvailable, runSg } from './cli'
1213
import { CLI_LANGUAGES, NAPI_LANGUAGES } from './constants'
1314
import { analyzeCode, getNapiError, isNapiAvailable, transformCode } from './napi'
1415
import { formatAnalyzeResult, formatReplaceResult, formatSearchResult, formatTransformResult, getEmptyResultHint } from './utils'
1516

17+
const log = createLogger('ast-grep')
18+
1619
/**
1720
* Format error for tool result, with special handling for Zod validation errors
1821
*/
@@ -150,17 +153,16 @@ export class AstGrepProvider implements Provider {
150153
// Pre-check CLI availability (don't fail, just log)
151154
const cliAvailable = await isCliAvailable()
152155
if (!cliAvailable) {
153-
console.log('[ast-grep] CLI not found, will download on first use')
156+
log.debug('CLI not found, will download on first use')
154157
}
155158

156159
// Check NAPI availability
157160
const napiAvailable = isNapiAvailable()
158161
if (!napiAvailable) {
159162
const error = getNapiError()
160163

161-
console.log(`[ast-grep] NAPI not available: ${error ?? 'unknown'}`)
162-
163-
console.log('[ast-grep] In-memory tools (analyze/transform) disabled')
164+
log.debug({ error }, 'NAPI not available')
165+
log.debug('In-memory tools (analyze/transform) disabled')
164166
}
165167

166168
this.connected = true

packages/dora/src/providers/ast-grep/napi.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
*/
77

88
import type { AnalyzeResult, MetaVariable, NapiLanguage, Range } from './types'
9+
import { createLogger } from '@pleaseai/logger'
910
import { NAPI_LANGUAGES } from './constants'
1011

12+
const log = createLogger('ast-grep')
13+
1114
type AstGrepNapiModule = any
1215

1316
type SgNode = any
@@ -33,7 +36,7 @@ export function isNapiAvailable(): boolean {
3336
catch (e) {
3437
napiLoadError = e instanceof Error ? e : new Error(String(e))
3538
// Log full error on first load failure for diagnostics
36-
console.error('[ast-grep] NAPI module load failed:', e)
39+
log.debug({ err: e }, 'NAPI module load failed')
3740
return false
3841
}
3942
}

0 commit comments

Comments
 (0)