diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ea3722be..a3e3f45f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -277,6 +277,9 @@ pnpm format # Prettier formatting **Note:** The build rule enforcement system will automatically prevent commits that don't meet quality standards. See the "Build Rule Enforcement & Quality Standards" section above for complete details. +For logger levels, structured fields, redaction, and performance guidance, see +[Logging Guidelines](docs/development/logging-guidelines.md). + ## Testing NeuroLink has a comprehensive testing suite to ensure reliability across all AI providers and features. Please add tests for any new features or bug fixes. diff --git a/docs-site/static/search-index.json b/docs-site/static/search-index.json index 20cf4d648..bca18b662 100644 --- a/docs-site/static/search-index.json +++ b/docs-site/static/search-index.json @@ -2090,9 +2090,9 @@ {"objectID":"b67926d8a755ad92e57bdd16c8f17d1f7f0a0ce03c42865cb1f341959b146e2e","title":"Post-Migration","url":"/docs/development/factory-migration#post-migration","content":"[ ] Verify all provider functionality\n[ ] Confirm performance improvements\n[ ] Validate error handling behavior\n[ ] Test failover scenarios\n[ ] Monitor production metrics\n[ ] Document new patterns for team\n[ ] Clean up legacy code","hierarchy":{"lvl0":"Development","lvl1":"Factory Pattern Migration Guide","lvl2":"Post-Migration","lvl3":""}}, {"objectID":"70058ed2ee55a4afc9808da9da91ee30ac2e23f2ac1113720e9e2e60b1053f19","title":"Key Performance Indicators","url":"/docs/development/factory-migration#key-performance-indicators","content":"This comprehensive migration guide ensures a smooth transition to NeuroLink's factory pattern architecture, maximizing the benefits of standardized provider management while minimizing migration risks.","hierarchy":{"lvl0":"Development","lvl1":"Factory Pattern Migration Guide","lvl2":"Key Performance Indicators","lvl3":""}}, {"objectID":"01352a99c713cd63d938c086b918f9cfaebdf1246aec71e0bd851c194caf370a","title":"๐Ÿ“š Related Documentation","url":"/docs/development/factory-migration#-related-documentation","content":"System Architecture - Overall system design\nTesting Strategy - Quality assurance approaches\nContributing Guide - Development workflow\nAdvanced Patterns - Factory implementation details","hierarchy":{"lvl0":"Development","lvl1":"Factory Pattern Migration Guide","lvl2":"๐Ÿ“š Related Documentation","lvl3":""}}, -{"objectID":"b5593a1a523fdc5889c9f1f0eff5b9644c34bade7dd677fda08dbbc87f023986","title":"Development","url":"/docs/development","content":"Development\n\nContributing to NeuroLink and extending its capabilities for your specific needs.\n\n๐ŸŽฏ Development Hub\n\nThis section covers everything needed for contributing to NeuroLink, understanding its architecture, and extending its functionality.\nContributing โ€” How to contribute to NeuroLink, including setup, coding standards, and submission guidelines.\nTesting โ€” Comprehensive testing strategies, test suite organization, and validation procedures.\nArchitecture โ€” Deep dive into NeuroLink's architecture, design patterns, and system organization.\nFactory Pattern Migration โ€” Guide for upgrading from older architectures to the new unified factory pattern system.\nDocumentation Versioning โ€” Managing documentation versions across releases using mike for version control and deployment.\nAutomated Link Checking โ€” Automated validation of documentation links with CI/CD integration to prevent broken references.\n\n๐Ÿš€ Quick Development Setup\n\n๐Ÿ—๏ธ Architecture Overview\n\nNeuroLink uses a Factory Pattern architecture that provides:\n\nCore Components\n\nDesign Principles\nUnified Interface: All providers implement the same interface\nType Safety: Full TypeScript support with strict typing\nExtensibility: Easy to add new providers and tools\nPerformance: Optimized for production use\nReliability: Comprehensive error handling and fallbacks\n\n๐Ÿ”ง Development Features\n\nEnterprise Automation (72+ Commands)\n\nNeuroLink includes comprehensive automation for development:\n\nSmart Testing System\nAdaptive test selection based on code changes\nProvider validation across all AI services\nPerformance benchmarking and regression detection\nComprehensive coverage reporting\n\nAutomated Content Generation\nScreenshot automation for documentation\nVideo generation for demonstrations\nDocumentation synchronization across files\nAsset optimization and management\n\n๐Ÿงช Testing Philosophy\n\nNeuroLink uses a multi-layered testing approach:\n\nTest Categories\nUnit Tests - Individual component testing\nIntegration Tests - Provider and tool interaction\nEnd-to-End Tests - Complete workflow validation\nPerformance Tests - Speed and resource usage\nRegression Tests - Prevent breaking changes\n\nTest Organization\n\nRunning Tests\n\n๐ŸŽจ Code Style & Standards\n\nTypeScript Configuration\nStrict mode enabled for maximum type safety\nPath mapping for clean imports\nESLint and Prettier for consistent formatting\nDocumentation comments for all public APIs\n\nNaming Conventions\nPascalCase for classes and interfaces\ncamelCase for functions and variables\nkebab-case for file names\nUPPER_CASE for constants\n\nFile Organization\n\n๐Ÿ”„ Contribution Workflow\nSetup Development Environment\nCreate Feature Branch\nDevelopment Process\nCommit & Submit\n\n๐Ÿ“š Learning Resources\n\nArchitecture Deep Dive\nFactory Pattern Guide - Understanding the core architecture\nMCP Integration - Tool system implementation\nProvider Development - Adding new AI providers\n\nBest Practices\nError handling patterns and strategies\nPerformance optimization techniques\nTesting methodologies and coverage\nDocumentation standards and automation\n\nCommunity\nGitHub Discussions for questions and ideas\nIssue tracking for bugs and feature requests\nCode reviews for learning and improvement\nRelease notes for staying updated\n\n๐Ÿ”— Related Resources\nCLI Guide - Understanding the command-line interface\nSDK Reference - API implementation details\nAdvanced Features - Enterprise capabilities\nExamples - Practical implementations","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"","lvl3":""}}, +{"objectID":"b5593a1a523fdc5889c9f1f0eff5b9644c34bade7dd677fda08dbbc87f023986","title":"Development","url":"/docs/development","content":"Development\n\nContributing to NeuroLink and extending its capabilities for your specific needs.\n\n๐ŸŽฏ Development Hub\n\nThis section covers everything needed for contributing to NeuroLink, understanding its architecture, and extending its functionality.\nContributing โ€” How to contribute to NeuroLink, including setup, coding standards, and submission guidelines.\nTesting โ€” Comprehensive testing strategies, test suite organization, and validation procedures.\nLogging Guidelines โ€” Log levels, structured fields, redaction, and per-instance routing.\nArchitecture โ€” Deep dive into NeuroLink's architecture, design patterns, and system organization.\nFactory Pattern Migration โ€” Guide for upgrading from older architectures to the new unified factory pattern system.\nDocumentation Versioning โ€” Managing documentation versions across releases using mike for version control and deployment.\nAutomated Link Checking โ€” Automated validation of documentation links with CI/CD integration to prevent broken references.\n\n๐Ÿš€ Quick Development Setup\n\n๐Ÿ—๏ธ Architecture Overview\n\nNeuroLink uses a Factory Pattern architecture that provides:\n\nCore Components\n\nDesign Principles\nUnified Interface: All providers implement the same interface\nType Safety: Full TypeScript support with strict typing\nExtensibility: Easy to add new providers and tools\nPerformance: Optimized for production use\nReliability: Comprehensive error handling and fallbacks\n\n๐Ÿ”ง Development Features\n\nEnterprise Automation (72+ Commands)\n\nNeuroLink includes comprehensive automation for development:\n\nSmart Testing System\nAdaptive test selection based on code changes\nProvider validation across all AI services\nPerformance benchmarking and regression detection\nComprehensive coverage reporting\n\nAutomated Content Generation\nScreenshot automation for documentation\nVideo generation for demonstrations\nDocumentation synchronization across files\nAsset optimization and management\n\n๐Ÿงช Testing Philosophy\n\nNeuroLink uses a multi-layered testing approach:\n\nTest Categories\nUnit Tests - Individual component testing\nIntegration Tests - Provider and tool interaction\nEnd-to-End Tests - Complete workflow validation\nPerformance Tests - Speed and resource usage\nRegression Tests - Prevent breaking changes\n\nTest Organization\n\nRunning Tests\n\n๐ŸŽจ Code Style & Standards\n\nTypeScript Configuration\nStrict mode enabled for maximum type safety\nPath mapping for clean imports\nESLint and Prettier for consistent formatting\nDocumentation comments for all public APIs\n\nNaming Conventions\nPascalCase for classes and interfaces\ncamelCase for functions and variables\nkebab-case for file names\nUPPER_CASE for constants\n\nFile Organization\n\n๐Ÿ”„ Contribution Workflow\nSetup Development Environment\nCreate Feature Branch\nDevelopment Process\nCommit & Submit\n\n๐Ÿ“š Learning Resources\n\nArchitecture Deep Dive\nFactory Pattern Guide - Understanding the core architecture\nMCP Integration - Tool system implementation\nProvider Development - Adding new AI providers\n\nBest Practices\nError handling patterns and strategies\nPerformance optimization techniques\nTesting methodologies and coverage\nDocumentation standards and automation\n\nCommunity\nGitHub Discussions for questions and ideas\nIssue tracking for bugs and feature requests\nCode reviews for learning and improvement\nRelease notes for staying updated\n\n๐Ÿ”— Related Resources\nCLI Guide - Understanding the command-line interface\nSDK Reference - API implementation details\nAdvanced Features - Enterprise capabilities\nExamples - Practical implementations","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"","lvl3":""}}, {"objectID":"7e615005bdec5429e8e613c0d3c0defeb45247422fadd40a4fac3649c5a3933b","title":"Development","url":"/docs/development#development","content":"Contributing to NeuroLink and extending its capabilities for your specific needs.","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"Development","lvl3":""}}, -{"objectID":"90a5298bb0c032d275a7a07b66e9795f8de3267dc17a2cb2981093354ad6ac16","title":"๐ŸŽฏ Development Hub","url":"/docs/development#-development-hub","content":"This section covers everything needed for contributing to NeuroLink, understanding its architecture, and extending its functionality.\nContributing โ€” How to contribute to NeuroLink, including setup, coding standards, and submission guidelines.\nTesting โ€” Comprehensive testing strategies, test suite organization, and validation procedures.\nArchitecture โ€” Deep dive into NeuroLink's architecture, design patterns, and system organization.\nFactory Pattern Migration โ€” Guide for upgrading from older architectures to the new unified factory pattern system.\nDocumentation Versioning โ€” Managing documentation versions across releases using mike for version control and deployment.\nAutomated Link Checking โ€” Automated validation of documentation links with CI/CD integration to prevent broken references.","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"๐ŸŽฏ Development Hub","lvl3":""}}, +{"objectID":"90a5298bb0c032d275a7a07b66e9795f8de3267dc17a2cb2981093354ad6ac16","title":"๐ŸŽฏ Development Hub","url":"/docs/development#-development-hub","content":"This section covers everything needed for contributing to NeuroLink, understanding its architecture, and extending its functionality.\nContributing โ€” How to contribute to NeuroLink, including setup, coding standards, and submission guidelines.\nTesting โ€” Comprehensive testing strategies, test suite organization, and validation procedures.\nLogging Guidelines โ€” Log levels, structured fields, redaction, and per-instance routing.\nArchitecture โ€” Deep dive into NeuroLink's architecture, design patterns, and system organization.\nFactory Pattern Migration โ€” Guide for upgrading from older architectures to the new unified factory pattern system.\nDocumentation Versioning โ€” Managing documentation versions across releases using mike for version control and deployment.\nAutomated Link Checking โ€” Automated validation of documentation links with CI/CD integration to prevent broken references.","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"๐ŸŽฏ Development Hub","lvl3":""}}, {"objectID":"5946aa015605c5d07b2c06eb361f2a408413b013014f8ea53c1121e099767678","title":"๐Ÿš€ Quick Development Setup","url":"/docs/development#-quick-development-setup","content":"`bash","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"๐Ÿš€ Quick Development Setup","lvl3":""}}, {"objectID":"15a2f66b412fdff80552d6b65a8f2d4beea2bcb7be6be8118f6fcbf6e1cbc02c","title":"Clone the repository","url":"/docs/development#clone-the-repository","content":"git clone https://github.com/juspay/neurolink\ncd neurolink","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"Clone the repository","lvl3":""}}, {"objectID":"8a73475040b7c11aeadadf82943d4c99d644301c0c5b978a029392fc2f7b7d6f","title":"Install dependencies","url":"/docs/development#install-dependencies","content":"pnpm install","hierarchy":{"lvl0":"Development","lvl1":"Development","lvl2":"Install dependencies","lvl3":""}}, @@ -2240,6 +2240,17 @@ {"objectID":"8655bbc0db2d8853389936e04247ce18bf5f825c17810ab505c2212ae7ef88d1","title":"Build-time Link Checking","url":"/docs/development/link-checking#build-time-link-checking","content":"Add to :\n\nCreate :","hierarchy":{"lvl0":"Development","lvl1":"Automated Link Checking","lvl2":"Build-time Link Checking","lvl3":""}}, {"objectID":"4677ec51bc0b0f13c2e326aabf374218f42cf9fd3e2809acda6a932217461adb","title":"Related Documentation","url":"/docs/development/link-checking#related-documentation","content":"Versioning - Documentation version management\nContributing - Contribution guidelines\nTesting - Testing strategies","hierarchy":{"lvl0":"Development","lvl1":"Automated Link Checking","lvl2":"Related Documentation","lvl3":""}}, {"objectID":"44c7a422d2affcdb3f335b78d80b0958be5d760990185fb0d4505d45b8a9a325","title":"Additional Resources","url":"/docs/development/link-checking#additional-resources","content":"markdown-link-check - Link checker tool\nremark-validate-links - Alternative validator\nGitHub Actions - CI/CD automation","hierarchy":{"lvl0":"Development","lvl1":"Automated Link Checking","lvl2":"Additional Resources","lvl3":""}}, +{"objectID":"e111ff87d03c1f77a7d1932885150cd17c19ce903dce394a0514ed74e4bd0270","title":"Logging Guidelines","url":"/docs/development/logging-guidelines","content":"Logging Guidelines\n\nHow NeuroLink logs, and what a contributor has to get right. This documents the\nconvention the codebase already follows โ€” it is not a proposal to change it.\n\nThe logger\n\nImport the shared logger. Never call in : ESLint's\n rule fails the build for , and the exceptions for\n// exist for a handful of legacy call sites, not as an\ninvitation.\n\nThere is one logger for the whole process. attaches\nthe process-wide sink; per-instance events reach a worker only through its\n bridge (see per-instance routing below).\n\nLevels\n\n| Level | Use for |\n| ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n| | Routine operation: request construction, cache hits, resolution steps, internal state. The default for anything that fires per request. |\n| | Events an operator would want in a quiet log: a server binding, a provider registering, an MCP server connecting. Not per-request chatter. |\n| | Something recoverable happened and the code carried on: a retry, a fallback, a deprecated option, a degraded capability. |\n| | An operation failed, or a failure was swallowed and the caller will not see it. |\n\n bypasses level filtering and writes to the console\nunconditionally. It is CLI output, not logging โ€” user-facing text that must\nappear regardless of . Of ~2,260 call sites, ~2,150 are in .\nDo not reach for it in .\n\nNothing below is visible by default\n\n suppresses every level except unless debug mode is on\n(, or ). then sets the\nfloor within debug mode. Two consequences worth internalising:\nA log is not emitted in a normal run, but its arguments are still\n evaluated. Prefer over silence.\nA condition a user must act on cannot be reported at alone โ€” they will\n never see it. Either raise it to or surface it on the result object.\n\nChoosing between and \n\nThe common mistake is logging an ordinary outcome at . If a branch is\nreached on a healthy request, it is , however unwelcome it looks locally.\n is the worked example: a detection that lands below the\nconfidence threshold is the normal case for most files, so it logs at \nand says so in a comment. A detection where no strategy identified a type at\nall is a genuine failure, and logs at .\n\nFormat\n\nPass a message and a structured data object. The message is a constant; the\nvariables go in the object, where a log consumer can index them.\n\nPrefix the message with the emitting component in brackets โ€” ,\n, , . Grepping a debug run is how most of this\ngets read.\n\nTemplate literals are not banned, and a short one carrying a single value is\nfine. What matters is that a value a consumer would want to filter on ends up in\nthe data object rather than baked into the message string.\n\nGuard expensive serialization\n\n evaluates its arguments before ever runs, so a\n of a large payload costs full price on every request even when\nnothing is logged. Guard it:\n\nThe guard is only needed for work that is expensive to produce. Passing an\nobject you already hold is free โ€” the logger does not serialize it unless it\nemits.\n\nNever log secrets\n\nAPI keys, tokens, credentials, presigned URL query strings, user prompt content\nand absolute host paths must not reach a log or a thrown error message.\n has the helpers, and they are the reason several\npast leaks are closed:\n\n| Helper | Use for |\n| --------------------------- | ------------------------------------------------------------------------------------------ |\n| | A URL in a log or error โ€” strips query and fragment, so a presigned token cannot survive. |\n| | A URL that may carry . |\n| | A filesystem path in a message. |\n| | An error from Node/undici โ€” these embed the full request URL or path in their own message. |\n| | A header bag before logging it. |\n| | An arbitrary object, with string truncation. |\n| | A large value in a debug log, length-capped. |\n| | Tool-call parameters before logging them (this one lives in ). |\n\nThe rule for errors is the same as for logs: an error message is read by more\npeople than a log line, not fewer.\n\nPer-instance routing\n\nThe logger routes per instance, so a worker's bridge\n() receives only that worker's own\nevents, not everything in the process. The SDK entry points โ€” ,\n, โ€” run their bodies inside an \nscope carrying the instance's id, so a log call anywhere beneath them is\nattr","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"","lvl3":""}}, +{"objectID":"ec8f0114d488deec97f5a673a32bb2715f189d83292738c48c9fbc75b8344202","title":"Logging Guidelines","url":"/docs/development/logging-guidelines#logging-guidelines","content":"How NeuroLink logs, and what a contributor has to get right. This documents the\nconvention the codebase already follows โ€” it is not a proposal to change it.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Logging Guidelines","lvl3":""}}, +{"objectID":"5d174f91c3ccb88a59544c066c3cbf3a2116b97ffacd2414a51f4d64f46fda14","title":"The logger","url":"/docs/development/logging-guidelines#the-logger","content":"Import the shared logger. Never call in : ESLint's\n rule fails the build for , and the exceptions for\n// exist for a handful of legacy call sites, not as an\ninvitation.\n\nThere is one logger for the whole process. attaches\nthe process-wide sink; per-instance events reach a worker only through its\n bridge (see per-instance routing below).","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"The logger","lvl3":""}}, +{"objectID":"73c3d51ed27bc5ea62bb86ab9dde6cc2b17cecce7a0452a5aa7593da612cf798","title":"Levels","url":"/docs/development/logging-guidelines#levels","content":"| Level | Use for |\n| ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n| | Routine operation: request construction, cache hits, resolution steps, internal state. The default for anything that fires per request. |\n| | Events an operator would want in a quiet log: a server binding, a provider registering, an MCP server connecting. Not per-request chatter. |\n| | Something recoverable happened and the code carried on: a retry, a fallback, a deprecated option, a degraded capability. |\n| | An operation failed, or a failure was swallowed and the caller will not see it. |\n\n bypasses level filtering and writes to the console\nunconditionally. It is CLI output, not logging โ€” user-facing text that must\nappear regardless of . Of ~2,260 call sites, ~2,150 are in .\nDo not reach for it in .","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Levels","lvl3":""}}, +{"objectID":"4dfd061a8a2bd9527c19e4f651358a6a1f9d1e6ecad7505f47dcc69e88ac5cd2","title":"Nothing below error is visible by default","url":"/docs/development/logging-guidelines#nothing-below-error-is-visible-by-default","content":"suppresses every level except unless debug mode is on\n(, or ). then sets the\nfloor within debug mode. Two consequences worth internalising:\nA log is not emitted in a normal run, but its arguments are still\n evaluated. Prefer over silence.\nA condition a user must act on cannot be reported at alone โ€” they will\n never see it. Either raise it to or surface it on the result object.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Nothing below error is visible by default","lvl3":""}}, +{"objectID":"19e83441e80eb578fc3e13da08db3aecd6953418f59ea037e40d543ff6f1e812","title":"Choosing between warn and debug","url":"/docs/development/logging-guidelines#choosing-between-warn-and-debug","content":"The common mistake is logging an ordinary outcome at . If a branch is\nreached on a healthy request, it is , however unwelcome it looks locally.\n is the worked example: a detection that lands below the\nconfidence threshold is the normal case for most files, so it logs at \nand says so in a comment. A detection where no strategy identified a type at\nall is a genuine failure, and logs at .","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Choosing between warn and debug","lvl3":""}}, +{"objectID":"5e7a37fe452805fbf06c4864c7c4bedb538788640158dd499a2de613d4c12cae","title":"Format","url":"/docs/development/logging-guidelines#format","content":"Pass a message and a structured data object. The message is a constant; the\nvariables go in the object, where a log consumer can index them.\n\nPrefix the message with the emitting component in brackets โ€” ,\n, , . Grepping a debug run is how most of this\ngets read.\n\nTemplate literals are not banned, and a short one carrying a single value is\nfine. What matters is that a value a consumer would want to filter on ends up in\nthe data object rather than baked into the message string.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Format","lvl3":""}}, +{"objectID":"53c83ffa5114d21fc9998ca5d409a69d0c10feeedb061bc0f757ebd2d3a0d9eb","title":"Guard expensive serialization","url":"/docs/development/logging-guidelines#guard-expensive-serialization","content":"evaluates its arguments before ever runs, so a\n of a large payload costs full price on every request even when\nnothing is logged. Guard it:\n\nThe guard is only needed for work that is expensive to produce. Passing an\nobject you already hold is free โ€” the logger does not serialize it unless it\nemits.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Guard expensive serialization","lvl3":""}}, +{"objectID":"611da1cea62fd0b5bf313f3659615743913252c6ba487823808c87bee60efd00","title":"Never log secrets","url":"/docs/development/logging-guidelines#never-log-secrets","content":"API keys, tokens, credentials, presigned URL query strings, user prompt content\nand absolute host paths must not reach a log or a thrown error message.\n has the helpers, and they are the reason several\npast leaks are closed:\n\n| Helper | Use for |\n| --------------------------- | ------------------------------------------------------------------------------------------ |\n| | A URL in a log or error โ€” strips query and fragment, so a presigned token cannot survive. |\n| | A URL that may carry . |\n| | A filesystem path in a message. |\n| | An error from Node/undici โ€” these embed the full request URL or path in their own message. |\n| | A header bag before logging it. |\n| | An arbitrary object, with string truncation. |\n| | A large value in a debug log, length-capped. |\n| | Tool-call parameters before logging them (this one lives in ). |\n\nThe rule for errors is the same as for logs: an error message is read by more\npeople than a log line, not fewer.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Never log secrets","lvl3":""}}, +{"objectID":"c8bf38e894176c3436e15411303845f458ecf58f7bc7da0d105ecaf057adf7f4","title":"Per-instance routing","url":"/docs/development/logging-guidelines#per-instance-routing","content":"The logger routes per instance, so a worker's bridge\n() receives only that worker's own\nevents, not everything in the process. The SDK entry points โ€” ,\n, โ€” run their bodies inside an \nscope carrying the instance's id, so a log call anywhere beneath them is\nattributed without threading an instance through every call site. Two things\nsit outside the scope by construction: logs emitted while a consumer drains a\nreturned stream (iteration runs in the consumer's context), and logs emitted\noutside any call โ€” construction, background MCP reconnects, module init โ€”\nwhich stay unattributed rather than being charged to an arbitrary instance.\n\n's JSDoc () is\nthe authoritative description of this behaviour; check it before relying on\nthe routing in a new integration.\n\nA process-wide sink installed with still receives\neverything either way.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Per-instance routing","lvl3":""}}, +{"objectID":"fcd10d804aaa898a37f6d1303fbf6b07499f6bcd5b7e90e8f727aedec7b3f8e8","title":"Checklist for a new log call","url":"/docs/development/logging-guidelines#checklist-for-a-new-log-call","content":"Level matches the table above, and a routine branch is .\nMessage is a constant with a prefix; variables are in the data\n object.\nAnything expensive to build is behind .\nNo key, token, credential, prompt body, presigned URL or absolute host path\n in either the message or the data โ€” run it through if in\n doubt.","hierarchy":{"lvl0":"Development","lvl1":"Logging Guidelines","lvl2":"Checklist for a new log call","lvl3":""}}, {"objectID":"b7ea7ebd627e70d0c3debd74687c71a4ad7cb1a63a6b24e2a4818736e0129778","title":"Provider-Agnostic Testing Framework - January 2025 Snapshot","url":"/docs/development/provider-testing","content":"โš ๏ธ HISTORICAL DOCUMENT (January 2025) โ€” This is a snapshot of the provider-agnostic testing milestone when 9 providers were live. The current product ships 40 providers (incl. voice and the decision-only type). For the up-to-date provider list and capability matrix, see the README and Provider Capabilities Audit.\n\nProvider-Agnostic Testing Framework - January 2025 Snapshot\n\nUpdated: January 20, 2025 \nStatus: COMPLETE SUCCESS โ€” 9/9 providers verified working at the time of writing \nObjective: Complete provider testing after resolving critical configuration bug\n\n๐ŸŽฏ MISSION ACCOMPLISHED\n\nProblem Solved\n\nThe previous testing framework was hardcoded to Google AI, making it impossible to validate other providers during migration. This has been completely fixed.\n\nSolution Implemented\n\nโœ… Provider-agnostic test runner \nโœ… Configurable environment validation \nโœ… Dynamic provider switching \nโœ… Hugging Face implementation complete\nโœ… Ready for comprehensive testing phase\n\n๐Ÿ”ง IMPLEMENTATION DETAILS\nEnhanced Test Runner ()\n\nProvider Configuration System\n\nUsage Examples\n\nEnvironment Validation\nโœ… Automatic API key detection\nโœ… Clear error messages for missing credentials\nโœ… Provider-specific configuration validation\nโœ… Dynamic environment variable setup\nProvider-Agnostic Test Files\n\nDynamic Provider Detection\n\nUpdated Test Files\nโœ… - Provider-agnostic\nโœ… - Provider-agnostic\n๐Ÿ”„ Additional test files can be updated using same pattern\n\n๐Ÿงช VALIDATION RESULTS\n\nGoogle AI Provider Testing\n\nOpenAI Provider Testing\n\nKey Observations\nโœ… Both providers pass all tests\nโœ… OpenAI is slightly faster (6.15s vs 9.08s)\nโœ… Same test suite validates both providers\nโœ… No code changes needed between providers\n\n๐Ÿš€ STRATEGIC BENEFITS\nMigration Confidence\nBaseline Established: Google AI provider validated and working\nTarget Confirmed: OpenAI provider already operational\nTest Coverage: Universal test suite applies to all providers\nRegression Prevention: Any breaking changes immediately detected\nDevelopment Velocity\nParallel Testing: Can test multiple providers simultaneously\nQuick Validation: Individual provider testing in \\<10 seconds\nClear Feedback: Provider-specific error messages and success metrics\nAutomated Reports: JSON reports saved per provider\nQuality Assurance\nNo Manual Testing: Automated validation across all providers\nConsistent Coverage: Same test scenarios for all providers\nPerformance Monitoring: Response time tracking per provider\nEnvironment Validation: Automatic credential checking\n\n๐Ÿ“‹ NEXT STEPS FOR PHASE 3\n\nโœ… Migration Complete - All Providers Operational\n\nWith the provider-agnostic testing framework and factory pattern complete:\n\nโœ… Factory Pattern Implementation Complete\nโœ… BaseProvider: All 9 providers (at the time of writing) extend BaseProvider (verified)\nโœ… Custom Vercel AI SDK: Azure, HuggingFace, Ollama use custom implementations\nโœ… Official Vercel AI SDK: OpenAI, Anthropic, Bedrock, Google AI, Mistral\nโœ… 100% Success Rate: All 9 providers in this snapshot tested and operational\n\nโœ… Architecture Achievements\nโœ… No External Package Issues: Custom implementations solve compatibility problems\nโœ… Universal Analytics: Analytics helper integrated across all providers\nโœ… Unified Interface: Single parameter handling system operational\nโœ… Enterprise Ready: Complete factory-first MCP architecture\n\nTesting Strategy for Phase 3\n\n๐ŸŽฏ SUCCESS CRITERIA MET\n\nOriginal Requirements\nโœ… Fix testing script to be provider agnostic\nโœ… Test with OpenAI first (already implemented)\nโœ… Validate provider-agnostic functionality working\n\nAdditional Achievements\nโœ… Support for 4 providers (Google AI, OpenAI, Anthropic, Bedrock)\nโœ… Automatic environment validation\nโœ… Clear error messaging\nโœ… Performance benchmarking\nโœ… JSON report generation\n\n๐Ÿ† CONCLUSION\n\nThe provider-agnostic testing framework is now complete and operational.\nProblem Solved: No longer bound to Google AI\nQuality Assured: Both existing providers validated\nFoundation Ready: Perfect infrastructure for Phase 3 migration\nDevelopment Ready: Can proceed with confidence\n\nWe can now begin Phase 3 migration knowing that every step can be validated immediately with comprehensive, provider-agnostic testing.","hierarchy":{"lvl0":"Development","lvl1":"Provider-Agnostic Testing Framework - January 2025 Snapshot","lvl2":"","lvl3":""}}, {"objectID":"40155d0164fbc188b20eb8158f339edd47393bae047ec9d23dc1a22745059fa8","title":"Provider-Agnostic Testing Framework - January 2025 Snapshot","url":"/docs/development/provider-testing#provider-agnostic-testing-framework---january-2025-snapshot","content":"Updated: January 20, 2025 \nStatus: COMPLETE SUCCESS โ€” 9/9 providers verified working at the time of writing \nObjective: Complete provider testing after resolving critical configuration bug","hierarchy":{"lvl0":"Development","lvl1":"Provider-Agnostic Testing Framework - January 2025 Snapshot","lvl2":"Provider-Agnostic Testing Framework - January 2025 Snapshot","lvl3":""}}, {"objectID":"3212634220f7620a7f5dd8d7ffa3a5fb8b359e4c6722504ae1d7881d0020a6da","title":"Problem Solved","url":"/docs/development/provider-testing#problem-solved","content":"The previous testing framework was hardcoded to Google AI, making it impossible to validate other providers during migration. This has been completely fixed.","hierarchy":{"lvl0":"Development","lvl1":"Provider-Agnostic Testing Framework - January 2025 Snapshot","lvl2":"Problem Solved","lvl3":""}}, diff --git a/docs/development/index.md b/docs/development/index.md index 9853871e0..5cf0a681f 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -12,6 +12,7 @@ This section covers everything needed for contributing to NeuroLink, understandi - **[Contributing](contributing.md)** โ€” How to contribute to NeuroLink, including setup, coding standards, and submission guidelines. - **[Testing](testing.md)** โ€” Comprehensive testing strategies, test suite organization, and validation procedures. +- **[Logging Guidelines](/docs/development/logging-guidelines)** โ€” Log levels, structured fields, redaction, and per-instance routing. - **[Architecture](architecture.md)** โ€” Deep dive into NeuroLink's architecture, design patterns, and system organization. - **[Factory Pattern Migration](factory-migration.md)** โ€” Guide for upgrading from older architectures to the new unified factory pattern system. - **[Documentation Versioning](versioning.md)** โ€” Managing documentation versions across releases using mike for version control and deployment. diff --git a/docs/development/logging-guidelines.md b/docs/development/logging-guidelines.md new file mode 100644 index 000000000..5b9dc258d --- /dev/null +++ b/docs/development/logging-guidelines.md @@ -0,0 +1,147 @@ +# Logging Guidelines + +How NeuroLink logs, and what a contributor has to get right. This documents the +convention the codebase already follows โ€” it is not a proposal to change it. + +## The logger + +Import the shared logger. Never call `console.*` in `src/`: ESLint's +`no-console` rule fails the build for `console.log`, and the exceptions for +`warn`/`error`/`info` exist for a handful of legacy call sites, not as an +invitation. + +```typescript +import { logger } from "../utils/logger.js"; +``` + +There is one logger for the whole process. `logger.setEventEmitter()` attaches +the process-wide sink; per-instance events reach a worker only through its +`onLog` bridge (see [per-instance routing](#per-instance-routing) below). + +## Levels + +| Level | Use for | +| ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `debug` | Routine operation: request construction, cache hits, resolution steps, internal state. The default for anything that fires per request. | +| `info` | Events an operator would want in a quiet log: a server binding, a provider registering, an MCP server connecting. Not per-request chatter. | +| `warn` | Something recoverable happened and the code carried on: a retry, a fallback, a deprecated option, a degraded capability. | +| `error` | An operation failed, or a failure was swallowed and the caller will not see it. | + +`logger.always()` bypasses level filtering and writes to the console +unconditionally. It is **CLI output**, not logging โ€” user-facing text that must +appear regardless of `--debug`. Of ~2,260 call sites, ~2,150 are in `src/cli/`. +Do not reach for it in `src/lib/`. + +### Nothing below `error` is visible by default + +`shouldLog()` suppresses every level except `error` unless debug mode is on +(`--debug`, or `NEUROLINK_DEBUG=true`). `NEUROLINK_LOG_LEVEL` then sets the +floor within debug mode. Two consequences worth internalising: + +- A `debug` log is not emitted in a normal run, but its arguments are still + evaluated. Prefer `debug` over silence. +- A condition a user must act on cannot be reported at `warn` alone โ€” they will + never see it. Either raise it to `error` or surface it on the result object. + +### Choosing between `warn` and `debug` + +The common mistake is logging an ordinary outcome at `warn`. If a branch is +reached on a healthy request, it is `debug`, however unwelcome it looks locally. +`FileDetector` is the worked example: a detection that lands below the +confidence threshold is the normal case for most files, so it logs at `debug` +and says so in a comment. A detection where _no_ strategy identified a type at +all is a genuine failure, and logs at `error`. + +## Format + +Pass a message and a structured data object. The message is a constant; the +variables go in the object, where a log consumer can index them. + +```typescript +// Good +logger.debug("[OpenAI] Request built", { + provider: this.providerName, + model, + toolCount: tools.length, +}); + +// Avoid โ€” nothing downstream can filter on this +logger.debug(`[OpenAI] Built request for ${model} with ${tools.length} tools`); +``` + +Prefix the message with the emitting component in brackets โ€” `[NeuroLink]`, +`[OpenAI]`, `[FileDetector]`, `[MCP]`. Grepping a debug run is how most of this +gets read. + +Template literals are not banned, and a short one carrying a single value is +fine. What matters is that a value a consumer would want to filter on ends up in +the data object rather than baked into the message string. + +## Guard expensive serialization + +`logger.debug(...)` evaluates its arguments before `shouldLog()` ever runs, so a +`JSON.stringify` of a large payload costs full price on every request even when +nothing is logged. Guard it: + +```typescript +if (logger.shouldLog("debug")) { + logger.debug("[Provider] Full response", { + body: safeDebugSerialize(response), + }); +} +``` + +The guard is only needed for work that is expensive to produce. Passing an +object you already hold is free โ€” the logger does not serialize it unless it +emits. + +## Never log secrets + +API keys, tokens, credentials, presigned URL query strings, user prompt content +and absolute host paths must not reach a log or a thrown error message. +`src/lib/utils/logSanitize.ts` has the helpers, and they are the reason several +past leaks are closed: + +| Helper | Use for | +| --------------------------- | ------------------------------------------------------------------------------------------ | +| `redactUrlForError(url)` | A URL in a log or error โ€” strips query and fragment, so a presigned token cannot survive. | +| `redactUrlCredentials` | A URL that may carry `user:password@`. | +| `redactPathFromMessage` | A filesystem path in a message. | +| `sanitizeErrorCause` | An error from Node/undici โ€” these embed the full request URL or path in their own message. | +| `sanitizeHeaders` | A header bag before logging it. | +| `sanitizeRecord` | An arbitrary object, with string truncation. | +| `safeDebugSerialize` | A large value in a debug log, length-capped. | +| `transformParamsForLogging` | Tool-call parameters before logging them (this one lives in `transformationUtils.ts`). | + +The rule for errors is the same as for logs: an error message is read by more +people than a log line, not fewer. + +## Per-instance routing + +The logger routes per instance, so a worker's `onLog` bridge +(`NeuroLink.createWorkerInstance({ onLog })`) receives only that worker's own +events, not everything in the process. The SDK entry points โ€” `generate`, +`stream`, `generateText` โ€” run their bodies inside an `AsyncLocalStorage` +scope carrying the instance's id, so a log call anywhere beneath them is +attributed without threading an instance through every call site. Two things +sit outside the scope by construction: logs emitted while a consumer drains a +returned stream (iteration runs in the consumer's context), and logs emitted +outside any call โ€” construction, background MCP reconnects, module init โ€” +which stay unattributed rather than being charged to an arbitrary instance. + +`WorkerInstanceOptions.onLog`'s JSDoc (`src/lib/types/isolatedAgent.ts`) is +the authoritative description of this behaviour; check it before relying on +the routing in a new integration. + +A process-wide sink installed with `logger.setEventEmitter()` still receives +everything either way. + +## Checklist for a new log call + +- Level matches the table above, and a routine branch is `debug`. +- Message is a constant with a `[Component]` prefix; variables are in the data + object. +- Anything expensive to build is behind `logger.shouldLog("debug")`. +- No key, token, credential, prompt body, presigned URL or absolute host path + in either the message or the data โ€” run it through `logSanitize.ts` if in + doubt. diff --git a/package.json b/package.json index 8d883a132..cb83ece63 100644 --- a/package.json +++ b/package.json @@ -274,7 +274,8 @@ "test:file-tool-roots": "pnpm exec tsx test/continuous-test-suite-file-tool-roots.ts", "test:native-failure-events": "pnpm exec tsx test/continuous-test-suite-native-failure-events.ts", "test:native-audio-model-aware": "pnpm exec tsx test/continuous-test-suite-native-audio-model-aware.ts", - "test:cli-json-output": "pnpm exec tsx test/continuous-test-suite-cli-json-output.ts" + "test:cli-json-output": "pnpm exec tsx test/continuous-test-suite-cli-json-output.ts", + "test:logging-guidelines": "pnpm exec tsx test/continuous-test-suite-logging-guidelines.ts" }, "files": [ "dist", diff --git a/test/continuous-test-suite-logging-guidelines.ts b/test/continuous-test-suite-logging-guidelines.ts new file mode 100644 index 000000000..5a0f73ae0 --- /dev/null +++ b/test/continuous-test-suite-logging-guidelines.ts @@ -0,0 +1,160 @@ +#!/usr/bin/env tsx +import "dotenv/config"; + +/** + * Continuous Test Suite โ€” Logging Guidelines doc accuracy + * + * `docs/development/logging-guidelines.md` documents the logger's + * "Per-instance routing": a log call inside one instance's scope reaches only + * that instance's sinks, a worker's `onLog` bridge + * (`NeuroLink.createWorkerInstance({ onLog })`) receives only that worker's + * own events, logs emitted outside any call stay unattributed, and a + * process-wide sink (`logger.setEventEmitter`) still receives everything. + * + * The suite checks that behaviour against the built SDK's public `logger` and + * `NeuroLink`, then checks that the guide states it. Every "did not receive" + * assertion is paired with a sink that must have received the same event, so + * a probe that never fired cannot pass as isolation. + * + * Run: pnpm run build && npx tsx test/continuous-test-suite-logging-guidelines.ts + * pnpm run test:logging-guidelines + */ + +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { NeuroLink, logger } from "../dist/index.js"; +import { assert, defineSuite } from "./helpers/harness.js"; +import { assertDistFresh } from "./helpers/distFreshness.js"; + +// Fail loudly rather than silently testing a stale build (see distFreshness.ts). +assertDistFresh(); + +const { test, runSuite } = defineSuite("Logging Guidelines doc accuracy", { + offline: true, +}); + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const GUIDE_PATH = resolve(REPO_ROOT, "docs/development/logging-guidelines.md"); + +/** A sink that records the message of every `log-event` it receives. */ +function recorder(): { + sink: { emit: (event: string, ...args: unknown[]) => boolean }; + saw: (marker: string) => boolean; +} { + const messages: string[] = []; + return { + sink: { + emit: (event, payload) => { + if ( + event === "log-event" && + typeof payload === "object" && + payload !== null && + "message" in payload && + typeof payload.message === "string" + ) { + messages.push(payload.message); + } + return true; + }, + }, + saw: (marker) => messages.some((m) => m.includes(marker)), + }; +} + +/** The "## Per-instance routing" section's body, up to the next `## ` heading. */ +function readPerInstanceRoutingSection(): string { + const md = readFileSync(GUIDE_PATH, "utf8"); + const start = md.indexOf("## Per-instance routing"); + assert(start !== -1, "guide is missing a '## Per-instance routing' section"); + const rest = md.slice(start + "## Per-instance routing".length); + const nextHeading = rest.indexOf("\n## "); + return rest.slice(0, nextHeading === -1 ? undefined : nextHeading); +} + +await runSuite(async () => { + await test("a log call inside one instance's scope reaches only that instance's sinks", () => { + const global = recorder(); + const a = recorder(); + const b = recorder(); + const idA = `guide-probe-a-${process.pid}`; + const idB = `guide-probe-b-${process.pid}`; + logger.setEventEmitter(global.sink); + logger.addScopedEventEmitter(idA, a.sink); + logger.addScopedEventEmitter(idB, b.sink); + try { + const marker = `scoped-probe-${Date.now()}`; + // error is always emitted regardless of NEUROLINK_DEBUG, so the probe + // does not depend on log-level configuration. + logger.runInInstanceScope(idA, () => logger.error(`[Test] ${marker}`)); + assert( + global.saw(marker), + "precondition: the process-wide sink must receive the probe", + ); + assert( + a.saw(marker), + "the scoped instance's own sink did not receive it", + ); + assert( + !b.saw(marker), + "a sibling instance's sink received another instance's log", + ); + } finally { + logger.removeScopedEventEmitter(idA, a.sink); + logger.removeScopedEventEmitter(idB, b.sink); + logger.clearEventEmitter(global.sink); + } + }); + + await test("an unscoped log reaches the process-wide sink but no worker's onLog bridge", async () => { + const host = new NeuroLink(); + const global = recorder(); + const heardByA: string[] = []; + const heardByB: string[] = []; + const workerA = host.createWorkerInstance({ + logTag: "probe-A", + onLog: (event) => heardByA.push(event.message), + }); + const workerB = host.createWorkerInstance({ + logTag: "probe-B", + onLog: (event) => heardByB.push(event.message), + }); + logger.setEventEmitter(global.sink); + try { + const marker = `unscoped-probe-${Date.now()}`; + logger.error(`[Test] ${marker}`); + assert( + global.saw(marker), + "precondition: the process-wide sink must receive the probe", + ); + assert( + !heardByA.some((m) => m.includes(marker)) && + !heardByB.some((m) => m.includes(marker)), + "an unattributed log was forwarded to a worker's onLog bridge", + ); + } finally { + logger.clearEventEmitter(global.sink); + await workerA.dispose?.(); + await workerB.dispose?.(); + await host.dispose?.(); + } + }); + + await test("the guide states per-instance routing and points at its source of truth", () => { + const section = readPerInstanceRoutingSection(); + assert( + /receives only that worker's own\s+events/.test(section), + "the Per-instance routing section no longer states what a worker's onLog bridge receives", + ); + assert( + /WorkerInstanceOptions\.onLog/.test(section) && + /isolatedAgent\.ts/.test(section), + "the section does not point at WorkerInstanceOptions.onLog's JSDoc (src/lib/types/isolatedAgent.ts)", + ); + assert( + !/not what ships today|until that lands/.test(section), + "the section still describes per-instance routing as unshipped", + ); + }); +});