├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── renovate.json └── workflows │ ├── build.yml │ ├── jenkins-security-scan.yml │ ├── m2.settings.xml │ └── release.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── CHANGELOG.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── hudson │ │ └── plugins │ │ └── logparser │ │ ├── ClassicParsingStrategy.java │ │ ├── CompiledPatterns.java │ │ ├── LineToStatus.java │ │ ├── LogParserAction.java │ │ ├── LogParserBuildStep.java │ │ ├── LogParserColumn.java │ │ ├── LogParserConsts.java │ │ ├── LogParserDisplayConsts.java │ │ ├── LogParserLogPart.java │ │ ├── LogParserParser.java │ │ ├── LogParserPublisher.java │ │ ├── LogParserReader.java │ │ ├── LogParserResult.java │ │ ├── LogParserStatusComputer.java │ │ ├── LogParserThread.java │ │ ├── LogParserUtils.java │ │ ├── LogParserWriter.java │ │ ├── ParserRuleFile.java │ │ ├── ParsingInput.java │ │ ├── ParsingRulePattern.java │ │ ├── ParsingStrategy.java │ │ ├── ParsingStrategyLocator.java │ │ ├── ReadWriteTextFile.java │ │ ├── StreamParsingStrategy.java │ │ └── action │ │ └── LogParserProjectAction.java ├── resources │ ├── hudson │ │ └── plugins │ │ │ └── logparser │ │ │ ├── LogParserAction │ │ │ ├── index.jelly │ │ │ └── summary.jelly │ │ │ ├── LogParserColumn │ │ │ ├── column.jelly │ │ │ ├── column.properties │ │ │ ├── columnHeader.jelly │ │ │ └── columnHeader.properties │ │ │ ├── LogParserPublisher │ │ │ ├── config.jelly │ │ │ ├── global.jelly │ │ │ └── help-projectRulePath.html │ │ │ ├── Messages.properties │ │ │ ├── ParserRuleFile │ │ │ └── config.jelly │ │ │ └── action │ │ │ └── LogParserProjectAction │ │ │ └── floatingBox.jelly │ └── index.jelly └── webapp │ ├── fail_on_error.html │ ├── global_config_help.html │ ├── global_legacy_formatting.html │ ├── help.html │ ├── images │ ├── blue.gif │ ├── gray.gif │ ├── red.gif │ └── yellow.gif │ ├── js │ └── log-parser-behaviour.js │ ├── parse_rule_choice.html │ ├── parser_graphs.html │ └── unstable_on_warning.html ├── spotbugs └── excludesFilter.xml └── test ├── java ├── hudson │ └── plugins │ │ └── logparser │ │ ├── LineToStatusTest.java │ │ ├── LogParserPublisherTest.java │ │ └── ParsingStrategyLocatorTest.java └── org │ └── jenkinsci │ └── plugins │ └── logparser │ ├── ConfigurationAsCodeTest.java │ └── LogParserWorkflowTest.java └── resources └── org └── jenkinsci └── plugins └── logparser ├── configuration-as-code-legacy-formatting.yaml ├── configuration-as-code-parsing-rules.yaml └── maven-project1 ├── .gitignore ├── Jenkinsfile ├── doc.txt ├── logparser-rules.txt ├── pom.xml └── src ├── main └── java │ └── com │ └── cloudbees │ └── manticore │ └── App.java └── test └── java └── com └── cloudbees └── manticore └── AppTest.java /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/log-parser-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Submitting Pull Requests 2 | 3 | We'd love for you to contribute to our source code and to make this package even better than it is 4 | today! Here are the guidelines we'd like you to follow: 5 | 6 | - [Issues and Bugs](#issue) 7 | - [Feature Requests](#feature) 8 | - [Coding Rules](#rules) 9 | - [Commit Message Guidelines](#commit) 10 | 11 | ## Found an Issue? 12 | 13 | If you find a bug in the source code or a mistake in the documentation, you can help us by 14 | submitting an issue to our GitHub Repository. Even better you can submit a Pull Request 15 | with a fix. But first search if the issue is already described! 16 | 17 | If not create a new issue: 18 | 19 | * Tell about your environment: 20 | * node version 21 | * nativescript version 22 | * used platform and versionÍ 23 | * Describe your issue 24 | * describe your steps leading to the issue 25 | * attach error logs or screenshots 26 | * if possible provide test case or screenshots 27 | 28 | ## Want a Feature? 29 | 30 | You can request a new feature by submitting an issue to our [GitHub Repository][github]. 31 | 32 | Please follow these basic steps to simplify pull request reviews - if you don't you'll probably just be asked to anyway.** 33 | 34 | * Please rebase your branch against the current develop, use the **develop** for pull requests 35 | * Please ensure that the test suite passes **and** that code is lint free before submitting a PR by running: 36 | * ```./mvnw test``` 37 | * Verify plugin is working via docker test ```docker compose up``` 38 | * If you've added new functionality, **please** include tests which validate its behaviour 39 | * Make reference to possible [issues](https://github.com/jenkinsci/log-parser-plugin/issues) on PR comment 40 | 41 | ### Resulting from long experience 42 | 43 | * To the largest extent possible, all fields shall be private. Use an IDE to generate the getters and setters. 44 | * If a class has more than one `volatile` member field, it is probable that there are subtle race conditions. Please consider where appropriate encapsulation of the multiple fields into an immutable value object replace the multiple `volatile` member fields with a single `volatile` reference to the value object (or perhaps better yet an `AtomicReference` to allow for `compareAndSet` - if compare-and-set logic is appropriate). 45 | * If it is `Serializable` it shall have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field shall be `1L`. 46 | 47 | ### Indentation 48 | 49 | 1. **Use spaces.** Tabs are banned. 50 | 2. **Java blocks are 4 spaces.** JavaScript blocks as for Java. **XML nesting is 2 spaces** 51 | 52 | ### Field Naming Conventions 53 | 54 | 1. "hungarian"-style notation is banned (i.e. instance variable names preceded by an 'm', etc) 55 | 2. If the field is `static final` then it shall be named in `ALL_CAPS_WITH_UNDERSCORES`. 56 | 3. Start variable names with a lowercase letter and use camelCase rather than under_scores. 57 | 4. Spelling and abreviations: If the word is widely used in the JVM runtime, stick with the spelling/abreviation in the JVM runtime, e.g. `color` over `colour`, `sync` over `synch`, `async` over `asynch`, etc. 58 | 5. It is acceptable to use `i`, `j`, `k` for loop indices and iterators. If you need more than three, you are likely doing something wrong and as such you shall either use full descriptive names or refactor. 59 | 6. It is acceptable to use `e` for the exception in a `try...catch` block. 60 | 7. You shall never use `l` (i.e. lower case `L`) as a variable name. 61 | 62 | ### Line Length 63 | 64 | To the greatest extent possible, please wrap lines to ensure that they do not exceed 120 characters. 65 | 66 | ### Maven POM file layout 67 | 68 | * The `pom.xml` file shall use the sequencing of elements as defined by the `mvn tidy:pom` command (after any indenting fix-up). 69 | * If you are introducing a property to the `pom.xml` the property must be used in at least two distinct places in the model or a comment justifying the use of a property shall be provided. 70 | * If the `` is in the groupId `org.apache.maven.plugins` you shall omit the ``. 71 | * All `` entries shall have an explicit version defined unless inherited from the parent. 72 | 73 | ### Java code style 74 | 75 | #### Modifiers 76 | 77 | * For fields, the order is: 78 | - public / protected / private 79 | - static 80 | - final 81 | - transient 82 | - volatile 83 | * For methods, the order is: 84 | - public / protected / private 85 | - abstract 86 | - static 87 | - final 88 | - synchronized 89 | - native 90 | - strictfp 91 | * For classes, the order is: 92 | - public / protected / private 93 | - abstract 94 | - static 95 | - final 96 | - strictfp 97 | 98 | #### Imports 99 | 100 | * For code in `src/main`: 101 | - `*` imports are banned. 102 | - `static` imports are strongly discouraged. 103 | - `static` `*` imports are discouraged unless code readability is significantly enhanced and the import is restricted to a single class. 104 | * For code in `src/test`: 105 | - `*` imports of anything other than JUnit classes and Hamcrest matchers are banned. 106 | - `static` imports of anything other than JUnit classes and Hamcrest matchers are strongly discouraged. 107 | - `import static org.hamcrest.Matchers.*`, `import static org.junit.Assert.*` are expressly permitted. Any other `static` `*` imports are discouraged unless code readability is significantly enhanced and the import is restricted to a single class. 108 | 109 | #### Annotation placement 110 | 111 | * Annotations on classes, interfaces, annotations, enums, methods, fields and local variables shall be on the lines immediately preceding the line where modifier(s) (e.g. `public` / `protected` / `private` / `final`, etc) would be appropriate. 112 | * Annotations on method arguments shall, to the largest extent possible, be on the same line as the method argument (and, if present, before the `final` modifier) 113 | 114 | #### Javadoc 115 | 116 | * Each class shall have a Javadoc comment. 117 | * Each field shall have a Javadoc comment. 118 | * Unless the method is `private`, it shall have a Javadoc comment. 119 | * When a method is overriding a method from a super-class / interface, unless the semantics of the method have changed it is sufficient to document the intent of implementing the super-method's contract with: 120 | ``` 121 | /** 122 | * {@inheritDoc} 123 | */ 124 | @Override 125 | ``` 126 | * Getters and Setters shall have a Javadoc comment. The following is prefered 127 | ``` 128 | /** 129 | * The count of widgets 130 | */ 131 | private int widgetCount; 132 | 133 | /** 134 | * Returns the count of widgets. 135 | * 136 | * @return the count of widgets. 137 | */ 138 | public int getWidgetCount() { 139 | return widgetCount; 140 | } 141 | 142 | /** 143 | * Sets the count of widgets. 144 | * 145 | * @param widgetCount the count of widgets. 146 | */ 147 | public void setWidgetCount(int widgetCount) { 148 | this.widgetCount = widgetCount; 149 | } 150 | ``` 151 | * When adding a new class / interface / etc, it shall have a `@since` doc comment. The version shall be `FIXME` to indicate that the person merging the change should replace the `FIXME` with the next release version number. The fields and methods within a class/interface (but not nested classes) will be assumed to have the `@since` annotation of their class/interface unless a different `@since` annotation is present. 152 | 153 | ## Coding Rules 154 | 155 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 156 | 157 | * All features or bug fixes **must be tested** by one or more [specs][unit-testing]. 158 | * All public API methods **must be documented** with jsdoc. 159 | 160 | 161 | * To the largest extent possible, all fields shall be private. Use an IDE to generate the getters and setters. 162 | * If a class has more than one `volatile` member field, it is probable that there are subtle race conditions. Please consider where appropriate encapsulation of the multiple fields into an immutable value object replace the multiple `volatile` member fields with a single `volatile` reference to the value object (or perhaps better yet an `AtomicReference` to allow for `compareAndSet` - if compare-and-set logic is appropriate). 163 | * If it is `Serializable` it shall have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field shall be `1L`. 164 | 165 | 166 | ## Git Commit Guidelines 167 | 168 | We're using [Angular Commit Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines) 169 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: continuoussecuritytooling 5 | #open_collective: moleculer 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=H8TR8246RCDJG 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛Bug Report 2 | description: File a bug report here 3 | title: "[BUG] " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please make sure to enter all needed information. 10 | 11 | - type: textarea 12 | id: prerequisites 13 | attributes: 14 | label: Prerequisites 15 | description: Please answer the following questions for yourself before submitting an issue. 16 | placeholder: | 17 | - [ ] I am running the latest version 18 | - [ ] I checked the documentation and found no answer 19 | - [ ] I checked to make sure that this issue has not already been filed 20 | - [ ] I'm reporting the issue to the correct repository 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: bug-description 26 | attributes: 27 | label: Description of the bug 28 | description: Give us a brief description of what happened and what should have happened 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | id: steps-to-reproduce 34 | attributes: 35 | label: Steps To Reproduce 36 | description: Steps to reproduce the behavior. 37 | placeholder: | 38 | 1. Go to '...' 39 | 2. Click on '...' 40 | 3. Scroll down to '...' 41 | 4. See error 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: additional-information 47 | attributes: 48 | label: Additional Information 49 | description: | 50 | Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. 51 | placeholder: | 52 | * Java version 53 | * Jenkins version 54 | * Operating System 55 | * Log files 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨Feature Request 2 | description: Request a new feature or enhancement 3 | labels: ["enhancement"] 4 | title: "Add a meaningful name" 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please describe the desired feature as clear as possible 10 | 11 | - type: textarea 12 | id: description 13 | attributes: 14 | label: Description 15 | description: Give us a brief description of the feature or enhancement you would like 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | id: acceptance-criteria 21 | attributes: 22 | label: Acceptance Criteria 23 | description: All thinks that must be fullfilled due the change. 24 | placeholder: | 25 | [ ] List item 26 | validations: 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## :memo: Description 2 | 3 | 4 | 5 | ### :dart: Relevant issues 6 | 7 | 8 | ### :gem: Type of change 9 | 10 | 11 | 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] New feature (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 15 | - [ ] This change requires a documentation update 16 | 17 | ### :scroll: Example code 18 | ```js 19 | 20 | ``` 21 | 22 | ## :vertical_traffic_light: How Has This Been Tested? 23 | 24 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 25 | 26 | - [ ] Test A 27 | - [ ] Test B 28 | 29 | ## :checkered_flag: Checklist: 30 | 31 | - [ ] My code follows the style guidelines of this project 32 | - [ ] I have performed a self-review of my own code 33 | - [ ] **I have added tests that prove my fix is effective or that my feature works** 34 | - [ ] **New and existing unit tests pass locally with my changes** 35 | - [ ] I have commented my code, particularly in hard-to-understand areas 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: maven 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":semanticCommitsDisabled", 6 | "schedule:daily" 7 | ], 8 | "automerge": true, 9 | "ignorePaths": [ 10 | ], 11 | "ignoreDeps": [ 12 | "org.apache.maven:maven-resolver-provider", 13 | "org.apache.maven:maven-settings-builder", 14 | "org.apache.maven.resolver:maven-resolver-api", 15 | "org.apache.maven.resolver:maven-resolver-connector-basic", 16 | "org.apache.maven.resolver:maven-resolver-impl", 17 | "org.apache.maven.resolver:maven-resolver-transport-file", 18 | "org.apache.maven.resolver:maven-resolver-transport-http", 19 | "org.apache.maven.resolver:maven-resolver-util", 20 | "org.eclipse.sisu:org.eclipse.sisu.inject" 21 | ], 22 | "packageRules": [ 23 | { 24 | "allowedVersions": "<7.0.0", 25 | "matchPackageNames": [ 26 | "com.google.inject:guice" 27 | ], 28 | "description": "We focus on Guice 6 until core adopts 7" 29 | }, 30 | { 31 | "groupName": "Selenium", 32 | "matchPackageNames": [ 33 | "/selenium/" 34 | ] 35 | }, 36 | { 37 | "matchDepNames": [ 38 | "docker", 39 | "docker/buildx", 40 | "firefox", 41 | "mozilla/geckodriver", 42 | "org.apache.maven:maven-core" 43 | ], 44 | "labels": [ 45 | "dependencies", 46 | "build-image" 47 | ] 48 | } 49 | ], 50 | "labels": [ 51 | "dependencies" 52 | ], 53 | "rebaseWhen": "conflicted" 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | merge_group: 10 | 11 | permissions: 12 | # This is required for requesting the JWT 13 | id-token: write 14 | security-events: write 15 | contents: read 16 | # This is required for actions/checkout 17 | actions: read 18 | issues: write 19 | # For reporting 20 | attestations: write 21 | checks: write 22 | # For Junit Reporting commenting 23 | pull-requests: write 24 | 25 | jobs: 26 | build: 27 | strategy: 28 | matrix: 29 | os: [ ubuntu-latest, windows-latest ] 30 | java: [ 17, 21 ] 31 | fail-fast: false 32 | 33 | runs-on: ${{ matrix.os }} 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Set up cache for ~./m2/repository 40 | uses: actions/cache@v3 41 | with: 42 | path: ~/.m2/repository 43 | key: maven-${{ matrix.os }}-java${{ matrix.java }}-${{ hashFiles('**/pom.xml') }}-${{ hashFiles('.mvn/wrapper/maven-wrapper.properties') }} 44 | restore-keys: | 45 | maven-${{ matrix.os }}-java${{ matrix.java }}- 46 | maven-${{ matrix.os }}- 47 | 48 | - name: Set up JDK 49 | uses: actions/setup-java@v3 50 | with: 51 | java-version: ${{ matrix.java }} 52 | distribution: "adopt" 53 | 54 | - name: Build with Maven 55 | run: ./mvnw --batch-mode clean install 56 | 57 | - name: Publish Test Report 58 | uses: mikepenz/action-junit-report@v5 59 | if: success() || failure() # always run even if the previous step fails 60 | with: 61 | report_paths: '**/target/*/TEST-*.xml' 62 | 63 | - name: Run PMD checks 64 | run: ./mvnw pmd:check 65 | 66 | build-results: 67 | name: Build results 68 | if: ${{ always() }} 69 | runs-on: ubuntu-latest 70 | needs: 71 | - build 72 | steps: 73 | - name: Check out 74 | uses: actions/checkout@v3 75 | with: 76 | fetch-depth: 0 77 | - name: Set up JDK 17 78 | uses: actions/setup-java@v3 79 | with: 80 | distribution: 'adopt' 81 | java-version: 17 82 | - run: exit 1 83 | # see https://stackoverflow.com/a/67532120/4907315 84 | if: >- 85 | ${{ 86 | contains(needs.*.result, 'failure') 87 | || contains(needs.*.result, 'cancelled') 88 | || contains(needs.*.result, 'skipped') 89 | }} 90 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.github/workflows/m2.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | central 7 | ${JENKINS_MAVEN_USER} 8 | ${JENKINS_MAVEN_PASS} 9 | 10 | 11 | snapshots 12 | ${JENKINS_MAVEN_USER} 13 | ${JENKINS_MAVEN_PASS} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | false 22 | 23 | central 24 | remote-snapshot-repos 25 | https://repo.jenkins-ci.org/artifactory/remote-snapshot-repos 26 | 27 | 28 | 29 | snapshots 30 | remote-snapshot-repos 31 | https://repo.jenkins-ci.org/artifactory/remote-snapshot-repos 32 | 33 | 34 | 35 | 36 | 37 | false 38 | 39 | central 40 | remote-snapshot-repos 41 | https://repo.jenkins-ci.org/artifactory/remote-snapshot-repos 42 | 43 | 44 | 45 | snapshots 46 | remote-snapshot-repos 47 | https://repo.jenkins-ci.org/artifactory/remote-snapshot-repos 48 | 49 | 50 | artifactory 51 | 52 | 53 | 54 | artifactory 55 | 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: create release 2 | run-name: "Release ${{ inputs.releaseversion }}" 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | releaseversion: 8 | description: 'Release version (..)' 9 | required: true 10 | type: string 11 | default: "X.Y.Z" 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | packages: write 18 | contents: write 19 | attestations: write 20 | id-token: write 21 | 22 | steps: 23 | - name: Check out 24 | uses: actions/checkout@v3 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: set git config 29 | env: 30 | GH_TOKEN: ${{ github.token }} 31 | run: | 32 | git config --global user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" 33 | git config --global user.name "$(gh api /users/${GITHUB_ACTOR} | jq .name -r)" 34 | 35 | - name: Set up JDK 17 36 | uses: actions/setup-java@v3 37 | with: 38 | distribution: 'adopt' 39 | java-version: 17 40 | cache: 'maven' 41 | 42 | - name: Setup settings.xml 43 | uses: s4u/maven-settings-action@v3.1.0 44 | with: 45 | override: true 46 | servers: | 47 | [{ 48 | "id": "repo.jenkins-ci.org", 49 | "username": "${{ vars.OSS_JENKINS_USER }}", 50 | "password": "${{ secrets.OSS_JENKINS_PASS }}" 51 | }] 52 | 53 | - name: Create release 54 | id: create-release 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | run: | 58 | export TZ="Europe/Berlin" 59 | git checkout -b release/${{ inputs.releaseversion }} 60 | mvn release:prepare release:perform -B -DreleaseVersion=${{ inputs.releaseversion }} -DskipTests=true -DskipITs=true -DlocalCheckout=true -D"arguments=-DskipTests=true -DskipITs=true" 61 | # manually push 62 | git push --tags 63 | git push origin release/${{ inputs.releaseversion }} 64 | git branch --set-upstream-to=origin/release/${{ inputs.releaseversion }} release/${{ inputs.releaseversion }} || true 65 | # write version info 66 | cat <target/config.json 67 | { 68 | "version": "${{ inputs.releaseversion }}" 69 | } 70 | EOF 71 | 72 | - name: Conventional Changelog Action 73 | id: create-changelog 74 | uses: TriPSs/conventional-changelog-action@v5 75 | with: 76 | input-file: CHANGELOG.md 77 | github-token: ${{ secrets.GITHUB_TOKEN }} 78 | version-file: target/config.json 79 | pre-release: true 80 | skip-bump: true 81 | skip-tag: true 82 | skip-on-empty: true 83 | tag-prefix: 'v' 84 | 85 | - name: tag-and-release 86 | id: tag-and-release 87 | uses: avakar/tag-and-release@v1 88 | with: 89 | release_name: ${{ github.event.inputs.releaseversion }} 90 | tag_name: v${{ github.event.inputs.releaseversion }} 91 | draft: true 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | 95 | - name: Create Pull Request 96 | id: create-pr 97 | uses: peter-evans/create-pull-request@v7 98 | with: 99 | commit-message: Update report 100 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 101 | author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> 102 | branch: release/${{ inputs.releaseversion }} 103 | base: develop 104 | title: '[Release] ${{ inputs.releaseversion }}' 105 | body: | 106 | Release Plugin v${{ inputs.releaseversion }} 107 | labels: | 108 | release 109 | assignees: hypery2k 110 | draft: false 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS stuff 2 | *.icloud 3 | ._* 4 | .DS_Store 5 | Thumbs.db 6 | Desktop.ini 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # IDE stuff 11 | .idea/* 12 | *.iml 13 | *.ipr 14 | .metadata 15 | bin/ 16 | tmp/ 17 | *.tmp 18 | *.bak 19 | *.swp 20 | *~.nib 21 | local.properties 22 | .settings/ 23 | .loadpath 24 | .recommenders 25 | 26 | # External tool builders 27 | .externalToolBuilders/ 28 | 29 | # Locally stored "Eclipse launch configurations" 30 | *.launch 31 | 32 | # PyDev specific (Python IDE for Eclipse) 33 | *.pydevproject 34 | 35 | # CDT-specific (C/C++ Development Tooling) 36 | .cproject 37 | 38 | # CDT- autotools 39 | .autotools 40 | 41 | # Java annotation processor (APT) 42 | .factorypath 43 | 44 | # PDT-specific (PHP Development Tools) 45 | .buildpath 46 | 47 | # sbteclipse plugin 48 | .target 49 | 50 | # Tern plugin 51 | .tern-project 52 | 53 | # TeXlipse plugin 54 | .texlipse 55 | 56 | # STS (Spring Tool Suite) 57 | .springBeans 58 | 59 | # Code Recommenders 60 | .recommenders/ 61 | 62 | # Annotation Processing 63 | .apt_generated/ 64 | 65 | # Scala IDE specific (Scala & Java development for Eclipse) 66 | .cache-main 67 | .scala_dependencies 68 | .worksheet 69 | 70 | ### Eclipse Patch ### 71 | # Eclipse Core 72 | .project 73 | 74 | # JDT-specific (Eclipse Java Development Tools) 75 | .classpath 76 | 77 | # Annotation Processing 78 | .apt_generated 79 | 80 | .sts4-cache/ 81 | 82 | ### Intellij ### 83 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 84 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 85 | 86 | # User-specific stuff 87 | .idea/**/workspace.xml 88 | .idea/**/tasks.xml 89 | .idea/**/usage.statistics.xml 90 | .idea/**/dictionaries 91 | .idea/**/shelf 92 | 93 | # Generated files 94 | .idea/**/contentModel.xml 95 | 96 | # Sensitive or high-churn files 97 | .idea/**/dataSources/ 98 | .idea/**/dataSources.ids 99 | .idea/**/dataSources.local.xml 100 | .idea/**/sqlDataSources.xml 101 | .idea/**/dynamic.xml 102 | .idea/**/uiDesigner.xml 103 | .idea/**/dbnavigator.xml 104 | 105 | # Gradle 106 | .idea/**/gradle.xml 107 | .idea/**/libraries 108 | 109 | # Gradle and Maven with auto-import 110 | # When using Gradle or Maven with auto-import, you should exclude module files, 111 | # since they will be recreated, and may cause churn. Uncomment if using 112 | # auto-import. 113 | # .idea/modules.xml 114 | # .idea/*.iml 115 | # .idea/modules 116 | 117 | # CMake 118 | cmake-build-*/ 119 | 120 | # Mongo Explorer plugin 121 | .idea/**/mongoSettings.xml 122 | 123 | # File-based project format 124 | *.iws 125 | 126 | # IntelliJ 127 | out/ 128 | 129 | # mpeltonen/sbt-idea plugin 130 | .idea_modules/ 131 | 132 | # JIRA plugin 133 | atlassian-ide-plugin.xml 134 | 135 | # Cursive Clojure plugin 136 | .idea/replstate.xml 137 | 138 | # Crashlytics plugin (for Android Studio and IntelliJ) 139 | com_crashlytics_export_strings.xml 140 | crashlytics.properties 141 | crashlytics-build.properties 142 | fabric.properties 143 | 144 | # Editor-based Rest Client 145 | .idea/httpRequests 146 | 147 | # Android studio 3.1+ serialized cache file 148 | .idea/caches/build_file_checksums.ser 149 | 150 | ### Intellij Patch ### 151 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 152 | 153 | *.iml 154 | # modules.xml 155 | # .idea/misc.xml 156 | # *.ipr 157 | 158 | # Sonarlint plugin 159 | .idea/sonarlint 160 | 161 | ### Java ### 162 | # Compiled class file 163 | *.class 164 | 165 | # Log file 166 | *.log 167 | 168 | # BlueJ files 169 | *.ctxt 170 | 171 | # Mobile Tools for Java (J2ME) 172 | .mtj.tmp/ 173 | 174 | # Package Files # 175 | *.jar 176 | *.war 177 | *.nar 178 | *.ear 179 | *.zip 180 | *.tar.gz 181 | *.rar 182 | 183 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 184 | hs_err_pid* 185 | 186 | ### Maven ### 187 | target/ 188 | pom.xml.tag 189 | pom.xml.releaseBackup 190 | pom.xml.versionsBackup 191 | pom.xml.next 192 | release.properties 193 | dependency-reduced-pom.xml 194 | buildNumber.properties 195 | .mvn/timing.properties 196 | .mvn/wrapper/maven-wrapper.jar 197 | 198 | ### NetBeans ### 199 | **/nbproject/private/ 200 | build/ 201 | nbbuild/ 202 | dist/ 203 | nbdist/ 204 | .nb-gradle/ 205 | 206 | 207 | # End of https://www.gitignore.io/api/java,maven,netbeans,intellij,eclipse 208 | 209 | .idea/ 210 | work/ 211 | nbactions.xml 212 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [](https://github.com/jenkinsci/log-parser-plugin/compare/v2.5.0...v) (2025-05-16) 2 | 3 | ## [2.5.0](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.6...v2.5.0) (2025-05-16) 4 | 5 | 6 | ### Features 7 | 8 | * Add column to jobs list ([d4c87ef](https://github.com/jenkinsci/log-parser-plugin/commit/d4c87efc9939aa2c2d79a1fa275b2d035f0c48ef)) 9 | * **java:** Require Jenkins core 2.492.3 and Java 17 ([2d96d37](https://github.com/jenkinsci/log-parser-plugin/commit/2d96d37d72bc7991659a6ca7115590d21f1e0e1b)) 10 | * **Timestamper:** Adding support for Timestamper ([dcce20a](https://github.com/jenkinsci/log-parser-plugin/commit/dcce20ad6bb80a7b04daa56a929feb1618da4901)) 11 | * upgrade to Jenkins LTS Core 2.462.3 for Java 11 support ([#150](https://github.com/jenkinsci/log-parser-plugin/issues/150)) ([5d5057e](https://github.com/jenkinsci/log-parser-plugin/commit/5d5057e0b52377af8e08d94f12f1a047c6eba280)) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * **Escpaing:** Remove xterm escape codes from log file ([1b40ade](https://github.com/jenkinsci/log-parser-plugin/commit/1b40ade9af7e6b6b9deb87cea6b4920866b9fb31)) 17 | 18 | ## [2.3.6](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.5...v2.3.6) (2024-11-25) 19 | 20 | ## [2.3.5](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.4...v2.3.5) (2024-08-07) 21 | 22 | ## [2.3.4](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.3...v2.3.4) (2024-06-03) 23 | 24 | ## [2.3.3](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.2...v2.3.3) (2023-12-23) 25 | 26 | ## [2.3.2](https://github.com/jenkinsci/log-parser-plugin/compare/v2.3.1...v2.3.2) (2023-10-15) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **JENKINS-72048:** Correcting NPE ([16f069e](https://github.com/jenkinsci/log-parser-plugin/commit/16f069e6faea50630b4869337aa1703188b1e52c)) 32 | 33 | ## [2.3.1](https://github.com/jenkinsci/log-parser-plugin/compare/d613f5ef8a166edc518f580165a8367746a463c0...v2.3.1) (2023-09-20) 34 | 35 | 36 | ### Features 37 | 38 | * Introduce `StreamParsingStrategy` to support builds with large logs ([#40](https://github.com/jenkinsci/log-parser-plugin/issues/40)) ([6b4b6e6](https://github.com/jenkinsci/log-parser-plugin/commit/6b4b6e6c95a7da4eaf49d6fe4be937da95ffc11b)) 39 | * rebaseline Jenkins to 2.387.3 ([d6b4cc8](https://github.com/jenkinsci/log-parser-plugin/commit/d6b4cc8e995221d354fd4d5cf09a5787c23af73b)) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * Colors & hints on trend graphs do not match to the data ([413b828](https://github.com/jenkinsci/log-parser-plugin/commit/413b828a52855a6345f72e869ba803544393160e)) 45 | * docker-compose up doesn't support --rm ([d613f5e](https://github.com/jenkinsci/log-parser-plugin/commit/d613f5ef8a166edc518f580165a8367746a463c0)) 46 | * Jenkins Log Parser Debug Icon not shown ([1e930e0](https://github.com/jenkinsci/log-parser-plugin/commit/1e930e00df73337ea75d8fe8d9caa38e3d7a792a)) 47 | * **Memory:** Correct potential OOM when parsing logs in workflows ([#36](https://github.com/jenkinsci/log-parser-plugin/issues/36)) ([bff7f9f](https://github.com/jenkinsci/log-parser-plugin/commit/bff7f9f53820aade452a4c44441bbfabc905931e)), closes [/github.com/jenkinsci/workflow-job-plugin/blob/1551f82/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowRun.java#L1105](https://github.com/jenkinsci//github.com/jenkinsci/workflow-job-plugin/blob/1551f82/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowRun.java/issues/L1105) 48 | * Now Jenkins is available on port 8081 and boots ([f269c53](https://github.com/jenkinsci/log-parser-plugin/commit/f269c53fa0ddb5ae28ae3719afe6d6ee3ccc9d8e)) 49 | 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:alpine 2 | 3 | RUN apk update && apk upgrade && \ 4 | apk --update add fontconfig ttf-dejavu bash git openssh openjdk8-jre 5 | RUN mkdir -p /data 6 | WORKDIR /data 7 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Builds a module using https://github.com/jenkins-infra/pipeline-library 2 | buildPlugin(useContainerAgent: true, configurations: [ 3 | [ platform: 'linux', jdk: 21 ], 4 | [ platform: 'windows', jdk: 17 ], 5 | ]) 6 | 7 | -------------------------------------------------------------------------------- /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 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := tests 2 | 3 | .PHONY: jenkins 4 | jenkins: 5 | docker-compose up mvn 6 | 7 | .PHONY: tests 8 | tests: 9 | docker-compose run --rm mvn mvn clean test 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | log-parser-plugin 2 | ================= 3 | 4 | [![Build Status](https://ci.jenkins.io/buildStatus/icon?job=Plugins%2Flog-parser-plugin%2Fmain)](https://ci.jenkins.io/blue/organizations/jenkins/Plugins%2Flog-parser-plugin/branches/) 5 | 6 | Parses the console output and highlights error/warning/info lines. It will make you analyze your build log in a pretty good way in Jenkins 7 | This log parser plugin will parse the console build logs which will be generated by Jenkins build. We can apply the following features in our logs by using this plugin: 8 | 9 | * Categorize the build log into the sections like ERRORS, INFO, DEBUG, WARNING, and HEADER. 10 | * Display these sections in summaries like the total number of errors and info on the build page. 11 | * Highlight the lines of our interest in the build log as per our needs. 12 | * Link the summary of errors and warnings with the full log, which makes us easy to search for a line of interest in the build log. 13 | 14 | 15 | Also see [logparser-rules.txt](src/test/resources/org/jenkinsci/plugins/logparser/maven-project1/logparser-rules.txt) for a sample parsing rule set. See [Wiki](https://wiki.jenkins-ci.org/JENKINS/Log-Parser-Plugin.html) for more details. 16 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mvn: 5 | build: 6 | context: . 7 | volumes: 8 | - mvn_home:/root/.m2 9 | - ${PWD}:/data 10 | command: "mvn hpi:run" 11 | ports: 12 | - 8081:8080 13 | volumes: 14 | mvn_home: 15 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 5.16 9 | 10 | 11 | 12 | log-parser 13 | hpi 14 | Log Parser Plugin 15 | 2.5.1-SNAPSHOT 16 | https://github.com/jenkinsci/${project.artifactId}-plugin 17 | Parses the console log generated by a build 18 | 19 | 20 | 2.492 21 | ${jenkins.baseline}.3 22 | 23 | 24 | 3.1.1 25 | 3.3 26 | 27 | 28 | 29 | 30 | MIT license 31 | All source code is under the MIT license. 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-pmd-plugin 40 | 3.26.0 41 | 42 | 17 43 | 44 | /rulesets/basic.xml 45 | /rulesets/braces.xml 46 | /rulesets/clone.xml 47 | /rulesets/codesize.xml 48 | /rulesets/design.xml 49 | /rulesets/finalizers.xml 50 | /rulesets/imports.xml 51 | /rulesets/j2ee.xml 52 | /rulesets/optimizations.xml 53 | /rulesets/strictexception.xml 54 | /rulesets/strings.xml 55 | /rulesets/unusedcode.xml 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | io.jenkins.tools.bom 66 | bom-${jenkins.baseline}.x 67 | 4836.vdf03ded1f27c 68 | import 69 | pom 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.jenkins-ci.plugins 77 | scm-api 78 | test 79 | 80 | 81 | io.jenkins 82 | configuration-as-code 83 | test 84 | 85 | 86 | org.jenkins-ci.plugins.workflow 87 | workflow-cps 88 | test 89 | 90 | 91 | org.jenkins-ci.plugins.workflow 92 | workflow-basic-steps 93 | test 94 | 95 | 96 | org.jenkins-ci.plugins.workflow 97 | workflow-durable-task-step 98 | test 99 | 100 | 101 | org.jenkins-ci.plugins.workflow 102 | workflow-job 103 | test 104 | 105 | 106 | org.mockito 107 | mockito-core 108 | test 109 | 110 | 111 | org.mockito 112 | mockito-junit-jupiter 113 | test 114 | 115 | 116 | org.assertj 117 | assertj-core 118 | 3.27.3 119 | test 120 | 121 | 122 | org.jenkins-ci.main 123 | jenkins-test-harness-tools 124 | 2.2 125 | test 126 | 127 | 128 | org.jenkins-ci.main 129 | maven-plugin 130 | test 131 | 132 | 133 | org.jenkins-ci.plugins 134 | mailer 135 | test 136 | 137 | 138 | org.jenkins-ci.plugins 139 | timestamper 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | org.apache.maven.plugins 148 | maven-javadoc-plugin 149 | 150 | -Xdoclint:none 151 | 152 | 153 | 154 | maven-release-plugin 155 | 156 | v@{project.version} 157 | 158 | 159 | 160 | 161 | 162 | 163 | https://${GITHUB_TOKEN}@github.com/jenkinsci/log-parser-plugin.git 164 | scm:git:${project.scm.url} 165 | scm:git:${project.scm.url} 166 | v2.4.2 167 | 168 | 169 | 170 | 171 | repo.jenkins-ci.org 172 | https://repo.jenkins-ci.org/public/ 173 | 174 | 175 | 176 | 177 | 178 | repo.jenkins-ci.org 179 | https://repo.jenkins-ci.org/public/ 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ClassicParsingStrategy.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.FilePath; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | import java.util.regex.Pattern; 17 | 18 | /** 19 | * This was the only available {@link ParsingStrategy} in 2.3.0 and earlier. 20 | *

21 | * For each build, this strategy will: 22 | *

    23 | *
  1. Copy the log into {@code java.io.tmpdir}
  2. 24 | *
  3. Stream the copied log file to count lines via {@link LogParserUtils#countLines(String)}
  4. 25 | *
  5. Create an {@link ExecutorService} via {@link Executors#newCachedThreadPool()}
  6. 26 | *
  7. Determine number of {@link LogParserThread} tasks from lines / ({@link LogParserUtils#getLinesPerThread()} + 1)
  8. 27 | *
  9. Submit and wait for all tasks to finish
  10. 28 | *
  11. Aggregate lines into status from each task
  12. 29 | *
30 | * 31 | * @see StreamParsingStrategy 32 | * @since 2.4.0 33 | */ 34 | class ClassicParsingStrategy implements ParsingStrategy { 35 | @Override 36 | public HashMap parse(ParsingInput input) { 37 | final Logger logger = Logger.getLogger(this.getClass().getName()); 38 | 39 | // Copy remote file to temp local location 40 | String tempDir = System.getProperty("java.io.tmpdir"); 41 | if (!tempDir.endsWith(File.separator)) { 42 | tempDir = tempDir + File.separator; 43 | } 44 | 45 | final String tempFileLocation = tempDir + "log-parser_" + input.getSignature(); 46 | final File tempFile = new File(tempFileLocation); 47 | final FilePath tempFilePath = new FilePath(tempFile); 48 | 49 | try { 50 | tempFilePath.copyFrom(input.getLog()); 51 | 52 | logger.log(Level.INFO, "Local temp file:" + tempFileLocation); 53 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(tempFilePath.read(), input.getCharset()))) { 54 | String[] parsingRulesArray = input.getParsingRulesArray(); 55 | Pattern[] compiledPatterns = input.getCompiledPatterns(); 56 | int threadCounter = 0; 57 | 58 | final ArrayList runners = new ArrayList<>(); 59 | final LogParserReader logParserReader = new LogParserReader(reader); 60 | 61 | final ExecutorService execSvc = Executors.newCachedThreadPool(); 62 | int linesInLog = LogParserUtils.countLines(tempFileLocation); 63 | final int threadsNeeded = linesInLog 64 | / LogParserUtils.getLinesPerThread() + 1; 65 | 66 | // Read and parse the log parts. Keep the threads and results in an 67 | // array for future reference when writing 68 | for (int i = 0; i < threadsNeeded; i++) { 69 | final LogParserThread logParserThread = new LogParserThread( 70 | logParserReader, parsingRulesArray, compiledPatterns, 71 | threadCounter); 72 | runners.add(logParserThread); 73 | execSvc.execute(logParserThread); 74 | threadCounter++; 75 | } 76 | 77 | // Wait for all threads to finish before sequentially writing the 78 | // outcome 79 | execSvc.shutdown(); 80 | execSvc.awaitTermination(3600, TimeUnit.SECONDS); 81 | 82 | // Sort the threads in the order of the log parts they read 83 | // It could be that thread #1 read log part #2 and thread #2 read log 84 | // part #1 85 | 86 | final int runnersSize = runners.size(); 87 | LogParserThread[] sortedRunners = new LogParserThread[runnersSize]; 88 | for (LogParserThread logParserThread : runners) { 89 | final LogParserLogPart logPart = logParserThread.getLogPart(); 90 | if (logPart != null) { 91 | final int logPartNum = logPart.getLogPartNum(); 92 | sortedRunners[logPartNum] = logParserThread; 93 | } 94 | } 95 | 96 | final HashMap result = new HashMap<>(); 97 | HashMap moreLineStatusMatches; 98 | for (int i = 0; i < runnersSize; i++) { 99 | final LogParserThread logParserThread = sortedRunners[i]; 100 | if (logParserThread != null) { 101 | moreLineStatusMatches = getLineStatusMatches( 102 | logParserThread.getLineStatuses(), i); 103 | result.putAll(moreLineStatusMatches); 104 | } 105 | } 106 | return result; 107 | } 108 | } catch (IOException | InterruptedException e) { 109 | throw new RuntimeException(e); 110 | } finally { 111 | // Delete temp file 112 | try { 113 | tempFilePath.delete(); 114 | } catch (IOException | InterruptedException e) { 115 | logger.log(Level.WARNING, "Failed to delete " + tempFilePath, e); 116 | } 117 | } 118 | } 119 | 120 | private HashMap getLineStatusMatches( 121 | final String[] statuses, final int logPart) { 122 | final HashMap result = new HashMap<>(); 123 | String status; 124 | int line_num; 125 | final int linesPerThread = LogParserUtils.getLinesPerThread(); 126 | if (statuses != null && statuses.length > 0) { 127 | for (int i = 0; i < statuses.length; i++) { 128 | status = statuses[i]; 129 | line_num = i + logPart * linesPerThread; 130 | result.put(String.valueOf(line_num), status); 131 | } 132 | } 133 | return result; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/CompiledPatterns.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Pattern; 8 | 9 | public class CompiledPatterns { 10 | 11 | private String errorMsg; 12 | private Pattern[] compiledPatterns; 13 | private List extraTags; 14 | 15 | public CompiledPatterns() { 16 | this.errorMsg = null; 17 | this.compiledPatterns = null; 18 | this.extraTags = new ArrayList<>(); 19 | } 20 | 21 | public String getError() { 22 | return errorMsg; 23 | } 24 | 25 | public void setError(final String errorMsg) { 26 | if (errorMsg == null || errorMsg.trim().length() == 0) { 27 | this.errorMsg = null; 28 | } else { 29 | this.errorMsg = errorMsg.trim(); 30 | } 31 | } 32 | 33 | @CheckForNull 34 | public Pattern[] getCompiledPatterns() { 35 | return compiledPatterns; 36 | } 37 | 38 | public void setCompiledPatters(final Pattern[] compiledPatterns) { 39 | this.compiledPatterns = compiledPatterns; 40 | } 41 | 42 | public List getExtraTags() { 43 | return extraTags; 44 | } 45 | 46 | public void setExtraTags(List extraTags) { 47 | this.extraTags = extraTags; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LineToStatus.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.console.ConsoleNote; 4 | 5 | import java.util.List; 6 | import java.util.function.UnaryOperator; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Extracted from {@link LogParserThread} so the same implementation could be 12 | * used in different {@link ParsingStrategy} implementations. 13 | * @since 2.4.0 14 | */ 15 | class LineToStatus implements UnaryOperator { 16 | private final List patterns; 17 | 18 | LineToStatus(List patterns) { 19 | this.patterns = patterns; 20 | } 21 | 22 | @Override 23 | public String apply(String s) { 24 | // For now, strip out ConsoleNote(s) before parsing. 25 | // Notes are injected into log lines, and can break start-of-line 26 | // patterns, and include html. Will likely need alternative way to 27 | // handle in the future. 28 | String line = ConsoleNote.removeNotes(s); 29 | for (ParsingRulePattern parsingRulePattern : patterns) { 30 | String rule = parsingRulePattern.getRule(); 31 | if (LogParserUtils.skipParsingRule(rule)) { 32 | continue; 33 | } 34 | Pattern pattern = parsingRulePattern.getPattern(); 35 | Matcher matcher = pattern.matcher(line); 36 | if (matcher.find()) { 37 | String status = rule.split("\\s")[0]; 38 | return LogParserUtils.standardizeStatus(status); 39 | } 40 | } 41 | return LogParserConsts.NONE; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.Functions; 4 | import hudson.model.Action; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.Job; 7 | import hudson.model.Run; 8 | import hudson.plugins.logparser.action.LogParserProjectAction; 9 | import hudson.util.Area; 10 | import hudson.util.ChartUtil; 11 | import hudson.util.ColorPalette; 12 | import hudson.util.DataSetBuilder; 13 | import hudson.util.ShiftedCategoryAxis; 14 | import hudson.util.StackedAreaRenderer2; 15 | 16 | import jenkins.tasks.SimpleBuildStep; 17 | 18 | import java.awt.Color; 19 | import java.io.File; 20 | import java.io.IOException; 21 | 22 | import java.util.Collection; 23 | import java.util.Collections; 24 | 25 | import javax.servlet.ServletException; 26 | 27 | import org.jfree.chart.ChartFactory; 28 | import org.jfree.chart.JFreeChart; 29 | import org.jfree.chart.axis.CategoryAxis; 30 | import org.jfree.chart.axis.CategoryLabelPositions; 31 | import org.jfree.chart.axis.NumberAxis; 32 | import org.jfree.chart.plot.CategoryPlot; 33 | import org.jfree.chart.plot.PlotOrientation; 34 | import org.jfree.chart.renderer.category.StackedAreaRenderer; 35 | import org.jfree.data.category.CategoryDataset; 36 | import org.jfree.ui.RectangleInsets; 37 | import org.kohsuke.stapler.StaplerRequest; 38 | import org.kohsuke.stapler.StaplerResponse; 39 | 40 | public class LogParserAction implements Action, SimpleBuildStep.LastBuildAction { 41 | 42 | final private Run build; 43 | final private LogParserResult result; 44 | final private boolean showGraphs; 45 | 46 | private static String urlName = "parsed_console"; 47 | 48 | @Deprecated 49 | public LogParserAction(final AbstractBuild build, final LogParserResult result, final boolean showGraphs) { 50 | this((Run) build, result, showGraphs); 51 | } 52 | 53 | public LogParserAction(final Run build, final LogParserResult result, final boolean showGraphs) { 54 | this.build = build; 55 | this.result = result; 56 | this.showGraphs = showGraphs; 57 | } 58 | 59 | public Collection getProjectActions() { 60 | if (showGraphs) { 61 | final Job job = build.getParent(); 62 | return Collections.singleton(new LogParserProjectAction(job)); 63 | } else { 64 | return Collections.emptyList(); 65 | } 66 | } 67 | 68 | @Override 69 | public String getIconFileName() { 70 | return "clipboard.gif"; 71 | } 72 | 73 | @Override 74 | public String getDisplayName() { 75 | return "Console Output (parsed)"; 76 | } 77 | 78 | @Override 79 | public String getUrlName() { 80 | return urlName; 81 | } 82 | 83 | public static String getUrlNameStat() { 84 | return urlName; 85 | } 86 | 87 | public Run getOwner() { 88 | return build; 89 | } 90 | 91 | // Used by the summary.jelly of this class to show some totals from the 92 | // result 93 | public LogParserResult getResult() { 94 | return result; 95 | } 96 | 97 | public LogParserAction getPreviousAction() { 98 | Run build = this.getOwner(); 99 | 100 | while (true) { 101 | 102 | build = build.getPreviousBuild(); 103 | 104 | if (build == null) 105 | return null; 106 | LogParserAction action = build.getAction(LogParserAction.class); 107 | if (action != null) 108 | return action; 109 | } 110 | } 111 | 112 | public void doDynamic(final StaplerRequest req, final StaplerResponse rsp) 113 | throws IOException, ServletException, InterruptedException { 114 | final String dir = result.getHtmlLogPath(); 115 | final String file = req.getRestOfPath(); 116 | final String fileArray[] = file.split("/"); 117 | final String lastFileInPath = fileArray[fileArray.length - 1]; 118 | final File f = new File(dir + "/" + lastFileInPath); 119 | rsp.serveFile(req, f.toURI().toURL()); 120 | 121 | } 122 | 123 | public void doGraph(StaplerRequest req, StaplerResponse rsp) 124 | throws IOException { 125 | if (ChartUtil.awtProblemCause != null) { 126 | // not available. send out error message 127 | rsp.sendRedirect2(req.getContextPath() + "/images/headless.png"); 128 | return; 129 | } 130 | 131 | if (req.checkIfModified(getOwner().getTimestamp(), rsp)) 132 | return; 133 | 134 | ChartUtil.generateGraph(req, rsp, createChart(req, buildDataSet()), 135 | calcDefaultSize()); 136 | } 137 | 138 | public void doGraphMap(StaplerRequest req, StaplerResponse rsp) 139 | throws IOException { 140 | if (req.checkIfModified(this.getOwner().getTimestamp(), rsp)) 141 | return; 142 | ChartUtil.generateClickableMap(req, rsp, 143 | createChart(req, buildDataSet()), calcDefaultSize()); 144 | } 145 | 146 | private Area calcDefaultSize() { 147 | Area res = Functions.getScreenResolution(); 148 | if (res != null && res.width <= 800) 149 | return new Area(250, 100); 150 | else 151 | return new Area(500, 200); 152 | } 153 | 154 | private CategoryDataset buildDataSet() { 155 | 156 | DataSetBuilder dsb = new DataSetBuilder<>(); 157 | 158 | for (LogParserAction a = this; a != null; a = a.getPreviousAction()) { 159 | dsb.add(a.result.getTotalErrors(), "0", 160 | new ChartUtil.NumberOnlyBuildLabel(a.getOwner())); 161 | dsb.add(a.result.getTotalWarnings(), "1", 162 | new ChartUtil.NumberOnlyBuildLabel(a.getOwner())); 163 | dsb.add(a.result.getTotalInfos(), "2", 164 | new ChartUtil.NumberOnlyBuildLabel(a.getOwner())); 165 | dsb.add(a.result.getTotalDebugs(), "3", 166 | new ChartUtil.NumberOnlyBuildLabel(a.getOwner())); 167 | for (String extraTag : a.result.getExtraTags()) { 168 | dsb.add(a.result.getTotalCountsByExtraTag(extraTag), extraTag, 169 | new ChartUtil.NumberOnlyBuildLabel(a.getOwner())); 170 | } 171 | } 172 | return dsb.build(); 173 | } 174 | 175 | private JFreeChart createChart(StaplerRequest req, CategoryDataset dataset) { 176 | 177 | final String relPath = getRelPath(req); 178 | 179 | final JFreeChart chart = ChartFactory.createStackedAreaChart( 180 | null, // chart title 181 | null, // unused 182 | "count", // range axis label 183 | dataset, // data 184 | PlotOrientation.VERTICAL, // orientation 185 | false, // include legend 186 | true, // tooltips 187 | false // urls 188 | ); 189 | 190 | // NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART... 191 | 192 | // set the background color for the chart... 193 | 194 | //final StandardLegend legend = (StandardLegend) chart.getLegend(); 195 | //legend.setAnchor(StandardLegend.SOUTH); 196 | 197 | chart.setBackgroundPaint(Color.white); 198 | 199 | final CategoryPlot plot = chart.getCategoryPlot(); 200 | 201 | //plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0)); 202 | plot.setBackgroundPaint(Color.WHITE); 203 | plot.setOutlinePaint(null); 204 | plot.setForegroundAlpha(0.8f); 205 | //plot.setDomainGridlinesVisible(true); 206 | //plot.setDomainGridlinePaint(Color.white); 207 | plot.setRangeGridlinesVisible(true); 208 | plot.setRangeGridlinePaint(Color.black); 209 | 210 | CategoryAxis domainAxis = new ShiftedCategoryAxis(null); 211 | plot.setDomainAxis(domainAxis); 212 | domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); 213 | domainAxis.setLowerMargin(0.0); 214 | domainAxis.setUpperMargin(0.0); 215 | domainAxis.setCategoryMargin(0.0); 216 | 217 | final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); 218 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 219 | 220 | StackedAreaRenderer ar = new StackedAreaRenderer2() { 221 | 222 | private static final long serialVersionUID = 1L; 223 | 224 | @Override 225 | public String generateURL(CategoryDataset dataset, int row, 226 | int column) { 227 | ChartUtil.NumberOnlyBuildLabel label = (ChartUtil.NumberOnlyBuildLabel) dataset 228 | .getColumnKey(column); 229 | return relPath + label.build.getNumber() + "/testReport/"; 230 | } 231 | 232 | @Override 233 | public String generateToolTip(CategoryDataset dataset, int row, 234 | int column) { 235 | ChartUtil.NumberOnlyBuildLabel label = (ChartUtil.NumberOnlyBuildLabel) dataset 236 | .getColumnKey(column); 237 | LogParserResult result = label.build.getAction(LogParserAction.class).getResult(); 238 | switch (row) { 239 | case 0: 240 | return "Errors: " + result.getTotalErrors(); 241 | case 1: 242 | return "Warnings: " + result.getTotalWarnings(); 243 | case 2: 244 | return "Infos: " + result.getTotalInfos(); 245 | default: 246 | return "Debugs: " + result.getTotalDebugs(); 247 | } 248 | } 249 | }; 250 | plot.setRenderer(ar); 251 | ar.setSeriesPaint(0, ColorPalette.RED); // error 252 | ar.setSeriesPaint(1, ColorPalette.YELLOW); // warning 253 | ar.setSeriesPaint(2, ColorPalette.BLUE); // info 254 | ar.setSeriesPaint(3, ColorPalette.GREY); // debug 255 | 256 | // crop extra space around the graph 257 | plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); 258 | 259 | return chart; 260 | } 261 | 262 | private String getRelPath(StaplerRequest req) { 263 | String relPath = req.getParameter("rel"); 264 | if (relPath == null) 265 | return ""; 266 | return relPath; 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserBuildStep.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.Launcher; 4 | import hudson.model.Action; 5 | import hudson.model.BuildListener; 6 | import hudson.model.AbstractBuild; 7 | import hudson.model.AbstractProject; 8 | import hudson.tasks.BuildStep; 9 | import hudson.tasks.BuildStepMonitor; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | 15 | public class LogParserBuildStep implements BuildStep { 16 | 17 | public boolean prebuild(final AbstractBuild build, final BuildListener listener) { 18 | return true; 19 | } 20 | 21 | public boolean perform(final AbstractBuild build, final Launcher launcher, final BuildListener listener) 22 | throws InterruptedException, IOException { 23 | return true; 24 | } 25 | 26 | public BuildStepMonitor getRequiredMonitorService() { 27 | return BuildStepMonitor.NONE; 28 | } 29 | 30 | public Action getProjectAction(final AbstractProject project) { 31 | return null; 32 | } 33 | 34 | public Collection getProjectActions(AbstractProject project) { 35 | return Collections.emptyList(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserColumn.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.Extension; 4 | import hudson.model.Job; 5 | import hudson.model.Run; 6 | import hudson.views.ListViewColumn; 7 | import hudson.views.ListViewColumnDescriptor; 8 | import net.sf.json.JSONObject; 9 | import org.kohsuke.stapler.StaplerRequest; 10 | 11 | public class LogParserColumn extends ListViewColumn { 12 | public int[] getResult(Job job) { 13 | if (job == null) { 14 | return null; 15 | } 16 | Run build = job.getLastCompletedBuild(); 17 | if (build == null) { 18 | return null; 19 | } 20 | LogParserAction action = build.getAction(LogParserAction.class); 21 | if (action == null) { 22 | return null; 23 | } 24 | LogParserResult result = action.getResult(); 25 | if (result == null) { 26 | return null; 27 | } 28 | 29 | return new int[]{result.getTotalErrors(), result.getTotalWarnings(), result.getTotalInfos(), result.getTotalDebugs()}; 30 | } 31 | 32 | public String getUrl(Job job) { 33 | if (job == null) { 34 | return null; 35 | } 36 | Run build = job.getLastCompletedBuild(); 37 | if (build == null) { 38 | return null; 39 | } 40 | return build.getUrl() + LogParserAction.getUrlNameStat(); 41 | } 42 | 43 | @Extension 44 | public static class LogParserColumnDescriptor extends ListViewColumnDescriptor { 45 | @Override 46 | public ListViewColumn newInstance(StaplerRequest req, JSONObject formData) throws FormException { 47 | return new LogParserColumn(); 48 | } 49 | 50 | @Override 51 | public String getDisplayName() { 52 | return Messages.LogParserColumn_Header(); 53 | } 54 | 55 | @Override 56 | public boolean shownByDefault() { 57 | return false; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserConsts.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import jenkins.model.Jenkins; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | public class LogParserConsts { 9 | 10 | public static final String ERROR = "ERROR"; 11 | public static final String WARNING = "WARNING"; 12 | public static final String INFO = "INFO"; 13 | public static final String DEBUG = "DEBUG"; 14 | public static final String NONE = "NONE"; 15 | public static final String START = "START"; // marks a beginning of a section 16 | public static final String DEFAULT = NONE; 17 | 18 | // Error messages 19 | public static final String CANNOT_PARSE = "log-parser plugin ERROR: Cannot parse log "; 20 | public static final String NOT_INT = " is not an integer - using default"; 21 | 22 | public static final List LEGAL_STATUS = Arrays.asList(ERROR, WARNING, INFO, DEBUG, NONE, START); 23 | public static final List STATUSES_WITH_LINK_FILES = Arrays.asList(ERROR, WARNING, INFO, DEBUG); 24 | public static final List STATUSES_WITH_SECTIONS_IN_LINK_FILES = Arrays.asList(ERROR, WARNING, DEBUG); 25 | 26 | public static String getHtmlOpeningTags() { 27 | final String hudsonRoot = Jenkins.get().getRootUrl(); 28 | final String pluginResourceUrl = String.format("%s/plugin/log-parser/", Jenkins.RESOURCE_PATH).substring(1); 29 | 30 | return "\n" + "\n" + "\t\n" 31 | + "\t\tlog-parser plugin page\n" 32 | + "\t\t\n" 33 | +"\t\n" 34 | + "\t\n"; 35 | } 36 | 37 | public static final String getHtmlClosingTags() { 38 | return "\t\n" + "\n"; 39 | } 40 | 41 | // Parsing in threads for performance 42 | public static final int LINES_PER_THREAD = 10000; // How many lines to parse 43 | // in each thread 44 | public static final int MAX_THREADS = 2; // How many concurrent threads to 45 | // run (unused when implementing 46 | // cached thread pool) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserDisplayConsts.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.util.HashMap; 4 | import org.apache.commons.lang.WordUtils; 5 | 6 | public class LogParserDisplayConsts { 7 | 8 | final private HashMap colorTable = new HashMap<>(); 9 | final private HashMap iconTable = new HashMap<>(); 10 | final private HashMap linkListDisplay = new HashMap<>(); 11 | final private HashMap linkListDisplayPlural = new HashMap<>(); 12 | 13 | public static final String DEFAULT_COLOR = "blue"; 14 | public static final String DEFAULT_ICON = "blue.gif"; 15 | 16 | public static String getDefaultLinkListDisplay(String status) { 17 | return WordUtils.capitalize(status); 18 | } 19 | 20 | public static String getDefaultLinkListDisplayPlural(String status) { 21 | return getDefaultLinkListDisplay(status) + "s"; 22 | } 23 | 24 | public LogParserDisplayConsts() { 25 | // Color of each status 26 | colorTable.put(LogParserConsts.ERROR, "red"); 27 | colorTable.put(LogParserConsts.WARNING, "orange"); 28 | colorTable.put(LogParserConsts.INFO, "blue"); 29 | colorTable.put(LogParserConsts.START, "blue"); 30 | colorTable.put(LogParserConsts.DEBUG, "grey"); 31 | 32 | // Icon for each status in the summary 33 | iconTable.put(LogParserConsts.ERROR, "red.gif"); 34 | iconTable.put(LogParserConsts.WARNING, "yellow.gif"); 35 | iconTable.put(LogParserConsts.INFO, "blue.gif"); 36 | iconTable.put(LogParserConsts.DEBUG, "gray.gif"); 37 | 38 | // How to display in link summary html 39 | linkListDisplay.put(LogParserConsts.ERROR, "Error"); 40 | linkListDisplay.put(LogParserConsts.WARNING, "Warning"); 41 | linkListDisplay.put(LogParserConsts.INFO, "Info"); 42 | linkListDisplay.put(LogParserConsts.DEBUG, "Debug"); 43 | 44 | linkListDisplayPlural.put(LogParserConsts.ERROR, "Errors"); 45 | linkListDisplayPlural.put(LogParserConsts.WARNING, "Warnings"); 46 | linkListDisplayPlural.put(LogParserConsts.INFO, "Infos"); 47 | linkListDisplayPlural.put(LogParserConsts.DEBUG, "Debugs"); 48 | } 49 | 50 | public HashMap getColorTable() { 51 | return colorTable; 52 | } 53 | 54 | public HashMap getIconTable() { 55 | return iconTable; 56 | } 57 | 58 | public HashMap getLinkListDisplay() { 59 | return linkListDisplay; 60 | } 61 | 62 | public HashMap getLinkListDisplayPlural() { 63 | return linkListDisplayPlural; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserLogPart.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | public class LogParserLogPart { 4 | 5 | private String[] lines; 6 | private int logPartNum; 7 | 8 | // Intentional - first object is created, then fields are set later on. 9 | public LogParserLogPart() { 10 | } 11 | 12 | public String[] getLines() { 13 | return lines; 14 | } 15 | 16 | public void setLines(final String[] lines) { 17 | this.lines = lines; 18 | } 19 | 20 | public int getLogPartNum() { 21 | return logPartNum; 22 | } 23 | 24 | public void setLogPartNum(final int logPartNum) { 25 | this.logPartNum = logPartNum; 26 | } 27 | 28 | public boolean isEmpty() { 29 | return lines[0] == null; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserParser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.FilePath; 4 | import hudson.console.ConsoleNote; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.Run; 7 | import hudson.remoting.VirtualChannel; 8 | import hudson.plugins.timestamper.api.TimestamperAPI; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.BufferedWriter; 12 | import java.io.FileWriter; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.nio.charset.Charset; 16 | import java.util.ArrayList; 17 | import java.util.Calendar; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.logging.Level; 22 | import java.util.logging.Logger; 23 | import java.util.regex.Pattern; 24 | 25 | public class LogParserParser { 26 | 27 | final private HashMap statusCount = new HashMap<>(); 28 | final private HashMap writers = new HashMap<>(); 29 | final private HashMap linkFiles = new HashMap<>(); 30 | 31 | final private String[] parsingRulesArray; 32 | final private Pattern[] compiledPatterns; 33 | final private CompiledPatterns compiledPatternsPlusError; 34 | final private List extraTags; 35 | 36 | // if key is 3-ERROR it shows how many errors are in section 3 37 | final private HashMap statusCountPerSection = new HashMap<>(); 38 | final private ArrayList headerForSection = new ArrayList<>(); 39 | private int sectionCounter = 0; 40 | 41 | final private LogParserDisplayConsts displayConstants = new LogParserDisplayConsts(); 42 | 43 | final private VirtualChannel channel; 44 | final private boolean preformattedHtml; 45 | 46 | public LogParserParser(final FilePath parsingRulesFile, 47 | final boolean preformattedHtml, final VirtualChannel channel) 48 | throws IOException { 49 | 50 | // init logger 51 | final Logger logger = Logger.getLogger(getClass().getName()); 52 | 53 | this.parsingRulesArray = LogParserUtils 54 | .readParsingRules(parsingRulesFile); 55 | 56 | // This causes each regular expression to be compiled once for better 57 | // performance 58 | this.compiledPatternsPlusError = LogParserUtils.compilePatterns( 59 | this.parsingRulesArray, logger); 60 | this.compiledPatterns = this.compiledPatternsPlusError 61 | .getCompiledPatterns(); 62 | this.extraTags = this.compiledPatternsPlusError.getExtraTags(); 63 | 64 | this.preformattedHtml = preformattedHtml; 65 | this.channel = channel; 66 | 67 | // Count of lines in this status 68 | statusCount.put(LogParserConsts.ERROR, 0); 69 | statusCount.put(LogParserConsts.WARNING, 0); 70 | statusCount.put(LogParserConsts.INFO, 0); 71 | statusCount.put(LogParserConsts.DEBUG, 0); 72 | for (String extraTag : this.extraTags) { 73 | statusCount.put(extraTag, 0); 74 | } 75 | } 76 | 77 | /* 78 | * This method creates the parsed log file : log.html It also creates the 79 | * lists of links to these errors/warnings/info messages respectively : 80 | * errorLinks.html, warningLinks.html, infoLinks.html 81 | */ 82 | @Deprecated 83 | public LogParserResult parseLog(final AbstractBuild build) throws IOException, InterruptedException { 84 | return this.parseLog((Run) build); 85 | } 86 | 87 | public LogParserResult parseLog(final Run build) throws IOException, InterruptedException { 88 | 89 | // init logger 90 | final Logger logger = Logger.getLogger(getClass().getName()); 91 | 92 | // Get console log file 93 | final InputStream log = build.getLogInputStream(); 94 | final String logDirectory = build.getRootDir().getAbsolutePath(); 95 | 96 | // Determine parsed log files 97 | final String parsedFilePath = logDirectory + "/log_content.html"; 98 | final String errorLinksFilePath = logDirectory + "/logerrorLinks.html"; 99 | final String warningLinksFilePath = logDirectory + "/logwarningLinks.html"; 100 | final String infoLinksFilePath = logDirectory + "/loginfoLinks.html"; 101 | final String debugLinksFilePath = logDirectory + "/logdebugLinks.html"; 102 | final Map linksFilePathByExtraTags = new HashMap<>(); 103 | for (String extraTag : this.extraTags) { 104 | linksFilePathByExtraTags.put(extraTag, logDirectory + "/log" + extraTag + "Links.html"); 105 | } 106 | final String buildRefPath = logDirectory + "/log_ref.html"; 107 | final String buildWrapperPath = logDirectory + "/log.html"; 108 | 109 | // Record file paths in HashMap 110 | linkFiles.put(LogParserConsts.ERROR, errorLinksFilePath); 111 | linkFiles.put(LogParserConsts.WARNING, warningLinksFilePath); 112 | linkFiles.put(LogParserConsts.INFO, infoLinksFilePath); 113 | linkFiles.put(LogParserConsts.DEBUG, debugLinksFilePath); 114 | for (String extraTag : this.extraTags) { 115 | linkFiles.put(extraTag, linksFilePathByExtraTags.get(extraTag)); 116 | } 117 | 118 | // Open console log for reading and all other files for writing 119 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(parsedFilePath))) { 120 | 121 | // Record writers to links files in hash 122 | writers.put(LogParserConsts.ERROR, new BufferedWriter(new FileWriter( 123 | errorLinksFilePath))); 124 | writers.put(LogParserConsts.WARNING, new BufferedWriter(new FileWriter( 125 | warningLinksFilePath))); 126 | writers.put(LogParserConsts.INFO, new BufferedWriter(new FileWriter( 127 | infoLinksFilePath))); 128 | writers.put(LogParserConsts.DEBUG, new BufferedWriter(new FileWriter( 129 | debugLinksFilePath))); 130 | for (String extraTag : this.extraTags) { 131 | writers.put(extraTag, new BufferedWriter(new FileWriter( 132 | linksFilePathByExtraTags.get(extraTag)))); 133 | } 134 | 135 | // Loop on the console log as long as there are input lines and parse 136 | // line by line 137 | // At the end of this loop, we will have: 138 | // - a parsed log with colored lines 139 | // - 4 links files which will be consolidated into one referencing html 140 | // file. 141 | 142 | // Create dummy header and section for beginning of log 143 | final String shortLink = " Beginning of log"; 144 | LogParserWriter.writeHeaderTemplateToAllLinkFiles(writers, sectionCounter); // This enters a line which will later be 145 | // replaced by the actual header and count for 146 | // this header 147 | headerForSection.add(shortLink); 148 | writer.write(LogParserConsts.getHtmlOpeningTags()); 149 | 150 | // write styles for log body 151 | final String styles = "\n"; 156 | writer.write(styles); 157 | 158 | if (this.preformattedHtml) 159 | writer.write("
");
160 |             // Read bulks of lines, parse
161 |             parseLogBody(build, writer, log,
162 |                     logger);
163 | 
164 |             // Write parsed output, links, etc.
165 |             //writeLogBody();
166 | 
167 |             // Close html footer
168 |             if (this.preformattedHtml)
169 |                 writer.write("
"); 170 | writer.write(LogParserConsts.getHtmlClosingTags()); 171 | } finally { 172 | for (BufferedWriter writer : writers.values()) { 173 | writer.close(); 174 | } 175 | } 176 | 177 | // Build the reference html from the warnings/errors/info html files 178 | // created in the loop above 179 | LogParserWriter.writeReferenceHtml(buildRefPath, headerForSection, 180 | statusCountPerSection, displayConstants.getIconTable(), 181 | displayConstants.getLinkListDisplay(), 182 | displayConstants.getLinkListDisplayPlural(), statusCount, 183 | linkFiles, extraTags); 184 | // Write the wrapping html for the reference page and the parsed log page 185 | LogParserWriter.writeWrapperHtml(buildWrapperPath); 186 | 187 | final String buildUrlPath = build.getUrl(); // job/cat_log/58 188 | final String buildActionPath = LogParserAction.getUrlNameStat(); // "parsed_console"; 189 | final String parsedLogURL = buildUrlPath + buildActionPath + "/log.html"; 190 | 191 | // Create result class 192 | final LogParserResult result = new LogParserResult(); 193 | result.setHtmlLogFile(parsedFilePath); 194 | result.setTotalErrors(statusCount.get(LogParserConsts.ERROR)); 195 | result.setTotalWarnings(statusCount.get(LogParserConsts.WARNING)); 196 | result.setTotalInfos(statusCount.get(LogParserConsts.INFO)); 197 | result.setTotalDebugs(statusCount.get(LogParserConsts.DEBUG)); 198 | for (String extraTag : this.extraTags) { 199 | result.putTotalCountsByExtraTag(extraTag, statusCount.get(extraTag)); 200 | } 201 | result.setErrorLinksFile(errorLinksFilePath); 202 | result.setWarningLinksFile(warningLinksFilePath); 203 | result.setInfoLinksFile(infoLinksFilePath); 204 | result.setDebugLinksFile(debugLinksFilePath); 205 | for (String extraTag : this.extraTags) { 206 | result.putLinksFileByExtraTag(extraTag, linksFilePathByExtraTags.get(extraTag)); 207 | } 208 | result.setParsedLogURL(parsedLogURL); 209 | result.setHtmlLogPath(logDirectory); 210 | result.setBadParsingRulesError(this.compiledPatternsPlusError.getError()); 211 | result.setExtraTags(this.extraTags); 212 | 213 | return result; 214 | 215 | } 216 | 217 | public String parseLine(final String line) throws IOException { 218 | return parseLine(line, null); 219 | } 220 | 221 | public static String convertEscapeSequencesToHtml(String text) { 222 | // Remove xterm color escape sequence with an empty space. 223 | return text.replaceAll("\u001B\\[(\\d{1,2})(;\\d{1,2})?(;\\d{1,2})?m", ""); 224 | } 225 | 226 | public String parseLine(final String line, final String status) 227 | throws IOException { 228 | String parsedLine = line; 229 | String effectiveStatus = status; 230 | if (status == null) { 231 | effectiveStatus = LogParserConsts.NONE; 232 | } else if (status.equals(LogParserConsts.START)) { 233 | effectiveStatus = LogParserConsts.INFO; 234 | } 235 | 236 | // need to strip out for display also (in addition to parsing). 237 | parsedLine = ConsoleNote.removeNotes(parsedLine); 238 | // Allows < to be seen in log which is html 239 | parsedLine = parsedLine.replaceAll("<", "<"); 240 | // Allows > to be seen in log which is html 241 | parsedLine = parsedLine.replaceAll(">", ">"); 242 | 243 | // Remove xterm color escape sequence with an empty space. 244 | parsedLine = convertEscapeSequencesToHtml(parsedLine); 245 | 246 | if (effectiveStatus != null 247 | && !effectiveStatus.equals(LogParserConsts.NONE)) { 248 | // Increment count of the status 249 | incrementCounter(effectiveStatus); 250 | incrementCounterPerSection(effectiveStatus, sectionCounter); 251 | // Color line according to the status 252 | final String parsedLineColored = colorLine(parsedLine, 253 | effectiveStatus); 254 | 255 | // Mark line and add to left side links of highlighted lines 256 | final String parsedLineColoredAndMarked = addMarkerAndLink( 257 | parsedLineColored, effectiveStatus, status); 258 | parsedLine = parsedLineColoredAndMarked; 259 | } 260 | final StringBuffer result = new StringBuffer(parsedLine); 261 | if (!preformattedHtml) 262 | result.append("
\n"); 263 | return result.toString(); 264 | } 265 | 266 | public void incrementCounter(final String status) { 267 | final int currentVal = statusCount.get(status); 268 | statusCount.put(status, currentVal + 1); 269 | } 270 | 271 | public void incrementCounterPerSection(final String status, 272 | final int sectionNumber) { 273 | final String key = LogParserUtils.getSectionCountKey(status, 274 | sectionNumber); 275 | Integer currentValInteger = statusCountPerSection.get(key); 276 | // No value - entered yet - initialize with 0 277 | if (currentValInteger == null) { 278 | currentValInteger = 0; 279 | } 280 | final int newVal = currentValInteger + 1; 281 | statusCountPerSection.put(key, newVal); 282 | } 283 | 284 | private String colorLine(final String line, final String status) { 285 | String color = displayConstants.getColorTable().get(status); 286 | if (color == null) { 287 | color = LogParserDisplayConsts.DEFAULT_COLOR; 288 | } 289 | final StringBuffer result = new StringBuffer(""); 294 | result.append(line); 295 | result.append(""); 296 | return result.toString(); 297 | } 298 | 299 | private String addMarkerAndLink(final String line, 300 | final String effectiveStatus, final String status) 301 | throws IOException { 302 | // Add marker 303 | final String statusCountStr = statusCount 304 | .get(effectiveStatus).toString(); 305 | final String marker = effectiveStatus + statusCountStr; 306 | 307 | // Add link 308 | final StringBuffer shortLink = new StringBuffer( 309 | " "); 312 | shortLink.append(line); 313 | shortLink.append(""); 314 | 315 | final StringBuffer link = new StringBuffer("
  • "); 316 | link.append(statusCountStr); 317 | link.append(shortLink); 318 | link.append("
  • "); 319 | 320 | final BufferedWriter linkWriter = (BufferedWriter) writers 321 | .get(effectiveStatus); 322 | linkWriter.write(link.toString()); 323 | linkWriter.newLine(); // Write system dependent end of line. 324 | 325 | // Mark the line 326 | final StringBuffer markedLine = new StringBuffer("

    "); 329 | markedLine.append(line); 330 | 331 | // Handle case where we are entering a new section 332 | if (status.equals(LogParserConsts.START)) { 333 | sectionCounter++; 334 | // This enters a line which will later be replaced by the actual 335 | // header and count for this header 336 | LogParserWriter.writeHeaderTemplateToAllLinkFiles(writers, sectionCounter); 337 | 338 | final StringBuffer brShortLink = new StringBuffer("
    "); 339 | brShortLink.append(shortLink); 340 | headerForSection.add(brShortLink.toString()); 341 | } 342 | 343 | return markedLine.toString(); 344 | } 345 | 346 | private void parseLogBody(final Run build, final BufferedWriter writer, final InputStream log, 347 | final Logger logger) throws IOException, InterruptedException { 348 | 349 | // Logging information - start 350 | final String signature = build.getParent().getName() + "_build_" 351 | + build.getNumber(); 352 | logger.log(Level.INFO, "LogParserParser: Start parsing : " + signature); 353 | final Calendar calendarStart = Calendar.getInstance(); 354 | Charset charset = build.getCharset(); 355 | 356 | final HashMap lineStatusMatches = channel.call( 357 | new LogParserStatusComputer(log, parsingRulesArray, compiledPatterns, signature, charset)); 358 | 359 | // Read log file from start - line by line and apply the statuses as 360 | // found by the threads. 361 | String query = "time=yyyy-MM-dd HH:MM:ss.SSS&appendLog"; 362 | try (BufferedReader reader = TimestamperAPI.get().read(build, query)) { 363 | String line; 364 | String status; 365 | int line_num = 0; 366 | while ((line = reader.readLine()) != null) { 367 | status = lineStatusMatches.get(String.valueOf(line_num)); 368 | final String parsedLine = parseLine(line, status); 369 | // This is for displaying sections in the links part 370 | writer.write(parsedLine); 371 | writer.newLine(); // Write system dependent end of line. 372 | line_num++; 373 | } 374 | } 375 | 376 | // Logging information - end 377 | final Calendar calendarEnd = Calendar.getInstance(); 378 | final long diffSeconds = (calendarEnd.getTimeInMillis() - calendarStart 379 | .getTimeInMillis()) / 1000; 380 | final long diffMinutes = diffSeconds / 60; 381 | logger.log(Level.INFO, "LogParserParser: Parsing took " + diffMinutes 382 | + " minutes (" + diffSeconds + ") seconds."); 383 | 384 | } 385 | 386 | } 387 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.Launcher; 7 | import hudson.Util; 8 | import hudson.model.AbstractProject; 9 | import hudson.model.Result; 10 | import hudson.model.Run; 11 | import hudson.model.TaskListener; 12 | import hudson.tasks.BuildStepDescriptor; 13 | import hudson.tasks.BuildStepMonitor; 14 | import hudson.tasks.Publisher; 15 | import hudson.tasks.Recorder; 16 | import hudson.util.ListBoxModel; 17 | import jenkins.model.Jenkins; 18 | import jenkins.tasks.SimpleBuildStep; 19 | import net.sf.json.JSONObject; 20 | import org.jenkinsci.Symbol; 21 | import org.kohsuke.stapler.DataBoundConstructor; 22 | import org.kohsuke.stapler.DataBoundSetter; 23 | import org.kohsuke.stapler.StaplerRequest; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.Serializable; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | 33 | public class LogParserPublisher extends Recorder implements SimpleBuildStep, Serializable { 34 | static final String NULL_PARSING_RULES = "Path to global parsing rules is null"; 35 | private static final long serialVersionUID = 1L; 36 | public boolean unstableOnWarning; 37 | public boolean failBuildOnError; 38 | public boolean showGraphs; 39 | public String parsingRulesPath = null; 40 | public boolean useProjectRule; 41 | public String projectRulePath = null; 42 | 43 | /** 44 | * Create new LogParserPublisher. 45 | * 46 | * @param unstableOnWarning mark build unstable if warnings found. 47 | * @param failBuildOnError mark build failed if errors found. 48 | * @param showGraphs show graphs on job page. 49 | * @param parsingRulesPath path to the global parsing rules. 50 | * @param useProjectRule true if we use a project specific rule. 51 | * @param projectRulePath path to project specific rules relative to 52 | * workspace root. 53 | */ 54 | @Deprecated 55 | @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Backwards compatibility") 56 | private LogParserPublisher(final boolean unstableOnWarning, 57 | final boolean failBuildOnError, final boolean showGraphs, 58 | final String parsingRulesPath, final boolean useProjectRule, 59 | final String projectRulePath) { 60 | 61 | this.unstableOnWarning = unstableOnWarning; 62 | this.failBuildOnError = failBuildOnError; 63 | this.showGraphs = showGraphs; 64 | this.parsingRulesPath = parsingRulesPath; 65 | this.useProjectRule = useProjectRule; 66 | this.projectRulePath = projectRulePath; 67 | } 68 | 69 | @DataBoundConstructor 70 | public LogParserPublisher(boolean useProjectRule, String projectRulePath, String parsingRulesPath) { 71 | super(); 72 | if (useProjectRule) { 73 | this.projectRulePath = Util.fixEmpty(projectRulePath); 74 | this.parsingRulesPath = null; 75 | } else { 76 | this.parsingRulesPath = Util.fixEmpty(parsingRulesPath); 77 | this.projectRulePath = null; 78 | } 79 | this.useProjectRule = useProjectRule; 80 | } 81 | 82 | @DataBoundSetter 83 | public void setUnstableOnWarning(boolean unstableOnWarning) { 84 | this.unstableOnWarning = unstableOnWarning; 85 | } 86 | 87 | @DataBoundSetter 88 | public void setFailBuildOnError(boolean failBuildOnError) { 89 | this.failBuildOnError = failBuildOnError; 90 | } 91 | 92 | @DataBoundSetter 93 | public void setShowGraphs(boolean showGraphs) { 94 | this.showGraphs = showGraphs; 95 | } 96 | 97 | @Override 98 | public void perform(Run build, FilePath workspace, Launcher launcher, TaskListener listener) throws 99 | InterruptedException, IOException { 100 | 101 | final Logger logger = Logger.getLogger(getClass().getName()); 102 | LogParserResult result = new LogParserResult(); 103 | try { 104 | // Create a parser with the parsing rules as configured : colors, regular expressions, etc. 105 | boolean preformattedHtml = !((DescriptorImpl) getDescriptor()).getLegacyFormatting(); 106 | final FilePath parsingRulesFile; 107 | if (useProjectRule) { 108 | parsingRulesFile = new FilePath(workspace, projectRulePath); 109 | } else if (parsingRulesPath == null) { 110 | logger.log(Level.SEVERE, LogParserConsts.CANNOT_PARSE + build, NULL_PARSING_RULES); 111 | result.setFailedToParseError(NULL_PARSING_RULES); 112 | build.setResult(Result.ABORTED); 113 | build.addAction(new LogParserAction(build, result, showGraphs)); 114 | return; 115 | } else { 116 | parsingRulesFile = new FilePath(new File(parsingRulesPath)); 117 | } 118 | final LogParserParser parser = new LogParserParser(parsingRulesFile, preformattedHtml, launcher.getChannel()); 119 | // Parse the build's log according to these rules and get the result 120 | result = parser.parseLog(build); 121 | 122 | // Mark build as failed/unstable if necessary 123 | if (this.failBuildOnError && result.getTotalErrors() > 0) { 124 | build.setResult(Result.FAILURE); 125 | } else if (this.unstableOnWarning && result.getTotalWarnings() > 0) { 126 | build.setResult(Result.UNSTABLE); 127 | } 128 | } catch (IOException e) { 129 | // Failure to parse should not fail the build - but should be 130 | // handled as a serious error. 131 | // This should catch all process problems during parsing, including 132 | // parser file not found.. 133 | logger.log(Level.SEVERE, LogParserConsts.CANNOT_PARSE + build, e); 134 | result.setFailedToParseError(e.toString()); 135 | } catch (InterruptedException e) { 136 | logger.log(Level.SEVERE, LogParserConsts.CANNOT_PARSE + build, e); 137 | result.setFailedToParseError(e.toString()); 138 | build.setResult(Result.ABORTED); 139 | } 140 | 141 | // Add an action created with the above results 142 | final LogParserAction action = new LogParserAction(build, result, showGraphs); 143 | build.addAction(action); 144 | } 145 | 146 | @Override 147 | public BuildStepDescriptor getDescriptor() { 148 | return Jenkins.get().getDescriptorByType(LogParserPublisher.DescriptorImpl.class); 149 | } 150 | 151 | @Extension @Symbol("logParser") 152 | public static final class DescriptorImpl extends 153 | BuildStepDescriptor { 154 | 155 | private List parsingRulesGlobal = new ArrayList<>(); 156 | private boolean useLegacyFormatting = false; 157 | 158 | public DescriptorImpl() { 159 | super(LogParserPublisher.class); 160 | load(); 161 | } 162 | 163 | @Override 164 | public String getDisplayName() { 165 | return "Console output (build log) parsing"; 166 | } 167 | 168 | @Override 169 | public String getHelpFile() { 170 | return "/plugin/log-parser/help.html"; 171 | } 172 | 173 | @Override 174 | public boolean isApplicable( 175 | final Class jobType) { 176 | return true; 177 | } 178 | 179 | public List getParsingRulesGlobal() { 180 | return parsingRulesGlobal; 181 | } 182 | 183 | @DataBoundSetter 184 | public void setParsingRulesGlobal(List parsingRulesChoices) { 185 | this.parsingRulesGlobal = parsingRulesChoices; 186 | } 187 | 188 | public boolean getLegacyFormatting() { 189 | return useLegacyFormatting; 190 | } 191 | 192 | @DataBoundSetter 193 | public void setLegacyFormatting(boolean useLegacyFormatting) { 194 | this.useLegacyFormatting = useLegacyFormatting; 195 | } 196 | 197 | @Override 198 | public boolean configure(final StaplerRequest req, final JSONObject json) 199 | throws FormException { 200 | useLegacyFormatting = false; 201 | parsingRulesGlobal = new ArrayList<>(); 202 | req.bindJSON(this, json); 203 | save(); 204 | return true; 205 | } 206 | 207 | public ListBoxModel doFillParsingRulesPathItems() { 208 | ListBoxModel items = new ListBoxModel(); 209 | for (ParserRuleFile file : parsingRulesGlobal) { 210 | items.add(file.getName(), file.getPath()); 211 | } 212 | return items; 213 | } 214 | } 215 | 216 | public BuildStepMonitor getRequiredMonitorService() { 217 | return BuildStepMonitor.NONE; 218 | } 219 | 220 | /* 221 | * This is read by the config.jelly : ${instance.parserRuleChoices} and 222 | * displays the available choices of parsing rules which were configured in 223 | * the global configurations 224 | */ 225 | public List getParserRuleChoices() { 226 | // Get the descriptor which holds the global configurations and extract 227 | // the available parsing rules from there 228 | return ((DescriptorImpl) this.getDescriptor()).getParsingRulesGlobal(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserReader.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | public class LogParserReader { 9 | 10 | final private BufferedReader reader; 11 | int logPartNum = 0; 12 | boolean endOfFile = false; 13 | 14 | public LogParserReader(final BufferedReader reader) { 15 | this.reader = reader; 16 | } 17 | 18 | public synchronized LogParserLogPart readLogPart(final int threadNum) throws IOException { 19 | final Logger logger = Logger.getLogger(this.getClass().getName()); 20 | logger.log(Level.INFO, "Start reading log part " + logPartNum + " in thread #" + threadNum); 21 | final int numLines = LogParserUtils.getLinesPerThread(); 22 | String[] lines = new String[numLines]; 23 | final LogParserLogPart result = new LogParserLogPart(); 24 | 25 | int counter = 0; 26 | String line; 27 | while (counter < numLines && (line = reader.readLine()) != null) { 28 | lines[counter++] = line; 29 | } 30 | logger.log(Level.INFO, "Done reading log part " + logPartNum); 31 | result.setLines(lines); 32 | result.setLogPartNum(logPartNum); 33 | 34 | if (result.isEmpty()) { 35 | this.endOfFile = true; 36 | } 37 | logPartNum++; // increment counter for next call of method by another thread 38 | 39 | return result; 40 | } 41 | 42 | public boolean isEndOfFile() { 43 | return endOfFile; 44 | } 45 | 46 | public void setEndOfFile(final boolean endOfFile) { 47 | this.endOfFile = endOfFile; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.io.Reader; 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | public class LogParserResult { 15 | 16 | private int totalErrors = 0; 17 | private int totalWarnings = 0; 18 | private int totalInfos = 0; 19 | private int totalDebugs = 0; 20 | private final Map totalCountsByExtraTag = new HashMap<>(); 21 | 22 | private String htmlLogFile; 23 | private String errorLinksFile; 24 | private String warningLinksFile; 25 | private String infoLinksFile; 26 | private String debugLinksFile; 27 | private final Map linkedFilesByExtraTag = new HashMap<>(); 28 | private Set extraTags = new HashSet<>(); 29 | 30 | private String parsedLogURL; 31 | private String htmlLogPath; 32 | 33 | private String failedToParseError; 34 | private String badParsingRulesError; 35 | 36 | protected Object readResolve() { 37 | if (extraTags == null) { // avoid NPE when deserializing old results 38 | extraTags = new HashSet<>(); 39 | } 40 | return this; 41 | } 42 | 43 | public String getBadParsingRulesError() { 44 | return badParsingRulesError; 45 | } 46 | 47 | public String getBadParsingRulesErrorDisplay() { 48 | return badParsingRulesError.replaceAll("\n", "
    "); 49 | } 50 | 51 | public void setBadParsingRulesError(final String badParsingRulesError) { 52 | this.badParsingRulesError = badParsingRulesError; 53 | } 54 | 55 | public String getFailedToParseError() { 56 | return failedToParseError; 57 | } 58 | 59 | public void setFailedToParseError(final String failedToParseError) { 60 | this.failedToParseError = failedToParseError; 61 | } 62 | 63 | public int getTotalErrors() { 64 | return totalErrors; 65 | } 66 | 67 | public int getTotalWarnings() { 68 | return totalWarnings; 69 | } 70 | 71 | public int getTotalInfos() { 72 | return totalInfos; 73 | } 74 | 75 | public int getTotalDebugs() { 76 | return totalDebugs; 77 | } 78 | 79 | public int getTotalCountsByExtraTag(String tag) { 80 | return totalCountsByExtraTag.get(tag); 81 | } 82 | 83 | public String getHtmlLogFile() { 84 | return htmlLogFile; 85 | } 86 | 87 | public String getHtmlLogPath() { 88 | return htmlLogPath; 89 | } 90 | 91 | public String getErrorLinksFile() { 92 | return errorLinksFile; 93 | } 94 | 95 | public String getWarningLinksFile() { 96 | return warningLinksFile; 97 | } 98 | 99 | public String getInfoLinksFile() { 100 | return infoLinksFile; 101 | } 102 | 103 | public String getDebugLinksFile() { 104 | return debugLinksFile; 105 | } 106 | 107 | public String getLinksFileByExtraTag(String tag) { 108 | return linkedFilesByExtraTag.get(tag); 109 | } 110 | 111 | public String getParsedLogURL() { 112 | return parsedLogURL; 113 | } 114 | 115 | public Reader getReader(final String filePath) throws IOException { 116 | final File logFile = new File(filePath); 117 | if (logFile.exists()) { 118 | return new FileReader(logFile); 119 | } 120 | return null; 121 | } 122 | 123 | public Reader getLogReader() throws IOException { 124 | return getReader(getHtmlLogFile()); 125 | } 126 | 127 | public Reader getErrorLinksReader() throws IOException { 128 | return getReader(getErrorLinksFile()); 129 | } 130 | 131 | public Reader getWarningLinksReader() throws IOException { 132 | return getReader(getWarningLinksFile()); 133 | } 134 | 135 | public Reader getInfoLinksReader() throws IOException { 136 | return getReader(getInfoLinksFile()); 137 | } 138 | 139 | public Reader getDebugLinkedReader() throws IOException { 140 | return getReader(getDebugLinksFile()); 141 | } 142 | 143 | public Reader getLinkedReaderByExtraTag(String tag) throws IOException { 144 | return getReader(getLinksFileByExtraTag(tag)); 145 | } 146 | 147 | public void setHtmlLogFile(final String file) { 148 | this.htmlLogFile = file; 149 | } 150 | 151 | public void setHtmlLogPath(final String dir) { 152 | this.htmlLogPath = dir; 153 | } 154 | 155 | public void setErrorLinksFile(final String file) { 156 | this.errorLinksFile = file; 157 | } 158 | 159 | public void setWarningLinksFile(final String file) { 160 | this.warningLinksFile = file; 161 | } 162 | 163 | public void setInfoLinksFile(final String file) { 164 | this.infoLinksFile = file; 165 | } 166 | 167 | public void setDebugLinksFile(final String file) { 168 | this.debugLinksFile = file; 169 | } 170 | 171 | public void putLinksFileByExtraTag(final String tag, final String file) { 172 | this.linkedFilesByExtraTag.put(tag, file); 173 | } 174 | 175 | public void setTotalErrors(final int totalErrors) { 176 | this.totalErrors = totalErrors; 177 | } 178 | 179 | public void setTotalWarnings(final int totalWarnings) { 180 | this.totalWarnings = totalWarnings; 181 | } 182 | 183 | public void setTotalInfos(final int totalInfos) { 184 | this.totalInfos = totalInfos; 185 | } 186 | 187 | public void setTotalDebugs(final int totalDebugs) { 188 | this.totalDebugs = totalDebugs; 189 | } 190 | 191 | public void putTotalCountsByExtraTag(final String tag, final int totalCounts) { 192 | this.totalCountsByExtraTag.put(tag, totalCounts); 193 | } 194 | 195 | public void setParsedLogURL(final String parsedLogURL) { 196 | this.parsedLogURL = parsedLogURL; 197 | } 198 | 199 | public File getHtmlLogFileToRead() { 200 | return new File(this.htmlLogFile); 201 | } 202 | 203 | public void setExtraTags(Collection extraTags) { 204 | this.extraTags.addAll(extraTags); 205 | } 206 | 207 | public Set getExtraTags() { 208 | return this.extraTags; 209 | } 210 | 211 | public String getHtmlContent() { 212 | final StringBuffer result = new StringBuffer(""); 213 | String line = ""; 214 | try { 215 | File file = null; 216 | RandomAccessFile f = null; 217 | try { 218 | file = this.getHtmlLogFileToRead(); 219 | f = new RandomAccessFile(file, "r"); 220 | 221 | while ((line = f.readLine()) != null) { 222 | result.append(line); 223 | result.append("
    "); 224 | } 225 | } finally { 226 | f.close(); 227 | } 228 | 229 | } catch (Exception e) { 230 | e.printStackTrace(); 231 | } 232 | return result.toString(); 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserStatusComputer.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.remoting.RemoteInputStream; 4 | import jenkins.security.MasterToSlaveCallable; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.charset.Charset; 9 | import java.util.HashMap; 10 | import java.util.regex.Pattern; 11 | 12 | public class LogParserStatusComputer extends MasterToSlaveCallable, RuntimeException> { 13 | 14 | private static final long serialVersionUID = 646211554890510833L; 15 | final private String[] parsingRulesArray; 16 | final private Pattern[] compiledPatterns; 17 | private final InputStream remoteLog; 18 | private final String signature; 19 | private final String charsetName; 20 | 21 | public LogParserStatusComputer( 22 | final InputStream log, 23 | final String[] parsingRulesArray, 24 | final Pattern[] compiledPatterns, 25 | final String signature, 26 | final Charset charset) { 27 | this.parsingRulesArray = parsingRulesArray; 28 | this.compiledPatterns = compiledPatterns; 29 | this.remoteLog = new RemoteInputStream(log, RemoteInputStream.Flag.GREEDY); 30 | this.signature = signature; 31 | this.charsetName = charset.name(); 32 | } 33 | 34 | /** 35 | * Prefer the other constructor that allows for passing in the {@link Charset}. 36 | * This constructor relies on the default charset. 37 | * @param log 38 | * @param parsingRulesArray 39 | * @param compiledPatterns 40 | * @param signature 41 | * @throws IOException 42 | * @throws InterruptedException 43 | */ 44 | @Deprecated 45 | public LogParserStatusComputer( 46 | final InputStream log, final String[] parsingRulesArray, 47 | final Pattern[] compiledPatterns, 48 | final String signature) throws IOException, InterruptedException { 49 | this(log, parsingRulesArray, compiledPatterns, signature, Charset.defaultCharset()); 50 | } 51 | 52 | public HashMap call() { 53 | try { 54 | return computeStatusMatches(remoteLog, signature, charsetName); 55 | // rethrow any exception here to report why the 56 | // parsing failed 57 | } catch (InterruptedException e) { 58 | throw new RuntimeException(e); 59 | } catch (IOException e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | 64 | private HashMap computeStatusMatches( 65 | final InputStream log, 66 | final String signature, 67 | final String charsetName) throws IOException, InterruptedException { 68 | // SLAVE PART START 69 | ParsingStrategyLocator locator = ParsingStrategyLocator.create(); 70 | ParsingStrategy strategy = locator.get(); 71 | 72 | ParsingInput input = new ParsingInput(parsingRulesArray, compiledPatterns, log, signature, charsetName); 73 | return strategy.parse(input); 74 | // SLAVE PART END 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserThread.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.IOException; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | import java.util.regex.Pattern; 9 | 10 | class LogParserThread extends Thread { 11 | 12 | private LogParserLogPart logPart; 13 | private final String[] parsingRulesArray; 14 | private final Pattern[] compiledPatterns; 15 | private final int threadNum; 16 | private String[] logPartStatuses; 17 | private int numOfLines; 18 | private final LogParserReader logParserReader; 19 | 20 | public LogParserThread(final LogParserReader logParserReader, 21 | final String[] parsingRulesArray, final Pattern[] compiledPatterns, 22 | final int threadNum) { 23 | this.parsingRulesArray = parsingRulesArray; 24 | this.compiledPatterns = compiledPatterns; 25 | this.threadNum = threadNum; 26 | this.logParserReader = logParserReader; 27 | } 28 | 29 | @Override 30 | public void run() { 31 | try { 32 | // Synchronized method so as not to read from the same file from 33 | // several threads. 34 | logPart = logParserReader.readLogPart(this.threadNum); 35 | } catch (IOException ignored) { 36 | } 37 | logPartStatuses = getLineStatuses(logPart.getLines()); 38 | 39 | } 40 | 41 | public String[] getLineStatuses() { 42 | return this.logPartStatuses; 43 | } 44 | 45 | public LogParserLogPart getLogPart() { 46 | return this.logPart; 47 | } 48 | 49 | public int getNumOfLines() { 50 | return this.numOfLines; 51 | } 52 | 53 | private String[] getLineStatuses(final String[] logPart) { 54 | 55 | final Logger logger = Logger.getLogger(this.getClass().getName()); 56 | logger.log(Level.INFO, "LogParserThread: Start parsing log part " 57 | + this.logPart.getLogPartNum()); 58 | 59 | numOfLines = 0; 60 | String[] result = new String[logPart.length]; 61 | for (int i = 0; i < logPart.length; i++) { 62 | final String line = logPart[i]; 63 | if (line == null) { 64 | continue; 65 | } 66 | numOfLines++; 67 | final String status = getLineStatus(line); 68 | result[i] = status; 69 | } 70 | 71 | logger.log(Level.INFO, "LogParserThread: Done parsing log part " 72 | + this.logPart.getLogPartNum()); 73 | 74 | return result; 75 | } 76 | 77 | private String getLineStatus(String line) { 78 | List patterns = new LinkedList<>(); 79 | for (int i = 0; i < parsingRulesArray.length; i++) { 80 | String rule = parsingRulesArray[i]; 81 | Pattern pattern = compiledPatterns[i]; 82 | patterns.add(new ParsingRulePattern(rule, pattern)); 83 | } 84 | LineToStatus toStatus = new LineToStatus(patterns); 85 | return toStatus.apply(line); 86 | } 87 | 88 | public int getThreadNum() { 89 | return threadNum; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserUtils.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.FilePath; 4 | 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.io.LineNumberReader; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Locale; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | import java.util.regex.Pattern; 15 | 16 | public final class LogParserUtils { 17 | 18 | public static String[] readParsingRules(final FilePath parsingRulesFile) throws IOException { 19 | try { 20 | return parsingRulesFile.readToString().split("\n"); 21 | } catch (InterruptedException ie) { 22 | throw new IOException(ie); 23 | } 24 | } 25 | 26 | public static boolean skipParsingRule(final String parsingRule) { 27 | return parsingRule == null || parsingRule.equals("") 28 | || parsingRule.charAt(0) == '#' 29 | || parsingRule.startsWith("\\s") 30 | || parsingRule.startsWith("\r") // Carriage return 31 | || parsingRule.contains("$header"); // for now - disregard rules 32 | // with header in them 33 | } 34 | 35 | public static String standardizeStatus(final String status) { 36 | String result = status; 37 | if (result.equalsIgnoreCase("ok")) { 38 | result = LogParserConsts.NONE; 39 | } else if (result.equalsIgnoreCase("end")) { 40 | result = LogParserConsts.INFO; 41 | } else if (result.equalsIgnoreCase("warn") 42 | || result.equalsIgnoreCase("end")) { 43 | result = LogParserConsts.WARNING; 44 | } else if (LogParserConsts.LEGAL_STATUS.contains(result.toUpperCase(Locale.ENGLISH))) { 45 | result = result.toUpperCase(Locale.ENGLISH); 46 | } 47 | 48 | // If some non-existent status is in the configuration - disregard it 49 | // Arbitrary tag: this condition check is commented out so that arbitrary status can come through 50 | 51 | //final List legals = LogParserConsts.LEGAL_STATUS; 52 | //if (!legals.contains(result)) { 53 | // result = LogParserConsts.DEFAULT; 54 | //} 55 | 56 | return result; 57 | } 58 | 59 | public static CompiledPatterns compilePatterns( 60 | final String[] parsingRulesArray, final Logger logger) { 61 | 62 | Pattern[] result = new Pattern[parsingRulesArray.length]; 63 | final StringBuffer badParsingRules = new StringBuffer(); 64 | List extraTags = new ArrayList<>(); 65 | 66 | for (int i = 0; i < parsingRulesArray.length; i++) { 67 | final String parsingRule = parsingRulesArray[i]; 68 | result[i] = null; 69 | if (!skipParsingRule(parsingRule)) { 70 | try { 71 | final String ruleParts[] = parsingRule.split("\\s"); 72 | String regexp = ruleParts[1]; 73 | String tag = ruleParts[0]; 74 | if (!LogParserConsts.LEGAL_STATUS.contains(tag.toUpperCase(Locale.ENGLISH)) 75 | && !Arrays.asList("OK", "END", "WARN").contains(tag.toUpperCase(Locale.ENGLISH))) { 76 | extraTags.add(tag); 77 | } 78 | 79 | final int firstDash = parsingRule.indexOf('/'); 80 | final int lastDash = parsingRule.lastIndexOf('/'); 81 | if (firstDash != -1 && lastDash != -1) { 82 | regexp = parsingRule.substring(firstDash + 1, lastDash); 83 | final Pattern p = Pattern.compile(regexp); 84 | result[i] = p; 85 | 86 | } 87 | } catch (Exception e) { 88 | // Could not use rule for some reason - ignore rule and log it 89 | final String errorMsg = "Bad parsing rule:" + parsingRule 90 | + ", Error:" + e.getMessage(); 91 | logger.log(Level.SEVERE, errorMsg); 92 | badParsingRules.append('\n'); 93 | badParsingRules.append(errorMsg); 94 | } 95 | } 96 | } 97 | 98 | final CompiledPatterns fullResult = new CompiledPatterns(); 99 | fullResult.setCompiledPatters(result); 100 | fullResult.setError(badParsingRules.toString()); 101 | fullResult.setExtraTags(extraTags); 102 | return fullResult; 103 | } 104 | 105 | public static String getSectionCountKey(final String status, 106 | final int sectionNumber) { 107 | return sectionNumber + "-" + status; 108 | } 109 | 110 | public static int getNumThreads() { 111 | int result = LogParserConsts.MAX_THREADS; 112 | final String maxThreadsByEnvStr = System 113 | .getenv("HUDSON_LOG_PARSER_THREADS"); 114 | if (maxThreadsByEnvStr != null) { 115 | try { 116 | result = Integer.parseInt(maxThreadsByEnvStr); 117 | } catch (Exception e) { 118 | // Do nothing - use the default; 119 | Logger.getLogger("getNumThreads").log(Level.FINEST, 120 | "HUDSON_LOG_PARSER_THREADS" + LogParserConsts.NOT_INT); 121 | } 122 | } 123 | return result; 124 | } 125 | 126 | public static int getLinesPerThread() { 127 | int result = LogParserConsts.LINES_PER_THREAD; 128 | final String linesByEnvStr = System 129 | .getenv("HUDSON_LOG_PARSER_LINES_PER_THREAD"); 130 | if (linesByEnvStr != null) { 131 | try { 132 | result = Integer.parseInt(linesByEnvStr); 133 | } catch (Exception e) { 134 | // Do nothing - use the default; 135 | Logger.getLogger("getLinesPerThread").log(Level.FINEST, 136 | "HUDSON_LOG_PARSER_LINES_PER_THREAD" + LogParserConsts.NOT_INT); 137 | } 138 | } 139 | return result; 140 | } 141 | 142 | public static int countLines(final String filename) throws IOException { 143 | try (LineNumberReader reader = new LineNumberReader(new FileReader(filename))) { 144 | int count = 0; 145 | while (reader.readLine() != null) { 146 | // Read the whole file to count the lines. 147 | count++; 148 | } 149 | count = reader.getLineNumber(); 150 | return count; 151 | } 152 | } 153 | 154 | private LogParserUtils() { 155 | // PMD warning to use singleton or bypass by private empty constructor 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/LogParserWriter.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import jenkins.model.Jenkins; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.BufferedWriter; 7 | import java.io.FileReader; 8 | import java.io.FileWriter; 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | 14 | public final class LogParserWriter { 15 | 16 | public static void writeHeaderTemplateToAllLinkFiles( 17 | final HashMap writers, 18 | final int sectionCounter) throws IOException { 19 | final List statuses = LogParserConsts.STATUSES_WITH_SECTIONS_IN_LINK_FILES; 20 | for (String status : statuses) { 21 | final BufferedWriter linkWriter = writers.get(status); 22 | String str = "HEADER HERE: #" + sectionCounter; 23 | linkWriter.write(str + "\n"); 24 | } 25 | 26 | } 27 | 28 | public static void writeWrapperHtml(final String buildWrapperPath) 29 | throws IOException { 30 | final String wrapperHtml = "\n" 31 | + "\n" 32 | + "\n" 33 | + "\n" 34 | + "<p>Viewing the build report requires a Frames-enabled browser</p>\n" 35 | + "<a href='build.log'>build log</a>\n" + "\n" 36 | + "\n"; 37 | 38 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(buildWrapperPath))) { 39 | writer.write(wrapperHtml); 40 | } 41 | } 42 | 43 | public static void writeReferenceHtml(final String buildRefPath, 44 | final ArrayList headerForSection, 45 | final HashMap statusCountPerSection, 46 | final HashMap iconTable, 47 | final HashMap linkListDisplay, 48 | final HashMap linkListDisplayPlural, 49 | final HashMap statusCount, 50 | final HashMap linkFiles, 51 | final List extraTags) throws IOException { 52 | 53 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(buildRefPath))) { 54 | // Hudson stylesheets 55 | writer.write(LogParserConsts.getHtmlOpeningTags()); 56 | // Write Errors 57 | writeLinks(writer, LogParserConsts.ERROR, headerForSection, 58 | statusCountPerSection, iconTable, linkListDisplay, 59 | linkListDisplayPlural, statusCount, linkFiles); 60 | // Write Warnings 61 | writeLinks(writer, LogParserConsts.WARNING, headerForSection, 62 | statusCountPerSection, iconTable, linkListDisplay, 63 | linkListDisplayPlural, statusCount, linkFiles); 64 | // Write Infos 65 | writeLinks(writer, LogParserConsts.INFO, headerForSection, 66 | statusCountPerSection, iconTable, linkListDisplay, 67 | linkListDisplayPlural, statusCount, linkFiles); 68 | // Write Debugs 69 | writeLinks(writer, LogParserConsts.DEBUG, headerForSection, 70 | statusCountPerSection, iconTable, linkListDisplay, 71 | linkListDisplayPlural, statusCount, linkFiles); 72 | // Write extra tags 73 | for (String extraTag : extraTags) { 74 | writeLinks(writer, extraTag, headerForSection, 75 | statusCountPerSection, iconTable, linkListDisplay, 76 | linkListDisplayPlural, statusCount, linkFiles); 77 | } 78 | writer.write(LogParserConsts.getHtmlClosingTags()); 79 | } 80 | } 81 | 82 | private static void writeLinks(final BufferedWriter writer, 83 | final String status, final ArrayList headerForSection, 84 | final HashMap statusCountPerSection, 85 | final HashMap iconTable, 86 | final HashMap linkListDisplay, 87 | final HashMap linkListDisplayPlural, 88 | final HashMap statusCount, 89 | final HashMap linkFiles) throws IOException { 90 | String statusIcon = iconTable.get(status); 91 | if (statusIcon == null) { 92 | statusIcon = LogParserDisplayConsts.DEFAULT_ICON; 93 | } 94 | String linkListDisplayStr = linkListDisplay.get(status); 95 | if (linkListDisplayStr == null) { 96 | linkListDisplayStr = LogParserDisplayConsts.getDefaultLinkListDisplay(status); 97 | } 98 | String linkListDisplayStrPlural = linkListDisplayPlural.get(status); 99 | if (linkListDisplayStrPlural == null) { 100 | linkListDisplayStrPlural = LogParserDisplayConsts.getDefaultLinkListDisplayPlural(status); 101 | } 102 | final String linkListCount = statusCount.get(status).toString(); 103 | 104 | final String hudsonRoot = Jenkins.get().getRootUrl(); 105 | final String iconLocation = String.format("%s/plugin/log-parser/images/", Jenkins.RESOURCE_PATH).substring(1); 106 | 107 | final String styles = 108 | "\n"; 115 | writer.write(styles); 116 | 117 | final String linksStart = "\""\n" 119 | + "" 120 | + linkListDisplayStr + " (" + linkListCount + ")
    \n" 121 | + "
      \n"; 123 | writer.write(linksStart); 124 | 125 | // Read the links file and insert here 126 | try (BufferedReader reader = new BufferedReader(new FileReader(linkFiles.get(status)))) { 127 | final String summaryLine = "
      (SUMMARY_INT_HERE LINK_LIST_DISPLAY_STR in this section)
      "; 128 | 129 | final String headerTemplateRegexp = "HEADER HERE:"; 130 | final String headerTemplateSplitBy = "#"; 131 | 132 | // If it's a header line - put the header of the section 133 | String line; 134 | while ((line = reader.readLine()) != null) { 135 | String curSummaryLine = null; 136 | if (line.startsWith(headerTemplateRegexp)) { 137 | final int headerNum = Integer.parseInt(line.split(headerTemplateSplitBy)[1]); 138 | line = headerForSection.get(headerNum); 139 | final String key = LogParserUtils.getSectionCountKey(status, headerNum); 140 | final Integer summaryInt = statusCountPerSection.get(key); 141 | if (summaryInt == null || summaryInt == 0) { 142 | // Don't write the header if there are no relevant lines for 143 | // this section 144 | line = null; 145 | } else { 146 | String linkListDisplayStrWithPlural = linkListDisplayStr; 147 | if (summaryInt > 1) { 148 | linkListDisplayStrWithPlural = linkListDisplayStrPlural; 149 | } 150 | curSummaryLine = summaryLine.replace("SUMMARY_INT_HERE", 151 | summaryInt.toString()).replace( 152 | "LINK_LIST_DISPLAY_STR", 153 | linkListDisplayStrWithPlural); 154 | } 155 | 156 | } 157 | 158 | if (line != null) { 159 | writer.write(line); 160 | writer.newLine(); // Write system dependent end of line. 161 | } 162 | if (curSummaryLine != null) { 163 | writer.write(curSummaryLine); 164 | writer.newLine(); // Write system dependent end of line. 165 | } 166 | } 167 | } 168 | 169 | final String linksEnd = "
    \n"; 170 | writer.write(linksEnd); 171 | 172 | } 173 | 174 | private LogParserWriter() { 175 | // PMD warning to use singleton or bypass by private empty constructor 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ParserRuleFile.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractDescribableImpl; 5 | import hudson.model.Descriptor; 6 | import org.jenkinsci.Symbol; 7 | import org.kohsuke.stapler.DataBoundConstructor; 8 | import org.kohsuke.stapler.DataBoundSetter; 9 | 10 | public class ParserRuleFile extends AbstractDescribableImpl { 11 | 12 | private String name = null; 13 | private String path = null; 14 | 15 | public ParserRuleFile() { 16 | // Empty constructor 17 | } 18 | 19 | @DataBoundConstructor 20 | public ParserRuleFile(final String name, final String path) { 21 | this.name = name; 22 | this.path = path; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public String getPath() { 30 | return path; 31 | } 32 | 33 | @DataBoundSetter 34 | public void setName(final String name) { 35 | this.name = name; 36 | } 37 | 38 | @DataBoundSetter 39 | public void setPath(final String path) { 40 | this.path = path; 41 | } 42 | 43 | @Extension @Symbol("rule") 44 | public static class DescriptorImpl extends Descriptor { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ParsingInput.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.InputStream; 4 | import java.nio.charset.Charset; 5 | import java.util.regex.Pattern; 6 | 7 | class ParsingInput { 8 | private final String[] parsingRulesArray; 9 | private final Pattern[] compiledPatterns; 10 | private final InputStream log; 11 | private final String signature; 12 | private final String charsetName; 13 | 14 | ParsingInput(String[] parsingRulesArray, Pattern[] compiledPatterns, InputStream log, String signature, String charsetName) { 15 | this.parsingRulesArray = parsingRulesArray; 16 | this.compiledPatterns = compiledPatterns; 17 | this.log = log; 18 | this.signature = signature; 19 | this.charsetName = charsetName; 20 | } 21 | 22 | public String[] getParsingRulesArray() { 23 | return parsingRulesArray; 24 | } 25 | 26 | public Pattern[] getCompiledPatterns() { 27 | return compiledPatterns; 28 | } 29 | 30 | public InputStream getLog() { 31 | return log; 32 | } 33 | 34 | public String getSignature() { 35 | return signature; 36 | } 37 | 38 | public Charset getCharset() { 39 | return Charset.forName(charsetName); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ParsingRulePattern.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.util.Objects; 4 | import java.util.StringJoiner; 5 | import java.util.regex.Pattern; 6 | 7 | class ParsingRulePattern { 8 | private final String rule; 9 | private final Pattern pattern; 10 | 11 | ParsingRulePattern(String rule, Pattern pattern) { 12 | this.rule = rule; 13 | this.pattern = pattern; 14 | } 15 | 16 | public String getRule() { 17 | return rule; 18 | } 19 | 20 | public Pattern getPattern() { 21 | return pattern; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | ParsingRulePattern that = (ParsingRulePattern) o; 29 | return Objects.equals(rule, that.rule) && Objects.equals(pattern, that.pattern); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(rule, pattern); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return new StringJoiner(", ", ParsingRulePattern.class.getSimpleName() + "[", "]") 40 | .add("rule='" + rule + "'") 41 | .add("pattern=" + pattern) 42 | .toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ParsingStrategy.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * Extracted from {@link LogParserStatusComputer} to support additional parsing implementations. 7 | * @see ClassicParsingStrategy 8 | * @see StreamParsingStrategy 9 | * @since 2.4.0 10 | */ 11 | interface ParsingStrategy { 12 | HashMap parse(ParsingInput input); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ParsingStrategyLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Properties; 6 | import java.util.logging.Logger; 7 | 8 | /** 9 | * Finds the desired {@link ParsingStrategy} by reading the system property {@code hudson.plugins.logparser.ParsingStrategy}. 10 | *

    11 | * The default strategy is {@link ClassicParsingStrategy}, which was the only available strategy through v2.3.0. 12 | * This class logs the strategy selected at INFO level. 13 | *

    14 | * An invalid parameter is logged at WARNING and falls back to {@link ClassicParsingStrategy}. 15 | * 16 | * @since 2.4.0 17 | */ 18 | class ParsingStrategyLocator { 19 | private static final String SYSTEM_PROPERTY = ParsingStrategy.class.getName(); 20 | private static final String CLASSIC = ClassicParsingStrategy.class.getName(); 21 | private static final String STREAM = StreamParsingStrategy.class.getName(); 22 | private static final Logger LOGGER = Logger.getLogger(ParsingStrategyLocator.class.getName()); 23 | private final Map systemProperties; 24 | 25 | ParsingStrategyLocator(Map systemProperties) { 26 | this.systemProperties = systemProperties; 27 | } 28 | 29 | static ParsingStrategyLocator create() { 30 | Map typedProperties = new HashMap<>(); 31 | Properties properties = System.getProperties(); 32 | for (Map.Entry entry : properties.entrySet()) { 33 | typedProperties.put(entry.getKey().toString(), entry.getValue().toString()); 34 | } 35 | return new ParsingStrategyLocator(typedProperties); 36 | } 37 | 38 | ParsingStrategy get() { 39 | String strategy = systemProperties.get(SYSTEM_PROPERTY); 40 | 41 | if (STREAM.equals(strategy)) { 42 | LOGGER.info("Using " + STREAM + " as requested by system property " + SYSTEM_PROPERTY); 43 | return new StreamParsingStrategy(); 44 | } 45 | if (strategy == null) { 46 | LOGGER.info("Defaulting to " + CLASSIC); 47 | } else if (CLASSIC.equals(strategy)) { 48 | LOGGER.info("Using " + CLASSIC + " as requested by system property " + SYSTEM_PROPERTY); 49 | } else { 50 | LOGGER.warning("Defaulting to " + CLASSIC + " because system property " + SYSTEM_PROPERTY + " was set to invalid strategy " + strategy); 51 | } 52 | return new ClassicParsingStrategy(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/ReadWriteTextFile.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileReader; 8 | import java.io.FileWriter; 9 | import java.io.IOException; 10 | import java.io.Writer; 11 | import java.util.logging.Logger; 12 | 13 | public final class ReadWriteTextFile { 14 | private static final Logger LOGGER = Logger.getLogger(ReadWriteTextFile.class.getName()); 15 | 16 | private ReadWriteTextFile() { 17 | // to suppress PMD warning 18 | } 19 | 20 | static public String getContents(final File aFile) { 21 | final StringBuilder contents = new StringBuilder(); 22 | 23 | try (BufferedReader input = new BufferedReader(new FileReader(aFile))) { 24 | String line = null; // not declared within while loop 25 | while ((line = input.readLine()) != null) { 26 | contents.append(line).append("\n"); 27 | } 28 | } catch (IOException ex) { 29 | LOGGER.warning("Failure reading from " + aFile.getPath()); 30 | } 31 | 32 | return contents.toString(); 33 | } 34 | 35 | static public void setContents(final File aFile, final String aContents) 36 | throws FileNotFoundException, IOException { 37 | if (aFile == null) { 38 | throw new IllegalArgumentException("File should not be null."); 39 | } 40 | if (!aFile.exists()) { 41 | boolean created = aFile.createNewFile(); 42 | if (created) { 43 | LOGGER.fine(aFile.getPath() + " created"); 44 | } else { 45 | LOGGER.fine(aFile.getPath() + " already exists"); 46 | } 47 | } 48 | if (!aFile.isFile()) { 49 | throw new IllegalArgumentException("Should not be a directory: " 50 | + aFile); 51 | } 52 | if (!aFile.canWrite()) { 53 | throw new IllegalArgumentException("File cannot be written: " 54 | + aFile); 55 | } 56 | 57 | // use buffering 58 | try (Writer output = new BufferedWriter(new FileWriter(aFile))) { 59 | // FileWriter always assumes default encoding is OK! 60 | output.write(aContents); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/StreamParsingStrategy.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.util.HashMap; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.regex.Pattern; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * This strategy relies on {@link BufferedReader#lines()}. 15 | *

    16 | * For each build, this strategy will: 17 | *

      18 | *
    • Lazily stream each line
    • 19 | *
    • Map it to a status via {@link LineToStatus}
    • 20 | *
    • Collect to a map of line number to status
    • 21 | *
    22 | * @since 2.4.0 23 | * @see ClassicParsingStrategy 24 | */ 25 | class StreamParsingStrategy implements ParsingStrategy { 26 | @Override 27 | public HashMap parse(ParsingInput input) { 28 | String[] parsingRulesArray = input.getParsingRulesArray(); 29 | Pattern[] compiledPatterns = input.getCompiledPatterns(); 30 | List parsingRulePatterns = new LinkedList<>(); 31 | for (int i = 0; i < parsingRulesArray.length; i++) { 32 | String rule = parsingRulesArray[i]; 33 | Pattern pattern = compiledPatterns[i]; 34 | parsingRulePatterns.add(new ParsingRulePattern(rule, pattern)); 35 | } 36 | LineToStatus toStatus = new LineToStatus(parsingRulePatterns); 37 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(input.getLog(), input.getCharset())); 38 | Stream lines = reader.lines()) { 39 | List statusByLine = lines.map(toStatus).collect(Collectors.toList()); 40 | final HashMap result = new HashMap<>(); 41 | for (int i = 0; i < statusByLine.size(); i++) { 42 | String status = statusByLine.get(i); 43 | if (!LogParserConsts.NONE.equals(status)) { 44 | result.put(Integer.toString(i), status); 45 | } 46 | } 47 | return result; 48 | } catch (IOException e) { 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/logparser/action/LogParserProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.logparser.action; 2 | 3 | import hudson.model.Action; 4 | import hudson.model.Run; 5 | import hudson.model.Job ; 6 | import hudson.plugins.logparser.LogParserAction; 7 | 8 | import java.io.IOException; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import org.kohsuke.stapler.StaplerRequest; 14 | import org.kohsuke.stapler.StaplerResponse; 15 | 16 | /** 17 | * Created by IntelliJ IDEA. 18 | * User: evilpupu 19 | * Date: 3.10.2011 20 | * Time: 0:14 21 | * To change this template use File | Settings | File Templates. 22 | */ 23 | public class LogParserProjectAction implements Action { 24 | 25 | public final Job job; 26 | 27 | public LogParserProjectAction(Job job) { 28 | this.job = job; 29 | } 30 | 31 | @Override 32 | public String getIconFileName() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public String getDisplayName() { 38 | return "logparser"; 39 | } 40 | 41 | @Override 42 | public String getUrlName() { 43 | return "logparser"; 44 | } 45 | 46 | public LogParserAction getLastLogParserAction() { 47 | final Run tb = job.getLastSuccessfulBuild(); 48 | 49 | Run b = job.getLastBuild(); 50 | while (b != null) { 51 | LogParserAction a = b.getAction(LogParserAction.class); 52 | if (a != null) { 53 | return a; 54 | } 55 | if (b == tb) { 56 | // if even the last successful build didn't produce the test result, 57 | // that means we just don't have any tests configured. 58 | return null; 59 | } 60 | b = b.getPreviousBuild(); 61 | } 62 | 63 | return null; 64 | } 65 | 66 | public void doTrend(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { 67 | LogParserAction a = this.getLastLogParserAction(); 68 | if (a != null) { 69 | a.doGraph(req, rsp); 70 | } else { 71 | rsp.setStatus(HttpServletResponse.SC_NOT_FOUND); 72 | } 73 | } 74 | 75 | public void doTrendMap( StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { 76 | LogParserAction a = this.getLastLogParserAction(); 77 | if (a != null) { 78 | a.doGraphMap(req, rsp); 79 | } else { 80 | rsp.setStatus(HttpServletResponse.SC_NOT_FOUND); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/logparser/LogParserAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    Console Output (parsed)

    15 | Show only this 16 |