diff --git a/.pipelines/PowerShellEditorServices-Official.yml b/.pipelines/PowerShellEditorServices-Official.yml index 79e2a85a3..c07eab61a 100644 --- a/.pipelines/PowerShellEditorServices-Official.yml +++ b/.pipelines/PowerShellEditorServices-Official.yml @@ -93,7 +93,7 @@ extends: Install-Module -Repository CFS -Name Microsoft.PowerShell.PSResourceGet ./tools/installPSResources.ps1 -PSRepository CFS displayName: Install PSResources - - pwsh: Invoke-Build TestFull -Configuration $(BuildConfiguration) -PSRepository CFS + - pwsh: Invoke-Build Build -Configuration $(BuildConfiguration) -PSRepository CFS displayName: Build and test - task: PublishTestResults@2 displayName: Publish test results @@ -132,7 +132,6 @@ extends: **/Nerdbank.Streams.dll; **/Newtonsoft.Json.dll; **/OmniSharp.Extensions*.dll; - **/Serilog*.dll; **/System.Reactive.dll; - task: ArchiveFiles@2 displayName: Zip signed artifacts diff --git a/CHANGELOG.md b/CHANGELOG.md index b44fa94b7..f94a43aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # PowerShell Editor Services Release History +## v4.0.0 +### Monday, November 18, 2024 + +See more details at the GitHub Release for [v4.0.0](https://github.com/PowerShell/PowerShellEditorServices/releases/tag/v4.0.0). + +Drop support for PowerShell <7.4 and logging overhaul + +PowerShell 7.2 LTS and 7.3 are now past end-of-support and are now unsupported. +This is an incompatible API change so we're bumping the major version. +Please update to PowerShell 7.4 LTS going forward. + +This release contains a logging overhaul which purposely removes our +dependency on Serilog and should lead to improved stability with +PowerShell 5.1 (by avoiding a major GAC assembly conflict). + ## v3.21.0 ### Wednesday, October 30, 2024 diff --git a/Directory.Packages.props b/Directory.Packages.props index 92a025bed..d854d6c51 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,8 @@ - + + @@ -12,10 +13,6 @@ - - - - diff --git a/NOTICE.txt b/NOTICE.txt index c99a59348..aabad504f 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -68,433 +68,6 @@ http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Serilog 3.1.1 - Apache-2.0 - - -Copyright 2013-23 Serilog -Copyright (c) 2013-23 Serilog -copyright (c) Serilog Contributors - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - - - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - - - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - - - - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - - - - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - - - - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - - - - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - - - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - - - - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - - - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Serilog.Extensions.Logging 8.0.0 - Apache-2.0 - - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - - - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - - - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - - - - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - - - - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - - - - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - - - - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - - - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - - - - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - - - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Serilog.Sinks.Async 1.5.0 - Apache-2.0 - - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - - - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - - - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - - - - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - - - - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - - - - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - - - - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - - - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - - - - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - - - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Serilog.Sinks.File 5.0.0 - Apache-2.0 - - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - - - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - - - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - - - - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - - - - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - - - - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - - - - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - - - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - - - - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. diff --git a/PowerShellEditorServices.Common.props b/PowerShellEditorServices.Common.props index cc0dc7531..1bc5e8f5c 100644 --- a/PowerShellEditorServices.Common.props +++ b/PowerShellEditorServices.Common.props @@ -1,6 +1,6 @@ - 3.21.0 + 4.0.0 Microsoft © Microsoft Corporation. diff --git a/PowerShellEditorServices.build.ps1 b/PowerShellEditorServices.build.ps1 index 02a5d6436..5002127b2 100644 --- a/PowerShellEditorServices.build.ps1 +++ b/PowerShellEditorServices.build.ps1 @@ -40,12 +40,11 @@ $script:BuildInfoPath = "src/PowerShellEditorServices.Hosting/BuildInfo.cs" $script:NetFramework = @{ PS51 = 'net462' - PS72 = 'net6.0' PS74 = 'net8.0' Standard = 'netstandard2.0' } -$script:HostCoreOutput = "src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetFramework.PS72)/publish" +$script:HostCoreOutput = "src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetFramework.PS74)/publish" $script:HostDeskOutput = "src/PowerShellEditorServices.Hosting/bin/$Configuration/$($script:NetFramework.PS51)/publish" $script:PsesOutput = "src/PowerShellEditorServices/bin/$Configuration/$($script:NetFramework.Standard)/publish" @@ -128,7 +127,7 @@ task RestorePsesModules -If (-not (Test-Path "module/PSReadLine") -or -not (Test Task Build FindDotNet, CreateBuildInfo, RestorePsesModules, { Write-Build DarkGreen "Building PowerShellEditorServices" Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs ./src/PowerShellEditorServices/PowerShellEditorServices.csproj -f $script:NetFramework.Standard } - Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs ./src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj -f $script:NetFramework.PS72 } + Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs ./src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj -f $script:NetFramework.PS74 } if (-not $script:IsNix) { Invoke-BuildExec { & dotnet publish $script:dotnetBuildArgs ./src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj -f $script:NetFramework.PS51 } @@ -201,11 +200,6 @@ Task TestPS74 Build, SetupHelpForTests, { Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetFramework.PS74 } } -Task TestPS72 Build, SetupHelpForTests, { - Set-Location ./test/PowerShellEditorServices.Test/ - Invoke-BuildExec { & dotnet $script:dotnetTestArgs $script:NetFramework.PS72 } -} - Task TestPS51 -If (-not $script:IsNix) Build, SetupHelpForTests, { Set-Location ./test/PowerShellEditorServices.Test/ # TODO: See https://github.com/dotnet/sdk/issues/18353 for x64 test host @@ -299,7 +293,7 @@ Task TestE2EPowerShellCLM -If (-not $script:IsNix) Build, SetupHelpForTests, { } } -Task Test TestPS72, TestPS74, TestE2EPwsh, TestPS51, TestE2EPowerShell +Task Test TestPS74, TestE2EPwsh, TestPS51, TestE2EPowerShell Task TestFull Test, TestE2EDaily, TestE2EPwshCLM, TestE2EPowerShellCLM diff --git a/README.md b/README.md index 6dec799a5..46c126336 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,25 @@ experience in almost any editor or integrated development environment (IDE). ## [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) clients using PowerShell Editor Services: +- [PowerShell for Visual Studio Code](https://github.com/PowerShell/vscode-powershell) +> [!NOTE] +> PowerShell for Azure Data Studio will no longer be updated or maintained. + The functionality in PowerShell Editor Services is available in the following editor extensions: +> [!WARNING] +> These clients are community maintained and may be very out of date. +It is recommended to use a generic [LSP plugin](#Usage) with your client if possible. -- [PowerShell for Visual Studio Code](https://github.com/PowerShell/vscode-powershell), also available in Azure Data Studio - [lsp-pwsh](https://github.com/emacs-lsp/lsp-mode/blob/master/clients/lsp-pwsh.el), an Emacs PowerShell plugin - [intellij-powershell](https://github.com/ant-druha/intellij-powershell), adds PowerShell language support to IntelliJ-based IDEs - [coc-powershell](https://github.com/yatli/coc-powershell), a Vim and Neovim plugin - [powershell.nvim](https://github.com/TheLeoP/powershell.nvim) a Neovim plugin -Please note that other than PowerShell for Visual Studio Code, these clients are community maintained and may be very out of date. -It is recommended that you simply use an LSP plugin for your editor and configure it as demonstrated [below](#Usage). +## Supported PowerShell Versions + +PSES runs as a PowerShell Module in [currently supported versions of PowerShell 7+](https://learn.microsoft.com/en-us/powershell/scripting/install/powershell-support-lifecycle). + +Windows PowerShell 5.1 is supported on a best-effort basis. ## Features @@ -87,7 +96,7 @@ $command = @( "-HostName 'My Client'", "-HostProfileId 'myclient'", "-HostVersion 1.0.0", - "-LogLevel Diagnostic" + "-LogLevel Trace" ) -join " " $pwsh_arguments = "-NoLogo -NoProfile -Command $command" @@ -146,7 +155,8 @@ The types of PowerShell Editor Services can change at any moment and should not ## Development -> NOTE: The easiest way to manually test changes you've made in PowerShellEditorServices is to follow the [vscode-powershell development doc](https://github.com/PowerShell/vscode-powershell/blob/main/docs/development.md) to get a local build of the VS Code extension to use your local build of PowerShellEditorServices. +> [!TIP] +> The easiest way to manually test changes you've made in PowerShellEditorServices is to follow the [vscode-powershell development doc](https://github.com/PowerShell/vscode-powershell/blob/main/docs/development.md). ### 1. Install PowerShell 7+ diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 02e3385b6..f93cdb035 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -19,7 +19,7 @@ RootModule = if ($PSEdition -eq 'Core') } # Version number of this module. -ModuleVersion = '3.21.0' +ModuleVersion = '4.0.0' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 69aa7b97f..6f2ec8851 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -138,7 +138,7 @@ public StartEditorServicesCommand() /// The minimum log level that should be emitted. /// [Parameter] - public PsesLogLevel LogLevel { get; set; } = PsesLogLevel.Normal; + public string LogLevel { get; set; } = PsesLogLevel.Warning.ToString(); /// /// Paths to additional PowerShell modules to be imported at startup. @@ -195,6 +195,11 @@ public StartEditorServicesCommand() [Parameter] public string StartupBanner { get; set; } + /// + /// Compatibility to store the currently supported PSESLogLevel Enum Value + /// + private PsesLogLevel _psesLogLevel = PsesLogLevel.Warning; + #pragma warning disable IDE0022 protected override void BeginProcessing() { @@ -218,8 +223,7 @@ protected override void BeginProcessing() [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "We have to wait here, it's the whole program.")] protected override void EndProcessing() { - _logger.Log(PsesLogLevel.Diagnostic, "Beginning EndProcessing block"); - + _logger.Log(PsesLogLevel.Trace, "Beginning EndProcessing block"); try { // First try to remove PSReadLine to decomplicate startup @@ -230,7 +234,7 @@ protected override void EndProcessing() EditorServicesConfig editorServicesConfig = CreateConfigObject(); using EditorServicesLoader psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, SessionDetailsPath, _loggerUnsubscribers); - _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices"); + _logger.Log(PsesLogLevel.Debug, "Loading EditorServices"); // Synchronously start editor services and wait here until it shuts down. psesLoader.LoadAndRunEditorServicesAsync().GetAwaiter().GetResult(); } @@ -258,7 +262,25 @@ protected override void EndProcessing() private void StartLogging() { - _logger = new HostLogger(LogLevel); + bool isLegacyPsesLogLevel = false; + if (!Enum.TryParse(LogLevel, true, out _psesLogLevel)) + { + // PSES used to have log levels that didn't match MEL levels, this is an adapter for those types and may eventually be removed once people migrate their settings. + isLegacyPsesLogLevel = true; + _psesLogLevel = LogLevel switch + { + "Diagnostic" => PsesLogLevel.Trace, + "Verbose" => PsesLogLevel.Debug, + "Normal" => PsesLogLevel.Information, + _ => PsesLogLevel.Trace + }; + } + + _logger = new HostLogger(_psesLogLevel); + if (isLegacyPsesLogLevel) + { + _logger.Log(PsesLogLevel.Warning, $"The log level '{LogLevel}' is deprecated and will be removed in a future release. Please update your settings or command line options to use one of the following options: 'Trace', 'Debug', 'Information', 'Warning', 'Error', 'Critical'."); + } // We need to not write log messages to Stdio // if it's being used as a protocol transport @@ -282,7 +304,7 @@ private void StartLogging() IDisposable fileLoggerUnsubscriber = _logger.Subscribe(fileLogger); fileLogger.AddUnsubscriber(fileLoggerUnsubscriber); _loggerUnsubscribers.Add(fileLoggerUnsubscriber); - _logger.Log(PsesLogLevel.Diagnostic, "Logging started"); + _logger.Log(PsesLogLevel.Trace, "Logging started"); } // Sanitizes user input and ensures the directory is created. @@ -300,7 +322,7 @@ private string GetLogDirPath() private void RemovePSReadLineForStartup() { - _logger.Log(PsesLogLevel.Verbose, "Removing PSReadLine"); + _logger.Log(PsesLogLevel.Debug, "Removing PSReadLine"); using SMA.PowerShell pwsh = SMA.PowerShell.Create(RunspaceMode.CurrentRunspace); bool hasPSReadLine = pwsh.AddCommand(new CmdletInfo(@"Microsoft.PowerShell.Core\Get-Module", typeof(GetModuleCommand))) .AddParameter("Name", "PSReadLine") @@ -315,13 +337,13 @@ private void RemovePSReadLineForStartup() .AddParameter("Name", "PSReadLine") .AddParameter("ErrorAction", "SilentlyContinue"); - _logger.Log(PsesLogLevel.Verbose, "Removed PSReadLine"); + _logger.Log(PsesLogLevel.Debug, "Removed PSReadLine"); } } private EditorServicesConfig CreateConfigObject() { - _logger.Log(PsesLogLevel.Diagnostic, "Creating host configuration"); + _logger.Log(PsesLogLevel.Trace, "Creating host configuration"); string bundledModulesPath = BundledModulesPath; if (!Path.IsPathRooted(bundledModulesPath)) @@ -350,7 +372,7 @@ private EditorServicesConfig CreateConfigObject() LogPath) { FeatureFlags = FeatureFlags, - LogLevel = LogLevel, + LogLevel = _psesLogLevel, ConsoleRepl = GetReplKind(), UseNullPSHostUI = Stdio, // If Stdio is used we can't write anything else out AdditionalModules = AdditionalModules, @@ -400,31 +422,31 @@ private string GetProfilePathFromProfileObject(PSObject profileObject, ProfileUs // * On Linux or macOS on any version greater than or equal to 7 private ConsoleReplKind GetReplKind() { - _logger.Log(PsesLogLevel.Diagnostic, "Determining REPL kind"); + _logger.Log(PsesLogLevel.Trace, "Determining REPL kind"); if (Stdio || !EnableConsoleRepl) { - _logger.Log(PsesLogLevel.Diagnostic, "REPL configured as None"); + _logger.Log(PsesLogLevel.Trace, "REPL configured as None"); return ConsoleReplKind.None; } if (UseLegacyReadLine) { - _logger.Log(PsesLogLevel.Diagnostic, "REPL configured as Legacy"); + _logger.Log(PsesLogLevel.Trace, "REPL configured as Legacy"); return ConsoleReplKind.LegacyReadLine; } - _logger.Log(PsesLogLevel.Diagnostic, "REPL configured as PSReadLine"); + _logger.Log(PsesLogLevel.Trace, "REPL configured as PSReadLine"); return ConsoleReplKind.PSReadLine; } private ITransportConfig GetLanguageServiceTransport() { - _logger.Log(PsesLogLevel.Diagnostic, "Configuring LSP transport"); + _logger.Log(PsesLogLevel.Trace, "Configuring LSP transport"); if (DebugServiceOnly) { - _logger.Log(PsesLogLevel.Diagnostic, "No LSP transport: PSES is debug only"); + _logger.Log(PsesLogLevel.Trace, "No LSP transport: PSES is debug only"); return null; } @@ -448,11 +470,11 @@ private ITransportConfig GetLanguageServiceTransport() private ITransportConfig GetDebugServiceTransport() { - _logger.Log(PsesLogLevel.Diagnostic, "Configuring debug transport"); + _logger.Log(PsesLogLevel.Trace, "Configuring debug transport"); if (LanguageServiceOnly) { - _logger.Log(PsesLogLevel.Diagnostic, "No Debug transport: PSES is language service only"); + _logger.Log(PsesLogLevel.Trace, "No Debug transport: PSES is language service only"); return null; } @@ -463,7 +485,7 @@ private ITransportConfig GetDebugServiceTransport() return new StdioTransportConfig(_logger); } - _logger.Log(PsesLogLevel.Diagnostic, "No debug transport: Transport is Stdio with debug disabled"); + _logger.Log(PsesLogLevel.Trace, "No debug transport: Transport is Stdio with debug disabled"); return null; } diff --git a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs index b2e683a2f..66bc7b1de 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs @@ -90,14 +90,14 @@ public EditorServicesConfig( public ConsoleReplKind ConsoleRepl { get; set; } = ConsoleReplKind.None; /// - /// Will suppress messages to PSHost (to prevent Stdio clobbering) + /// Will suppress messages to PSHost (to prevent Stdio clobbering) /// public bool UseNullPSHostUI { get; set; } /// - /// The minimum log level to log events with. + /// The minimum log level to log events with. Defaults to warning but is usually overriden by the startup process. /// - public PsesLogLevel LogLevel { get; set; } = PsesLogLevel.Normal; + public PsesLogLevel LogLevel { get; set; } = PsesLogLevel.Warning; /// /// Configuration for the language server protocol transport to use. diff --git a/src/PowerShellEditorServices.Hosting/Configuration/HostLogger.cs b/src/PowerShellEditorServices.Hosting/Configuration/HostLogger.cs index 7ce6f66a9..bebb7e2b9 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/HostLogger.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/HostLogger.cs @@ -12,21 +12,51 @@ namespace Microsoft.PowerShell.EditorServices.Hosting { /// - /// User-facing log level for editor services configuration. + /// Log Level for HostLogger. This is a direct copy of LogLevel from Microsoft.Extensions.Logging, and will map to + /// MEL.LogLevel once MEL is bootstrapped, but we don't want to load any MEL assemblies until the Assembly Load + /// Context is set up. /// - /// - /// The underlying values of this enum attempt to align to both - /// and - /// . - /// public enum PsesLogLevel { - Diagnostic = 0, - Verbose = 1, - Normal = 2, + /// + /// Logs that contain the most detailed messages. These messages may contain sensitive application data. + /// These messages are disabled by default and should never be enabled in a production environment. + /// + Trace = 0, + + /// + /// Logs that are used for interactive investigation during development. These logs should primarily contain + /// information useful for debugging and have no long-term value. + /// + Debug = 1, + + /// + /// Logs that track the general flow of the application. These logs should have long-term value. + /// + Information = 2, + + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the + /// application execution to stop. + /// Warning = 3, + + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a + /// failure in the current activity, not an application-wide failure. + /// Error = 4, - None = 5 + + /// + /// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires + /// immediate attention. + /// + Critical = 5, + + /// + /// Not used for writing log messages. Specifies that a logging category should not write any messages. + /// + None = 6, } /// @@ -181,15 +211,9 @@ public void LogException( /// Since it's likely that the process will end when PSES shuts down, /// there's no good reason to need objects rather than writing directly to the host. /// - internal class PSHostLogger : IObserver<(PsesLogLevel logLevel, string message)> + /// The PowerShell host user interface object to log output to. + internal class PSHostLogger(PSHostUserInterface ui) : IObserver<(PsesLogLevel logLevel, string message)> { - private readonly PSHostUserInterface _ui; - - /// - /// Create a new PowerShell host logger. - /// - /// The PowerShell host user interface object to log output to. - public PSHostLogger(PSHostUserInterface ui) => _ui = ui; public void OnCompleted() { @@ -201,35 +225,35 @@ public void OnCompleted() public void OnNext((PsesLogLevel logLevel, string message) value) { - switch (value.logLevel) + (PsesLogLevel logLevel, string message) = value; + switch (logLevel) { - case PsesLogLevel.Diagnostic: - _ui.WriteDebugLine(value.message); - return; - - case PsesLogLevel.Verbose: - _ui.WriteVerboseLine(value.message); - return; - - case PsesLogLevel.Normal: - _ui.WriteLine(value.message); - return; - + case PsesLogLevel.Trace: + ui.WriteDebugLine("[Trace] " + message); + break; + case PsesLogLevel.Debug: + ui.WriteDebugLine(message); + break; + case PsesLogLevel.Information: + ui.WriteVerboseLine(message); + break; case PsesLogLevel.Warning: - _ui.WriteWarningLine(value.message); - return; - + ui.WriteWarningLine(message); + break; case PsesLogLevel.Error: - _ui.WriteErrorLine(value.message); - return; - + case PsesLogLevel.Critical: + ui.WriteErrorLine(message); + break; default: - _ui.WriteLine(value.message); - return; + ui.WriteDebugLine("UNKNOWN:" + message); + break; } } } + /// + /// A simple log sink that logs to a stream, typically used to log to a file. + /// internal class StreamLogger : IObserver<(PsesLogLevel logLevel, string message)>, IDisposable { public static StreamLogger CreateWithNewFile(string path) @@ -284,9 +308,7 @@ public void OnCompleted() } _cancellationSource.Cancel(); - _writerThread.Join(); - _unsubscriber.Dispose(); _fileWriter.Flush(); _fileWriter.Close(); @@ -299,29 +321,17 @@ public void OnCompleted() public void OnNext((PsesLogLevel logLevel, string message) value) { - string message = null; - switch (value.logLevel) + string message = value.logLevel switch { - case PsesLogLevel.Diagnostic: - message = $"[DBG]: {value.message}"; - break; - - case PsesLogLevel.Verbose: - message = $"[VRB]: {value.message}"; - break; - - case PsesLogLevel.Normal: - message = $"[INF]: {value.message}"; - break; - - case PsesLogLevel.Warning: - message = $"[WRN]: {value.message}"; - break; - - case PsesLogLevel.Error: - message = $"[ERR]: {value.message}"; - break; - } + // String interpolation often considered a logging sin is OK here because our filtering happens before. + PsesLogLevel.Trace => $"[TRC]: {value.message}", + PsesLogLevel.Debug => $"[DBG]: {value.message}", + PsesLogLevel.Information => $"[INF]: {value.message}", + PsesLogLevel.Warning => $"[WRN]: {value.message}", + PsesLogLevel.Error => $"[ERR]: {value.message}", + PsesLogLevel.Critical => $"[CRT]: {value.message}", + _ => value.message, + }; _messageQueue.Add(message); } diff --git a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs index c5f351f91..bca1acd5e 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/SessionFileWriter.cs @@ -63,7 +63,7 @@ public SessionFileWriter(HostLogger logger, string sessionFilePath, Version powe /// The reason for the startup failure. public void WriteSessionFailure(string reason) { - _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure"); + _logger.Log(PsesLogLevel.Trace, "Writing session failure"); Dictionary sessionObject = new() { @@ -81,7 +81,7 @@ public void WriteSessionFailure(string reason) /// The debug adapter transport configuration. public void WriteSessionStarted(ITransportConfig languageServiceTransport, ITransportConfig debugAdapterTransport) { - _logger.Log(PsesLogLevel.Diagnostic, "Writing session started"); + _logger.Log(PsesLogLevel.Trace, "Writing session started"); Dictionary sessionObject = new() { @@ -142,7 +142,7 @@ private void WriteSessionObject(Dictionary sessionObject) File.WriteAllText(_sessionFilePath, content, s_sessionFileEncoding); } - _logger.Log(PsesLogLevel.Verbose, $"Session file written to {_sessionFilePath} with content:\n{content}"); + _logger.Log(PsesLogLevel.Debug, $"Session file written to {_sessionFilePath} with content:\n{content}"); } } } diff --git a/src/PowerShellEditorServices.Hosting/Configuration/TransportConfig.cs b/src/PowerShellEditorServices.Hosting/Configuration/TransportConfig.cs index be763737c..db64a27a7 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/TransportConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/TransportConfig.cs @@ -53,7 +53,7 @@ public sealed class StdioTransportConfig : ITransportConfig public Task<(Stream inStream, Stream outStream)> ConnectStreamsAsync() { - _logger.Log(PsesLogLevel.Diagnostic, "Connecting stdio streams"); + _logger.Log(PsesLogLevel.Trace, "Connecting stdio streams"); return Task.FromResult((Console.OpenStandardInput(), Console.OpenStandardOutput())); } } @@ -102,11 +102,11 @@ private DuplexNamedPipeTransportConfig(HostLogger logger, string pipeName) public async Task<(Stream inStream, Stream outStream)> ConnectStreamsAsync() { - _logger.Log(PsesLogLevel.Diagnostic, "Creating named pipe"); + _logger.Log(PsesLogLevel.Trace, "Creating named pipe"); NamedPipeServerStream namedPipe = NamedPipeUtils.CreateNamedPipe(_pipeName, PipeDirection.InOut); - _logger.Log(PsesLogLevel.Diagnostic, "Waiting for named pipe connection"); + _logger.Log(PsesLogLevel.Trace, "Waiting for named pipe connection"); await namedPipe.WaitForConnectionAsync().ConfigureAwait(false); - _logger.Log(PsesLogLevel.Diagnostic, "Named pipe connected"); + _logger.Log(PsesLogLevel.Trace, "Named pipe connected"); return (namedPipe, namedPipe); } } @@ -173,18 +173,18 @@ private SimplexNamedPipeTransportConfig(HostLogger logger, string inPipeName, st public async Task<(Stream inStream, Stream outStream)> ConnectStreamsAsync() { - _logger.Log(PsesLogLevel.Diagnostic, "Starting in pipe connection"); + _logger.Log(PsesLogLevel.Trace, "Starting in pipe connection"); NamedPipeServerStream inPipe = NamedPipeUtils.CreateNamedPipe(_inPipeName, PipeDirection.InOut); Task inPipeConnected = inPipe.WaitForConnectionAsync(); - _logger.Log(PsesLogLevel.Diagnostic, "Starting out pipe connection"); + _logger.Log(PsesLogLevel.Trace, "Starting out pipe connection"); NamedPipeServerStream outPipe = NamedPipeUtils.CreateNamedPipe(_outPipeName, PipeDirection.Out); Task outPipeConnected = outPipe.WaitForConnectionAsync(); - _logger.Log(PsesLogLevel.Diagnostic, "Wating for pipe connections"); + _logger.Log(PsesLogLevel.Trace, "Wating for pipe connections"); await Task.WhenAll(inPipeConnected, outPipeConnected).ConfigureAwait(false); - _logger.Log(PsesLogLevel.Diagnostic, "Simplex named pipe transport connected"); + _logger.Log(PsesLogLevel.Trace, "Simplex named pipe transport connected"); return (inPipe, outPipe); } } diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index cf5c45c75..eea23353c 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -70,20 +70,20 @@ public static EditorServicesLoader Create( Version powerShellVersion = GetPSVersion(); SessionFileWriter sessionFileWriter = new(logger, sessionDetailsPath, powerShellVersion); - logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); + logger.Log(PsesLogLevel.Trace, "Session file writer created"); #if CoreCLR // In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into - logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading"); + logger.Log(PsesLogLevel.Debug, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading"); PsesLoadContext psesLoadContext = new(s_psesDependencyDirPath); - if (hostConfig.LogLevel == PsesLogLevel.Diagnostic) + if (hostConfig.LogLevel == PsesLogLevel.Trace) { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { logger.Log( - PsesLogLevel.Diagnostic, + PsesLogLevel.Trace, $"Loaded into load context {AssemblyLoadContext.GetLoadContext(args.LoadedAssembly)}: {args.LoadedAssembly}"); }; } @@ -91,9 +91,9 @@ public static EditorServicesLoader Create( AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext _, AssemblyName asmName) => { #if ASSEMBLY_LOAD_STACKTRACE - logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}. Stacktrace:\n{new StackTrace()}"); + logger.Log(PsesLogLevel.Trace, $"Assembly resolve event fired for {asmName}. Stacktrace:\n{new StackTrace()}"); #else - logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}"); + logger.Log(PsesLogLevel.Trace, $"Assembly resolve event fired for {asmName}"); #endif // We only want the Editor Services DLL; the new ALC will lazily load its dependencies automatically @@ -104,15 +104,15 @@ public static EditorServicesLoader Create( string asmPath = Path.Combine(s_psesDependencyDirPath, $"{asmName.Name}.dll"); - logger.Log(PsesLogLevel.Verbose, "Loading PSES DLL using new assembly load context"); + logger.Log(PsesLogLevel.Debug, "Loading PSES DLL using new assembly load context"); return psesLoadContext.LoadFromAssemblyPath(asmPath); }; #else // In .NET Framework we add an event here to redirect dependency loading in the current AppDomain for PSES' dependencies - logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for dependency loading"); + logger.Log(PsesLogLevel.Debug, "Adding AssemblyResolve event handler for dependency loading"); - if (hostConfig.LogLevel == PsesLogLevel.Diagnostic) + if (hostConfig.LogLevel == PsesLogLevel.Trace) { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { @@ -122,7 +122,7 @@ public static EditorServicesLoader Create( } logger.Log( - PsesLogLevel.Diagnostic, + PsesLogLevel.Trace, $"Loaded '{args.LoadedAssembly.GetName()}' from '{args.LoadedAssembly.Location}'"); }; } @@ -131,9 +131,9 @@ public static EditorServicesLoader Create( AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) => { #if ASSEMBLY_LOAD_STACKTRACE - logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}. Stacktrace:\n{new StackTrace()}"); + logger.Log(PsesLogLevel.Trace, $"Assembly resolve event fired for {args.Name}. Stacktrace:\n{new StackTrace()}"); #else - logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}"); + logger.Log(PsesLogLevel.Trace, $"Assembly resolve event fired for {args.Name}"); #endif AssemblyName asmName = new(args.Name); @@ -143,7 +143,7 @@ public static EditorServicesLoader Create( string baseDirAsmPath = Path.Combine(s_psesBaseDirPath, dllName); if (File.Exists(baseDirAsmPath)) { - logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES base dir into LoadFile context"); + logger.Log(PsesLogLevel.Trace, $"Loading {args.Name} from PSES base dir into LoadFile context"); return Assembly.LoadFile(baseDirAsmPath); } @@ -151,7 +151,7 @@ public static EditorServicesLoader Create( string asmPath = Path.Combine(s_psesDependencyDirPath, dllName); if (File.Exists(asmPath)) { - logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES dependency dir into LoadFile context"); + logger.Log(PsesLogLevel.Trace, $"Loading {args.Name} from PSES dependency dir into LoadFile context"); return Assembly.LoadFile(asmPath); } @@ -212,10 +212,10 @@ public Task LoadAndRunEditorServicesAsync() ValidateConfiguration(); // Method with no implementation that forces the PSES assembly to load, triggering an AssemblyResolve event - _logger.Log(PsesLogLevel.Verbose, "Loading PowerShell Editor Services"); + _logger.Log(PsesLogLevel.Information, "Loading PowerShell Editor Services Assemblies"); LoadEditorServices(); - _logger.Log(PsesLogLevel.Verbose, "Starting EditorServices"); + _logger.Log(PsesLogLevel.Information, "Starting PowerShell Editor Services"); _editorServicesRunner = new EditorServicesRunner(_logger, _hostConfig, _sessionFileWriter, _loggersToUnsubscribe); @@ -225,7 +225,7 @@ public Task LoadAndRunEditorServicesAsync() public void Dispose() { - _logger.Log(PsesLogLevel.Diagnostic, "Loader disposed"); + _logger.Log(PsesLogLevel.Trace, "Loader disposed"); _editorServicesRunner?.Dispose(); // TODO: @@ -242,7 +242,7 @@ private void CheckPowerShellVersion() { PSLanguageMode languageMode = Runspace.DefaultRunspace.SessionStateProxy.LanguageMode; - _logger.Log(PsesLogLevel.Verbose, $@" + _logger.Log(PsesLogLevel.Trace, $@" == PowerShell Details == - PowerShell version: {_powerShellVersion} - Language mode: {languageMode} @@ -261,7 +261,7 @@ private void CheckPowerShellVersion() #if !CoreCLR private void CheckDotNetVersion() { - _logger.Log(PsesLogLevel.Verbose, "Checking that .NET Framework version is at least 4.8"); + _logger.Log(PsesLogLevel.Debug, "Checking that .NET Framework version is at least 4.8"); using RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full"); object netFxValue = key?.GetValue("Release"); if (netFxValue == null || netFxValue is not int netFxVersion) @@ -269,7 +269,7 @@ private void CheckDotNetVersion() return; } - _logger.Log(PsesLogLevel.Verbose, $".NET registry version: {netFxVersion}"); + _logger.Log(PsesLogLevel.Debug, $".NET registry version: {netFxVersion}"); if (netFxVersion < Net48Version) { @@ -283,26 +283,26 @@ private void UpdatePSModulePath() { if (string.IsNullOrEmpty(_hostConfig.BundledModulePath)) { - _logger.Log(PsesLogLevel.Diagnostic, "BundledModulePath not set, skipping"); + _logger.Log(PsesLogLevel.Trace, "BundledModulePath not set, skipping"); return; } string psModulePath = Environment.GetEnvironmentVariable("PSModulePath").TrimEnd(Path.PathSeparator); if ($"{psModulePath}{Path.PathSeparator}".Contains($"{_hostConfig.BundledModulePath}{Path.PathSeparator}")) { - _logger.Log(PsesLogLevel.Diagnostic, "BundledModulePath already set, skipping"); + _logger.Log(PsesLogLevel.Trace, "BundledModulePath already set, skipping"); return; } psModulePath = $"{psModulePath}{Path.PathSeparator}{_hostConfig.BundledModulePath}"; Environment.SetEnvironmentVariable("PSModulePath", psModulePath); - _logger.Log(PsesLogLevel.Verbose, $"Updated PSModulePath to: '{psModulePath}'"); + _logger.Log(PsesLogLevel.Trace, $"Updated PSModulePath to: '{psModulePath}'"); } private void LogHostInformation() { - _logger.Log(PsesLogLevel.Verbose, $"PID: {System.Diagnostics.Process.GetCurrentProcess().Id}"); + _logger.Log(PsesLogLevel.Trace, $"PID: {System.Diagnostics.Process.GetCurrentProcess().Id}"); - _logger.Log(PsesLogLevel.Verbose, $@" + _logger.Log(PsesLogLevel.Debug, $@" == Build Details == - Editor Services version: {BuildInfo.BuildVersion} - Build origin: {BuildInfo.BuildOrigin} @@ -310,7 +310,7 @@ private void LogHostInformation() - Build time: {BuildInfo.BuildTime} "); - _logger.Log(PsesLogLevel.Verbose, $@" + _logger.Log(PsesLogLevel.Debug, $@" == Host Startup Configuration Details == - Host name: {_hostConfig.HostInfo.Name} - Host version: {_hostConfig.HostInfo.Version} @@ -333,14 +333,14 @@ private void LogHostInformation() + CurrentUserCurrentHost: {_hostConfig.ProfilePaths.CurrentUserCurrentHost ?? ""} "); - _logger.Log(PsesLogLevel.Verbose, $@" + _logger.Log(PsesLogLevel.Debug, $@" == Console Details == - Console input encoding: {Console.InputEncoding.EncodingName} - Console output encoding: {Console.OutputEncoding.EncodingName} - PowerShell output encoding: {GetPSOutputEncoding()} "); - _logger.Log(PsesLogLevel.Verbose, $@" + _logger.Log(PsesLogLevel.Debug, $@" == Environment Details == - OS description: {RuntimeInformation.OSDescription} - OS architecture: {RuntimeInformation.OSArchitecture} @@ -359,7 +359,7 @@ private static string GetPSOutputEncoding() [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "Checking user-defined configuration")] private void ValidateConfiguration() { - _logger.Log(PsesLogLevel.Diagnostic, "Validating configuration"); + _logger.Log(PsesLogLevel.Debug, "Validating configuration"); bool lspUsesStdio = _hostConfig.LanguageServiceTransport is StdioTransportConfig; bool debugUsesStdio = _hostConfig.DebugServiceTransport is StdioTransportConfig; diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 0f47bdad8..1c9de268c 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -42,7 +42,7 @@ public EditorServicesRunner( _config = config; _sessionFileWriter = sessionFileWriter; // NOTE: This factory helps to isolate `Microsoft.Extensions.Logging/DependencyInjection`. - _serverFactory = EditorServicesServerFactory.Create(_config.LogPath, (int)_config.LogLevel, logger); + _serverFactory = new(logger); _alreadySubscribedDebug = false; _loggersToUnsubscribe = loggersToUnsubscribe; } @@ -60,11 +60,11 @@ public Task RunUntilShutdown() Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown(); // Now write the session file - _logger.Log(PsesLogLevel.Diagnostic, "Writing session file"); + _logger.Log(PsesLogLevel.Trace, "Writing session file"); _sessionFileWriter.WriteSessionStarted(_config.LanguageServiceTransport, _config.DebugServiceTransport); // Finally, wait for Editor Services to shut down - _logger.Log(PsesLogLevel.Diagnostic, "Waiting on PSES run/shutdown"); + _logger.Log(PsesLogLevel.Debug, "Waiting on PSES run/shutdown"); return runAndAwaitShutdown; } @@ -124,7 +124,7 @@ private async Task CreateEditorServicesAndRunUntilShutdown() { try { - _logger.Log(PsesLogLevel.Diagnostic, "Creating/running editor services"); + _logger.Log(PsesLogLevel.Debug, "Creating/running editor services"); bool creatingLanguageServer = _config.LanguageServiceTransport != null; bool creatingDebugServer = _config.DebugServiceTransport != null; @@ -140,6 +140,9 @@ private async Task CreateEditorServicesAndRunUntilShutdown() return; } + _logger.Log(PsesLogLevel.Information, "PSES Startup Completed. Starting Language Server."); + _logger.Log(PsesLogLevel.Information, "Please check the LSP log file in your client for further messages. In VSCode, this is the 'PowerShell' output pane"); + // We want LSP and maybe debugging // To do that we: // - Create the LSP server @@ -149,7 +152,6 @@ private async Task CreateEditorServicesAndRunUntilShutdown() // - Wait for the LSP server to finish // Unsubscribe the host logger here so that the Extension Terminal is not polluted with input after the first prompt - _logger.Log(PsesLogLevel.Verbose, "Starting server, deregistering host logger and registering shutdown listener"); if (_loggersToUnsubscribe != null) { foreach (IDisposable loggerToUnsubscribe in _loggersToUnsubscribe) @@ -193,11 +195,11 @@ private async Task CreateEditorServicesAndRunUntilShutdown() private async Task RunTempDebugSessionAsync(HostStartupInfo hostDetails) { - _logger.Log(PsesLogLevel.Diagnostic, "Running temp debug session"); + _logger.Log(PsesLogLevel.Information, "Starting temporary debug session"); PsesDebugServer debugServer = await CreateDebugServerForTempSessionAsync(hostDetails).ConfigureAwait(false); - _logger.Log(PsesLogLevel.Verbose, "Debug server created"); + _logger.Log(PsesLogLevel.Debug, "Debug server created"); await debugServer.StartAsync().ConfigureAwait(false); - _logger.Log(PsesLogLevel.Verbose, "Debug server started"); + _logger.Log(PsesLogLevel.Debug, "Debug server started"); await debugServer.WaitForShutdown().ConfigureAwait(false); } @@ -210,47 +212,47 @@ private async Task StartDebugServer(Task debugServerCreation) // To do this, we set an event to allow it to create a new debug server as its session ends if (!_alreadySubscribedDebug) { - _logger.Log(PsesLogLevel.Diagnostic, "Subscribing debug server for session ended event"); + _logger.Log(PsesLogLevel.Trace, "Subscribing debug server for session ended event"); _alreadySubscribedDebug = true; debugServer.SessionEnded += DebugServer_OnSessionEnded; } - _logger.Log(PsesLogLevel.Diagnostic, "Starting debug server"); + _logger.Log(PsesLogLevel.Trace, "Starting debug server"); await debugServer.StartAsync().ConfigureAwait(false); } private Task RestartDebugServerAsync(PsesDebugServer debugServer) { - _logger.Log(PsesLogLevel.Diagnostic, "Restarting debug server"); + _logger.Log(PsesLogLevel.Debug, "Restarting debug server"); Task debugServerCreation = RecreateDebugServerAsync(debugServer); return StartDebugServer(debugServerCreation); } private async Task CreateLanguageServerAsync(HostStartupInfo hostDetails) { - _logger.Log(PsesLogLevel.Verbose, $"Creating LSP transport with endpoint {_config.LanguageServiceTransport.EndpointDetails}"); + _logger.Log(PsesLogLevel.Trace, $"Creating LSP transport with endpoint {_config.LanguageServiceTransport.EndpointDetails}"); (Stream inStream, Stream outStream) = await _config.LanguageServiceTransport.ConnectStreamsAsync().ConfigureAwait(false); - _logger.Log(PsesLogLevel.Diagnostic, "Creating language server"); + _logger.Log(PsesLogLevel.Debug, "Creating language server"); return _serverFactory.CreateLanguageServer(inStream, outStream, hostDetails); } private async Task CreateDebugServerWithLanguageServerAsync(PsesLanguageServer languageServer) { - _logger.Log(PsesLogLevel.Verbose, $"Creating debug adapter transport with endpoint {_config.DebugServiceTransport.EndpointDetails}"); + _logger.Log(PsesLogLevel.Trace, $"Creating debug adapter transport with endpoint {_config.DebugServiceTransport.EndpointDetails}"); (Stream inStream, Stream outStream) = await _config.DebugServiceTransport.ConnectStreamsAsync().ConfigureAwait(false); - _logger.Log(PsesLogLevel.Diagnostic, "Creating debug adapter"); + _logger.Log(PsesLogLevel.Debug, "Creating debug adapter"); return _serverFactory.CreateDebugServerWithLanguageServer(inStream, outStream, languageServer); } private async Task RecreateDebugServerAsync(PsesDebugServer debugServer) { - _logger.Log(PsesLogLevel.Diagnostic, "Recreating debug adapter transport"); + _logger.Log(PsesLogLevel.Debug, "Recreating debug adapter transport"); (Stream inStream, Stream outStream) = await _config.DebugServiceTransport.ConnectStreamsAsync().ConfigureAwait(false); - _logger.Log(PsesLogLevel.Diagnostic, "Recreating debug adapter"); + _logger.Log(PsesLogLevel.Debug, "Recreating debug adapter"); return _serverFactory.RecreateDebugServer(inStream, outStream, debugServer); } @@ -263,7 +265,7 @@ private async Task CreateDebugServerForTempSessionAsync(HostSta private HostStartupInfo CreateHostStartupInfo() { - _logger.Log(PsesLogLevel.Diagnostic, "Creating startup info object"); + _logger.Log(PsesLogLevel.Debug, "Creating startup info object"); ProfilePathInfo profilePaths = null; if (_config.ProfilePaths.AllUsersAllHosts != null @@ -288,7 +290,7 @@ private HostStartupInfo CreateHostStartupInfo() _config.AdditionalModules, _config.InitialSessionState, _config.LogPath, - (int)_config.LogLevel, + (int)_config.LogLevel, //This maps to MEL log levels, we use int so this is easily supplied externally. consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None, useNullPSHostUI: _config.UseNullPSHostUI, usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine, @@ -308,7 +310,7 @@ private void WriteStartupBanner() [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "Intentionally fire and forget.")] private void DebugServer_OnSessionEnded(object sender, EventArgs args) { - _logger.Log(PsesLogLevel.Verbose, "Debug session ended, restarting debug service..."); + _logger.Log(PsesLogLevel.Debug, "Debug session ended, restarting debug service..."); PsesDebugServer oldServer = (PsesDebugServer)sender; oldServer.Dispose(); _alreadySubscribedDebug = false; diff --git a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj index 8233ead02..7b73ff8a8 100644 --- a/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj +++ b/src/PowerShellEditorServices.Hosting/PowerShellEditorServices.Hosting.csproj @@ -2,7 +2,7 @@ - net6.0;net462 + net8.0;net462 Microsoft.PowerShell.EditorServices.Hosting diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index a9bbab38e..b981ea450 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -2,81 +2,30 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.IO; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Server; -using Serilog; -using Serilog.Events; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using Microsoft.PowerShell.EditorServices.Services.Extension; -#if DEBUG -using Serilog.Debugging; -#endif +// The HostLogger type isn't directly referenced from this assembly, however it uses a common IObservable interface and this alias helps make it more clear the purpose. We can use Microsoft.Extensions.Logging from this point because the ALC should be loaded, but we need to only expose the IObservable to the Hosting assembly so it doesn't try to load MEL before the ALC is ready. +using HostLogger = System.IObservable<(int logLevel, string message)>; namespace Microsoft.PowerShell.EditorServices.Hosting { /// - /// Factory class for hiding dependencies of Editor Services. + /// Factory for creating the LSP server and debug server instances. /// - /// - /// Dependency injection and logging are wrapped by factory methods on this class so that the - /// host assembly can construct the LSP and debug servers without directly depending on and . - /// internal sealed class EditorServicesServerFactory : IDisposable { + private readonly HostLogger _hostLogger; + /// - /// Create a new Editor Services factory. This method will instantiate logging. + /// Creates a loggerfactory for this instance /// - /// - /// - /// This can only be called once because it sets global state (the logger) and that call is - /// in . - /// - /// - /// TODO: Why is this a static function wrapping a constructor instead of just a - /// constructor? In the end it returns an instance (albeit a "singleton"). - /// - /// - /// The path of the log file to use. - /// The minimum log level to use. - /// The host logger? - public static EditorServicesServerFactory Create(string logDirectoryPath, int minimumLogLevel, IObservable<(int logLevel, string message)> hostLogger) - { - // NOTE: Ignore the suggestion to use Environment.ProcessId as it doesn't work for - // .NET 4.6.2 (for Windows PowerShell), and this won't be caught in CI. - int currentPID = Process.GetCurrentProcess().Id; - string logPath = Path.Combine(logDirectoryPath, $"PowerShellEditorServices-{currentPID}.log"); - Log.Logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.Async(config => config.File(logPath)) - .MinimumLevel.Is((LogEventLevel)minimumLogLevel) - .CreateLogger(); - -#if DEBUG - SelfLog.Enable(msg => Debug.WriteLine(msg)); -#endif - - LoggerFactory loggerFactory = new(); - loggerFactory.AddSerilog(); - - // Hook up logging from the host so that its recorded in the log file - hostLogger.Subscribe(new HostLoggerAdapter(loggerFactory)); - - return new EditorServicesServerFactory(loggerFactory); - } - - // TODO: Can we somehow refactor this member so the language and debug servers can be - // instantiated using their constructors instead of tying them to this factory with `Create` - // methods? - private readonly ILoggerFactory _loggerFactory; - - private EditorServicesServerFactory(ILoggerFactory loggerFactory) => _loggerFactory = loggerFactory; + /// The hostLogger that will be provided to the language services for logging handoff + internal EditorServicesServerFactory(HostLogger hostLogger) => _hostLogger = hostLogger; /// /// Create the LSP server. @@ -92,7 +41,7 @@ public static EditorServicesServerFactory Create(string logDirectoryPath, int mi public PsesLanguageServer CreateLanguageServer( Stream inputStream, Stream outputStream, - HostStartupInfo hostStartupInfo) => new(_loggerFactory, inputStream, outputStream, hostStartupInfo); + HostStartupInfo hostStartupInfo) => new(_hostLogger, inputStream, outputStream, hostStartupInfo); /// /// Create the debug server given a language server instance. @@ -110,7 +59,7 @@ public PsesDebugServer CreateDebugServerWithLanguageServer( PsesLanguageServer languageServer) { return new PsesDebugServer( - _loggerFactory, + _hostLogger, inputStream, outputStream, languageServer.LanguageServer.Services); @@ -132,7 +81,7 @@ public PsesDebugServer RecreateDebugServer( PsesDebugServer debugServer) { return new PsesDebugServer( - _loggerFactory, + _hostLogger, inputStream, outputStream, debugServer.ServiceProvider); @@ -153,7 +102,6 @@ public PsesDebugServer CreateDebugServerForTempSession( ServiceProvider serviceProvider = new ServiceCollection() .AddLogging(builder => builder .ClearProviders() - .AddSerilog() .SetMinimumLevel(LogLevel.Trace)) // TODO: Why randomly set to trace? .AddSingleton(_ => null) // TODO: Why add these for a debug server?! @@ -171,25 +119,14 @@ public PsesDebugServer CreateDebugServerForTempSession( serviceProvider.GetService(); return new PsesDebugServer( - _loggerFactory, + _hostLogger, inputStream, outputStream, serviceProvider, isTemp: true); } - /// - /// TODO: This class probably should not be as the primary - /// intention of that interface is to provide cleanup of unmanaged resources, which the - /// logger certainly is not. Nor is this class used with a . Instead, - /// this class should call in a finalizer. This - /// could potentially even be done with by passing dispose=true. - /// - public void Dispose() - { - Log.CloseAndFlush(); - _loggerFactory.Dispose(); - } + // TODO: Clean up host logger? Shouldn't matter since we start a new process after shutdown. + public void Dispose() { } } } diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs index 964509626..d1f1b27db 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -107,9 +107,8 @@ public sealed class HostStartupInfo /// The minimum log level of log events to be logged. /// /// - /// This is cast to all of , , and , hence it is an int. + /// This primitive maps to and /// public int LogLevel { get; } diff --git a/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs b/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs index 3290b2b78..9ffe3c950 100644 --- a/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs +++ b/src/PowerShellEditorServices/Logging/HostLoggerAdapter.cs @@ -9,23 +9,18 @@ namespace Microsoft.PowerShell.EditorServices.Logging /// /// Adapter class to allow logging events sent by the host to be recorded by PSES' logging infrastructure. /// - internal class HostLoggerAdapter : IObserver<(int logLevel, string message)> + internal class HostLoggerAdapter(ILogger logger) : IObserver<(int logLevel, string message)> { - private readonly ILogger _logger; + public void OnError(Exception error) => logger.LogError(error, "Error in host logger"); /// - /// Create a new host logger adapter. + /// Log the message received from the host into MEL. /// - /// Factory to create logger instances with. - public HostLoggerAdapter(ILoggerFactory loggerFactory) => _logger = loggerFactory.CreateLogger("HostLogs"); + public void OnNext((int logLevel, string message) value) => logger.Log((LogLevel)value.logLevel, value.message); public void OnCompleted() { // Nothing to do; we simply don't send more log messages } - - public void OnError(Exception error) => _logger.LogError(error, "Error in host logger"); - - public void OnNext((int logLevel, string message) value) => _logger.Log((LogLevel)value.logLevel, value.message); } } diff --git a/src/PowerShellEditorServices/Logging/LanguageServerLogger.cs b/src/PowerShellEditorServices/Logging/LanguageServerLogger.cs new file mode 100644 index 000000000..0b22ddd95 --- /dev/null +++ b/src/PowerShellEditorServices/Logging/LanguageServerLogger.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Window; + +namespace Microsoft.PowerShell.EditorServices.Logging; + +internal class LanguageServerLogger(ILanguageServerFacade responseRouter, string categoryName) : ILogger +{ + public IDisposable? BeginScope(TState state) where TState : notnull => Disposable.Empty; + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log( + LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter + ) + { + if (responseRouter is null) + { + throw new InvalidOperationException("Log received without a valid responseRouter dependency. This is a bug, please report it."); + } + // Any Omnisharp or trace logs are directly LSP protocol related and we send them to the trace channel + // TODO: Dynamically adjust if SetTrace is reported + // BUG: There is an omnisharp filter incorrectly filtering this. As a workaround we will use logMessage for now. + // https://github.com/OmniSharp/csharp-language-server-protocol/issues/1390 + // + // { + // // Everything with omnisharp goes directly to trace + // string eventMessage = string.Empty; + // string exceptionName = exception?.GetType().Name ?? string.Empty; + // if (eventId.Name is not null) + // { + // eventMessage = eventId.Id == 0 ? eventId.Name : $"{eventId.Name} [{eventId.Id}] "; + // } + + // LogTraceParams trace = new() + // { + // Message = categoryName + ": " + eventMessage + exceptionName, + // Verbose = formatter(state, exception) + // }; + // responseRouter.Client.LogTrace(trace); + // } + + // Drop all omnisharp messages to trace. This isn't a MEL filter because it's specific only to this provider. + if (categoryName.StartsWith("OmniSharp.", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Trace; + } + + (MessageType messageType, string messagePrepend) = GetMessageInfo(logLevel); + + // The vscode-languageserver-node client doesn't support LogOutputChannel as of 2024-11-24 and also doesn't + // provide a way to middleware the incoming log messages, so our output channel has no idea what the logLevel + // is. As a workaround, we send the severity in-line with the message for the client to parse. + // BUG: https://github.com/microsoft/vscode-languageserver-node/issues/1116 + if (responseRouter.Client?.ClientSettings?.ClientInfo?.Name == "Visual Studio Code") + { + messagePrepend = logLevel switch + { + LogLevel.Critical => "CRITICAL: ", + LogLevel.Error => "", + LogLevel.Warning => "", + LogLevel.Information => "", + LogLevel.Debug => "", + LogLevel.Trace => "", + _ => string.Empty + }; + + // The vscode formatter prepends some extra stuff to Info specifically, so we drop Info to Log, but it will get logged correctly on the other side thanks to our inline indicator that our custom parser on the other side will pick up and process. + if (messageType == MessageType.Info) + { + messageType = MessageType.Log; + } + } + + LogMessageParams logMessage = new() + { + Type = messageType, + Message = messagePrepend + categoryName + ": " + formatter(state, exception) + + (exception != null ? " - " + exception : "") + " | " + + //Hopefully this isn't too expensive in the long run + FormatState(state, exception) + }; + responseRouter.Window.Log(logMessage); + } + + /// + /// Formats the state object into a string for logging. + /// + /// + /// This is copied from Omnisharp, we can probably do better. + /// + /// + /// + /// + /// + private static string FormatState(TState state, Exception? exception) + { + return state switch + { + IEnumerable> dict => string.Join(" ", dict.Where(z => z.Key != "{OriginalFormat}").Select(z => $"{z.Key}='{z.Value}'")), + _ => JsonConvert.SerializeObject(state).Replace("\"", "'") + }; + } + + /// + /// Maps MEL log levels to LSP message types + /// + private static (MessageType messageType, string messagePrepend) GetMessageInfo(LogLevel logLevel) + => logLevel switch + { + LogLevel.Critical => (MessageType.Error, "Critical: "), + LogLevel.Error => (MessageType.Error, string.Empty), + LogLevel.Warning => (MessageType.Warning, string.Empty), + LogLevel.Information => (MessageType.Info, string.Empty), + LogLevel.Debug => (MessageType.Log, string.Empty), + LogLevel.Trace => (MessageType.Log, "Trace: "), + _ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null) + }; +} + +internal class LanguageServerLoggerProvider(ILanguageServerFacade languageServer) : ILoggerProvider +{ + public ILogger CreateLogger(string categoryName) => new LanguageServerLogger(languageServer, categoryName); + + public void Dispose() { } +} + + +public static class LanguageServerLoggerExtensions +{ + /// + /// Adds a custom logger provider for PSES LSP, that provides more granular categorization than the default Omnisharp logger, such as separating Omnisharp and PSES messages to different channels. + /// + public static ILoggingBuilder AddPsesLanguageServerLogging(this ILoggingBuilder builder) + { + builder.Services.AddSingleton(); + return builder; + } + + public static ILoggingBuilder AddLspClientConfigurableMinimumLevel( + this ILoggingBuilder builder, + LogLevel initialLevel = LogLevel.Trace + ) + { + builder.Services.AddOptions(); + builder.Services.AddSingleton(sp => + { + IOptionsMonitor optionsMonitor = sp.GetRequiredService>(); + return new(initialLevel, optionsMonitor); + }); + builder.Services.AddSingleton>(sp => + sp.GetRequiredService()); + + return builder; + } +} + +internal class DynamicLogLevelOptions( + LogLevel initialLevel, + IOptionsMonitor optionsMonitor) : IConfigureOptions +{ + private LogLevel _currentLevel = initialLevel; + private readonly IOptionsMonitor _optionsMonitor = optionsMonitor; + + public void Configure(LoggerFilterOptions options) => options.MinLevel = _currentLevel; + + public void SetLogLevel(LogLevel level) + { + _currentLevel = level; + // Trigger reload of options to apply new log level + _optionsMonitor.CurrentValue.MinLevel = level; + } +} diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index 9a1771500..bf90a93ae 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -36,10 +36,6 @@ - - - - diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 6aca82e04..31f6a988f 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -5,13 +5,15 @@ using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using OmniSharp.Extensions.DebugAdapter.Server; using OmniSharp.Extensions.LanguageServer.Server; +// See EditorServicesServerFactory.cs for the explanation of this alias. +using HostLogger = System.IObservable<(int logLevel, string message)>; + namespace Microsoft.PowerShell.EditorServices.Server { /// @@ -26,16 +28,17 @@ internal class PsesDebugServer : IDisposable private PsesInternalHost _psesHost; private bool _startedPses; private readonly bool _isTemp; - protected readonly ILoggerFactory _loggerFactory; + // FIXME: This was never actually used in the debug server. Since we never have a debug server without an LSP, we could probably remove this and either reuse the MEL from the LSP, or create a new one here. It is probably best to only use this for exceptions that we can't reasonably send via the DAP protocol, which should only be anything before the initialize request. + protected readonly HostLogger _hostLogger; public PsesDebugServer( - ILoggerFactory factory, + HostLogger hostLogger, Stream inputStream, Stream outputStream, IServiceProvider serviceProvider, bool isTemp = false) { - _loggerFactory = factory; + _hostLogger = hostLogger; _inputStream = inputStream; _outputStream = outputStream; ServiceProvider = serviceProvider; @@ -63,7 +66,6 @@ public async Task StartAsync() .WithOutput(_outputStream) .WithServices(serviceCollection => serviceCollection - .AddLogging() .AddOptions() .AddPsesDebugServices(ServiceProvider, this)) // TODO: Consider replacing all WithHandler with AddSingleton @@ -130,7 +132,6 @@ public void Dispose() _debugAdapterServer?.Dispose(); _inputStream.Dispose(); _outputStream.Dispose(); - _loggerFactory.Dispose(); _serverStopped.SetResult(true); // TODO: If the debugger has stopped, should we clear the breakpoints? } diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 58d52bb3a..c106d34c6 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -8,14 +9,18 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerShell.EditorServices.Handlers; using Microsoft.PowerShell.EditorServices.Hosting; +using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Extension; using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.General; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Server; -using Serilog; + +// See EditorServicesServerFactory.cs for the explanation of this alias. +using HostLogger = System.IObservable<(int logLevel, string message)>; namespace Microsoft.PowerShell.EditorServices.Server { @@ -24,7 +29,7 @@ namespace Microsoft.PowerShell.EditorServices.Server /// internal class PsesLanguageServer { - internal ILoggerFactory LoggerFactory { get; } + internal HostLogger HostLogger { get; } internal ILanguageServer LanguageServer { get; private set; } private readonly LogLevel _minimumLogLevel; private readonly Stream _inputStream; @@ -32,6 +37,7 @@ internal class PsesLanguageServer private readonly HostStartupInfo _hostDetails; private readonly TaskCompletionSource _serverStart; private PsesInternalHost _psesHost; + private IDisposable hostLoggerSubscription; /// /// Create a new language server instance. @@ -41,18 +47,18 @@ internal class PsesLanguageServer /// cref="EditorServicesServerFactory.CreateLanguageServer"/>. It is essentially a /// singleton. The factory hides the logger. /// - /// Factory to create loggers with. + /// The host logger to hand off for monitoring. /// Protocol transport input stream. /// Protocol transport output stream. /// Host configuration to instantiate the server and services /// with. public PsesLanguageServer( - ILoggerFactory factory, + HostLogger hostLogger, Stream inputStream, Stream outputStream, HostStartupInfo hostStartupInfo) { - LoggerFactory = factory; + HostLogger = hostLogger; _minimumLogLevel = (LogLevel)hostStartupInfo.LogLevel; _inputStream = inputStream; _outputStream = outputStream; @@ -82,10 +88,9 @@ public async Task StartAsync() serviceCollection.AddPsesLanguageServices(_hostDetails); }) .ConfigureLogging(builder => builder - .AddSerilog(Log.Logger) // TODO: Set dispose to true? - .AddLanguageProtocolLogging() + .ClearProviders() + .AddPsesLanguageServerLogging() .SetMinimumLevel(_minimumLogLevel)) - // TODO: Consider replacing all WithHandler with AddSingleton .WithHandler() .WithHandler() .WithHandler() @@ -124,6 +129,11 @@ public async Task StartAsync() .OnInitialize( (languageServer, initializeParams, cancellationToken) => { + // Wire up the HostLogger to the LanguageServer's logger once we are initialized, so that any messages still logged to the HostLogger get sent across the LSP channel. There is no more logging to disk at this point. + hostLoggerSubscription = HostLogger.Subscribe(new HostLoggerAdapter( + languageServer.Services.GetService>() + )); + // Set the workspace path from the parameters. WorkspaceService workspaceService = languageServer.Services.GetService(); if (initializeParams.WorkspaceFolders is not null) @@ -158,7 +168,9 @@ public async Task StartAsync() _psesHost = languageServer.Services.GetService(); return _psesHost.TryStartAsync(hostStartOptions, cancellationToken); - }); + } + ) + .OnShutdown(_ => hostLoggerSubscription.Dispose()); }).ConfigureAwait(false); _serverStart.SetResult(true); diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/PsesSemanticTokensHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/PsesSemanticTokensHandler.cs index e5e69e60f..494843bb5 100644 --- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/PsesSemanticTokensHandler.cs +++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/PsesSemanticTokensHandler.cs @@ -108,6 +108,11 @@ private static SemanticTokenType MapSemanticTokenType(Token token) return SemanticTokenType.Operator; } + if ((token.TokenFlags & TokenFlags.AttributeName) != 0) + { + return SemanticTokenType.Decorator; + } + if ((token.TokenFlags & TokenFlags.TypeName) != 0) { return SemanticTokenType.Type; @@ -142,8 +147,8 @@ private static SemanticTokenType MapSemanticTokenType(Token token) case TokenKind.Number: return SemanticTokenType.Number; - case TokenKind.Generic: - return SemanticTokenType.Function; + case TokenKind.Label: + return SemanticTokenType.Label; } return null; diff --git a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs index 654001da7..a3c68eadb 100644 --- a/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs +++ b/test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Debug; using OmniSharp.Extensions.DebugAdapter.Client; using OmniSharp.Extensions.DebugAdapter.Protocol.Models; using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; @@ -33,8 +34,10 @@ public class DebugAdapterProtocolMessageTests : IAsyncLifetime, IDisposable public async Task InitializeAsync() { - LoggerFactory factory = new(); - _psesProcess = new PsesStdioProcess(factory, true); + LoggerFactory debugLoggerFactory = new(); + debugLoggerFactory.AddProvider(new DebugLoggerProvider()); + + _psesProcess = new PsesStdioProcess(debugLoggerFactory, true); await _psesProcess.Start(); TaskCompletionSource initialized = new(); @@ -50,6 +53,11 @@ public async Task InitializeAsync() options .WithInput(_psesProcess.OutputStream) .WithOutput(_psesProcess.InputStream) + .ConfigureLogging(builder => + builder + .AddDebug() + .SetMinimumLevel(LogLevel.Trace) + ) // The OnStarted delegate gets run when we receive the _Initialized_ event from the server: // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized .OnStarted((_, _) => diff --git a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj index 3756f071d..2ea39d3fd 100644 --- a/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj +++ b/test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj @@ -7,6 +7,7 @@ + diff --git a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs index 8d744dc11..90ecaaf5c 100644 --- a/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs +++ b/test/PowerShellEditorServices.Test.E2E/Processes/ServerProcess.cs @@ -38,8 +38,8 @@ protected ServerProcess(ILoggerFactory loggerFactory) Exited = _exitedSubject = new AsyncSubject(); - _inStreamLazy = new Lazy(() => new LoggingStream(GetInputStream())); - _outStreamLazy = new Lazy(() => new LoggingStream(GetOutputStream())); + _inStreamLazy = new Lazy(GetInputStream); + _outStreamLazy = new Lazy(GetOutputStream); } /// diff --git a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs index c9f3c01d4..196fa9ca6 100644 --- a/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs +++ b/test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs @@ -20,7 +20,8 @@ public void TokenizesFunctionElements() { const string text = @" function Get-Sum { - param( [int]$a, [int]$b ) + param( [parameter()] [int]$a, [int]$b ) + :loopLabel while (0) {break loopLabel} return $a + $b } "; @@ -38,10 +39,21 @@ function Get-Sum { case "function": case "param": case "return": + case "while": + case "break": Assert.Single(mappedTokens, sToken => SemanticTokenType.Keyword == sToken.Type); break; - case "Get-Sum": - Assert.Single(mappedTokens, sToken => SemanticTokenType.Function == sToken.Type); + case "parameter": + Assert.Single(mappedTokens, sToken => SemanticTokenType.Decorator == sToken.Type); + break; + case "0": + Assert.Single(mappedTokens, sToken => SemanticTokenType.Number == sToken.Type); + break; + case ":loopLabel": + Assert.Single(mappedTokens, sToken => SemanticTokenType.Label == sToken.Type); + break; + case "loopLabel": + Assert.Single(mappedTokens, sToken => SemanticTokenType.Property == sToken.Type); break; case "$a": case "$b": @@ -74,7 +86,6 @@ public void TokenizesStringExpansion() Token stringExpandableToken = scriptFile.ScriptTokens[1]; mappedTokens = new List(PsesSemanticTokensHandler.ConvertToSemanticTokens(stringExpandableToken)); Assert.Collection(mappedTokens, - sToken => Assert.Equal(SemanticTokenType.Function, sToken.Type), sToken => Assert.Equal(SemanticTokenType.Function, sToken.Type), sToken => Assert.Equal(SemanticTokenType.Function, sToken.Type) ); @@ -103,7 +114,11 @@ function Get-A*A { Assert.Single(mappedTokens, sToken => SemanticTokenType.Keyword == sToken.Type); break; case "Get-A*A": - Assert.Single(mappedTokens, sToken => SemanticTokenType.Function == sToken.Type); + if (t.TokenFlags.HasFlag(TokenFlags.CommandName)) + { + Assert.Single(mappedTokens, sToken => SemanticTokenType.Function == sToken.Type); + } + break; } } diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 01c23bcee..8d4ce9c79 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -2,7 +2,7 @@ - net8.0;net6.0;net462 + net8.0;net462 Microsoft.PowerShell.EditorServices.Test x64 @@ -21,11 +21,6 @@ - - - - -