├── .config
└── dotnet-tools.json
├── .devcontainer
└── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github
├── CODEOWNERS
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── 10_bug_report.yml
│ ├── 20_feature_request.yml
│ ├── 30_question.yml
│ └── config.yml
├── PULL_REQUEST_TEMPLATE.md
├── actionlint-matcher.json
├── dependabot.yml
└── workflows
│ ├── build.yml
│ ├── bump-version.yml
│ ├── codeql.yml
│ ├── dependency-review.yml
│ ├── lint.yml
│ ├── ossf-scorecard.yml
│ └── release.yml
├── .gitignore
├── .markdownlint.json
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── .vsconfig
├── CODE_OF_CONDUCT.md
├── Directory.Build.props
├── Directory.Packages.props
├── LICENSE
├── Logging.XUnit.ruleset
├── Logging.XUnit.slnx
├── NuGet.config
├── README.md
├── SECURITY.md
├── build.ps1
├── docs
└── images
│ ├── output-terminal.png
│ ├── output-vs.png
│ └── output-vscode.png
├── global.json
├── package-icon.png
├── package-readme.md
├── src
├── Logging.XUnit.v3
│ ├── MartinCostello.Logging.XUnit.v3.csproj
│ └── PublicAPI
│ │ ├── PublicAPI.Shipped.txt
│ │ ├── PublicAPI.Unshipped.txt
│ │ ├── net10.0
│ │ ├── PublicAPI.Shipped.txt
│ │ └── PublicAPI.Unshipped.txt
│ │ └── net8.0
│ │ ├── PublicAPI.Shipped.txt
│ │ └── PublicAPI.Unshipped.txt
├── Logging.XUnit
│ ├── MartinCostello.Logging.XUnit.csproj
│ └── PublicAPI
│ │ ├── PublicAPI.Shipped.txt
│ │ ├── PublicAPI.Unshipped.txt
│ │ ├── net10.0
│ │ ├── PublicAPI.Shipped.txt
│ │ └── PublicAPI.Unshipped.txt
│ │ └── net8.0
│ │ ├── PublicAPI.Shipped.txt
│ │ └── PublicAPI.Unshipped.txt
└── Shared
│ ├── AmbientTestOutputHelperAccessor.cs
│ ├── IMessageSinkAccessor.cs
│ ├── IMessageSinkExtensions.cs
│ ├── ITestOutputHelperAccessor.cs
│ ├── ITestOutputHelperExtensions.cs
│ ├── MessageSinkAccessor.cs
│ ├── StringSyntaxAttribute.cs
│ ├── TestOutputHelperAccessor.cs
│ ├── XUnitLogScope.cs
│ ├── XUnitLogger.IMessageSink.cs
│ ├── XUnitLogger.ITestOutputHelper.cs
│ ├── XUnitLogger.cs
│ ├── XUnitLoggerExtensions.IMessageSink.cs
│ ├── XUnitLoggerExtensions.ITestOutputHelper.cs
│ ├── XUnitLoggerOptions.cs
│ ├── XUnitLoggerProvider.IMessageSink.cs
│ ├── XUnitLoggerProvider.ITestOutputHelper.cs
│ └── XUnitLoggerProvider.cs
├── stylecop.json
└── tests
├── Logging.XUnit.Tests
└── MartinCostello.Logging.XUnit.Tests.csproj
├── Logging.XUnit.v3.Tests
└── MartinCostello.Logging.XUnit.v3.Tests.csproj
├── SampleApp
├── Program.cs
├── Properties
│ └── launchSettings.json
├── SampleApp.csproj
├── appsettings.Development.json
└── appsettings.json
└── Shared
├── AssemblyTests.cs
├── Constructor.cs
├── Examples.cs
├── Integration
├── DatabaseFixture.cs
├── DatabaseTests.cs
├── HttpApplicationTests.cs
├── HttpServerCollection.cs
├── HttpServerFixture.cs
└── PrintableDiagnosticMessage.cs
├── IntegrationTests.cs
├── XUnitLoggerExtensionsTests.cs
├── XUnitLoggerProviderTests.cs
├── XUnitLoggerTests.cs
└── xunit.runner.json
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-validate": {
6 | "version": "0.0.1-preview.537",
7 | "commands": [
8 | "dotnet-validate"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "C# (.NET)",
3 | "customizations": {
4 | "vscode": {
5 | "extensions": [
6 | "editorconfig.editorconfig",
7 | "ms-dotnettools.csharp",
8 | "ms-vscode.PowerShell"
9 | ]
10 | }
11 | },
12 | "postCreateCommand": "./build.ps1 -SkipTests",
13 | "remoteEnv": {
14 | "PATH": "/root/.dotnet/tools:${containerWorkspaceFolder}/.dotnet:${containerEnv:PATH}"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = crlf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{config,csproj,json,props,ruleset,targets,vsconfig,yml}]
12 | indent_size = 2
13 |
14 | # Code files
15 | [*.{cs,csx,vb,vbx}]
16 | file_header_template = Copyright (c) Martin Costello, 2018. All rights reserved.\nLicensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
17 | indent_size = 4
18 | insert_final_newline = true
19 | charset = utf-8-bom
20 |
21 | ###############################
22 | # .NET Coding Conventions #
23 | ###############################
24 | [*.{cs,vb}]
25 |
26 | # Enable style analyzers
27 | dotnet_analyzer_diagnostic.category-Style.severity = warning
28 |
29 | dotnet_diagnostic.IDE0005.severity = silent
30 | dotnet_diagnostic.IDE0045.severity = silent
31 | dotnet_diagnostic.IDE0046.severity = silent
32 | dotnet_diagnostic.IDE0058.severity = silent
33 | dotnet_diagnostic.IDE0072.severity = silent
34 | dotnet_diagnostic.IDE0079.severity = silent
35 |
36 | # Organize usings
37 | dotnet_sort_system_directives_first = true
38 |
39 | # this. preferences
40 | dotnet_style_qualification_for_field = false:none
41 | dotnet_style_qualification_for_property = false:none
42 | dotnet_style_qualification_for_method = false:none
43 | dotnet_style_qualification_for_event = false:none
44 |
45 | # Language keywords vs BCL types preferences
46 | dotnet_style_predefined_type_for_locals_parameters_members = true:none
47 | dotnet_style_predefined_type_for_member_access = true:none
48 |
49 | # Modifier preferences
50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:none
51 | dotnet_style_readonly_field = true:suggestion
52 |
53 | # Expression-level preferences
54 | dotnet_style_object_initializer = true:suggestion
55 | dotnet_style_collection_initializer = true:suggestion
56 | dotnet_style_explicit_tuple_names = true:suggestion
57 | dotnet_style_null_propagation = true:suggestion
58 | dotnet_style_coalesce_expression = true:suggestion
59 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:none
60 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
61 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
62 | dotnet_style_prefer_auto_properties = true:none
63 |
64 | ###############################
65 | # Naming Conventions #
66 | ###############################
67 |
68 | # Style Definitions
69 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
70 |
71 | # Use PascalCase for constant fields
72 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
73 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
74 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
75 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
76 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
77 | dotnet_naming_symbols.constant_fields.required_modifiers = const
78 |
79 | ###############################
80 | # C# Coding Conventions #
81 | ###############################
82 | [*.cs]
83 | # var preferences
84 | csharp_style_var_for_built_in_types = true:none
85 | csharp_style_var_when_type_is_apparent = true:none
86 | csharp_style_var_elsewhere = true:none
87 |
88 | # Expression-bodied members
89 | csharp_style_expression_bodied_methods = false:none
90 | csharp_style_expression_bodied_constructors = false:none
91 | csharp_style_expression_bodied_operators = false:none
92 | csharp_style_expression_bodied_properties = true:none
93 | csharp_style_expression_bodied_indexers = true:none
94 | csharp_style_expression_bodied_accessors = true:none
95 | csharp_style_expression_bodied_local_functions = when_on_single_line
96 |
97 | # Pattern matching preferences
98 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
99 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
100 |
101 | # Null-checking preferences
102 | csharp_style_throw_expression = true:suggestion
103 | csharp_style_conditional_delegate_call = true:suggestion
104 |
105 | # Modifier preferences
106 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
107 |
108 | # Expression-level preferences
109 | csharp_prefer_braces = true:none
110 | csharp_style_deconstructed_variable_declaration = true:suggestion
111 | csharp_prefer_simple_default_expression = true:suggestion
112 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
113 | csharp_style_inlined_variable_declaration = true:suggestion
114 |
115 | # Namespace preferences
116 | csharp_style_namespace_declarations = file_scoped
117 |
118 | ###############################
119 | # C# Formatting Rules #
120 | ###############################
121 | # New line preferences
122 | csharp_new_line_before_open_brace = all
123 | csharp_new_line_before_else = true
124 | csharp_new_line_before_catch = true
125 | csharp_new_line_before_finally = true
126 | csharp_new_line_before_members_in_object_initializers = true
127 | csharp_new_line_before_members_in_anonymous_types = true
128 | csharp_new_line_between_query_expression_clauses = true
129 |
130 | # Indentation preferences
131 | csharp_indent_case_contents = true
132 | csharp_indent_switch_labels = true
133 | csharp_indent_labels = flush_left
134 |
135 | # Space preferences
136 | csharp_space_after_cast = false
137 | csharp_space_after_keywords_in_control_flow_statements = true
138 | csharp_space_between_method_call_parameter_list_parentheses = false
139 | csharp_space_between_method_declaration_parameter_list_parentheses = false
140 | csharp_space_between_parentheses = false
141 | csharp_space_before_colon_in_inheritance_clause = true
142 | csharp_space_after_colon_in_inheritance_clause = true
143 | csharp_space_around_binary_operators = before_and_after
144 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
145 | csharp_space_between_method_call_name_and_opening_parenthesis = false
146 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
147 |
148 | # Wrapping preferences
149 | csharp_preserve_single_line_statements = true
150 | csharp_preserve_single_line_blocks = true
151 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sh eol=lf
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @martincostello
2 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | To contribute changes (source code, scripts, configuration) to this repository please follow the steps below. These steps are a guideline for contributing and do not necessarily need to be followed for all changes.
4 |
5 | 1. If you intend to fix a bug please create an issue before forking the repository.
6 | 1. Fork the `main` branch of this repository from the latest commit.
7 | 1. Create a branch from your fork's `main` branch to help isolate your changes from any further work on `main`. If fixing an issue try to reference its name in your branch name (e.g. `issue-42`) to make changes easier to track the changes.
8 | 1. Work on your proposed changes on your fork. If you are fixing an issue include at least one unit test that reproduces it if the code changes to fix it have not been applied; if you are adding new functionality please include unit tests appropriate to the changes you are making. The [code coverage figure](https://codecov.io/gh/martincostello/xunit-logging) should be maintained where possible.
9 | 1. When you think your changes are complete, test that the code builds cleanly using `build.ps1`. There should be no compiler warnings and all tests should pass.
10 | 1. Once your changes build cleanly locally submit a Pull Request back to the `main` branch from your fork's branch. Ideally commits to your branch should be squashed before creating the Pull Request. If the Pull Request fixes an issue please reference it in the title and/or description. Please keep changes focused around a specific topic rather than include multiple types of changes in a single Pull Request.
11 | 1. After your Pull Request is created it will build against the repository's continuous integrations.
12 | 1. Once the Pull Request has been reviewed by the project's [contributors](https://github.com/martincostello/xunit-logging/graphs/contributors) and the status checks pass your Pull Request will be merged back to the `main` branch, assuming that the changes are deemed appropriate.
13 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [martincostello]
2 | buy_me_a_coffee: martincostello
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ''
3 | ---
4 |
5 | ### Expected behaviour
6 |
7 |
8 |
9 | ### Actual behaviour
10 |
11 |
12 |
13 | ### Steps to reproduce
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/10_bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐛 Bug report
2 | description: Something not behaving as expected?
3 | title: '[Bug]: '
4 | labels: ['bug']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Please check for an existing issue and the [README](https://github.com/martincostello/xunit-logging/blob/main/README.md) before submitting a bug report.
10 |
11 | If you're not using the latest release, please try upgrading to the latest version first to see if the issue resolves itself.
12 | - type: dropdown
13 | id: version
14 | attributes:
15 | label: NuGet Package(s)
16 | multiple: true
17 | description: What NuGet packages(s) are you using?
18 | options:
19 | - MartinCostello.Logging.XUnit
20 | - MartinCostello.Logging.XUnit.v3
21 | validations:
22 | required: true
23 | - type: input
24 | attributes:
25 | label: Version
26 | description: Which version of the library are you experiencing the issue with?
27 | placeholder: 0.5.1
28 | validations:
29 | required: true
30 | - type: textarea
31 | attributes:
32 | label: Describe the bug
33 | description: A clear and concise description of what the bug is.
34 | validations:
35 | required: true
36 | - type: textarea
37 | attributes:
38 | label: Expected behaviour
39 | description: A clear and concise description of what you expected to happen.
40 | validations:
41 | required: false
42 | - type: textarea
43 | attributes:
44 | label: Actual behaviour
45 | description: What actually happens.
46 | validations:
47 | required: false
48 | - type: textarea
49 | attributes:
50 | label: Steps to reproduce
51 | description: |
52 | Provide a link to a [minimalistic project which reproduces this issue (repro)](https://stackoverflow.com/help/mcve) hosted in a **public** GitHub repository.
53 | Code snippets, such as a failing unit test or small console app, which demonstrate the issue wrapped in a [fenced code block](https://docs.github.com/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) are also acceptable.
54 |
55 | This issue will be closed if:
56 | - The behaviour you're reporting cannot be easily reproduced.
57 | - The issue is a duplicate of an existing issue.
58 | - The behaviour you're reporting is by design.
59 | validations:
60 | required: false
61 | - type: textarea
62 | attributes:
63 | label: Exception(s) (if any)
64 | description: Include any exception(s) and/or stack trace(s) you get when facing this issue.
65 | render: text
66 | validations:
67 | required: false
68 | - type: input
69 | attributes:
70 | label: .NET Version
71 | description: |
72 | Run `dotnet --version` to get the .NET SDK version you're using.
73 | Alternatively, which target framework(s) (e.g. `net8.0`) does the project you're using the package with target?
74 | placeholder: 9.0.100
75 | validations:
76 | required: false
77 | - type: textarea
78 | attributes:
79 | label: Anything else?
80 | description: |
81 | Links? References? Anything that will give us more context about the issue you are encountering is useful.
82 |
83 | 💡Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
84 | validations:
85 | required: false
86 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/20_feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 💡 Feature request
2 | description: Suggest a feature request or improvement
3 | title: '[Feature request]: '
4 | labels: ['feature-request']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Please check for an existing issue and the [README](https://github.com/martincostello/xunit-logging/blob/main/README.md) before submitting a feature request.
10 | - type: textarea
11 | attributes:
12 | label: Is your feature request related to a specific problem? Or an existing feature?
13 | description: A clear and concise description of what the problem is. Motivating examples help prioritise things.
14 | placeholder: I am trying to [...] but [...]
15 | validations:
16 | required: true
17 | - type: textarea
18 | attributes:
19 | label: Describe the solution you'd like
20 | description: |
21 | A clear and concise description of what you want to happen. Include any alternative solutions you've considered.
22 | validations:
23 | required: true
24 | - type: textarea
25 | attributes:
26 | label: Additional context
27 | description: |
28 | Add any other context or screenshots about the feature request here.
29 | validations:
30 | required: false
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/30_question.yml:
--------------------------------------------------------------------------------
1 | name: 🤔 Question?
2 | description: You have something specific to achieve and the existing documentation hasn't covered how.
3 | title: '[Question]: '
4 | labels: ['question']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Please check for an existing issue and the [README](https://github.com/martincostello/xunit-logging/blob/main/README.md) before asking a question.
10 | - type: textarea
11 | attributes:
12 | label: What do you want to achieve?
13 | description: A clear and concise description of what you're trying to do.
14 | placeholder: I am trying to [...] but [...]
15 | validations:
16 | required: true
17 | - type: textarea
18 | attributes:
19 | label: What code or approach do you have so far?
20 | description: |
21 | Provide a [minimalistic project which shows what you have so far](https://stackoverflow.com/help/mcve) hosted in a **public** GitHub repository.
22 | Code snippets wrapped in a [fenced code block](https://docs.github.com/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) are also acceptable.
23 | validations:
24 | required: true
25 | - type: textarea
26 | attributes:
27 | label: Additional context
28 | description: |
29 | Add any other context or screenshots related to your question here.
30 | validations:
31 | required: false
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Contact me
4 | url: https://martincostello.com/bluesky
5 | about: You can also contact me on Bluesky.
6 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.github/actionlint-matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "actionlint",
5 | "pattern": [
6 | {
7 | "regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$",
8 | "file": 1,
9 | "line": 2,
10 | "column": 3,
11 | "message": 4,
12 | "code": 5
13 | }
14 | ]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "05:30"
8 | timezone: Europe/London
9 | - package-ecosystem: nuget
10 | directory: "/"
11 | groups:
12 | xunit:
13 | patterns:
14 | - xunit.runner.visualstudio
15 | - xunit.v3
16 | schedule:
17 | interval: daily
18 | time: "05:30"
19 | timezone: Europe/London
20 | open-pull-requests-limit: 99
21 | ignore:
22 | - dependency-name: Microsoft.Extensions.Logging
23 | - dependency-name: xunit.abstractions
24 | - dependency-name: xunit.extensibility.execution
25 | - dependency-name: xunit.v3.extensibility.core
26 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | tags: [ v* ]
7 | pull_request:
8 | branches:
9 | - main
10 | - dotnet-vnext
11 | - dotnet-nightly
12 | workflow_dispatch:
13 |
14 | env:
15 | DOTNET_CLI_TELEMETRY_OPTOUT: true
16 | DOTNET_NOLOGO: true
17 | DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: 1
18 | FORCE_COLOR: 3
19 | NUGET_XMLDOC_MODE: skip
20 | TERM: xterm
21 |
22 | permissions:
23 | contents: read
24 |
25 | jobs:
26 | build:
27 | name: ${{ matrix.os-name }}
28 | runs-on: ${{ matrix.runner }}
29 | timeout-minutes: 20
30 |
31 | outputs:
32 | dotnet-sdk-version: ${{ steps.setup-dotnet.outputs.dotnet-version }}
33 | dotnet-validate-version: ${{ steps.get-dotnet-validate-version.outputs.dotnet-validate-version }}
34 | package-names: ${{ steps.build.outputs.package-names }}
35 | package-version: ${{ steps.build.outputs.package-version }}
36 |
37 | permissions:
38 | attestations: write
39 | contents: write
40 | id-token: write
41 |
42 | strategy:
43 | fail-fast: false
44 | matrix:
45 | include:
46 | - os-name: macos
47 | runner: macos-latest
48 | - os-name: linux
49 | runner: ubuntu-latest
50 | - os-name: windows
51 | runner: windows-latest
52 |
53 | steps:
54 |
55 | - name: Checkout code
56 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
57 | with:
58 | filter: 'tree:0'
59 | show-progress: false
60 |
61 | - name: Setup .NET SDK
62 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
63 | id: setup-dotnet
64 |
65 | - name: Build, Test and Package
66 | id: build
67 | shell: pwsh
68 | run: ./build.ps1
69 |
70 | - name: Upload coverage to Codecov
71 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
72 | with:
73 | flags: ${{ matrix.os-name }}
74 | token: ${{ secrets.CODECOV_TOKEN }}
75 |
76 | - name: Upload test results to Codecov
77 | uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
78 | if: ${{ !cancelled() }}
79 | with:
80 | flags: ${{ matrix.os-name }}
81 | token: ${{ secrets.CODECOV_TOKEN }}
82 |
83 | - name: Generate SBOM
84 | uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
85 | with:
86 | artifact-name: build-${{ matrix.os-name }}.spdx.json
87 | output-file: ./artifacts/build.spdx.json
88 | path: ./artifacts/bin
89 | upload-release-assets: ${{ runner.os == 'Windows' }}
90 |
91 | - name: Attest artifacts
92 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
93 | if: |
94 | runner.os == 'Windows' &&
95 | github.event.repository.fork == false &&
96 | (github.ref_name == github.event.repository.default_branch || startsWith(github.ref, 'refs/tags/v'))
97 | with:
98 | subject-path: |
99 | ./artifacts/bin/MartinCostello.Logging.XUnit/release*/*.dll
100 | ./artifacts/package/release/*
101 |
102 | - name: Publish artifacts
103 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
104 | with:
105 | name: artifacts-${{ matrix.os-name }}
106 | path: ./artifacts
107 |
108 | - name: Publish NuGet packages
109 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
110 | with:
111 | name: packages-${{ matrix.os-name }}
112 | path: ./artifacts/package/release
113 | if-no-files-found: error
114 |
115 | - name: Get dotnet-validate version
116 | id: get-dotnet-validate-version
117 | shell: pwsh
118 | run: |
119 | $dotnetValidateVersion = (Get-Content "./.config/dotnet-tools.json" | Out-String | ConvertFrom-Json).tools.'dotnet-validate'.version
120 | "dotnet-validate-version=${dotnetValidateVersion}" >> $env:GITHUB_OUTPUT
121 |
122 | validate-packages:
123 | needs: build
124 | runs-on: ubuntu-latest
125 | steps:
126 |
127 | - name: Download packages
128 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
129 | with:
130 | name: packages-windows
131 |
132 | - name: Setup .NET SDK
133 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
134 | with:
135 | dotnet-version: ${{ needs.build.outputs.dotnet-sdk-version }}
136 |
137 | - name: Validate NuGet packages
138 | shell: pwsh
139 | env:
140 | DOTNET_VALIDATE_VERSION: ${{ needs.build.outputs.dotnet-validate-version }}
141 | run: |
142 | dotnet tool install --global dotnet-validate --version ${env:DOTNET_VALIDATE_VERSION} --allow-roll-forward
143 | $packages = Get-ChildItem -Filter "*.nupkg" | ForEach-Object { $_.FullName }
144 | $invalidPackages = 0
145 | foreach ($package in $packages) {
146 | dotnet validate package local $package
147 | if ($LASTEXITCODE -ne 0) {
148 | $invalidPackages++
149 | }
150 | }
151 | if ($invalidPackages -gt 0) {
152 | Write-Output "::error::$invalidPackages NuGet package(s) failed validation."
153 | exit 1
154 | }
155 |
156 | publish-feedz-io:
157 | needs: [ build, validate-packages ]
158 | runs-on: ubuntu-latest
159 | if: |
160 | github.event.repository.fork == false &&
161 | (github.ref_name == github.event.repository.default_branch ||
162 | startsWith(github.ref, 'refs/tags/v'))
163 |
164 | environment:
165 | name: feedz.io
166 | url: https://feedz.io/org/${{ github.repository_owner }}/repository/xunit-logging/packages/MartinCostello.Logging.XUnit
167 |
168 | steps:
169 |
170 | - name: Download packages
171 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
172 | with:
173 | name: packages-windows
174 |
175 | - name: Setup .NET SDK
176 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
177 | with:
178 | dotnet-version: ${{ needs.build.outputs.dotnet-sdk-version }}
179 |
180 | - name: Push NuGet packages to feedz.io
181 | shell: bash
182 | env:
183 | API_KEY: ${{ secrets.FEEDZ_IO_TOKEN }}
184 | PACKAGE_VERSION: ${{ needs.build.outputs.package-version }}
185 | SOURCE: "https://f.feedz.io/${{ github.repository }}/nuget/index.json"
186 | run: dotnet nuget push "*.nupkg" --api-key "${API_KEY}" --skip-duplicate --source "${SOURCE}" && echo "::notice title=feedz.io::Published version ${PACKAGE_VERSION} to feedz.io."
187 |
188 | publish-nuget:
189 | needs: [ build, validate-packages ]
190 | runs-on: ubuntu-latest
191 | if: |
192 | github.event.repository.fork == false &&
193 | startsWith(github.ref, 'refs/tags/v')
194 |
195 | environment:
196 | name: NuGet.org
197 | url: https://www.nuget.org/packages/MartinCostello.Logging.XUnit
198 |
199 | steps:
200 |
201 | - name: Download packages
202 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
203 | with:
204 | name: packages-windows
205 |
206 | - name: Setup .NET SDK
207 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
208 | with:
209 | dotnet-version: ${{ needs.build.outputs.dotnet-sdk-version }}
210 |
211 | - name: Push NuGet packages to NuGet.org
212 | shell: bash
213 | env:
214 | API_KEY: ${{ secrets.NUGET_TOKEN }}
215 | PACKAGE_VERSION: ${{ needs.build.outputs.package-version }}
216 | SOURCE: https://api.nuget.org/v3/index.json
217 | run: dotnet nuget push "*.nupkg" --api-key "${API_KEY}" --skip-duplicate --source "${SOURCE}" && echo "::notice title=nuget.org::Published version ${PACKAGE_VERSION} to NuGet.org."
218 |
219 | - name: Publish nuget_packages_published
220 | uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
221 | with:
222 | event-type: nuget_packages_published
223 | repository: ${{ github.repository_owner }}/github-automation
224 | token: ${{ secrets.COSTELLOBOT_TOKEN }}
225 | client-payload: |-
226 | {
227 | "repository": "${{ github.repository }}",
228 | "packages": "${{ needs.build.outputs.package-names }}",
229 | "version": "${{ needs.build.outputs.package-version }}"
230 | }
231 |
--------------------------------------------------------------------------------
/.github/workflows/bump-version.yml:
--------------------------------------------------------------------------------
1 | name: bump-version
2 |
3 | on:
4 | release:
5 | types: [ published ]
6 | workflow_dispatch:
7 | inputs:
8 | version:
9 | description: 'The optional version string for the next release.'
10 | required: false
11 | type: string
12 | default: ''
13 |
14 | permissions: {}
15 |
16 | jobs:
17 | bump-version:
18 | runs-on: [ ubuntu-latest ]
19 |
20 | concurrency:
21 | group: '${{ github.workflow }}-bump'
22 | cancel-in-progress: false
23 |
24 | steps:
25 |
26 | - name: Checkout code
27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28 | with:
29 | filter: 'tree:0'
30 | show-progress: false
31 | token: ${{ secrets.COSTELLOBOT_TOKEN }}
32 |
33 | - name: Bump version
34 | id: bump-version
35 | shell: pwsh
36 | env:
37 | NEXT_VERSION: ${{ inputs.version }}
38 | run: |
39 | $properties = Join-Path "." "Directory.Build.props"
40 |
41 | $xml = [xml](Get-Content $properties)
42 | $versionPrefix = $xml.SelectSingleNode('Project/PropertyGroup/VersionPrefix')
43 | $publishedVersion = $versionPrefix.InnerText
44 |
45 | if (-Not [string]::IsNullOrEmpty(${env:NEXT_VERSION})) {
46 | $version = [System.Version]::new(${env:NEXT_VERSION})
47 | $assemblyVersionProperty = $xml.SelectSingleNode('Project/PropertyGroup/AssemblyVersion')
48 | $assemblyVersion = [System.Version]::new($version.Major, ($version.Major -eq 0 ? $version.Minor : 0), 0, 0)
49 | $assemblyVersionProperty.InnerText = $assemblyVersion.ToString()
50 | } else {
51 | $version = [System.Version]::new($publishedVersion)
52 | $version = [System.Version]::new($version.Major, $version.Minor, $version.Build + 1)
53 | }
54 |
55 | $updatedVersion = $version.ToString()
56 | $versionPrefix.InnerText = $updatedVersion
57 |
58 | $packageValidationBaselineVersion = $xml.SelectSingleNode('Project/PropertyGroup/PackageValidationBaselineVersion')
59 | $packageValidationBaselineVersion.InnerText = $publishedVersion
60 |
61 | $settings = New-Object System.Xml.XmlWriterSettings
62 | $settings.Encoding = New-Object System.Text.UTF8Encoding($false)
63 | $settings.Indent = $true
64 | $settings.OmitXmlDeclaration = $true
65 |
66 | $writer = [System.Xml.XmlWriter]::Create($properties, $settings)
67 |
68 | $xml.Save($writer)
69 |
70 | $writer.Flush()
71 | $writer.Close()
72 | $writer = $null
73 |
74 | "" >> $properties
75 |
76 | "version=${updatedVersion}" >> $env:GITHUB_OUTPUT
77 |
78 | - name: Push changes to GitHub
79 | id: push-changes
80 | shell: pwsh
81 | env:
82 | GIT_COMMIT_USER_EMAIL: ${{ vars.GIT_COMMIT_USER_EMAIL }}
83 | GIT_COMMIT_USER_NAME: ${{ vars.GIT_COMMIT_USER_NAME }}
84 | NEXT_VERSION: ${{ steps.bump-version.outputs.version }}
85 | run: |
86 | $gitStatus = (git status --porcelain)
87 |
88 | if ([string]::IsNullOrEmpty($gitStatus)) {
89 | throw "No changes to commit."
90 | }
91 |
92 | git config color.diff always
93 | git --no-pager diff
94 |
95 | $branchName = "bump-version-${env:NEXT_VERSION}"
96 |
97 | git config user.email ${env:GIT_COMMIT_USER_EMAIL} | Out-Null
98 | git config user.name ${env:GIT_COMMIT_USER_NAME} | Out-Null
99 | git fetch origin --no-tags | Out-Null
100 | git rev-parse --verify --quiet "remotes/origin/${branchName}" | Out-Null
101 |
102 | if ($LASTEXITCODE -eq 0) {
103 | Write-Output "Branch ${branchName} already exists."
104 | exit 0
105 | }
106 |
107 | git checkout -b $branchName
108 | git add .
109 | git commit -m "Bump version`n`nBump version to ${env:NEXT_VERSION} for the next release."
110 | git push -u origin $branchName
111 |
112 | "branch-name=${branchName}" >> $env:GITHUB_OUTPUT
113 | "updated-version=true" >> $env:GITHUB_OUTPUT
114 | "version=${env:NEXT_VERSION}" >> $env:GITHUB_OUTPUT
115 |
116 | - name: Create pull request
117 | if: steps.push-changes.outputs.updated-version == 'true'
118 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
119 | env:
120 | BASE_BRANCH: ${{ github.event.repository.default_branch }}
121 | HEAD_BRANCH: ${{ steps.push-changes.outputs.branch-name }}
122 | NEXT_VERSION: ${{ steps.push-changes.outputs.version }}
123 | with:
124 | github-token: ${{ secrets.COSTELLOBOT_TOKEN }}
125 | script: |
126 | const nextVersion = process.env.NEXT_VERSION;
127 | const { repo, owner } = context.repo;
128 | const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
129 |
130 | const { data: pr } = await github.rest.pulls.create({
131 | title: 'Bump version',
132 | owner,
133 | repo,
134 | head: process.env.HEAD_BRANCH,
135 | base: process.env.BASE_BRANCH,
136 | draft: true,
137 | body: [
138 | `Bump version to \`${nextVersion}\` for the next release.`,
139 | '',
140 | `This pull request was generated by [GitHub Actions](${workflowUrl}).`
141 | ].join('\n')
142 | });
143 |
144 | core.notice(`Created pull request ${owner}/${repo}#${pr.number}: ${pr.html_url}`);
145 |
146 | try {
147 | const { data: milestones } = await github.rest.issues.listMilestones({
148 | owner,
149 | repo,
150 | state: 'open',
151 | });
152 |
153 | const title = `v${nextVersion}`;
154 | let milestone = milestones.find((p) => p.title === title);
155 |
156 | if (!milestone) {
157 | const created = await github.rest.issues.createMilestone({
158 | owner,
159 | repo,
160 | title,
161 | });
162 | milestone = created.data;
163 | }
164 |
165 | await github.rest.issues.update({
166 | owner,
167 | repo,
168 | issue_number: pr.number,
169 | milestone: milestone.number
170 | });
171 | } catch (error) {
172 | // Ignore
173 | }
174 |
175 | close-milestone:
176 | runs-on: [ ubuntu-latest ]
177 | if: github.event_name == 'release'
178 |
179 | concurrency:
180 | group: '${{ github.workflow }}-milestone'
181 | cancel-in-progress: false
182 |
183 | steps:
184 |
185 | - name: Close milestone
186 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
187 | env:
188 | RELEASE_DATE: ${{ github.event.release.published_at }}
189 | RELEASE_VERSION: ${{ github.event.release.tag_name }}
190 | with:
191 | github-token: ${{ secrets.COSTELLOBOT_TOKEN }}
192 | script: |
193 | const { repo, owner } = context.repo;
194 |
195 | const { data: milestones } = await github.rest.issues.listMilestones({
196 | owner,
197 | repo,
198 | state: 'open',
199 | });
200 |
201 | const milestone = milestones.find((p) => p.title === process.env.RELEASE_VERSION);
202 |
203 | if (!milestone) {
204 | return;
205 | }
206 |
207 | try {
208 | await github.rest.issues.updateMilestone({
209 | owner,
210 | repo,
211 | milestone_number: milestone.number,
212 | state: 'closed',
213 | due_on: process.env.RELEASE_DATE,
214 | });
215 | } catch (error) {
216 | // Ignore
217 | }
218 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: codeql
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches:
8 | - main
9 | - dotnet-vnext
10 | - dotnet-nightly
11 | schedule:
12 | - cron: '0 6 * * MON'
13 | workflow_dispatch:
14 |
15 | permissions: {}
16 |
17 | jobs:
18 | analysis:
19 | runs-on: ubuntu-latest
20 |
21 | permissions:
22 | actions: read
23 | contents: read
24 | security-events: write
25 |
26 | strategy:
27 | fail-fast: false
28 | matrix:
29 | language: [ 'actions', 'csharp' ]
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34 | with:
35 | filter: 'tree:0'
36 | show-progress: false
37 |
38 | - name: Initialize CodeQL
39 | uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
40 | with:
41 | build-mode: none
42 | languages: ${{ matrix.language }}
43 | queries: security-and-quality
44 |
45 | - name: Perform CodeQL Analysis
46 | uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
47 | with:
48 | category: '/language:${{ matrix.language }}'
49 |
50 | codeql:
51 | if: ${{ !cancelled() }}
52 | needs: [ analysis ]
53 | runs-on: ubuntu-latest
54 |
55 | steps:
56 | - name: Report status
57 | shell: bash
58 | env:
59 | SCAN_SUCCESS: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
60 | run: |
61 | if [ "${SCAN_SUCCESS}" == "true" ]
62 | then
63 | echo 'CodeQL analysis successful ✅'
64 | else
65 | echo 'CodeQL analysis failed ❌'
66 | exit 1
67 | fi
68 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | name: dependency-review
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | - dotnet-vnext
8 | - dotnet-nightly
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | dependency-review:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 |
19 | - name: Checkout code
20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
21 | with:
22 | filter: 'tree:0'
23 | show-progress: false
24 |
25 | - name: Review dependencies
26 | uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
27 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths-ignore:
7 | - '**/*.gitattributes'
8 | - '**/*.gitignore'
9 | - '**/*.md'
10 | pull_request:
11 | branches:
12 | - main
13 | - dotnet-vnext
14 | - dotnet-nightly
15 | workflow_dispatch:
16 |
17 | permissions:
18 | contents: read
19 |
20 | env:
21 | FORCE_COLOR: 3
22 | POWERSHELL_YAML_VERSION: '0.4.12'
23 | PSSCRIPTANALYZER_VERSION: '1.24.0'
24 | TERM: xterm
25 |
26 | jobs:
27 | lint:
28 | runs-on: ubuntu-latest
29 |
30 | steps:
31 |
32 | - name: Checkout code
33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34 | with:
35 | filter: 'tree:0'
36 | show-progress: false
37 |
38 | - name: Add actionlint problem matcher
39 | run: echo "::add-matcher::.github/actionlint-matcher.json"
40 |
41 | - name: Lint workflows
42 | uses: docker://rhysd/actionlint@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9 # v1.7.7
43 | with:
44 | args: -color
45 |
46 | - name: Lint markdown
47 | uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20.0.0
48 | with:
49 | config: '.markdownlint.json'
50 | globs: |
51 | **/*.md
52 |
53 | - name: Lint PowerShell in workflows
54 | uses: martincostello/lint-actions-powershell@5942e3350ee5bd8f8933cec4e1185d13f0ea688f # v1.0.0
55 | with:
56 | powershell-yaml-version: ${{ env.POWERSHELL_YAML_VERSION }}
57 | psscriptanalyzer-version: ${{ env.PSSCRIPTANALYZER_VERSION }}
58 | treat-warnings-as-errors: true
59 |
60 | - name: Lint PowerShell scripts
61 | shell: pwsh
62 | run: |
63 | $settings = @{
64 | IncludeDefaultRules = $true
65 | Severity = @("Error", "Warning")
66 | }
67 | $issues = Invoke-ScriptAnalyzer -Path ${env:GITHUB_WORKSPACE} -Recurse -ReportSummary -Settings $settings
68 | foreach ($issue in $issues) {
69 | $severity = $issue.Severity.ToString()
70 | $level = $severity.Contains("Error") ? "error" : $severity.Contains("Warning") ? "warning" : "notice"
71 | Write-Output "::${level} file=$($issue.ScriptName),line=$($issue.Line),title=PSScriptAnalyzer::$($issue.Message)"
72 | }
73 | if ($issues.Count -gt 0) {
74 | exit 1
75 | }
76 |
--------------------------------------------------------------------------------
/.github/workflows/ossf-scorecard.yml:
--------------------------------------------------------------------------------
1 | name: ossf-scorecard
2 |
3 | on:
4 | branch_protection_rule:
5 | push:
6 | branches: [ main ]
7 | schedule:
8 | - cron: '0 5 * * MON'
9 | workflow_dispatch:
10 |
11 | permissions: read-all
12 |
13 | jobs:
14 | analysis:
15 | name: analysis
16 | runs-on: ubuntu-latest
17 |
18 | permissions:
19 | id-token: write
20 | security-events: write
21 |
22 | steps:
23 | - name: Checkout code
24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25 | with:
26 | filter: 'tree:0'
27 | persist-credentials: false
28 | show-progress: false
29 |
30 | - name: Run analysis
31 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
32 | with:
33 | publish_results: true
34 | results_file: results.sarif
35 | results_format: sarif
36 |
37 | - name: Upload artifact
38 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
39 | with:
40 | name: SARIF
41 | path: results.sarif
42 | retention-days: 5
43 |
44 | - name: Upload to code-scanning
45 | uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
46 | with:
47 | sarif_file: results.sarif
48 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | publish:
7 | description: 'If true, does not create the release as a draft.'
8 | required: false
9 | type: boolean
10 | default: false
11 |
12 | permissions: {}
13 |
14 | jobs:
15 | release:
16 | runs-on: [ ubuntu-latest ]
17 |
18 | concurrency:
19 | group: ${{ github.workflow }}
20 | cancel-in-progress: false
21 |
22 | steps:
23 |
24 | - name: Checkout code
25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26 | with:
27 | filter: 'tree:0'
28 | show-progress: false
29 | token: ${{ secrets.COSTELLOBOT_TOKEN }}
30 |
31 | - name: Get version
32 | id: get-version
33 | shell: pwsh
34 | run: |
35 | $properties = Join-Path "." "Directory.Build.props"
36 | $xml = [xml](Get-Content $properties)
37 | $version = $xml.SelectSingleNode('Project/PropertyGroup/VersionPrefix').InnerText
38 | "version=${version}" >> $env:GITHUB_OUTPUT
39 |
40 | - name: Create release
41 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
42 | env:
43 | DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
44 | DRAFT: ${{ inputs.publish != true }}
45 | VERSION: ${{ steps.get-version.outputs.version }}
46 | with:
47 | github-token: ${{ secrets.COSTELLOBOT_TOKEN }}
48 | script: |
49 | const { repo, owner } = context.repo;
50 | const draft = process.env.DRAFT === 'true';
51 | const version = process.env.VERSION;
52 | const tag_name = `v${version}`;
53 | const name = tag_name;
54 |
55 | const { data: notes } = await github.rest.repos.generateReleaseNotes({
56 | owner,
57 | repo,
58 | tag_name,
59 | target_commitish: process.env.DEFAULT_BRANCH,
60 | });
61 |
62 | const body = notes.body
63 | .split('\n')
64 | .filter((line) => !line.includes(' @costellobot '))
65 | .filter((line) => !line.includes(' @dependabot '))
66 | .filter((line) => !line.includes(' @github-actions '))
67 | .join('\n');
68 |
69 | const { data: release } = await github.rest.repos.createRelease({
70 | owner,
71 | repo,
72 | tag_name,
73 | name,
74 | body,
75 | draft,
76 | });
77 |
78 | core.notice(`Created release ${release.name}: ${release.html_url}`);
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dotnet
3 | .idea
4 | .metadata
5 | .settings
6 | .vs
7 | _ReSharper*
8 | _reports
9 | _UpgradeReport_Files/
10 | artifacts/
11 | Backup*/
12 | bin
13 | Bin
14 | coverage
15 | coverage.*
16 | junit.xml
17 | MSBuild_Logs/
18 | obj
19 | packages
20 | TestResults
21 | UpgradeLog*.htm
22 | UpgradeLog*.XML
23 | *.binlog
24 | *.coverage
25 | *.DotSettings
26 | *.GhostDoc.xml
27 | *.log
28 | *.nupkg
29 | !.packages/*.nupkg
30 | *.opensdf
31 | *.[Pp]ublish.xml
32 | *.publishproj
33 | *.pubxml
34 | *.sdf
35 | *.sln.cache
36 | *.sln.docstates
37 | *.sln.ide
38 | *.suo
39 | *.user
40 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "MD013": false,
3 | "MD040": false
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "editorconfig.editorconfig",
4 | "ms-dotnettools.csharp",
5 | "ms-vscode.PowerShell"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Run tests",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "dotnet",
10 | "args": [
11 | "test"
12 | ],
13 | "cwd": "${workspaceFolder}/tests/Logging.XUnit.Tests",
14 | "console": "internalConsole",
15 | "stopAtEntry": false,
16 | "internalConsoleOptions": "openOnSessionStart"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet build",
7 | "type": "shell",
8 | "group": "build",
9 | "presentation": {
10 | "reveal": "silent"
11 | },
12 | "problemMatcher": "$msCompile"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vsconfig:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "components": [
4 | "Microsoft.VisualStudio.Component.CoreEditor",
5 | "Microsoft.VisualStudio.Workload.CoreEditor",
6 | "Microsoft.NetCore.Component.Runtime.9.0",
7 | "Microsoft.NetCore.Component.SDK",
8 | "Microsoft.VisualStudio.Component.Roslyn.Compiler",
9 | "Microsoft.VisualStudio.Component.Roslyn.LanguageServices"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team [through a GitHub issue](https://github.com/martincostello/xunit-logging/issues). All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 | $(MSBuildThisFileDirectory)Logging.XUnit.ruleset
5 | true
6 | direct
7 | xunit;logging
8 | true
9 | true
10 | 0.6.0.0
11 | 0.6.0
12 | 0.6.1
13 |
14 |
15 | true
16 | $(NoWarn);419;1570;1573;1574;1584;1591;SA0001;SA1602
17 |
18 |
19 | true
20 | cobertura,json
21 | [SampleApp]*,[xunit.*]*
22 | 93
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2018 Martin Costello
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
191 |
--------------------------------------------------------------------------------
/Logging.XUnit.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Logging.XUnit.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xunit Logging
2 |
3 | [![Build status][build-badge]][build-status]
4 | [![codecov][coverage-badge]][coverage-report]
5 | [![OpenSSF Scorecard][scorecard-badge]][scorecard-report]
6 |
7 | | **xunit version** | **Package** | **NuGet Version** |
8 | |:------------------|:------------|:------------------|
9 | | xunit v2 | [MartinCostello.Logging.XUnit][package-download-v2] | [![NuGet][package-badge-version-v2]][package-download-v2] [![NuGet Downloads][package-badge-downloads-v2]][package-download-v2] |
10 | | xunit v3 | [MartinCostello.Logging.XUnit.v3][package-download-v3] | [![NuGet][package-badge-version-v3]][package-download-v3] [![NuGet Downloads][package-badge-downloads-v3]][package-download-v3] |
11 |
12 | ## Introduction
13 |
14 | `MartinCostello.Logging.XUnit` and `MartinCostello.Logging.XUnit.v3` provide extensions to hook into
15 | the `ILogger` infrastructure to output logs from your xunit tests to the test output.
16 |
17 | Projects using xunit v2 should use the `MartinCostello.Logging.XUnit` package, while projects using
18 | xunit v3 should use the `MartinCostello.Logging.XUnit.v3` package.
19 |
20 | > [!NOTE]
21 | > This library is designed for the Microsoft logging implementation of `ILoggerFactory`.
22 | > For other logging implementations, such as [Serilog][serilog], consider using packages such as [Serilog.Sinks.XUnit][serilog-sinks-xunit] instead.
23 |
24 | ### Installation
25 |
26 | To install the library from NuGet using the .NET SDK run one of the following commands.
27 |
28 | #### For xunit v2
29 |
30 | ```console
31 | dotnet add package MartinCostello.Logging.XUnit
32 | ```
33 |
34 | #### For xunit v3
35 |
36 | ```console
37 | dotnet add package MartinCostello.Logging.XUnit.v3
38 | ```
39 |
40 | ### Usage
41 |
42 | #### Dependency Injection
43 |
44 | ```csharp
45 | using Microsoft.Extensions.DependencyInjection;
46 | using Microsoft.Extensions.Logging;
47 | using Xunit;
48 | using Xunit.Abstractions; // For xunit v2 - not required for xunit v3
49 |
50 | namespace MyApp.Calculator;
51 |
52 | public class CalculatorTests(ITestOutputHelper outputHelper)
53 | {
54 | [Fact]
55 | public void Calculator_Sums_Two_Integers()
56 | {
57 | // Arrange
58 | using var serviceProvider = new ServiceCollection()
59 | .AddLogging((builder) => builder.AddXUnit(outputHelper))
60 | .AddSingleton()
61 | .BuildServiceProvider();
62 |
63 | var calculator = services.GetRequiredService();
64 |
65 | // Act
66 | int actual = calculator.Sum(1, 2);
67 |
68 | // Assert
69 | Assert.AreEqual(3, actual);
70 | }
71 | }
72 |
73 | public sealed class Calculator(ILogger logger)
74 | {
75 | public int Sum(int x, int y)
76 | {
77 | int sum = x + y;
78 |
79 | logger.LogInformation("The sum of {x} and {y} is {sum}.", x, y, sum);
80 |
81 | return sum;
82 | }
83 | }
84 | ```
85 |
86 | #### Standalone Logging Components
87 |
88 | ```csharp
89 | using Microsoft.Extensions.DependencyInjection;
90 | using Microsoft.Extensions.Logging;
91 | using Xunit;
92 | using Xunit.Abstractions; // For xunit v2 - not required for xunit v3
93 |
94 | namespace MyApp.Calculator;
95 |
96 | public class CalculatorTests(ITestOutputHelper outputHelper)
97 | {
98 | [Fact]
99 | public void Calculator_Sums_Two_Integers()
100 | {
101 | // Arrange
102 | var loggerFactory = LoggerFactory.Create(builder => builder
103 | .AddProvider(new XUnitLoggerProvider(outputHelper, xunitLoggerOptions))
104 | .SetMinimumLevel(LogLevel.Trace));
105 |
106 | var logger = loggerFactory.CreateLogger();
107 |
108 | var calculator = new Calculator(logger);
109 |
110 | // Act
111 | int actual = calculator.Sum(1, 2);
112 |
113 | // Assert
114 | Assert.AreEqual(3, actual);
115 | }
116 | }
117 |
118 | public sealed class Calculator(ILogger logger)
119 | {
120 | public int Sum(int x, int y)
121 | {
122 | int sum = x + y;
123 |
124 | logger.LogInformation("The sum of {x} and {y} is {sum}.", x, y, sum);
125 |
126 | return sum;
127 | }
128 | }
129 | ```
130 |
131 | See below for links to more examples:
132 |
133 | - [Unit tests][example-unit-tests]
134 | - [Integration tests for an ASP.NET Core HTTP application][example-integration-tests]
135 |
136 | ## Example Output
137 |
138 | If your tests (and the system under test) are correctly configured, then you should see output from the
139 | tests in the same places you would expected to see [output from xunit tests][xunit-output].
140 |
141 | See below for some examples from the [`Http_Get_Many`][example-test] test in this repository.
142 |
143 |
144 |
145 | ### Visual Studio
146 |
147 |
148 | Click to expand
149 |
150 | ![Visual Studio Output][output-vs]
151 |
152 |
153 |
154 | ### Visual Studio Code
155 |
156 |
157 | Click to expand
158 |
159 | ![Visual Studio Code Output][output-vscode]
160 |
161 |
162 |
163 | ### Windows Terminal and .NET CLI
164 |
165 |
166 | Click to expand
167 |
168 | ![Windows Terminal Output][output-windows-terminal]
169 |
170 |
171 |
172 | ## Migrating to xunit v3
173 |
174 | [Xunit v3][xunit-v3-whats-new] contains many major architectural changes which means the same package
175 | that supports logging for xunit v2 cannot be used with xunit v3. The equivalent NuGet package to support
176 | logging for xunit v3 is the new [MartinCostello.Logging.XUnit.v3][package-download-v3] package.
177 |
178 | To migrate usage of `MartinCostello.Logging.XUnit` to `MartinCostello.Logging.XUnit.v3` for xunit v3:
179 |
180 | 1. Follow the relevant steps to migrate any test projects from [xunit v2 to v3][xunit-v3-migration].
181 | - The most relevant change in xunit v3 is that the `ITestOutputHelper` type has moved from the `Xunit.Abstractions` namespace to `Xunit`.
182 | 1. Change any package references from `MartinCostello.Logging.XUnit` to `MartinCostello.Logging.XUnit.v3`.
183 |
184 | ```diff
185 | -
186 | +
187 | ```
188 |
189 | ## Feedback
190 |
191 | Any feedback or issues can be added to the issues for this project in [GitHub][issues].
192 |
193 | ## Repository
194 |
195 | The repository is hosted in [GitHub][repo]:
196 |
197 | ## License
198 |
199 | This project is licensed under the [Apache 2.0][license] license.
200 |
201 | ## Building and Testing
202 |
203 | Compiling the solution yourself requires Git and the [.NET SDK][dotnet-sdk] to be installed (version `9.0.100` or later).
204 |
205 | To build and test the solution locally from a terminal/command-line, run the following set of commands:
206 |
207 | ```powershell
208 | git clone https://github.com/martincostello/xunit-logging.git
209 | cd xunit-logging
210 | ./build.ps1
211 | ```
212 |
213 | [build-badge]: https://github.com/martincostello/xunit-logging/actions/workflows/build.yml/badge.svg?branch=main&event=push
214 | [build-status]: https://github.com/martincostello/xunit-logging/actions?query=workflow%3Abuild+branch%3Amain+event%3Apush "Continuous Integration for this project"
215 | [coverage-badge]: https://codecov.io/gh/martincostello/xunit-logging/branch/main/graph/badge.svg
216 | [coverage-report]: https://codecov.io/gh/martincostello/xunit-logging "Code coverage report for this project"
217 | [scorecard-badge]: https://api.securityscorecards.dev/projects/github.com/martincostello/xunit-logging/badge
218 | [scorecard-report]: https://securityscorecards.dev/viewer/?uri=github.com/martincostello/xunit-logging "OpenSSF Scorecard for this project"
219 | [dotnet-sdk]: https://dot.net/download "Download the .NET SDK"
220 | [example-integration-tests]: https://github.com/martincostello/xunit-logging/blob/main/tests/Shared/Integration/HttpApplicationTests.cs "Integration test examples"
221 | [example-test]: https://github.com/martincostello/xunit-logging/blob/8951660a4667cfa5b07e2558b741b335a31d1e59/tests/Shared/Integration/HttpApplicationTests.cs#L26-L58 "Http_Get_Many"
222 | [example-unit-tests]: https://github.com/martincostello/xunit-logging/blob/main/tests/Shared/Examples.cs "Unit test examples"
223 | [issues]: https://github.com/martincostello/xunit-logging/issues "Issues for this project on GitHub.com"
224 | [license]: https://www.apache.org/licenses/LICENSE-2.0.txt "The Apache 2.0 license"
225 | [output-vs]: ./docs/images/output-vs.png "xunit Output in Visual Studio"
226 | [output-vscode]: ./docs/images/output-vscode.png "xunit Output in Visual Studio Code"
227 | [output-windows-terminal]: ./docs/images/output-terminal.png "xunit Output in Windows Terminal with the .NET CLI"
228 | [package-badge-downloads-v2]: https://img.shields.io/nuget/dt/MartinCostello.Logging.XUnit?logo=nuget&label=Downloads&color=blue
229 | [package-badge-downloads-v3]: https://img.shields.io/nuget/dt/MartinCostello.Logging.XUnit.v3?logo=nuget&label=Downloads&color=blue
230 | [package-badge-version-v2]: https://img.shields.io/nuget/v/MartinCostello.Logging.XUnit?logo=nuget&label=Latest&color=blue
231 | [package-badge-version-v3]: https://img.shields.io/nuget/v/MartinCostello.Logging.XUnit.v3?logo=nuget&label=Latest&color=blue
232 | [package-download-v2]: https://www.nuget.org/packages/MartinCostello.Logging.XUnit "Download MartinCostello.Logging.XUnit from NuGet"
233 | [package-download-v3]: https://www.nuget.org/packages/MartinCostello.Logging.XUnit.v3 "Download MartinCostello.Logging.XUnit.v3 from NuGet"
234 | [repo]: https://github.com/martincostello/xunit-logging "This project on GitHub.com"
235 | [serilog]: https://serilog.net/ "Serilog website"
236 | [serilog-sinks-xunit]: https://github.com/trbenning/serilog-sinks-xunit "Serilog.Sinks.XUnit on GitHub"
237 | [xunit-output]: https://xunit.net/docs/capturing-output "Capturing Output - xUnit.net Documentation"
238 | [xunit-v3-migration]: https://xunit.net/docs/getting-started/v3/migration "Migrating from xunit v2 to v3"
239 | [xunit-v3-whats-new]: https://xunit.net/docs/getting-started/v3/whats-new "What's New in v3"
240 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | To privately report a security vulnerability, please create a security advisory in the [repository's Security tab](https://github.com/martincostello/xunit-logging/security/advisories).
6 |
7 | Further details can be found in the [GitHub documentation](https://docs.github.com/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability).
8 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env pwsh
2 |
3 | #Requires -PSEdition Core
4 | #Requires -Version 7
5 |
6 | param(
7 | [Parameter(Mandatory = $false)][switch] $SkipTests
8 | )
9 |
10 | $ErrorActionPreference = "Stop"
11 | $InformationPreference = "Continue"
12 | $ProgressPreference = "SilentlyContinue"
13 |
14 | $solutionPath = $PSScriptRoot
15 | $sdkFile = Join-Path $solutionPath "global.json"
16 |
17 | $libraryProjects = @(
18 | (Join-Path $solutionPath "src" "Logging.XUnit" "MartinCostello.Logging.XUnit.csproj")
19 | (Join-Path $solutionPath "src" "Logging.XUnit.v3" "MartinCostello.Logging.XUnit.v3.csproj")
20 | )
21 |
22 | $dotnetVersion = (Get-Content $sdkFile | Out-String | ConvertFrom-Json).sdk.version
23 |
24 | $installDotNetSdk = $false;
25 |
26 | if (($null -eq (Get-Command "dotnet" -ErrorAction SilentlyContinue)) -and ($null -eq (Get-Command "dotnet.exe" -ErrorAction SilentlyContinue))) {
27 | Write-Information "The .NET SDK is not installed."
28 | $installDotNetSdk = $true
29 | }
30 | else {
31 | Try {
32 | $installedDotNetVersion = (dotnet --version 2>&1 | Out-String).Trim()
33 | }
34 | Catch {
35 | $installedDotNetVersion = "?"
36 | }
37 |
38 | if ($installedDotNetVersion -ne $dotnetVersion) {
39 | Write-Information "The required version of the .NET SDK is not installed. Expected $dotnetVersion."
40 | $installDotNetSdk = $true
41 | }
42 | }
43 |
44 | if ($installDotNetSdk) {
45 |
46 | ${env:DOTNET_INSTALL_DIR} = Join-Path $solutionPath ".dotnet"
47 | $sdkPath = Join-Path ${env:DOTNET_INSTALL_DIR} "sdk" $dotnetVersion
48 |
49 | if (!(Test-Path $sdkPath)) {
50 | if (!(Test-Path ${env:DOTNET_INSTALL_DIR})) {
51 | mkdir ${env:DOTNET_INSTALL_DIR} | Out-Null
52 | }
53 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor "Tls12"
54 | if (($PSVersionTable.PSVersion.Major -ge 6) -And !$IsWindows) {
55 | $installScript = Join-Path ${env:DOTNET_INSTALL_DIR} "install.sh"
56 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile $installScript -UseBasicParsing
57 | chmod +x $installScript
58 | & $installScript --jsonfile $sdkFile --install-dir ${env:DOTNET_INSTALL_DIR} --no-path --skip-non-versioned-files
59 | }
60 | else {
61 | $installScript = Join-Path ${env:DOTNET_INSTALL_DIR} "install.ps1"
62 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile $installScript -UseBasicParsing
63 | & $installScript -JsonFile $sdkFile -InstallDir ${env:DOTNET_INSTALL_DIR} -NoPath -SkipNonVersionedFiles
64 | }
65 | }
66 | }
67 | else {
68 | ${env:DOTNET_INSTALL_DIR} = Split-Path -Path (Get-Command dotnet).Path
69 | }
70 |
71 | $dotnet = Join-Path ${env:DOTNET_INSTALL_DIR} "dotnet"
72 |
73 | if ($installDotNetSdk) {
74 | ${env:PATH} = "${env:DOTNET_INSTALL_DIR};${env:PATH}"
75 | }
76 |
77 | function DotNetPack {
78 | param([string]$Project)
79 |
80 | & $dotnet pack $Project --include-symbols --include-source
81 |
82 | if ($LASTEXITCODE -ne 0) {
83 | throw "dotnet pack failed with exit code $LASTEXITCODE"
84 | }
85 | }
86 |
87 | function DotNetTest {
88 | param()
89 |
90 | $additionalArgs = @()
91 |
92 | if (-Not [string]::IsNullOrEmpty($env:GITHUB_SHA)) {
93 | $additionalArgs += "--logger:GitHubActions;report-warnings=false"
94 | $additionalArgs += "--logger:junit;LogFilePath=junit.xml"
95 | }
96 |
97 | & $dotnet test --configuration "Release" $additionalArgs
98 |
99 | if ($LASTEXITCODE -ne 0) {
100 | throw "dotnet test failed with exit code $LASTEXITCODE"
101 | }
102 | }
103 |
104 | Write-Information "Packaging libraries..."
105 | ForEach ($libraryProject in $libraryProjects) {
106 | DotNetPack $libraryProject
107 | }
108 |
109 | if (-Not $SkipTests) {
110 | Write-Information "Running tests..."
111 | DotNetTest
112 | }
113 |
--------------------------------------------------------------------------------
/docs/images/output-terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martincostello/xunit-logging/36464418f76d01b76e7550818529ba46a868a715/docs/images/output-terminal.png
--------------------------------------------------------------------------------
/docs/images/output-vs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martincostello/xunit-logging/36464418f76d01b76e7550818529ba46a868a715/docs/images/output-vs.png
--------------------------------------------------------------------------------
/docs/images/output-vscode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martincostello/xunit-logging/36464418f76d01b76e7550818529ba46a868a715/docs/images/output-vscode.png
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.300",
4 | "allowPrerelease": false,
5 | "rollForward": "latestMajor",
6 | "paths": [ ".dotnet", "$host$" ],
7 | "errorMessage": "The required version of the .NET SDK could not be found. Please run ./build.ps1 to bootstrap the .NET SDK."
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/package-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martincostello/xunit-logging/36464418f76d01b76e7550818529ba46a868a715/package-icon.png
--------------------------------------------------------------------------------
/package-readme.md:
--------------------------------------------------------------------------------
1 | # xunit Logging
2 |
3 | ## Introduction
4 |
5 | `MartinCostello.Logging.XUnit` and `MartinCostello.Logging.XUnit.v3` provide extensions to hook
6 | into the `ILogger` infrastructure to output logs from your xunit tests to the test output.
7 |
8 | ### Usage
9 |
10 | ```csharp
11 | using Microsoft.Extensions.DependencyInjection;
12 | using Microsoft.Extensions.Logging;
13 | using Xunit;
14 | using Xunit.Abstractions; // For xunit v2 - not required for xunit v3
15 |
16 | namespace MyApp.Calculator;
17 |
18 | public class CalculatorTests(ITestOutputHelper outputHelper)
19 | {
20 | [Fact]
21 | public void Calculator_Sums_Two_Integers()
22 | {
23 | // Arrange
24 | using var serviceProvider = new ServiceCollection()
25 | .AddLogging((builder) => builder.AddXUnit(outputHelper))
26 | .AddSingleton()
27 | .BuildServiceProvider();
28 |
29 | var calculator = services.GetRequiredService();
30 |
31 | // Act
32 | int actual = calculator.Sum(1, 2);
33 |
34 | // Assert
35 | Assert.AreEqual(3, actual);
36 | }
37 | }
38 |
39 | public sealed class Calculator(ILogger logger)
40 | {
41 | public int Sum(int x, int y)
42 | {
43 | int sum = x + y;
44 |
45 | logger.LogInformation("The sum of {x} and {y} is {sum}.", x, y, sum);
46 |
47 | return sum;
48 | }
49 | }
50 | ```
51 |
52 | ## Feedback
53 |
54 | Any feedback or issues can be added to the issues for this project in [GitHub][issues].
55 |
56 | ## License
57 |
58 | This project is licensed under the [Apache 2.0][license] license.
59 |
60 | [issues]: https://github.com/martincostello/xunit-logging/issues "Issues for this package on GitHub.com"
61 | [license]: https://www.apache.org/licenses/LICENSE-2.0.txt "The Apache 2.0 license"
62 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/MartinCostello.Logging.XUnit.v3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Logging Extensions for xunit v3
4 | $(DefineConstants);XUNIT_V3
5 | Extensions for Microsoft.Extensions.Logging for xunit v3.
6 | true
7 | true
8 | true
9 | Library
10 | MartinCostello.Logging.XUnit.v3
11 | MartinCostello.Logging.XUnit
12 | net8.0;net472
13 | xunit v3 Logging Extensions
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | <_Parameter1>ed8d1c5e-3ee7-45fe-8d1d-94257a71f02a
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.IMessageSinkAccessor
3 | MartinCostello.Logging.XUnit.IMessageSinkAccessor.MessageSink.get -> Xunit.Sdk.IMessageSink?
4 | MartinCostello.Logging.XUnit.IMessageSinkAccessor.MessageSink.set -> void
5 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor
6 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor.OutputHelper.get -> Xunit.ITestOutputHelper?
7 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor.OutputHelper.set -> void
8 | MartinCostello.Logging.XUnit.XUnitLogger
9 | MartinCostello.Logging.XUnit.XUnitLogger.BeginScope(TState state) -> System.IDisposable?
10 | MartinCostello.Logging.XUnit.XUnitLogger.Filter.get -> System.Func!
11 | MartinCostello.Logging.XUnit.XUnitLogger.Filter.set -> void
12 | MartinCostello.Logging.XUnit.XUnitLogger.IncludeScopes.get -> bool
13 | MartinCostello.Logging.XUnit.XUnitLogger.IncludeScopes.set -> void
14 | MartinCostello.Logging.XUnit.XUnitLogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) -> bool
15 | MartinCostello.Logging.XUnit.XUnitLogger.Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception? exception, System.Func! formatter) -> void
16 | MartinCostello.Logging.XUnit.XUnitLogger.MessageSinkMessageFactory.get -> System.Func!
17 | MartinCostello.Logging.XUnit.XUnitLogger.MessageSinkMessageFactory.set -> void
18 | MartinCostello.Logging.XUnit.XUnitLogger.Name.get -> string!
19 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
20 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
21 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, Xunit.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
22 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, Xunit.Sdk.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
23 | MartinCostello.Logging.XUnit.XUnitLoggerOptions
24 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.Filter.get -> System.Func!
25 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.Filter.set -> void
26 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.IncludeScopes.get -> bool
27 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.IncludeScopes.set -> void
28 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.MessageSinkMessageFactory.get -> System.Func!
29 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.MessageSinkMessageFactory.set -> void
30 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimestampFormat.get -> string?
31 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimestampFormat.set -> void
32 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.XUnitLoggerOptions() -> void
33 | MartinCostello.Logging.XUnit.XUnitLoggerProvider
34 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.Dispose() -> void
35 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
36 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
37 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(Xunit.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
38 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(Xunit.Sdk.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
39 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.~XUnitLoggerProvider() -> void
40 | Microsoft.Extensions.Logging.XUnitLoggerExtensions
41 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggerFactory!
42 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> Microsoft.Extensions.Logging.ILoggerFactory!
43 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper, Microsoft.Extensions.Logging.LogLevel minLevel) -> Microsoft.Extensions.Logging.ILoggerFactory!
44 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
45 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper, System.Func! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
46 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.ITestOutputHelper! outputHelper, System.Func! filter) -> Microsoft.Extensions.Logging.ILoggerFactory!
47 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggerFactory!
48 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> Microsoft.Extensions.Logging.ILoggerFactory!
49 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink, Microsoft.Extensions.Logging.LogLevel minLevel) -> Microsoft.Extensions.Logging.ILoggerFactory!
50 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
51 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink, System.Func! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
52 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Sdk.IMessageSink! messageSink, System.Func! filter) -> Microsoft.Extensions.Logging.ILoggerFactory!
53 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder!
54 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor) -> Microsoft.Extensions.Logging.ILoggingBuilder!
55 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
56 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor) -> Microsoft.Extensions.Logging.ILoggingBuilder!
57 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
58 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggingBuilder!
59 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.ITestOutputHelper! outputHelper, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
60 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Sdk.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggingBuilder!
61 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Sdk.IMessageSink! messageSink, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
62 | static Xunit.IMessageSinkExtensions.ToLogger(this Xunit.Sdk.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILogger!
63 | static Xunit.IMessageSinkExtensions.ToLoggerFactory(this Xunit.Sdk.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggerFactory!
64 | static Xunit.ITestOutputHelperExtensions.ToLogger(this Xunit.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILogger!
65 | static Xunit.ITestOutputHelperExtensions.ToLoggerFactory(this Xunit.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggerFactory!
66 | virtual MartinCostello.Logging.XUnit.XUnitLogger.WriteMessage(Microsoft.Extensions.Logging.LogLevel logLevel, int eventId, string? message, System.Exception? exception) -> void
67 | virtual MartinCostello.Logging.XUnit.XUnitLoggerProvider.CreateLogger(string! categoryName) -> Microsoft.Extensions.Logging.ILogger!
68 | virtual MartinCostello.Logging.XUnit.XUnitLoggerProvider.Dispose(bool disposing) -> void
69 | Xunit.IMessageSinkExtensions
70 | Xunit.ITestOutputHelperExtensions
71 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/net10.0/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
3 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
4 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/net10.0/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
3 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
4 |
--------------------------------------------------------------------------------
/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/MartinCostello.Logging.XUnit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Logging Extensions for xunit
4 | Extensions for Microsoft.Extensions.Logging for xunit.
5 | true
6 | true
7 | true
8 | Library
9 | MartinCostello.Logging.XUnit
10 | MartinCostello.Logging.XUnit
11 | netstandard2.0;net8.0
12 | xunit Logging Extensions
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | <_Parameter1>af808007-f06a-410b-886d-152b3f39c43f
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.IMessageSinkAccessor
3 | MartinCostello.Logging.XUnit.IMessageSinkAccessor.MessageSink.get -> Xunit.Abstractions.IMessageSink?
4 | MartinCostello.Logging.XUnit.IMessageSinkAccessor.MessageSink.set -> void
5 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor
6 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor.OutputHelper.get -> Xunit.Abstractions.ITestOutputHelper?
7 | MartinCostello.Logging.XUnit.ITestOutputHelperAccessor.OutputHelper.set -> void
8 | MartinCostello.Logging.XUnit.XUnitLogger
9 | MartinCostello.Logging.XUnit.XUnitLogger.BeginScope(TState state) -> System.IDisposable?
10 | MartinCostello.Logging.XUnit.XUnitLogger.Filter.get -> System.Func!
11 | MartinCostello.Logging.XUnit.XUnitLogger.Filter.set -> void
12 | MartinCostello.Logging.XUnit.XUnitLogger.IncludeScopes.get -> bool
13 | MartinCostello.Logging.XUnit.XUnitLogger.IncludeScopes.set -> void
14 | MartinCostello.Logging.XUnit.XUnitLogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) -> bool
15 | MartinCostello.Logging.XUnit.XUnitLogger.Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception? exception, System.Func! formatter) -> void
16 | MartinCostello.Logging.XUnit.XUnitLogger.MessageSinkMessageFactory.get -> System.Func!
17 | MartinCostello.Logging.XUnit.XUnitLogger.MessageSinkMessageFactory.set -> void
18 | MartinCostello.Logging.XUnit.XUnitLogger.Name.get -> string!
19 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
20 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
21 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, Xunit.Abstractions.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
22 | MartinCostello.Logging.XUnit.XUnitLogger.XUnitLogger(string! name, Xunit.Abstractions.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions? options) -> void
23 | MartinCostello.Logging.XUnit.XUnitLoggerOptions
24 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.Filter.get -> System.Func!
25 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.Filter.set -> void
26 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.IncludeScopes.get -> bool
27 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.IncludeScopes.set -> void
28 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.MessageSinkMessageFactory.get -> System.Func!
29 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.MessageSinkMessageFactory.set -> void
30 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimestampFormat.get -> string?
31 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimestampFormat.set -> void
32 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.XUnitLoggerOptions() -> void
33 | MartinCostello.Logging.XUnit.XUnitLoggerProvider
34 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.Dispose() -> void
35 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
36 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
37 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(Xunit.Abstractions.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
38 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.XUnitLoggerProvider(Xunit.Abstractions.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> void
39 | MartinCostello.Logging.XUnit.XUnitLoggerProvider.~XUnitLoggerProvider() -> void
40 | Microsoft.Extensions.Logging.XUnitLoggerExtensions
41 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggerFactory!
42 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> Microsoft.Extensions.Logging.ILoggerFactory!
43 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink, Microsoft.Extensions.Logging.LogLevel minLevel) -> Microsoft.Extensions.Logging.ILoggerFactory!
44 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
45 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink, System.Func! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
46 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.IMessageSink! messageSink, System.Func! filter) -> Microsoft.Extensions.Logging.ILoggerFactory!
47 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggerFactory!
48 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper, MartinCostello.Logging.XUnit.XUnitLoggerOptions! options) -> Microsoft.Extensions.Logging.ILoggerFactory!
49 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper, Microsoft.Extensions.Logging.LogLevel minLevel) -> Microsoft.Extensions.Logging.ILoggerFactory!
50 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
51 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper, System.Func! configure) -> Microsoft.Extensions.Logging.ILoggerFactory!
52 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggerFactory! factory, Xunit.Abstractions.ITestOutputHelper! outputHelper, System.Func! filter) -> Microsoft.Extensions.Logging.ILoggerFactory!
53 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder!
54 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor) -> Microsoft.Extensions.Logging.ILoggingBuilder!
55 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.IMessageSinkAccessor! accessor, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
56 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor) -> Microsoft.Extensions.Logging.ILoggingBuilder!
57 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, MartinCostello.Logging.XUnit.ITestOutputHelperAccessor! accessor, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
58 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Abstractions.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggingBuilder!
59 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Abstractions.IMessageSink! messageSink, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
60 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Abstractions.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggingBuilder!
61 | static Microsoft.Extensions.Logging.XUnitLoggerExtensions.AddXUnit(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, Xunit.Abstractions.ITestOutputHelper! outputHelper, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder!
62 | static Xunit.Abstractions.IMessageSinkExtensions.ToLogger(this Xunit.Abstractions.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILogger!
63 | static Xunit.Abstractions.IMessageSinkExtensions.ToLoggerFactory(this Xunit.Abstractions.IMessageSink! messageSink) -> Microsoft.Extensions.Logging.ILoggerFactory!
64 | static Xunit.Abstractions.ITestOutputHelperExtensions.ToLogger(this Xunit.Abstractions.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILogger!
65 | static Xunit.Abstractions.ITestOutputHelperExtensions.ToLoggerFactory(this Xunit.Abstractions.ITestOutputHelper! outputHelper) -> Microsoft.Extensions.Logging.ILoggerFactory!
66 | virtual MartinCostello.Logging.XUnit.XUnitLogger.WriteMessage(Microsoft.Extensions.Logging.LogLevel logLevel, int eventId, string? message, System.Exception? exception) -> void
67 | virtual MartinCostello.Logging.XUnit.XUnitLoggerProvider.CreateLogger(string! categoryName) -> Microsoft.Extensions.Logging.ILogger!
68 | virtual MartinCostello.Logging.XUnit.XUnitLoggerProvider.Dispose(bool disposing) -> void
69 | Xunit.Abstractions.IMessageSinkExtensions
70 | Xunit.Abstractions.ITestOutputHelperExtensions
71 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/net10.0/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
3 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
4 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/net10.0/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
3 | MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
4 |
--------------------------------------------------------------------------------
/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
--------------------------------------------------------------------------------
/src/Shared/AmbientTestOutputHelperAccessor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// A class representing an implementation of that
8 | /// stores the as an asynchronous local value. This class cannot be inherited.
9 | ///
10 | internal sealed class AmbientTestOutputHelperAccessor : ITestOutputHelperAccessor
11 | {
12 | ///
13 | /// A backing field for the for the current thread.
14 | ///
15 | private static readonly AsyncLocal _current = new();
16 |
17 | ///
18 | /// Gets or sets the current .
19 | ///
20 | public ITestOutputHelper? OutputHelper
21 | {
22 | get { return _current.Value; }
23 | set { _current.Value = value; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Shared/IMessageSinkAccessor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// Defines a property for accessing an .
8 | ///
9 | public interface IMessageSinkAccessor
10 | {
11 | ///
12 | /// Gets or sets the to use.
13 | ///
14 | IMessageSink? MessageSink { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Shared/IMessageSinkExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.ComponentModel;
5 | using Microsoft.Extensions.Logging;
6 |
7 | #if XUNIT_V3
8 | namespace Xunit;
9 | #else
10 | #pragma warning disable IDE0130
11 | namespace Xunit.Abstractions;
12 | #endif
13 |
14 | ///
15 | /// A class containing extension methods for the interface. This class cannot be inherited.
16 | ///
17 | [EditorBrowsable(EditorBrowsableState.Never)]
18 | public static class IMessageSinkExtensions
19 | {
20 | ///
21 | /// Returns an that logs to the message sink.
22 | ///
23 | /// The to create the logger factory from.
24 | ///
25 | /// An that writes messages to the message sink.
26 | ///
27 | ///
28 | /// is .
29 | ///
30 | public static ILoggerFactory ToLoggerFactory(this IMessageSink messageSink)
31 | {
32 | #if NET
33 | ArgumentNullException.ThrowIfNull(messageSink);
34 | #else
35 | if (messageSink == null)
36 | {
37 | throw new ArgumentNullException(nameof(messageSink));
38 | }
39 | #endif
40 |
41 | return new LoggerFactory().AddXUnit(messageSink);
42 | }
43 |
44 | ///
45 | /// Returns an that logs to the message sink.
46 | ///
47 | /// The type of the logger to create.
48 | /// The to create the logger from.
49 | ///
50 | /// An that writes messages to the message sink.
51 | ///
52 | ///
53 | /// is .
54 | ///
55 | public static ILogger ToLogger(this IMessageSink messageSink)
56 | => messageSink.ToLoggerFactory().CreateLogger();
57 | }
58 |
--------------------------------------------------------------------------------
/src/Shared/ITestOutputHelperAccessor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// Defines a property for accessing an .
8 | ///
9 | public interface ITestOutputHelperAccessor
10 | {
11 | ///
12 | /// Gets or sets the to use.
13 | ///
14 | ITestOutputHelper? OutputHelper { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Shared/ITestOutputHelperExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.ComponentModel;
5 | using Microsoft.Extensions.Logging;
6 |
7 | #if XUNIT_V3
8 | namespace Xunit;
9 | #else
10 | #pragma warning disable IDE0130
11 | namespace Xunit.Abstractions;
12 | #endif
13 |
14 | ///
15 | /// A class containing extension methods for the interface. This class cannot be inherited.
16 | ///
17 | [EditorBrowsable(EditorBrowsableState.Never)]
18 | public static class ITestOutputHelperExtensions
19 | {
20 | ///
21 | /// Returns an that logs to the output helper.
22 | ///
23 | /// The to create the logger factory from.
24 | ///
25 | /// An that writes messages to the test output helper.
26 | ///
27 | ///
28 | /// is .
29 | ///
30 | public static ILoggerFactory ToLoggerFactory(this ITestOutputHelper outputHelper)
31 | {
32 | #if NET
33 | ArgumentNullException.ThrowIfNull(outputHelper);
34 | #else
35 | if (outputHelper == null)
36 | {
37 | throw new ArgumentNullException(nameof(outputHelper));
38 | }
39 | #endif
40 |
41 | return new LoggerFactory().AddXUnit(outputHelper);
42 | }
43 |
44 | ///
45 | /// Returns an that logs to the output helper.
46 | ///
47 | /// The type of the logger to create.
48 | /// The to create the logger from.
49 | ///
50 | /// An that writes messages to the test output helper.
51 | ///
52 | ///
53 | /// is .
54 | ///
55 | public static ILogger ToLogger(this ITestOutputHelper outputHelper)
56 | => outputHelper.ToLoggerFactory().CreateLogger();
57 | }
58 |
--------------------------------------------------------------------------------
/src/Shared/MessageSinkAccessor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// A class representing the default implementation of . This class cannot be inherited.
8 | ///
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// The to use.
13 | ///
14 | /// is .
15 | ///
16 | internal sealed class MessageSinkAccessor(IMessageSink messageSink) : IMessageSinkAccessor
17 | {
18 | ///
19 | /// Gets or sets the current .
20 | ///
21 | public IMessageSink? MessageSink { get; set; } = messageSink ?? throw new ArgumentNullException(nameof(messageSink));
22 | }
23 |
--------------------------------------------------------------------------------
/src/Shared/StringSyntaxAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | //// Based on https://github.com/dotnet/runtime/blob/65067052e433eda400c5e7cc9f7b21c84640f901/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/StringSyntaxAttribute.cs
5 |
6 | #if NETSTANDARD || !NETCOREAPP
7 |
8 | #pragma warning disable IDE0130
9 | #pragma warning disable SA1600
10 |
11 | namespace System.Diagnostics.CodeAnalysis;
12 |
13 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
14 | [ExcludeFromCodeCoverage]
15 | internal sealed class StringSyntaxAttribute(string syntax, params object?[] arguments) : Attribute
16 | {
17 | public const string DateTimeFormat = nameof(DateTimeFormat);
18 |
19 | public StringSyntaxAttribute(string syntax)
20 | : this(syntax, [])
21 | {
22 | }
23 |
24 | public string Syntax { get; } = syntax;
25 |
26 | public object?[] Arguments { get; } = arguments;
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/src/Shared/TestOutputHelperAccessor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// A class representing the default implementation of . This class cannot be inherited.
8 | ///
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// The to use.
13 | ///
14 | /// is .
15 | ///
16 | internal sealed class TestOutputHelperAccessor(ITestOutputHelper outputHelper) : ITestOutputHelperAccessor
17 | {
18 | ///
19 | /// Gets or sets the current .
20 | ///
21 | public ITestOutputHelper? OutputHelper { get; set; } = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
22 | }
23 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLogScope.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | ///
7 | /// A class representing a scope for logging. This class cannot be inherited.
8 | ///
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// The state object for the scope.
13 | internal sealed class XUnitLogScope(object state)
14 | {
15 | ///
16 | /// The scope for the current thread.
17 | ///
18 | private static readonly AsyncLocal _value = new();
19 |
20 | ///
21 | /// Gets the state object for the scope.
22 | ///
23 | public object State { get; } = state;
24 |
25 | ///
26 | /// Gets or sets the current scope.
27 | ///
28 | internal static XUnitLogScope? Current
29 | {
30 | get { return _value.Value; }
31 | set { _value.Value = value; }
32 | }
33 |
34 | ///
35 | /// Gets the parent scope.
36 | ///
37 | internal XUnitLogScope? Parent { get; private set; }
38 |
39 | ///
40 | public override string? ToString()
41 | => State.ToString();
42 |
43 | ///
44 | /// Pushes a new value into the scope.
45 | ///
46 | /// The state object for the scope.
47 | ///
48 | /// An that pops the scope.
49 | ///
50 | internal static IDisposable Push(object state)
51 | {
52 | var temp = Current;
53 |
54 | Current = new XUnitLogScope(state)
55 | {
56 | Parent = temp,
57 | };
58 |
59 | return new DisposableScope();
60 | }
61 |
62 | ///
63 | /// A class the disposes of the current scope. This class cannot be inherited.
64 | ///
65 | private sealed class DisposableScope : IDisposable
66 | {
67 | ///
68 | public void Dispose()
69 | {
70 | Current = Current?.Parent;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLogger.IMessageSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace MartinCostello.Logging.XUnit;
7 |
8 | ///
9 | /// A class representing an to use with xunit.
10 | ///
11 | public partial class XUnitLogger
12 | {
13 | ///
14 | /// The to use. This field is read-only.
15 | ///
16 | private readonly IMessageSinkAccessor? _messageSinkAccessor;
17 |
18 | ///
19 | /// Gets or sets the message sink message factory to use when writing to an .
20 | ///
21 | private Func _messageSinkMessageFactory;
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// The name for messages produced by the logger.
27 | /// The to use.
28 | /// The to use.
29 | ///
30 | /// or is .
31 | ///
32 | public XUnitLogger(string name, IMessageSink messageSink, XUnitLoggerOptions? options)
33 | : this(name, new MessageSinkAccessor(messageSink), options)
34 | {
35 | }
36 |
37 | ///
38 | /// Initializes a new instance of the class.
39 | ///
40 | /// The name for messages produced by the logger.
41 | /// The to use.
42 | /// The to use.
43 | ///
44 | /// or is .
45 | ///
46 | public XUnitLogger(string name, IMessageSinkAccessor accessor, XUnitLoggerOptions? options)
47 | : this(name, options)
48 | {
49 | _messageSinkAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
50 | }
51 |
52 | ///
53 | /// Gets or sets the message sink message factory to use when writing to an .
54 | ///
55 | ///
56 | /// is .
57 | ///
58 | public Func MessageSinkMessageFactory
59 | {
60 | get { return _messageSinkMessageFactory; }
61 | set { _messageSinkMessageFactory = value ?? throw new ArgumentNullException(nameof(value)); }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLogger.ITestOutputHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace MartinCostello.Logging.XUnit;
7 |
8 | ///
9 | /// A class representing an to use with xunit.
10 | ///
11 | public partial class XUnitLogger
12 | {
13 | ///
14 | /// The to use. This field is read-only.
15 | ///
16 | private readonly ITestOutputHelperAccessor? _outputHelperAccessor;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The name for messages produced by the logger.
22 | /// The to use.
23 | /// The to use.
24 | ///
25 | /// or is .
26 | ///
27 | public XUnitLogger(string name, ITestOutputHelper outputHelper, XUnitLoggerOptions? options)
28 | : this(name, new TestOutputHelperAccessor(outputHelper), options)
29 | {
30 | }
31 |
32 | ///
33 | /// Initializes a new instance of the class.
34 | ///
35 | /// The name for messages produced by the logger.
36 | /// The to use.
37 | /// The to use.
38 | ///
39 | /// or is .
40 | ///
41 | public XUnitLogger(string name, ITestOutputHelperAccessor accessor, XUnitLoggerOptions? options)
42 | : this(name, options)
43 | {
44 | _outputHelperAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.Text;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace MartinCostello.Logging.XUnit;
8 |
9 | ///
10 | /// A class representing an to use with xunit.
11 | ///
12 | public partial class XUnitLogger : ILogger
13 | {
14 | //// Based on https://github.com/dotnet/runtime/blob/65067052e433eda400c5e7cc9f7b21c84640f901/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs#L41-L66
15 |
16 | ///
17 | /// The padding to use for log levels.
18 | ///
19 | private const string LogLevelPadding = ": ";
20 |
21 | ///
22 | /// The padding to use for messages. This field is read-only.
23 | ///
24 | private static readonly string MessagePadding = new(' ', GetLogLevelString(LogLevel.Debug).Length + LogLevelPadding.Length);
25 |
26 | ///
27 | /// The padding to use for new lines. This field is read-only.
28 | ///
29 | private static readonly string NewLineWithMessagePadding = Environment.NewLine + MessagePadding;
30 |
31 | ///
32 | /// The current builder to use to generate log messages.
33 | ///
34 | [ThreadStatic]
35 | private static StringBuilder? _logBuilder;
36 |
37 | ///
38 | /// The format string used to format the timestamp in log messages.
39 | ///
40 | private readonly string _timestampFormat;
41 |
42 | #if NET8_0_OR_GREATER
43 |
44 | ///
45 | /// The time provider used in log messages.
46 | ///
47 | private readonly TimeProvider _timeProvider;
48 |
49 | #endif
50 |
51 | ///
52 | /// Gets or sets the filter to use.
53 | ///
54 | private Func _filter;
55 |
56 | ///
57 | /// Initializes a new instance of the class.
58 | ///
59 | /// The name for messages produced by the logger.
60 | /// The to use.
61 | private XUnitLogger(string name, XUnitLoggerOptions? options)
62 | {
63 | Name = name ?? throw new ArgumentNullException(nameof(name));
64 |
65 | _filter = options?.Filter ?? (static (_, _) => true);
66 | _messageSinkMessageFactory = options?.MessageSinkMessageFactory ?? (static (message) => new DiagnosticMessage(message));
67 | _timestampFormat = options?.TimestampFormat ?? "u";
68 | IncludeScopes = options?.IncludeScopes ?? false;
69 | #if NET8_0_OR_GREATER
70 | _timeProvider = options?.TimeProvider ?? TimeProvider.System;
71 | Clock = () => _timeProvider.GetLocalNow();
72 | #else
73 | Clock = static () => DateTimeOffset.Now;
74 | #endif
75 | }
76 |
77 | ///
78 | /// Gets or sets the category filter to apply to logs.
79 | ///
80 | ///
81 | /// is .
82 | ///
83 | public Func Filter
84 | {
85 | get => _filter;
86 | set => _filter = value ?? throw new ArgumentNullException(nameof(value));
87 | }
88 |
89 | ///
90 | /// Gets or sets a value indicating whether to include scopes.
91 | ///
92 | public bool IncludeScopes { get; set; }
93 |
94 | ///
95 | /// Gets the name of the logger.
96 | ///
97 | public string Name { get; }
98 |
99 | ///
100 | /// Gets or sets a delegate representing the system clock.
101 | ///
102 | internal Func Clock { get; set; }
103 |
104 | ///
105 | public IDisposable? BeginScope(TState state)
106 | where TState : notnull
107 | {
108 | #if NET
109 | ArgumentNullException.ThrowIfNull(state);
110 | #else
111 | if (state == null)
112 | {
113 | throw new ArgumentNullException(nameof(state));
114 | }
115 | #endif
116 |
117 | return XUnitLogScope.Push(state);
118 | }
119 |
120 | ///
121 | public bool IsEnabled(LogLevel logLevel)
122 | {
123 | if (logLevel == LogLevel.None)
124 | {
125 | return false;
126 | }
127 |
128 | return Filter(Name, logLevel);
129 | }
130 |
131 | ///
132 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
133 | {
134 | if (!IsEnabled(logLevel))
135 | {
136 | return;
137 | }
138 |
139 | #if NET
140 | ArgumentNullException.ThrowIfNull(formatter);
141 | #else
142 | if (formatter == null)
143 | {
144 | throw new ArgumentNullException(nameof(formatter));
145 | }
146 | #endif
147 |
148 | string? message = formatter(state, exception);
149 |
150 | if (!string.IsNullOrEmpty(message) || exception != null)
151 | {
152 | WriteMessage(logLevel, eventId.Id, message, exception);
153 | }
154 | }
155 |
156 | ///
157 | /// Writes a message to the or associated with the instance.
158 | ///
159 | /// The message to write will be written on this level.
160 | /// The Id of the event.
161 | /// The message to write.
162 | /// The exception related to this message.
163 | public virtual void WriteMessage(LogLevel logLevel, int eventId, string? message, Exception? exception)
164 | {
165 | ITestOutputHelper? outputHelper = _outputHelperAccessor?.OutputHelper;
166 | IMessageSink? messageSink = _messageSinkAccessor?.MessageSink;
167 |
168 | if (outputHelper is null && messageSink is null)
169 | {
170 | return;
171 | }
172 |
173 | StringBuilder? logBuilder = _logBuilder;
174 | _logBuilder = null;
175 |
176 | logBuilder ??= new StringBuilder();
177 |
178 | string logLevelString = GetLogLevelString(logLevel);
179 |
180 | logBuilder.Append(LogLevelPadding);
181 | logBuilder.Append(Name);
182 | logBuilder.Append('[');
183 | logBuilder.Append(eventId);
184 | logBuilder.Append(']');
185 | logBuilder.AppendLine();
186 |
187 | if (IncludeScopes)
188 | {
189 | GetScopeInformation(logBuilder);
190 | }
191 |
192 | bool hasMessage = !string.IsNullOrEmpty(message);
193 |
194 | if (hasMessage)
195 | {
196 | logBuilder.Append(MessagePadding);
197 |
198 | int length = logBuilder.Length;
199 | logBuilder.Append(message);
200 | logBuilder.Replace(Environment.NewLine, NewLineWithMessagePadding, length, message!.Length);
201 | }
202 |
203 | if (exception != null)
204 | {
205 | if (hasMessage)
206 | {
207 | logBuilder.AppendLine();
208 | }
209 |
210 | logBuilder.Append(exception);
211 | }
212 |
213 | // Prefix the formatted message so it renders like this:
214 | // [{timestamp}] {logLevelString}{message}
215 | logBuilder.Insert(0, logLevelString);
216 | logBuilder.Insert(0, "] ");
217 | logBuilder.Insert(0, Clock().ToString(_timestampFormat, CultureInfo.CurrentCulture));
218 | logBuilder.Insert(0, '[');
219 |
220 | string line = logBuilder.ToString();
221 |
222 | try
223 | {
224 | outputHelper?.WriteLine(line);
225 |
226 | if (messageSink != null)
227 | {
228 | var sinkMessage = _messageSinkMessageFactory(line);
229 | messageSink.OnMessage(sinkMessage);
230 | }
231 | }
232 | catch (InvalidOperationException)
233 | {
234 | // Ignore exception if the application tries to log after the test ends
235 | // but before the ITestOutputHelper is detached, e.g. "There is no currently active test."
236 | }
237 |
238 | logBuilder.Clear();
239 |
240 | if (logBuilder.Capacity > 1024)
241 | {
242 | logBuilder.Capacity = 1024;
243 | }
244 |
245 | _logBuilder = logBuilder;
246 | }
247 |
248 | ///
249 | /// Returns the string to use for the specified logging level.
250 | ///
251 | /// The log level to get the representation for.
252 | ///
253 | /// A containing the text representation of .
254 | ///
255 | private static string GetLogLevelString(LogLevel logLevel)
256 | {
257 | return logLevel switch
258 | {
259 | LogLevel.Critical => "crit",
260 | LogLevel.Debug => "dbug",
261 | LogLevel.Error => "fail",
262 | LogLevel.Information => "info",
263 | LogLevel.Trace => "trce",
264 | LogLevel.Warning => "warn",
265 | _ => throw new ArgumentOutOfRangeException(nameof(logLevel)),
266 | };
267 | }
268 |
269 | ///
270 | /// Gets the scope information for the current operation.
271 | ///
272 | /// The to write the scope to.
273 | private static void GetScopeInformation(StringBuilder builder)
274 | {
275 | var current = XUnitLogScope.Current;
276 |
277 | var stack = new Stack();
278 | while (current != null)
279 | {
280 | stack.Push(current);
281 | current = current.Parent;
282 | }
283 |
284 | var depth = 0;
285 | static string DepthPadding(int depth) => new(' ', depth * 2);
286 |
287 | while (stack.Count > 0)
288 | {
289 | var elem = stack.Pop();
290 | foreach (var property in StringifyScope(elem))
291 | {
292 | builder.Append(MessagePadding)
293 | .Append(DepthPadding(depth))
294 | .Append("=> ")
295 | .Append(property)
296 | .AppendLine();
297 | }
298 |
299 | depth++;
300 | }
301 | }
302 |
303 | ///
304 | /// Returns one or more stringified properties from the log scope.
305 | ///
306 | /// The to stringify.
307 | /// An enumeration of scope properties from the current scope.
308 | private static IEnumerable StringifyScope(XUnitLogScope scope)
309 | {
310 | if (scope.State is IEnumerable> pairs)
311 | {
312 | foreach (var pair in pairs)
313 | {
314 | yield return $"{pair.Key}: {pair.Value}";
315 | }
316 | }
317 | else if (scope.State is IEnumerable entries)
318 | {
319 | foreach (var entry in entries)
320 | {
321 | yield return entry;
322 | }
323 | }
324 | else
325 | {
326 | yield return scope.ToString();
327 | }
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLoggerExtensions.IMessageSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using MartinCostello.Logging.XUnit;
5 | using Microsoft.Extensions.DependencyInjection.Extensions;
6 |
7 | namespace Microsoft.Extensions.Logging;
8 |
9 | ///
10 | /// A class containing extension methods for configuring logging to xunit. This class cannot be inherited.
11 | ///
12 | public static partial class XUnitLoggerExtensions
13 | {
14 | ///
15 | /// Adds an xunit logger to the logging builder.
16 | ///
17 | /// The to use.
18 | /// The to use.
19 | ///
20 | /// The instance of specified by .
21 | ///
22 | ///
23 | /// or is .
24 | ///
25 | public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, IMessageSinkAccessor accessor)
26 | {
27 | #if NET
28 | ArgumentNullException.ThrowIfNull(builder);
29 | ArgumentNullException.ThrowIfNull(accessor);
30 | #else
31 | if (builder == null)
32 | {
33 | throw new ArgumentNullException(nameof(builder));
34 | }
35 |
36 | if (accessor == null)
37 | {
38 | throw new ArgumentNullException(nameof(accessor));
39 | }
40 | #endif
41 |
42 | return builder.AddXUnit(accessor, static (_) => { });
43 | }
44 |
45 | ///
46 | /// Adds an xunit logger to the logging builder.
47 | ///
48 | /// The to use.
49 | /// The to use.
50 | /// A delegate to a method to use to configure the logging options.
51 | ///
52 | /// The instance of specified by .
53 | ///
54 | ///
55 | /// , or is .
56 | ///
57 | public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, IMessageSinkAccessor accessor, Action configure)
58 | {
59 | #if NET
60 | ArgumentNullException.ThrowIfNull(builder);
61 | ArgumentNullException.ThrowIfNull(accessor);
62 | ArgumentNullException.ThrowIfNull(configure);
63 | #else
64 | if (builder == null)
65 | {
66 | throw new ArgumentNullException(nameof(builder));
67 | }
68 |
69 | if (accessor == null)
70 | {
71 | throw new ArgumentNullException(nameof(accessor));
72 | }
73 |
74 | if (configure == null)
75 | {
76 | throw new ArgumentNullException(nameof(configure));
77 | }
78 | #endif
79 |
80 | var options = new XUnitLoggerOptions();
81 |
82 | configure(options);
83 |
84 | #pragma warning disable CA2000
85 | builder.AddProvider(new XUnitLoggerProvider(accessor, options));
86 | #pragma warning restore CA2000
87 |
88 | builder.Services.TryAddSingleton(accessor);
89 |
90 | return builder;
91 | }
92 |
93 | ///
94 | /// Adds an xunit logger to the logging builder.
95 | ///
96 | /// The to use.
97 | /// The to use.
98 | ///
99 | /// The instance of specified by .
100 | ///
101 | ///
102 | /// or is .
103 | ///
104 | public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, IMessageSink messageSink)
105 | {
106 | #if NET
107 | ArgumentNullException.ThrowIfNull(builder);
108 | ArgumentNullException.ThrowIfNull(messageSink);
109 | #else
110 | if (builder == null)
111 | {
112 | throw new ArgumentNullException(nameof(builder));
113 | }
114 |
115 | if (messageSink == null)
116 | {
117 | throw new ArgumentNullException(nameof(messageSink));
118 | }
119 | #endif
120 |
121 | return builder.AddXUnit(messageSink, static (_) => { });
122 | }
123 |
124 | ///
125 | /// Adds an xunit logger to the logging builder.
126 | ///
127 | /// The to use.
128 | /// The to use.
129 | /// A delegate to a method to use to configure the logging options.
130 | ///
131 | /// The instance of specified by .
132 | ///
133 | ///
134 | /// , or is .
135 | ///
136 | public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, IMessageSink messageSink, Action configure)
137 | {
138 | #if NET
139 | ArgumentNullException.ThrowIfNull(builder);
140 | ArgumentNullException.ThrowIfNull(messageSink);
141 | ArgumentNullException.ThrowIfNull(configure);
142 | #else
143 | if (builder == null)
144 | {
145 | throw new ArgumentNullException(nameof(builder));
146 | }
147 |
148 | if (messageSink == null)
149 | {
150 | throw new ArgumentNullException(nameof(messageSink));
151 | }
152 |
153 | if (configure == null)
154 | {
155 | throw new ArgumentNullException(nameof(configure));
156 | }
157 | #endif
158 |
159 | var options = new XUnitLoggerOptions();
160 |
161 | configure(options);
162 |
163 | #pragma warning disable CA2000
164 | return builder.AddProvider(new XUnitLoggerProvider(messageSink, options));
165 | #pragma warning restore CA2000
166 | }
167 |
168 | ///
169 | /// Adds an xunit logger to the factory.
170 | ///
171 | /// The to use.
172 | /// The to use.
173 | /// The minimum to be logged.
174 | ///
175 | /// The instance of specified by .
176 | ///
177 | ///
178 | /// or is .
179 | ///
180 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink, LogLevel minLevel)
181 | {
182 | #if NET
183 | ArgumentNullException.ThrowIfNull(factory);
184 | ArgumentNullException.ThrowIfNull(messageSink);
185 | #else
186 | if (factory == null)
187 | {
188 | throw new ArgumentNullException(nameof(factory));
189 | }
190 |
191 | if (messageSink == null)
192 | {
193 | throw new ArgumentNullException(nameof(messageSink));
194 | }
195 | #endif
196 |
197 | return factory.AddXUnit(messageSink, (_, level) => level >= minLevel);
198 | }
199 |
200 | ///
201 | /// Adds an xunit logger to the factory.
202 | ///
203 | /// The to use.
204 | /// The to use.
205 | /// The category filter to apply to logs.
206 | ///
207 | /// The instance of specified by .
208 | ///
209 | ///
210 | /// , or is .
211 | ///
212 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink, Func filter)
213 | {
214 | #if NET
215 | ArgumentNullException.ThrowIfNull(factory);
216 | ArgumentNullException.ThrowIfNull(messageSink);
217 | ArgumentNullException.ThrowIfNull(filter);
218 | #else
219 | if (factory == null)
220 | {
221 | throw new ArgumentNullException(nameof(factory));
222 | }
223 |
224 | if (messageSink == null)
225 | {
226 | throw new ArgumentNullException(nameof(messageSink));
227 | }
228 |
229 | if (filter == null)
230 | {
231 | throw new ArgumentNullException(nameof(filter));
232 | }
233 | #endif
234 |
235 | return factory.AddXUnit(messageSink, (options) => options.Filter = filter);
236 | }
237 |
238 | ///
239 | /// Adds an xunit logger to the factory.
240 | ///
241 | /// The to use.
242 | /// The to use.
243 | ///
244 | /// The instance of specified by .
245 | ///
246 | ///
247 | /// or is .
248 | ///
249 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink)
250 | {
251 | #if NET
252 | ArgumentNullException.ThrowIfNull(factory);
253 | ArgumentNullException.ThrowIfNull(messageSink);
254 | #else
255 | if (factory == null)
256 | {
257 | throw new ArgumentNullException(nameof(factory));
258 | }
259 |
260 | if (messageSink == null)
261 | {
262 | throw new ArgumentNullException(nameof(messageSink));
263 | }
264 | #endif
265 |
266 | return factory.AddXUnit(messageSink, static (_) => { });
267 | }
268 |
269 | ///
270 | /// Adds an xunit logger to the factory.
271 | ///
272 | /// The to use.
273 | /// The to use.
274 | /// The options to use for logging to xunit.
275 | ///
276 | /// The instance of specified by .
277 | ///
278 | ///
279 | /// , OR is .
280 | ///
281 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink, XUnitLoggerOptions options)
282 | {
283 | #if NET
284 | ArgumentNullException.ThrowIfNull(factory);
285 | ArgumentNullException.ThrowIfNull(messageSink);
286 | ArgumentNullException.ThrowIfNull(options);
287 | #else
288 | if (factory == null)
289 | {
290 | throw new ArgumentNullException(nameof(factory));
291 | }
292 |
293 | if (messageSink == null)
294 | {
295 | throw new ArgumentNullException(nameof(messageSink));
296 | }
297 |
298 | if (options == null)
299 | {
300 | throw new ArgumentNullException(nameof(options));
301 | }
302 | #endif
303 |
304 | return factory.AddXUnit(messageSink, () => options);
305 | }
306 |
307 | ///
308 | /// Adds an xunit logger to the factory.
309 | ///
310 | /// The to use.
311 | /// The to use.
312 | /// A delegate to a method to use to configure the logging options.
313 | ///
314 | /// The instance of specified by .
315 | ///
316 | ///
317 | /// , or is .
318 | ///
319 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink, Action configure)
320 | {
321 | #if NET
322 | ArgumentNullException.ThrowIfNull(factory);
323 | ArgumentNullException.ThrowIfNull(messageSink);
324 | ArgumentNullException.ThrowIfNull(configure);
325 | #else
326 | if (factory == null)
327 | {
328 | throw new ArgumentNullException(nameof(factory));
329 | }
330 |
331 | if (messageSink == null)
332 | {
333 | throw new ArgumentNullException(nameof(messageSink));
334 | }
335 |
336 | if (configure == null)
337 | {
338 | throw new ArgumentNullException(nameof(configure));
339 | }
340 | #endif
341 |
342 | return factory.AddXUnit(messageSink, () =>
343 | {
344 | var options = new XUnitLoggerOptions();
345 | configure(options);
346 | return options;
347 | });
348 | }
349 |
350 | ///
351 | /// Adds an xunit logger to the factory.
352 | ///
353 | /// The to use.
354 | /// The to use.
355 | /// A delegate to a method that returns a configured to use.
356 | ///
357 | /// The instance of specified by .
358 | ///
359 | ///
360 | /// , or is .
361 | ///
362 | public static ILoggerFactory AddXUnit(this ILoggerFactory factory, IMessageSink messageSink, Func configure)
363 | {
364 | #if NET
365 | ArgumentNullException.ThrowIfNull(factory);
366 | ArgumentNullException.ThrowIfNull(messageSink);
367 | ArgumentNullException.ThrowIfNull(configure);
368 | #else
369 | if (factory == null)
370 | {
371 | throw new ArgumentNullException(nameof(factory));
372 | }
373 |
374 | if (messageSink == null)
375 | {
376 | throw new ArgumentNullException(nameof(messageSink));
377 | }
378 |
379 | if (configure == null)
380 | {
381 | throw new ArgumentNullException(nameof(configure));
382 | }
383 | #endif
384 |
385 | var options = configure();
386 |
387 | #pragma warning disable CA2000
388 | factory.AddProvider(new XUnitLoggerProvider(messageSink, options));
389 | #pragma warning restore CA2000
390 |
391 | return factory;
392 | }
393 | }
394 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLoggerOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.Diagnostics.CodeAnalysis;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace MartinCostello.Logging.XUnit;
8 |
9 | ///
10 | /// A class representing configuration options for logging to xunit.
11 | ///
12 | public class XUnitLoggerOptions
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | public XUnitLoggerOptions()
18 | {
19 | }
20 |
21 | ///
22 | /// Gets or sets the category filter to apply to logs.
23 | ///
24 | public Func Filter { get; set; } = static (c, l) => true; // By default log everything
25 |
26 | ///
27 | /// Gets or sets the message sink message factory to use when writing to a .
28 | ///
29 | public Func MessageSinkMessageFactory { get; set; } = static (m) => new DiagnosticMessage(m);
30 |
31 | ///
32 | /// Gets or sets a value indicating whether to include scopes.
33 | ///
34 | public bool IncludeScopes { get; set; }
35 |
36 | ///
37 | /// Gets or sets format string used to format the timestamp in log messages. Defaults to u.
38 | ///
39 | [StringSyntax(StringSyntaxAttribute.DateTimeFormat)]
40 | public string? TimestampFormat { get; set; }
41 |
42 | #if NET8_0_OR_GREATER
43 |
44 | ///
45 | /// Gets or sets the time provider used in log messages. Defaults to .
46 | ///
47 | public TimeProvider TimeProvider { get; set; } = TimeProvider.System;
48 |
49 | #endif
50 | }
51 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLoggerProvider.IMessageSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace MartinCostello.Logging.XUnit;
7 |
8 | ///
9 | /// A class representing an to use with xunit.
10 | ///
11 | public partial class XUnitLoggerProvider
12 | {
13 | ///
14 | /// The to use. This field is readonly.
15 | ///
16 | private readonly IMessageSinkAccessor? _messageSinkAccessor;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The to use.
22 | /// The options to use for logging to xunit.
23 | ///
24 | /// or is .
25 | ///
26 | public XUnitLoggerProvider(IMessageSink messageSink, XUnitLoggerOptions options)
27 | : this(new MessageSinkAccessor(messageSink), options)
28 | {
29 | }
30 |
31 | ///
32 | /// Initializes a new instance of the class.
33 | ///
34 | /// The to use.
35 | /// The options to use for logging to xunit.
36 | ///
37 | /// or is .
38 | ///
39 | public XUnitLoggerProvider(IMessageSinkAccessor accessor, XUnitLoggerOptions options)
40 | {
41 | _messageSinkAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
42 | _options = options ?? throw new ArgumentNullException(nameof(options));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLoggerProvider.ITestOutputHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace MartinCostello.Logging.XUnit;
7 |
8 | ///
9 | /// A class representing an to use with xunit.
10 | ///
11 | public partial class XUnitLoggerProvider
12 | {
13 | ///
14 | /// The to use. This field is readonly.
15 | ///
16 | private readonly ITestOutputHelperAccessor? _outputHelperAccessor;
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// The to use.
22 | /// The options to use for logging to xunit.
23 | ///
24 | /// or is .
25 | ///
26 | public XUnitLoggerProvider(ITestOutputHelper outputHelper, XUnitLoggerOptions options)
27 | : this(new TestOutputHelperAccessor(outputHelper), options)
28 | {
29 | }
30 |
31 | ///
32 | /// Initializes a new instance of the class.
33 | ///
34 | /// The to use.
35 | /// The options to use for logging to xunit.
36 | ///
37 | /// or is .
38 | ///
39 | public XUnitLoggerProvider(ITestOutputHelperAccessor accessor, XUnitLoggerOptions options)
40 | {
41 | _outputHelperAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
42 | _options = options ?? throw new ArgumentNullException(nameof(options));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Shared/XUnitLoggerProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Concurrent;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace MartinCostello.Logging.XUnit;
8 |
9 | ///
10 | /// A class representing an to use with xunit.
11 | ///
12 | [ProviderAlias("XUnit")]
13 | public partial class XUnitLoggerProvider : ILoggerProvider
14 | {
15 | ///
16 | /// The cached loggers to use for each category. This field is readonly.
17 | ///
18 | private readonly ConcurrentDictionary _loggers = [];
19 |
20 | ///
21 | /// The to use. This field is readonly.
22 | ///
23 | private readonly XUnitLoggerOptions _options;
24 |
25 | ///
26 | /// Finalizes an instance of the class.
27 | ///
28 | ~XUnitLoggerProvider()
29 | {
30 | Dispose(false);
31 | }
32 |
33 | ///
34 | public virtual ILogger CreateLogger(string categoryName)
35 | {
36 | return _loggers.GetOrAdd(categoryName, (name) =>
37 | _outputHelperAccessor is not null ?
38 | new(name, _outputHelperAccessor, _options) :
39 | new(name, _messageSinkAccessor!, _options));
40 | }
41 |
42 | ///
43 | public void Dispose()
44 | {
45 | Dispose(true);
46 | GC.SuppressFinalize(this);
47 | }
48 |
49 | ///
50 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
51 | ///
52 | ///
53 | /// to release both managed and unmanaged resources;
54 | /// to release only unmanaged resources.
55 | ///
56 | protected virtual void Dispose(bool disposing)
57 | {
58 | // Nothing to dispose of
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "https://github.com/martincostello/xunit-logging",
6 | "copyrightText": "Copyright (c) {ownerName}, {year}. All rights reserved.\nLicensed under the {licenseName} license. See the {licenseFile} file in the project root for full license information.",
7 | "documentExposedElements": true,
8 | "documentInterfaces": true,
9 | "documentInternalElements": true,
10 | "documentPrivateElements": true,
11 | "documentPrivateFields": true,
12 | "fileNamingConvention": "metadata",
13 | "xmlHeader": false,
14 | "variables": {
15 | "licenseFile": "LICENSE",
16 | "licenseName": "Apache 2.0",
17 | "ownerName": "Martin Costello",
18 | "year": "2018"
19 | }
20 | },
21 | "layoutRules": {
22 | "newlineAtEndOfFile": "require"
23 | },
24 | "maintainabilityRules": {},
25 | "namingRules": {
26 | "allowCommonHungarianPrefixes": true,
27 | "allowedHungarianPrefixes": []
28 | },
29 | "orderingRules": {
30 | "elementOrder": [
31 | "kind",
32 | "accessibility",
33 | "constant",
34 | "static",
35 | "readonly"
36 | ],
37 | "systemUsingDirectivesFirst": true,
38 | "usingDirectivesPlacement": "outsideNamespace"
39 | },
40 | "readabilityRules": {},
41 | "spacingRules": {}
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Logging.XUnit.Tests/MartinCostello.Logging.XUnit.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tests for MartinCostello.Logging.XUnit.
4 | false
5 | MartinCostello.Logging.XUnit
6 | net9.0
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/Logging.XUnit.v3.Tests/MartinCostello.Logging.XUnit.v3.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(DefineConstants);XUNIT_V3
4 | Tests for MartinCostello.Logging.XUnit.v3.
5 | false
6 | Exe
7 | MartinCostello.Logging.XUnit
8 | net9.0
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tests/SampleApp/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | var app = builder.Build();
7 |
8 | app.MapGet("/api/values", () => Results.Json(new string[] { "a", "b", "c" }));
9 |
10 | app.MapGet("/api/values/{id}", (string id) => "value");
11 |
12 | app.MapPost("/api/values", (object value) => Results.NoContent());
13 |
14 | app.MapPut("/api/values/{id}", (string id) => Results.NoContent());
15 |
16 | app.MapDelete("/api/values/{id}", (string id) => Results.NoContent());
17 |
18 | app.Run();
19 |
20 | namespace SampleApp
21 | {
22 | public partial class Program
23 | {
24 | // Expose the Program class for use with WebApplicationFactory
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/SampleApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:52657",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": false,
15 | "launchUrl": "api/values",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "SampleApp": {
21 | "commandName": "SampleApp",
22 | "launchBrowser": false,
23 | "launchUrl": "api/values",
24 | "applicationUrl": "http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/SampleApp/SampleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 | $(NoWarn);CA1801;CA1822;CA1861;SA1600;SA1601
5 | net9.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/SampleApp/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tests/SampleApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/tests/Shared/AssemblyTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | public static class AssemblyTests
7 | {
8 | [Fact]
9 | public static void Library_Is_Strong_Named()
10 | {
11 | // Arrange
12 | var assembly = typeof(XUnitLoggerOptions).Assembly;
13 |
14 | // Act
15 | var name = assembly.GetName();
16 | var actual = name.GetPublicKey();
17 |
18 | // Assert
19 | actual.ShouldNotBeNull();
20 | actual.ShouldNotBeEmpty();
21 | Convert.ToHexStringLower(actual).ShouldBe("00240000048000009400000006020000002400005253413100040000010001004b0b2efbada897147aa03d2076278890aefe2f8023562336d206ec8a719b06e89461c31b43abec615918d509158629f93385930c030494509e418bf396d69ce7dbe0b5b2db1a81543ab42777cb98210677fed69dbeb3237492a7ad69e87a1911ed20eb2d7c300238dc6f6403e3d04a1351c5cb369de4e022b18fbec70f7d21ed");
22 |
23 | actual = name.GetPublicKeyToken();
24 | actual.ShouldNotBeNull();
25 | actual.ShouldNotBeEmpty();
26 | Convert.ToHexStringLower(actual).ShouldBe("9a192a7522c9e1a0");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Shared/Constructor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit;
5 |
6 | public enum Constructor
7 | {
8 | ITestOutputHelper,
9 |
10 | IMessageSink,
11 | }
12 |
--------------------------------------------------------------------------------
/tests/Shared/Examples.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace MartinCostello.Logging.XUnit;
8 |
9 | public class Examples(ITestOutputHelper outputHelper)
10 | {
11 | [Fact]
12 | public void Calculator_Sums_Two_Equal_Integers()
13 | {
14 | // Arrange using conversion to a logger
15 | var calculator = new Calculator(outputHelper.ToLogger());
16 |
17 | // Act
18 | int actual = calculator.Sum(2, 2);
19 |
20 | // Assert
21 | actual.ShouldBe(4);
22 | }
23 |
24 | [Fact]
25 | public void Calculator_Sums_Two_Different_Integers()
26 | {
27 | // Arrange using the logging provider
28 | var services = new ServiceCollection()
29 | .AddLogging((builder) => builder.AddXUnit(outputHelper))
30 | .AddSingleton();
31 |
32 | IServiceProvider provider = services.BuildServiceProvider();
33 |
34 | var calculator = provider.GetRequiredService();
35 |
36 | // Act
37 | int actual = calculator.Sum(1, 2);
38 |
39 | // Assert
40 | actual.ShouldBe(3);
41 | }
42 |
43 | private sealed class Calculator(ILogger logger)
44 | {
45 | public int Sum(int x, int y)
46 | {
47 | int sum = x + y;
48 |
49 | logger.LogInformation("The sum of {X} and {Y} is {Sum}.", x, y, sum);
50 |
51 | return sum;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/DatabaseFixture.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace MartinCostello.Logging.XUnit.Integration;
7 |
8 | public sealed class DatabaseFixture : IAsyncLifetime
9 | {
10 | private readonly ILogger _initializeLogger;
11 | private readonly ILogger _disposeLogger;
12 | private string? _connectionString;
13 |
14 | public DatabaseFixture(IMessageSink messageSink)
15 | {
16 | using var loggerFactory = new LoggerFactory();
17 |
18 | _initializeLogger = loggerFactory.AddXUnit(messageSink, c => c.MessageSinkMessageFactory = CreateMessage).CreateLogger();
19 | _disposeLogger = messageSink.ToLogger();
20 |
21 | #if XUNIT_V3
22 | static IMessageSinkMessage CreateMessage(string message) => new DiagnosticMessage() { Message = message };
23 | #else
24 | static IMessageSinkMessage CreateMessage(string message) => new PrintableDiagnosticMessage(message);
25 | #endif
26 | }
27 |
28 | public string ConnectionString => _connectionString ?? throw new InvalidOperationException("The connection string is only available after InitializeAsync has completed.");
29 |
30 | #if XUNIT_V3
31 | ValueTask IAsyncLifetime.InitializeAsync()
32 | {
33 | _initializeLogger.LogInformation("Initializing database");
34 | _connectionString = "Server=localhost";
35 | return ValueTask.CompletedTask;
36 | }
37 |
38 | ValueTask IAsyncDisposable.DisposeAsync()
39 | {
40 | _disposeLogger.LogInformation("Disposing database");
41 | return ValueTask.CompletedTask;
42 | }
43 | #else
44 | Task IAsyncLifetime.InitializeAsync()
45 | {
46 | _initializeLogger.LogInformation("Initializing database");
47 | _connectionString = "Server=localhost";
48 | return Task.CompletedTask;
49 | }
50 |
51 | Task IAsyncLifetime.DisposeAsync()
52 | {
53 | _disposeLogger.LogInformation("Disposing database");
54 | return Task.CompletedTask;
55 | }
56 | #endif
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/DatabaseTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit.Integration;
5 |
6 | public class DatabaseTests(DatabaseFixture databaseFixture) : IClassFixture
7 | {
8 | public DatabaseFixture DatabaseFixture { get; } = databaseFixture;
9 |
10 | [Fact]
11 | public void Run_Database_Test()
12 | {
13 | DatabaseFixture.ConnectionString.ShouldNotBeEmpty();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/HttpApplicationTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using System.Net.Http.Json;
5 | using System.Net.Mime;
6 | using System.Text;
7 |
8 | namespace MartinCostello.Logging.XUnit.Integration;
9 |
10 | [Collection(HttpServerCollection.Name)]
11 | public sealed class HttpApplicationTests : IDisposable
12 | {
13 | public HttpApplicationTests(HttpServerFixture fixture, ITestOutputHelper outputHelper)
14 | {
15 | Fixture = fixture;
16 | Fixture.OutputHelper = outputHelper;
17 | }
18 |
19 | private HttpServerFixture Fixture { get; }
20 |
21 | public void Dispose()
22 | {
23 | Fixture.OutputHelper = null;
24 | }
25 |
26 | [Fact]
27 | public async Task Http_Get_Many()
28 | {
29 | // Arrange
30 | using var httpClient = Fixture.CreateClient();
31 |
32 | // Act
33 | #if XUNIT_V3
34 | using var response = await httpClient.GetAsync("api/values", TestContext.Current.CancellationToken);
35 | #else
36 | using var response = await httpClient.GetAsync("api/values");
37 | #endif
38 |
39 | // Assert
40 | response.IsSuccessStatusCode.ShouldBeTrue();
41 | }
42 |
43 | [Fact]
44 | public async Task Http_Get_Single()
45 | {
46 | // Arrange
47 | using var httpClient = Fixture.CreateClient();
48 |
49 | // Act
50 | #if XUNIT_V3
51 | using var response = await httpClient.GetAsync("api/values/a", TestContext.Current.CancellationToken);
52 | #else
53 | using var response = await httpClient.GetAsync("api/values/a");
54 | #endif
55 |
56 | // Assert
57 | response.IsSuccessStatusCode.ShouldBeTrue();
58 | }
59 |
60 | [Fact]
61 | public async Task Http_Post()
62 | {
63 | // Arrange
64 | using var httpClient = Fixture.CreateClient();
65 |
66 | // Act
67 | #if XUNIT_V3
68 | using var response = await httpClient.PostAsJsonAsync("api/values", new { }, TestContext.Current.CancellationToken);
69 | #else
70 | using var response = await httpClient.PostAsJsonAsync("api/values", new { });
71 | #endif
72 |
73 | // Assert
74 | response.IsSuccessStatusCode.ShouldBeTrue();
75 | }
76 |
77 | [Fact]
78 | public async Task Http_Put()
79 | {
80 | // Arrange
81 | using var httpClient = Fixture.CreateClient();
82 |
83 | // Act
84 | using var content = new StringContent(@"""d""", Encoding.UTF8, MediaTypeNames.Application.Json);
85 | #if XUNIT_V3
86 | using var response = await httpClient.PutAsync("api/values/d", content, TestContext.Current.CancellationToken);
87 | #else
88 | using var response = await httpClient.PutAsync("api/values/d", content);
89 | #endif
90 |
91 | // Assert
92 | response.IsSuccessStatusCode.ShouldBeTrue();
93 | }
94 |
95 | [Fact]
96 | public async Task Http_Delete()
97 | {
98 | // Arrange
99 | using var httpClient = Fixture.CreateClient();
100 |
101 | // Act
102 | #if XUNIT_V3
103 | using var response = await httpClient.DeleteAsync("api/values/d", TestContext.Current.CancellationToken);
104 | #else
105 | using var response = await httpClient.DeleteAsync("api/values/d");
106 | #endif
107 |
108 | // Assert
109 | response.IsSuccessStatusCode.ShouldBeTrue();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/HttpServerCollection.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | namespace MartinCostello.Logging.XUnit.Integration;
5 |
6 | ///
7 | /// A class representing the collection fixture for an HTTP server. This class cannot be inherited.
8 | ///
9 | [CollectionDefinition(Name)]
10 | public sealed class HttpServerCollection : ICollectionFixture
11 | {
12 | ///
13 | /// The name of the test fixture.
14 | ///
15 | public const string Name = "HTTP server collection";
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/HttpServerFixture.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.AspNetCore.Mvc.Testing;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace MartinCostello.Logging.XUnit.Integration;
9 |
10 | ///
11 | /// A test fixture representing an HTTP server hosting the sample application. This class cannot be inherited.
12 | ///
13 | public sealed class HttpServerFixture : WebApplicationFactory, ITestOutputHelperAccessor
14 | {
15 | ///
16 | public ITestOutputHelper? OutputHelper { get; set; }
17 |
18 | ///
19 | protected override void ConfigureWebHost(IWebHostBuilder builder)
20 | => builder.ConfigureLogging((p) => p.AddXUnit(this));
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Shared/Integration/PrintableDiagnosticMessage.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | #if !XUNIT_V3
5 |
6 | namespace MartinCostello.Logging.XUnit.Integration;
7 |
8 | ///
9 | /// See https://github.com/xunit/xunit/pull/2148#issuecomment-839838421.
10 | ///
11 | internal sealed class PrintableDiagnosticMessage(string message) : DiagnosticMessage(message)
12 | {
13 | public override string ToString() => Message;
14 | }
15 | #endif
16 |
--------------------------------------------------------------------------------
/tests/Shared/IntegrationTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 | using NSubstitute;
7 |
8 | namespace MartinCostello.Logging.XUnit;
9 |
10 | public static class IntegrationTests
11 | {
12 | [Fact]
13 | public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelper()
14 | {
15 | // Arrange
16 | var outputHelper = Substitute.For();
17 | var logger = BootstrapBuilder((builder) => builder.AddXUnit(outputHelper));
18 |
19 | // Act
20 | logger.LogError("This is a brand new problem, a problem without any clues.");
21 | logger.LogInformation("If you know the clues, it's easy to get through.");
22 |
23 | // Assert
24 | outputHelper.Received(2).WriteLine(Arg.Is((p) => p != null));
25 | }
26 |
27 | [Fact]
28 | public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelper_With_Configuration()
29 | {
30 | // Arrange
31 | var outputHelper = Substitute.For();
32 |
33 | var logger = BootstrapBuilder(
34 | (builder) =>
35 | {
36 | builder.AddXUnit(
37 | outputHelper,
38 | (options) => options.Filter = (_, level) => level >= LogLevel.Error);
39 | });
40 |
41 | // Act
42 | logger.LogError("This is a brand new problem, a problem without any clues.");
43 | logger.LogTrace("If you know the clues, it's easy to get through.");
44 |
45 | // Assert
46 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
47 | }
48 |
49 | [Fact]
50 | public static void Can_Configure_xunit_For_ILoggerBuilderAccessor_TestOutputHelper()
51 | {
52 | // Arrange
53 | var outputHelper = Substitute.For();
54 |
55 | var accessor = Substitute.For();
56 | accessor.OutputHelper.Returns(outputHelper);
57 |
58 | var logger = BootstrapBuilder((builder) => builder.AddXUnit(accessor));
59 |
60 | // Act
61 | logger.LogError("This is a brand new problem, a problem without any clues.");
62 | logger.LogInformation("If you know the clues, it's easy to get through.");
63 |
64 | // Assert
65 | outputHelper.Received(2).WriteLine(Arg.Is((p) => p != null));
66 | }
67 |
68 | [Fact]
69 | public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelperAccessor_With_Configuration()
70 | {
71 | // Arrange
72 | var outputHelper = Substitute.For();
73 |
74 | var accessor = Substitute.For();
75 | accessor.OutputHelper.Returns(outputHelper);
76 |
77 | var logger = BootstrapBuilder(
78 | (builder) =>
79 | {
80 | builder.AddXUnit(
81 | outputHelper,
82 | (options) => options.Filter = (_, level) => level >= LogLevel.Error);
83 | });
84 |
85 | // Act
86 | logger.LogError("This is a brand new problem, a problem without any clues.");
87 | logger.LogTrace("If you know the clues, it's easy to get through.");
88 |
89 | // Assert
90 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
91 | }
92 |
93 | [Fact]
94 | public static void Can_Configure_xunit_For_ILoggerFactory()
95 | {
96 | // Arrange
97 | var outputHelper = Substitute.For();
98 | var logger = BootstrapFactory((builder) => builder.AddXUnit(outputHelper));
99 |
100 | // Act
101 | logger.LogError("This is a brand new problem, a problem without any clues.");
102 | logger.LogInformation("If you know the clues, it's easy to get through.");
103 |
104 | // Assert
105 | outputHelper.Received(2).WriteLine(Arg.Is((p) => p != null));
106 | }
107 |
108 | [Fact]
109 | public static void Can_Configure_xunit_For_ILoggerFactory_With_Filter()
110 | {
111 | // Arrange
112 | var outputHelper = Substitute.For();
113 | var logger = BootstrapFactory((builder) => builder.AddXUnit(outputHelper, (_, level) => level >= LogLevel.Error));
114 |
115 | // Act
116 | logger.LogError("This is a brand new problem, a problem without any clues.");
117 | logger.LogWarning("If you know the clues, it's easy to get through.");
118 |
119 | // Assert
120 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
121 | }
122 |
123 | [Fact]
124 | public static void Can_Configure_xunit_For_ILoggerFactory_With_Minimum_Level()
125 | {
126 | // Arrange
127 | var outputHelper = Substitute.For();
128 | var logger = BootstrapFactory((builder) => builder.AddXUnit(outputHelper, LogLevel.Information));
129 |
130 | // Act
131 | logger.LogError("This is a brand new problem, a problem without any clues.");
132 | logger.LogTrace("If you know the clues, it's easy to get through.");
133 |
134 | // Assert
135 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
136 | }
137 |
138 | [Fact]
139 | public static void Can_Configure_xunit_For_ILoggerFactory_With_Options()
140 | {
141 | // Arrange
142 | var outputHelper = Substitute.For();
143 |
144 | var options = new XUnitLoggerOptions()
145 | {
146 | Filter = (_, level) => level >= LogLevel.Error,
147 | };
148 |
149 | var logger = BootstrapFactory((builder) => builder.AddXUnit(outputHelper, options));
150 |
151 | // Act
152 | logger.LogError("This is a brand new problem, a problem without any clues.");
153 | logger.LogWarning("If you know the clues, it's easy to get through.");
154 |
155 | // Assert
156 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
157 | }
158 |
159 | [Fact]
160 | public static void Can_Configure_xunit_For_ILoggerFactory_With_Options_Factory()
161 | {
162 | // Arrange
163 | var outputHelper = Substitute.For();
164 |
165 | var options = new XUnitLoggerOptions()
166 | {
167 | Filter = (_, level) => level >= LogLevel.Error,
168 | TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff",
169 | };
170 |
171 | var logger = BootstrapFactory((builder) => builder.AddXUnit(outputHelper, () => options));
172 |
173 | // Act
174 | logger.LogError("This is a brand new problem, a problem without any clues.");
175 | logger.LogWarning("If you know the clues, it's easy to get through.");
176 |
177 | // Assert
178 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
179 | }
180 |
181 | [Fact]
182 | public static void Can_Configure_xunit_For_ILoggerFactory_With_Configure_Options()
183 | {
184 | // Arrange
185 | var outputHelper = Substitute.For();
186 | var logger = BootstrapFactory(
187 | (builder) =>
188 | {
189 | builder.AddXUnit(
190 | outputHelper,
191 | (options) => options.Filter = (_, level) => level >= LogLevel.Error);
192 | });
193 |
194 | // Act
195 | logger.LogError("This is a brand new problem, a problem without any clues.");
196 | logger.LogWarning("If you know the clues, it's easy to get through.");
197 |
198 | // Assert
199 | outputHelper.Received(1).WriteLine(Arg.Is((p) => p != null));
200 | }
201 |
202 | [Fact]
203 | public static void Can_Configure_xunit_For_ILoggerBuilder()
204 | {
205 | // Arrange
206 | var serviceProvider = new ServiceCollection()
207 | .AddLogging((builder) => builder.AddXUnit())
208 | .BuildServiceProvider();
209 |
210 | var outputHelper = Substitute.For();
211 |
212 | serviceProvider.GetRequiredService().OutputHelper = outputHelper;
213 |
214 | var logger = serviceProvider.GetRequiredService>();
215 |
216 | // Act
217 | logger.LogError("This is a brand new problem, a problem without any clues.");
218 | logger.LogInformation("If you know the clues, it's easy to get through.");
219 |
220 | // Assert
221 | outputHelper.Received(2).WriteLine(Arg.Is((p) => p != null));
222 | }
223 |
224 | private static ILogger BootstrapBuilder(Action configure)
225 | {
226 | return new ServiceCollection()
227 | .AddLogging(configure)
228 | .BuildServiceProvider()
229 | .GetRequiredService>();
230 | }
231 |
232 | private static ILogger BootstrapFactory(Action configure)
233 | {
234 | var services = new ServiceCollection()
235 | .AddLogging()
236 | .BuildServiceProvider();
237 |
238 | var factory = services.GetRequiredService();
239 |
240 | configure(factory);
241 |
242 | return factory.CreateLogger();
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/tests/Shared/XUnitLoggerExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Extensions.Logging.Abstractions;
7 | using NSubstitute;
8 |
9 | namespace MartinCostello.Logging.XUnit;
10 |
11 | public static class XUnitLoggerExtensionsTests
12 | {
13 | [Fact]
14 | public static void AddXUnit_TestOutputHelper_For_ILoggerBuilder_Validates_Parameters()
15 | {
16 | // Arrange
17 | var builder = Substitute.For();
18 | var outputHelper = Substitute.For();
19 | var accessor = Substitute.For();
20 |
21 | ILoggingBuilder nullBuilder = null!;
22 | ITestOutputHelperAccessor nullAccessor = null!;
23 | ITestOutputHelper nullHelper = null!;
24 |
25 | // Act and Assert
26 | Assert.Throws("builder", nullBuilder.AddXUnit);
27 | Assert.Throws("builder", () => nullBuilder.AddXUnit(outputHelper));
28 | Assert.Throws("builder", () => nullBuilder.AddXUnit(outputHelper, ConfigureAction));
29 | Assert.Throws("builder", () => nullBuilder.AddXUnit(accessor));
30 | Assert.Throws("builder", () => nullBuilder.AddXUnit(accessor, ConfigureAction));
31 | Assert.Throws("accessor", () => builder.AddXUnit(nullAccessor));
32 | Assert.Throws("accessor", () => builder.AddXUnit(nullAccessor, ConfigureAction));
33 | Assert.Throws("outputHelper", () => builder.AddXUnit(nullHelper));
34 | Assert.Throws("outputHelper", () => builder.AddXUnit(nullHelper, ConfigureAction));
35 | Assert.Throws("configure", () => builder.AddXUnit(outputHelper, null!));
36 | Assert.Throws("configure", () => builder.AddXUnit(accessor, null!));
37 | }
38 |
39 | [Fact]
40 | public static void AddXUnit_MessageSink_For_ILoggerBuilder_Validates_Parameters()
41 | {
42 | // Arrange
43 | var builder = Substitute.For();
44 | var messageSink = Substitute.For();
45 | var accessor = Substitute.For();
46 |
47 | ILoggingBuilder nullBuilder = null!;
48 | IMessageSinkAccessor nullAccessor = null!;
49 | IMessageSink nullSink = null!;
50 |
51 | // Act and Assert
52 | Assert.Throws("builder", () => nullBuilder.AddXUnit(messageSink));
53 | Assert.Throws("builder", () => nullBuilder.AddXUnit(messageSink, ConfigureAction));
54 | Assert.Throws("builder", () => nullBuilder.AddXUnit(accessor));
55 | Assert.Throws("builder", () => nullBuilder.AddXUnit(accessor, ConfigureAction));
56 | Assert.Throws("accessor", () => builder.AddXUnit(nullAccessor));
57 | Assert.Throws("accessor", () => builder.AddXUnit(nullAccessor, ConfigureAction));
58 | Assert.Throws("messageSink", () => builder.AddXUnit(nullSink));
59 | Assert.Throws("messageSink", () => builder.AddXUnit(nullSink, ConfigureAction));
60 | Assert.Throws("configure", () => builder.AddXUnit(messageSink, null!));
61 | Assert.Throws("configure", () => builder.AddXUnit(accessor, null!));
62 | }
63 |
64 | [Fact]
65 | public static void AddXUnit_TestOutputHelper_For_ILoggerFactory_Validates_Parameters()
66 | {
67 | // Arrange
68 | ILoggerFactory factory = NullLoggerFactory.Instance;
69 | var logLevel = LogLevel.Information;
70 | var outputHelper = Substitute.For();
71 | var options = new XUnitLoggerOptions();
72 |
73 | ILoggerFactory nullFactory = null!;
74 | ITestOutputHelper nullHelper = null!;
75 |
76 | // Act and Assert
77 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper));
78 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper, options));
79 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper, ConfigureAction));
80 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper, ConfigureFunction));
81 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper, Filter));
82 | Assert.Throws("factory", () => nullFactory.AddXUnit(outputHelper, logLevel));
83 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper));
84 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper, ConfigureAction));
85 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper, ConfigureFunction));
86 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper, Filter));
87 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper, logLevel));
88 | Assert.Throws("outputHelper", () => factory.AddXUnit(nullHelper, options));
89 | Assert.Throws("options", () => factory.AddXUnit(outputHelper, (null as XUnitLoggerOptions)!));
90 | Assert.Throws("configure", () => factory.AddXUnit(outputHelper, (null as Action)!));
91 | Assert.Throws("configure", () => factory.AddXUnit(outputHelper, (null as Func)!));
92 | Assert.Throws("filter", () => factory.AddXUnit(outputHelper, (null as Func)!));
93 | }
94 |
95 | [Fact]
96 | public static void AddXUnit_MessageSink_For_ILoggerFactory_Validates_Parameters()
97 | {
98 | // Arrange
99 | ILoggerFactory factory = NullLoggerFactory.Instance;
100 | var logLevel = LogLevel.Information;
101 | var messageSink = Substitute.For();
102 | var options = new XUnitLoggerOptions();
103 |
104 | ILoggerFactory nullFactory = null!;
105 | IMessageSink nullSink = null!;
106 |
107 | // Act and Assert
108 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink));
109 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink, options));
110 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink, ConfigureAction));
111 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink, ConfigureFunction));
112 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink, Filter));
113 | Assert.Throws("factory", () => nullFactory.AddXUnit(messageSink, logLevel));
114 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink));
115 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink, ConfigureAction));
116 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink, ConfigureFunction));
117 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink, Filter));
118 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink, logLevel));
119 | Assert.Throws("messageSink", () => factory.AddXUnit(nullSink, options));
120 | Assert.Throws("options", () => factory.AddXUnit(messageSink, (null as XUnitLoggerOptions)!));
121 | Assert.Throws("configure", () => factory.AddXUnit(messageSink, (null as Action)!));
122 | Assert.Throws("configure", () => factory.AddXUnit(messageSink, (null as Func)!));
123 | Assert.Throws("filter", () => factory.AddXUnit(messageSink, (null as Func)!));
124 | }
125 |
126 | [Fact]
127 | public static void ToLoggerFactory_Validates_Parameters()
128 | {
129 | // Arrange
130 | ITestOutputHelper outputHelper = null!;
131 | IMessageSink messageSink = null!;
132 |
133 | // Act and Assert
134 | Assert.Throws("outputHelper", outputHelper.ToLoggerFactory);
135 | Assert.Throws("messageSink", messageSink.ToLoggerFactory);
136 | }
137 |
138 | [Fact]
139 | public static void AddXUnit_Registers_Services()
140 | {
141 | // Arrange
142 | var services = new ServiceCollection();
143 |
144 | // Act
145 | services.AddLogging(c => c.AddXUnit());
146 |
147 | // Assert
148 | var serviceProvider = services.BuildServiceProvider();
149 | serviceProvider.GetService().ShouldBeOfType();
150 | serviceProvider.GetService().ShouldBeOfType();
151 | }
152 |
153 | [Fact]
154 | public static void AddXUnit_ITestOutputHelperAccessor_Registers_Services()
155 | {
156 | // Arrange
157 | var services = new ServiceCollection();
158 | var accessor = Substitute.For();
159 |
160 | // Act
161 | services.AddLogging(c => c.AddXUnit(accessor));
162 |
163 | // Assert
164 | var serviceProvider = services.BuildServiceProvider();
165 | serviceProvider.GetService().ShouldBeOfType();
166 | serviceProvider.GetService().ShouldBe(accessor);
167 | }
168 |
169 | [Fact]
170 | public static void AddXUnit_IMessageSinkAccessor_Registers_Services()
171 | {
172 | // Arrange
173 | var services = new ServiceCollection();
174 | var accessor = Substitute.For();
175 |
176 | // Act
177 | services.AddLogging(c => c.AddXUnit(accessor));
178 |
179 | // Assert
180 | var serviceProvider = services.BuildServiceProvider();
181 | serviceProvider.GetService().ShouldBeOfType();
182 | serviceProvider.GetService().ShouldBe(accessor);
183 | }
184 |
185 | [Fact]
186 | public static void AddXUnit_ITestOutputHelper_Registers_Services()
187 | {
188 | // Arrange
189 | var services = new ServiceCollection();
190 | var testOutputHelper = Substitute.For();
191 |
192 | // Act
193 | services.AddLogging(c => c.AddXUnit(testOutputHelper));
194 |
195 | // Assert
196 | var serviceProvider = services.BuildServiceProvider();
197 | serviceProvider.GetService().ShouldBeOfType();
198 | }
199 |
200 | [Fact]
201 | public static void AddXUnit_IMessageSink_Registers_Services()
202 | {
203 | // Arrange
204 | var services = new ServiceCollection();
205 | var messageSink = Substitute.For();
206 |
207 | // Act
208 | services.AddLogging(c => c.AddXUnit(messageSink));
209 |
210 | // Assert
211 | var serviceProvider = services.BuildServiceProvider();
212 | serviceProvider.GetService().ShouldBeOfType();
213 | }
214 |
215 | [Fact]
216 | public static void AddXUnit_IMessageSink_With_LogLevel_Works()
217 | {
218 | // Arrange
219 | var factory = NullLoggerFactory.Instance;
220 | var messageSink = Substitute.For();
221 | var minLevel = LogLevel.Debug;
222 |
223 | // Act
224 | factory.AddXUnit(messageSink, minLevel);
225 |
226 | // Assert
227 | ILogger logger = factory.CreateLogger("SomeLogger");
228 | logger.LogInformation("Some message");
229 | }
230 |
231 | [Fact]
232 | public static void AddXUnit_IMessageSink_With_Filter_Works()
233 | {
234 | // Arrange
235 | var factory = NullLoggerFactory.Instance;
236 | var messageSink = Substitute.For();
237 |
238 | // Act
239 | factory.AddXUnit(messageSink, (_) => { });
240 |
241 | // Assert
242 | ILogger logger = factory.CreateLogger("SomeLogger");
243 | logger.LogInformation("Some message");
244 | }
245 |
246 | [Fact]
247 | public static void AddXUnit_IMessageSink_With_Options_Works()
248 | {
249 | // Arrange
250 | var factory = NullLoggerFactory.Instance;
251 | var messageSink = Substitute.For();
252 | var options = new XUnitLoggerOptions();
253 |
254 | // Act
255 | factory.AddXUnit(messageSink, options);
256 |
257 | // Assert
258 | ILogger logger = factory.CreateLogger("SomeLogger");
259 | logger.LogInformation("Some message");
260 | }
261 |
262 | private static void ConfigureAction(XUnitLoggerOptions options)
263 | {
264 | }
265 |
266 | private static XUnitLoggerOptions ConfigureFunction() => new();
267 |
268 | private static bool Filter(string? categoryName, LogLevel level) => true;
269 | }
270 |
--------------------------------------------------------------------------------
/tests/Shared/XUnitLoggerProviderTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Martin Costello, 2018. All rights reserved.
2 | // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 | using NSubstitute;
6 |
7 | namespace MartinCostello.Logging.XUnit;
8 |
9 | public static class XUnitLoggerProviderTests
10 | {
11 | [Fact]
12 | public static void XUnitLoggerProvider_TestOutputHelper_Constructor_Validates_Parameters()
13 | {
14 | // Arrange
15 | var outputHelper = Substitute.For();
16 | var accessor = Substitute.For();
17 | var options = new XUnitLoggerOptions();
18 |
19 | // Act and Assert
20 | Assert.Throws("outputHelper", () => new XUnitLoggerProvider((null as ITestOutputHelper)!, options));
21 | Assert.Throws("accessor", () => new XUnitLoggerProvider((null as ITestOutputHelperAccessor)!, options));
22 | Assert.Throws("options", () => new XUnitLoggerProvider(outputHelper, null!));
23 | Assert.Throws("options", () => new XUnitLoggerProvider(accessor, null!));
24 | }
25 |
26 | [Fact]
27 | public static void XUnitLoggerProvider_MessageSink_Constructor_Validates_Parameters()
28 | {
29 | // Arrange
30 | var messageSink = Substitute.For();
31 | var accessor = Substitute.For();
32 | var options = new XUnitLoggerOptions();
33 |
34 | // Act and Assert
35 | Assert.Throws("messageSink", () => new XUnitLoggerProvider((null as IMessageSink)!, options));
36 | Assert.Throws("accessor", () => new XUnitLoggerProvider((null as IMessageSinkAccessor)!, options));
37 | Assert.Throws("options", () => new XUnitLoggerProvider(messageSink, null!));
38 | Assert.Throws("options", () => new XUnitLoggerProvider(accessor, null!));
39 | }
40 |
41 | [Theory]
42 | [InlineData(Constructor.ITestOutputHelper)]
43 | [InlineData(Constructor.IMessageSink)]
44 | public static void XUnitLoggerProvider_Creates_Logger(Constructor constructor)
45 | {
46 | // Arrange
47 | var testOutputHelper = Substitute.For();
48 | var messageSink = Substitute.For();
49 | var options = new XUnitLoggerOptions();
50 |
51 | string categoryName = "MyLogger";
52 |
53 | using var target = constructor switch
54 | {
55 | Constructor.ITestOutputHelper => new XUnitLoggerProvider(testOutputHelper, options),
56 | Constructor.IMessageSink => new XUnitLoggerProvider(messageSink, options),
57 | _ => throw new ArgumentOutOfRangeException(nameof(constructor), constructor, null),
58 | };
59 |
60 | // Act
61 | ILogger actual = target.CreateLogger(categoryName);
62 |
63 | // Assert
64 | actual.ShouldNotBeNull();
65 |
66 | var xunit = actual.ShouldBeOfType();
67 | xunit.Name.ShouldBe(categoryName);
68 | xunit.Filter.ShouldBeSameAs(options.Filter);
69 | xunit.MessageSinkMessageFactory.ShouldBeSameAs(options.MessageSinkMessageFactory);
70 | xunit.IncludeScopes.ShouldBeFalse();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/Shared/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "diagnosticMessages": true,
4 | "methodDisplay": "method"
5 | }
6 |
--------------------------------------------------------------------------------