├── .github ├── CODEOWNERS ├── release-drafter.yml ├── dependabot.yml └── workflows │ └── release-drafter.yml ├── .mvn ├── maven.config └── extensions.xml ├── src └── main │ ├── resources │ ├── index.jelly │ └── io │ │ └── jenkins │ │ └── plugins │ │ └── LogFlowVisualizer │ │ ├── input │ │ ├── LogFlowInputSimple │ │ │ └── config.jelly │ │ └── LogFlowInputAdvanced │ │ │ └── config.jelly │ │ ├── actions │ │ ├── LogFlowOffsetAction │ │ │ ├── fromToScript.js │ │ │ └── index.jelly │ │ ├── LogFlowProjectAction │ │ │ ├── index.jelly │ │ │ ├── ProjectActionFloatingBox.css │ │ │ ├── floatingBox.jelly │ │ │ ├── ProjectActionSummary.css │ │ │ └── summary.jelly │ │ ├── LogFlowHistoryDiffAction │ │ │ ├── HistoryDiffAction.css │ │ │ └── index.jelly │ │ └── LogFlowHTMLAction │ │ │ ├── HTMLAction.css │ │ │ └── index.jelly │ │ └── LogFlowRecorder │ │ └── config.jelly │ └── java │ └── io │ └── jenkins │ └── plugins │ └── LogFlowVisualizer │ ├── input │ ├── LogFlowInputDescriptor.java │ ├── LogFlowInput.java │ ├── LogFlowInputSimple.java │ └── LogFlowInputAdvanced.java │ ├── model │ ├── HistoryType.java │ ├── LineWithOffset.java │ ├── LineType.java │ ├── OutputHistoryMarked.java │ └── LineOutput.java │ ├── actions │ ├── LogFlowOffsetAction.java │ ├── LogFlowHTMLAction.java │ ├── LogFlowHistoryDiffAction.java │ └── LogFlowProjectAction.java │ ├── LogFlowPictureMaker.java │ ├── LogFlowFilter.java │ └── LogFlowRecorder.java ├── .gitignore ├── Jenkinsfile ├── LICENSE.md ├── pom.xml └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/virtual-pipeline-plugin-developers 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | tag-template: virtual-pipeline-$NEXT_MINOR_VERSION 3 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Log Flow Visualizer plugin for viewing log marks created by the user. 5 |
6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # mvn hpi:run 4 | work 5 | 6 | # IntelliJ IDEA project files 7 | *.iml 8 | *.iws 9 | *.ipr 10 | .idea 11 | 12 | # Eclipse project files 13 | .settings 14 | .classpath 15 | .project 16 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | useContainerAgent: true, 7 | configurations: [ 8 | [platform: 'linux', jdk: 17], // use 'docker' if you have containerized tests 9 | [platform: 'windows', jdk: 11], 10 | ]) 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: maven 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.7 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInputSimple/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowOffsetAction/fromToScript.js: -------------------------------------------------------------------------------- 1 | const urlParameters = new URLSearchParams(window.location.search); 2 | const fromParameter = urlParameters.get('from'); 3 | const toParameter = urlParameters.get('to'); 4 | console.log(toParameter) 5 | const elemFrom = document.getElementsByName('from')[0]; 6 | const elemTo = document.getElementsByName('to')[0]; 7 | elemFrom.value = fromParameter; 8 | if (elemFrom.value !== "") { 9 | elemTo.value = Number(fromParameter) + 2000; 10 | } -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Automates creation of Release Drafts using Release Drafter 2 | # More Info: https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - main 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Drafts your next Release notes as Pull Requests are merged into the default branch 15 | - uses: release-drafter/release-drafter@v5 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

8 | OFFSET LOG PAGE 9 |

10 |

11 | HTML LOG PAGE 12 |

13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHistoryDiffAction/HistoryDiffAction.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .colOne { 7 | width: 400px; 8 | overflow:auto; 9 | overflow-wrap: anywhere; 10 | word-break: break-all; 11 | white-space: nowrap; 12 | } 13 | 14 | .separator{ 15 | margin: 20px; 16 | } 17 | .colTwo { 18 | width: 400px; 19 | overflow:auto; 20 | overflow-wrap: anywhere; 21 | word-break: break-all; 22 | white-space: nowrap; 23 | } 24 | 25 | .EQUAL{ 26 | background-color: var(--light-yellow); 27 | } 28 | .CHANGE{ 29 | background-color: var(--light-cyan); 30 | } 31 | .INSERT{ 32 | background-color: var(--light-green); 33 | } 34 | .DELETE{ 35 | background-color: var(--light-red); 36 | } -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction/ProjectActionFloatingBox.css: -------------------------------------------------------------------------------- 1 | .codeSection{ 2 | width: 500px; 3 | height: 400px; 4 | overflow:auto; 5 | overflow-wrap: anywhere; 6 | word-break: break-all; 7 | white-space: nowrap; 8 | } 9 | .codeWrapper{ 10 | } 11 | .codeWrapper a{ 12 | text-decoration: none; 13 | } 14 | a.START_MARK{ 15 | color: var(--dark-grey); 16 | } 17 | a.END_MARK{ 18 | color: var(--medium-grey); 19 | } 20 | a.ONE_LINE{ 21 | color: var(--green); 22 | } 23 | a.LIMIT_REACHED_LINE{ 24 | color: var(--warning); 25 | } 26 | a.CONTENT_LINE{ 27 | color: var(--primary); 28 | } 29 | .DIFFERENT_PREVIOUS{ 30 | background-color: var(--danger-hover); 31 | } 32 | .DIFFERENT_CURRENT{ 33 | background-color: var(--line-green); 34 | } -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInputAdvanced/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/LogFlowRecorder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowOffsetAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Offset logs

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
-------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHTMLAction/HTMLAction.css: -------------------------------------------------------------------------------- 1 | pre{ 2 | counter-reset: line -1; 3 | white-space: pre; 4 | overflow: auto; 5 | overflow-wrap: anywhere; 6 | word-break: break-all; 7 | } 8 | code{ 9 | counter-increment: line; 10 | display: inline-flex; 11 | } 12 | code:before{ 13 | content: counter(line); 14 | } 15 | code:before{ 16 | -webkit-user-select: none; 17 | } 18 | .logsWrapper{ 19 | overflow: auto; 20 | overflow-wrap: anywhere; 21 | word-break: break-all; 22 | } 23 | 24 | .lineLink a{ 25 | pointer-events: none; 26 | cursor: default; 27 | text-decoration: none; 28 | scroll-margin-top: 100px; 29 | } 30 | 31 | a:hover, a:visited, a:link, a:active, a:selection{ 32 | text-decoration: none; 33 | font-weight: normal; 34 | } 35 | div a { 36 | text-decoration: none; 37 | display: inline-block; 38 | width: 100%; 39 | } 40 | p { 41 | margin: 0px; 42 | } 43 | .START_MARK{ 44 | color: var(--dark-grey); 45 | } 46 | .END_MARK{ 47 | color: var(--medium-grey); 48 | } 49 | .ONE_LINE{ 50 | color: var(--success); 51 | } 52 | .LIMIT_REACHED_LINE{ 53 | color: var(--warning); 54 | } 55 | .CONTENT_LINE{ 56 | color: var(--primary); 57 | } 58 | h3{ 59 | margin-top: 2rem; 60 | } -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInputDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.input; 14 | 15 | import hudson.model.Descriptor; 16 | 17 | public class LogFlowInputDescriptor extends Descriptor { 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/model/HistoryType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.model; 14 | 15 | public enum HistoryType { 16 | SAME, 17 | DIFFERENT_PREVIOUS, 18 | DIFFERENT_CURRENT, 19 | JUST_SAME_REGEX, 20 | DEFAULT 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Latest build Log Flow Visualizer results

6 |
7 | 8 | 9 | 23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction/ProjectActionSummary.css: -------------------------------------------------------------------------------- 1 | .codeSection{ 2 | width: 500px; 3 | height: 400px; 4 | overflow:auto; 5 | overflow-wrap: anywhere; 6 | word-break: break-all; 7 | white-space: nowrap; 8 | } 9 | .codeWrapper{ 10 | word-wrap: break-word; 11 | font-size: var(--font-size-sm); 12 | } 13 | .codeWrapper a{ 14 | text-decoration: none; 15 | } 16 | a.START_MARK{ 17 | color: var(--dark-grey); 18 | font-size: var(--font-size-sm); 19 | } 20 | a.END_MARK{ 21 | color: var(--medium-grey); 22 | } 23 | a.ONE_LINE{ 24 | color: var(--green); 25 | } 26 | a.LIMIT_REACHED_LINE{ 27 | color: var(--warning); 28 | } 29 | a.CONTENT_LINE{ 30 | color: var(--primary); 31 | } 32 | 33 | div.START_MARK{ 34 | color: var(--dark-grey); 35 | font-size: var(--font-size-sm); 36 | } 37 | div.END_MARK{ 38 | color: var(--medium-grey); 39 | } 40 | div.ONE_LINE{ 41 | color: var(--green); 42 | } 43 | div.LIMIT_REACHED_LINE{ 44 | color: var(--warning); 45 | } 46 | div.CONTENT_LINE{ 47 | color: var(--primary); 48 | } 49 | 50 | .DIFFERENT_PREVIOUS{ 51 | background-color: var(--danger-hover); 52 | } 53 | .DIFFERENT_CURRENT{ 54 | background-color: var(--line-green); 55 | } 56 | .JUST_SAME_REGEX{ 57 | background-color: var(--light-yellow); 58 | } 59 | 60 | .legend{ 61 | margin-top: 2rem; 62 | } -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/model/LineWithOffset.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.model; 14 | 15 | public class LineWithOffset { 16 | private final String line; 17 | private final long offset; 18 | 19 | public LineWithOffset(String line, long offset) { 20 | this.line = line; 21 | this.offset = offset; 22 | } 23 | 24 | public String getLine() { 25 | return line; 26 | } 27 | 28 | public long getOffset() { 29 | return offset; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/model/LineType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.model; 14 | 15 | public enum LineType { 16 | ONE_LINE, // for lines generated by single input 17 | START_MARK, // for lines matching startMark 18 | CONTENT_LINE, // for lines that are part of the advanced regex content 19 | END_MARK, // for lines matching startMark 20 | LIMIT_REACHED_LINE, // same as CONTENT_LINE, but marking reached limit of lines 21 | DEFAULT // used for lines with no marking meaning 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.input; 14 | 15 | import hudson.ExtensionPoint; 16 | import hudson.model.Describable; 17 | import hudson.model.Descriptor; 18 | import jenkins.model.Jenkins; 19 | 20 | public abstract class LogFlowInput implements Describable, ExtensionPoint { 21 | protected LogFlowInput() { 22 | 23 | } 24 | 25 | @Override 26 | public Descriptor getDescriptor() { 27 | return Jenkins.get().getDescriptorOrDie(this.getClass()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/model/OutputHistoryMarked.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.model; 14 | 15 | public class OutputHistoryMarked extends LineOutput { 16 | private final HistoryType historyType; 17 | 18 | public OutputHistoryMarked(String regex, String line, int index, Boolean deleteMark, LineType type, HistoryType historyType, long lineOffset, Boolean display) { 19 | super(regex, line, index, deleteMark, type, lineOffset, display); 20 | this.historyType = historyType; 21 | } 22 | 23 | public HistoryType getHistoryType() { 24 | return historyType; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHistoryDiffAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Log Flow Visualizer History Difference

9 |
10 |
11 |
12 |
PREVIOUS BUILD MARKS (Build #${it.compareBuildNumber})
13 |
14 |
15 |
16 |
CURRENT BUILD MARKS (Build #${it.currentBuildNumber})
17 |
18 |
19 | 20 | 21 |
22 |
23 |
${row.oldLine}
24 |
25 |
||
26 |
27 |
${row.newLine}
28 |
29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHTMLAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

HTML LOGS

8 |

Legend

9 |
10 |
Simple oneline marks
11 |
Start of the Advanced mark
12 |
Line inside the Advanced mark
13 |
End of the Advanced mark
14 |
The line that reached upper limit for matched lines
15 |
Line that was not matched
16 |
17 |

Marked logs

18 | 19 |
20 |
21 |                     
22 |                         
23 | 24 | 27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
-------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction/summary.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 |

Log Flow Visualizer results

8 |
9 | 10 | 11 | 25 | 26 |
27 |

Legend

28 |
Simple oneline marks
29 |
Start of the Advanced mark
30 |
Line inside the Advanced mark
31 |
End of the Advanced mark
32 |
The line that reached upper limit for matched lines
33 |
Line that was not matched
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/model/LineOutput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.model; 14 | 15 | //possibly make advanced class from this 16 | public class LineOutput { 17 | private String regex; 18 | 19 | private String line; 20 | 21 | private int index; 22 | 23 | private Boolean deleteMark; 24 | 25 | private LineType type; 26 | 27 | private Boolean display; 28 | 29 | private long lineStartOffset; 30 | 31 | public LineOutput(String regex, String line, int index, Boolean deleteMark, LineType type, long lineStartOffset, Boolean display) { 32 | this.regex = regex; 33 | this.line = line; 34 | this.index = index; 35 | this.deleteMark = deleteMark; 36 | this.type = type; 37 | this.lineStartOffset = lineStartOffset; 38 | this.display = display; 39 | } 40 | 41 | // just for the purpose of good deserialization 42 | public LineOutput() { 43 | super(); 44 | } 45 | 46 | public LineType getType() { 47 | return type; 48 | } 49 | 50 | public long getLineStartOffset() { 51 | return lineStartOffset; 52 | } 53 | 54 | public Boolean getDisplay() { 55 | return display; 56 | } 57 | 58 | public String getLine() { 59 | return line; 60 | } 61 | 62 | public void setLine(String line) { 63 | this.line = line; 64 | } 65 | 66 | public String getRegex() { 67 | return regex; 68 | } 69 | 70 | public int getIndex() { 71 | return index; 72 | } 73 | 74 | 75 | public Boolean getDeleteMark() { 76 | return deleteMark; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 4.87 9 | 10 | 11 | 12 | io.jenkins.plugins 13 | log-flow-visualizer 14 | ${revision}${changelist} 15 | hpi 16 | 17 | Log Flow Visualizer 18 | https://github.com/jenkinsci/${project.artifactId}-plugin 19 | 20 | 21 | MIT License 22 | https://opensource.org/licenses/MIT 23 | 24 | 25 | 26 | 27 | scm:git:https://github.com/${gitHubRepo} 28 | scm:git:https://github.com/${gitHubRepo} 29 | ${scmTag} 30 | https://github.com/${gitHubRepo} 31 | 32 | 33 | 34 | 1.0 35 | -SNAPSHOT 36 | 37 | 38 | 2.452.4 39 | jenkinsci/${project.artifactId}-plugin 40 | 41 | 42 | 43 | 44 | io.jenkins.tools.bom 45 | bom-2.452.x 46 | 3413.v0d896b_76a_30d 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | io.github.java-diff-utils 55 | java-diff-utils 56 | 4.12 57 | 58 | 59 | org.jenkins-ci.plugins 60 | jackson2-api 61 | 62 | 63 | 64 | 65 | 66 | repo.jenkins-ci.org 67 | https://repo.jenkins-ci.org/public/ 68 | 69 | 70 | 71 | 72 | repo.jenkins-ci.org 73 | https://repo.jenkins-ci.org/public/ 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInputSimple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.input; 14 | 15 | import hudson.DescriptorExtensionList; 16 | import hudson.Extension; 17 | import hudson.util.FormValidation; 18 | import jenkins.model.Jenkins; 19 | import org.jenkinsci.Symbol; 20 | import org.kohsuke.stapler.DataBoundConstructor; 21 | import org.kohsuke.stapler.QueryParameter; 22 | import org.kohsuke.stapler.verb.POST; 23 | import org.springframework.lang.NonNull; 24 | 25 | import java.util.regex.Pattern; 26 | import java.util.regex.PatternSyntaxException; 27 | 28 | public class LogFlowInputSimple extends LogFlowInput { 29 | 30 | private final String regex; 31 | 32 | private final Boolean deleteMark; 33 | 34 | 35 | @DataBoundConstructor 36 | public LogFlowInputSimple(String regex, Boolean deleteMark) { 37 | super(); 38 | this.regex = regex; 39 | this.deleteMark = deleteMark; 40 | } 41 | 42 | public String getRegex() { 43 | return regex; 44 | } 45 | 46 | 47 | public Boolean getDeleteMark() { 48 | return deleteMark; 49 | } 50 | 51 | 52 | public DescriptorExtensionList getFormatDescriptors() { 53 | return Jenkins.get().getDescriptorList(LogFlowInput.class); 54 | } 55 | 56 | 57 | @Extension @Symbol("simpleInput") 58 | public static final class DescriptorImpl extends LogFlowInputDescriptor { 59 | 60 | @SuppressWarnings("lgtm[jenkins/no-permission-check]") 61 | @POST 62 | public FormValidation doCheckRegex(@QueryParameter String regex) { 63 | if (regex.isEmpty()) { 64 | return FormValidation.error("Regex is empty"); 65 | } 66 | try { 67 | Pattern.compile(regex); 68 | } catch (PatternSyntaxException exception) { 69 | return FormValidation.error("Regex is invalid"); 70 | } 71 | return FormValidation.ok(); 72 | } 73 | 74 | @Override 75 | @NonNull 76 | public String getDisplayName() { 77 | return "Simple Regex Format"; 78 | } 79 | 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log Flow Visualizer 2 | 3 | ## Introduction 4 | 5 | Log Flow Visualizer (formerly known as **Virtual Pipeline**) Jenkins plugin allows the user to define marks (using regular expressions) that can be found in the main 6 | log and them visualize then on the Project and Job Page. 7 | 8 | 9 | 10 | Two configurations for a mark are available at the moment: 11 | * Simple - oneline, single regex 12 | * Advanced - start and end regex, option to match lines between 13 | 14 | Found marks will be displayed after each Build on the Project page and the Build page with the possibility to: 15 | * jump to the exact position in the log with highlighted marks 16 | * request part of the log based on the marks offset 17 | * compare the marks against previous build 18 | * generate picture of marks as an artifact 19 | 20 | ## Getting started 21 | 22 | The plugin can be built using the command: 23 | ``` 24 | mvn clean install 25 | ``` 26 | Created `.hpi` file in the target directory can be imported into a running Jenkins instance in the Manage Jenkins Section. 27 | 28 | --- 29 | To use the plugin in Jenkins instance, add it to your project as a `Log Flow Visualizer` post-build step (for Freestyle Project, or see [ Pipeline usage section ](#pipeline-usage) for pipeline usage) and set up the configuration according to your needs. 30 | 31 | After next build, you should be able to see the matched marks in a summary on Project and Build page for this build. 32 | 33 | --- 34 | For example if your log looks something like this: 35 | ```agsl 36 | Executing commands during a build 37 | Section Build 38 | building command one 39 | building command two 40 | building command three 41 | Section Test 42 | testing command one 43 | testing command two 44 | testing command three 45 | Section Deploy 46 | deploy command one 47 | deploy command two 48 | End of the log 49 | ``` 50 | you can define Simple mark with regex `Section.*`. This will result in a summary: 51 | ```agsl 52 | Section Build [offset: *some number*] 53 | Section Test [offset: *some number*] 54 | Section Deploy [offset: *some number*] 55 | ``` 56 | with links leading to the exact location of each mark match. 57 | In addition, if you don't see section Deploy in the summary, 58 | it means it is not contained in the log and something probably went wrong in the Test section. 59 | 60 | --- 61 | By clicking the text part of mark in summary, we get to the exact position of the mark in the page with full log. 62 | 63 | By clicking the offset part of the mark in summary, we get to the Offset page with prefilled offset of the mark area. 64 | This feature is meant for searching large logs. 65 | 66 | --- 67 | All the pages (HTML Logs, Offset Logs, History Diff) can be accessed in the sidebar of Build page as well. 68 | 69 | ## Pipeline usage 70 | 71 | For pipeline usage, it is possible to generate logFlowVisualizer as a step in Pipeline Syntax Snippet generator. 72 | 73 | Example usage, creating one simple and one advanced mark: 74 | ``` 75 | logFlowVisualizer compareAgainstLastStableBuild: false, configurations: [[$class: 'LogFlowInputSimple', deleteMark: false, regex: '.*markIWantToFind.*'], [$class: 'LogFlowInputAdvanced', deleteMark: false, endMark: 'END', maxContentLength: 30, numberOfLineToDisplay: 0, startMark: 'START']], generatePicture: false 76 | ``` 77 | 78 | ### Pipeline behaviour 79 | Log Flow visualizer is applied only to steps before the logFlowVisualizer step itself. 80 | 81 | In case of multiple logFlowVisualizer steps applied to the same pipeline, only the first one encountered is applied. 82 | 83 | 84 | 85 | 86 | ## Thesis reference 87 | 88 | More information describing the plugin can be found as a part of thesis [here](https://is.muni.cz/th/otyw7/Jenkins_Virtual_Pipeline.pdf). 89 | 90 | ## LICENSE 91 | 92 | Licensed under MIT, see [LICENSE](LICENSE.md) 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowOffsetAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.actions; 14 | /////////////// 15 | 16 | import hudson.model.Action; 17 | import hudson.model.Run; 18 | import jenkins.model.Jenkins; 19 | import jenkins.tasks.SimpleBuildStep; 20 | import org.kohsuke.stapler.StaplerRequest; 21 | import org.kohsuke.stapler.StaplerResponse; 22 | import org.kohsuke.stapler.WebMethod; 23 | import org.kohsuke.stapler.verb.GET; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.PrintWriter; 28 | import java.io.RandomAccessFile; 29 | import java.util.ArrayList; 30 | import java.util.Collection; 31 | import java.util.List; 32 | 33 | 34 | public class LogFlowOffsetAction implements SimpleBuildStep.LastBuildAction { 35 | private final Run run; 36 | 37 | public LogFlowOffsetAction(Run run) { 38 | this.run = run; 39 | } 40 | 41 | 42 | @GET 43 | @WebMethod(name = "get-search-offset") 44 | public void doSearchOffset(StaplerRequest req, StaplerResponse res) throws IOException { 45 | Jenkins.get().checkPermission(Jenkins.READ); 46 | res.setContentType("text/plain"); 47 | res.setCharacterEncoding("UTF-8"); 48 | PrintWriter out = res.getWriter(); 49 | 50 | try { 51 | long from = Long.parseLong(req.getParameter("from")); 52 | long to = Long.parseLong(req.getParameter("to")); 53 | } catch (NumberFormatException e) { 54 | res.setStatus(404); 55 | out.print("Couldn't parse input"); 56 | return; 57 | } 58 | 59 | long from = Long.parseLong(req.getParameter("from")); 60 | long to = Long.parseLong(req.getParameter("to")); 61 | 62 | RandomAccessFile logs = new RandomAccessFile(this.run.getRootDir() + File.separator + "log", "r"); 63 | 64 | Long checkFrom = from; 65 | Long checkTo = to; 66 | 67 | List resultLines = new ArrayList<>(); 68 | 69 | if (from > to || from < 0 || to < 0 || checkFrom == null || checkTo == null) { 70 | res.setStatus(404); 71 | out.print("Invalid parameters"); 72 | } else { 73 | logs.seek(from); 74 | String line; 75 | // always prints the rest of the line of given offset 76 | while ((line = logs.readLine()) != null) { 77 | resultLines.add(line); 78 | 79 | //checking the upper limit 80 | if (logs.getFilePointer() >= to) { 81 | break; 82 | } 83 | } 84 | 85 | for (String resultLine : 86 | resultLines) { 87 | out.println(resultLine); 88 | } 89 | 90 | } 91 | out.flush(); 92 | logs.close(); 93 | } 94 | 95 | @Override 96 | public Collection getProjectActions() { 97 | return new ArrayList<>(); 98 | } 99 | 100 | @Override 101 | public String getIconFileName() { 102 | return "symbol-logs"; 103 | } 104 | 105 | @Override 106 | public String getDisplayName() { 107 | return "Log Flow Visualizer Offset Logs"; 108 | } 109 | 110 | @Override 111 | public String getUrlName() { 112 | return "logsOffset"; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/LogFlowPictureMaker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer; 14 | 15 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 16 | 17 | import java.awt.*; 18 | import java.awt.image.BufferedImage; 19 | import java.util.List; 20 | 21 | public class LogFlowPictureMaker { 22 | 23 | private final int width; 24 | private int height; 25 | private Font font = new Font("Arial", Font.PLAIN, 20); 26 | private Color backColor = Color.WHITE; 27 | private Color textColor = Color.BLACK; 28 | 29 | public LogFlowPictureMaker(int width, int height, Font font, Color backColor, Color textColor) { 30 | this.width = width; 31 | this.height = height; 32 | this.font = font; 33 | this.backColor = backColor; 34 | this.textColor = textColor; 35 | } 36 | 37 | public LogFlowPictureMaker(int width, int height) { 38 | this.width = width; 39 | this.height = height; 40 | } 41 | 42 | public Font getFont() { 43 | return font; 44 | } 45 | 46 | public Color getBackColor() { 47 | return backColor; 48 | } 49 | 50 | public Color getTextColor() { 51 | return textColor; 52 | } 53 | 54 | public int getWidth() { 55 | return width; 56 | } 57 | 58 | public int getHeight() { 59 | return height; 60 | } 61 | 62 | public void setHeight(int height) { 63 | this.height = height; 64 | } 65 | 66 | public BufferedImage createPicture(List lines) { 67 | int toDisplayCounter = 0; 68 | for (LineOutput line : 69 | lines) { 70 | if (line.getDisplay()) { 71 | toDisplayCounter += 1; 72 | } 73 | } 74 | this.setHeight((5 + toDisplayCounter) * (font.getSize())); //setting size according to number of displayed lines 75 | 76 | BufferedImage image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); 77 | Graphics2D graphics2D = image.createGraphics(); 78 | graphics2D.setColor(this.getBackColor()); 79 | graphics2D.fillRect(0, 0, this.getWidth(), this.getHeight()); 80 | graphics2D.setColor(this.getTextColor()); 81 | graphics2D.setFont(this.getFont()); 82 | 83 | int lineHeight = graphics2D.getFontMetrics().getHeight(); 84 | int y = 25; 85 | for (LineOutput line : lines) { 86 | if (!line.getDisplay()) { 87 | continue; 88 | } 89 | graphics2D.setColor(this.getTextColor()); 90 | switch (line.getType()) { 91 | case ONE_LINE: 92 | graphics2D.setColor(Color.green); 93 | break; 94 | case START_MARK: 95 | graphics2D.setColor(Color.darkGray); 96 | break; 97 | case END_MARK: 98 | graphics2D.setColor(Color.lightGray); 99 | break; 100 | case LIMIT_REACHED_LINE: 101 | graphics2D.setColor(Color.orange); 102 | break; 103 | case CONTENT_LINE: 104 | graphics2D.setColor(Color.blue); 105 | break; 106 | default: 107 | graphics2D.setColor(textColor); 108 | break; 109 | } 110 | //int x = (width - graphics2D.getFontMetrics().stringWidth(line.getLine())) / 2; 111 | int x = 5; 112 | graphics2D.drawString(line.getIndex() + " " + line.getLine(), x, y); 113 | y += lineHeight; 114 | } 115 | return image; 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHTMLAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.actions; 14 | 15 | import com.fasterxml.jackson.core.type.TypeReference; 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | import hudson.console.ConsoleNote; 18 | import hudson.model.Action; 19 | import hudson.model.Run; 20 | import io.jenkins.plugins.LogFlowVisualizer.model.LineType; 21 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 22 | import jenkins.tasks.SimpleBuildStep; 23 | 24 | import java.io.BufferedReader; 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.Reader; 28 | import java.util.ArrayList; 29 | import java.util.Collection; 30 | import java.util.List; 31 | import java.util.ListIterator; 32 | 33 | public class LogFlowHTMLAction implements SimpleBuildStep.LastBuildAction { 34 | 35 | private final Run run; 36 | private final File cacheFile; 37 | 38 | public LogFlowHTMLAction(Run run, File cacheFile) { 39 | this.run = run; 40 | this.cacheFile = cacheFile; 41 | } 42 | 43 | public List getLogs() throws IOException { 44 | Reader reader = run.getLogReader(); 45 | BufferedReader bufferedReader = new BufferedReader(reader); 46 | List result = new ArrayList<>(); 47 | String line = bufferedReader.readLine(); 48 | while (line != null) { 49 | // clear user notes 50 | line = ConsoleNote.removeNotes(line); 51 | result.add(line); 52 | line = bufferedReader.readLine(); 53 | } 54 | bufferedReader.close(); 55 | return result; 56 | } 57 | 58 | public List getMarkedLogs() throws IOException { 59 | List fullLogs = this.getLogs(); 60 | List markedLogs = this.getAllCacheFromNamedFile(this.cacheFile); 61 | 62 | List result = new ArrayList<>(); 63 | 64 | ListIterator iteratorMarked = markedLogs.listIterator(); 65 | LineOutput currentMarkedLine = null; 66 | if (iteratorMarked.hasNext()) { 67 | currentMarkedLine = iteratorMarked.next(); 68 | } 69 | 70 | for (int index = 0; index < fullLogs.size(); index++) { 71 | 72 | if ((index == currentMarkedLine.getIndex())) { 73 | result.add(currentMarkedLine); //matched line with full information 74 | if (iteratorMarked.hasNext()){ //for the last match 75 | currentMarkedLine = iteratorMarked.next(); 76 | } 77 | }else { 78 | //default for line with no marked meaning 79 | result.add(new LineOutput("", fullLogs.get(index), index, false, 80 | LineType.DEFAULT, 0, false)); 81 | } 82 | } 83 | return result; 84 | } 85 | 86 | private List getAllCacheFromNamedFile(File file) { 87 | ObjectMapper objectMapper = new ObjectMapper(); 88 | try { 89 | return objectMapper.readValue(file, new TypeReference<>() { 90 | }); 91 | } catch (IOException e) { 92 | throw new RuntimeException(e); 93 | } 94 | 95 | } 96 | 97 | @Override 98 | public Collection getProjectActions() { 99 | return new ArrayList<>(); 100 | } 101 | 102 | @Override 103 | public String getIconFileName() { 104 | return "symbol-logs"; 105 | } 106 | 107 | @Override 108 | public String getDisplayName() { 109 | return "Log Flow Visualizer HTML Logs"; 110 | } 111 | 112 | @Override 113 | public String getUrlName() { 114 | return "html"; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/input/LogFlowInputAdvanced.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.input; 14 | 15 | import hudson.Extension; 16 | import hudson.util.FormValidation; 17 | import org.jenkinsci.Symbol; 18 | import org.kohsuke.stapler.DataBoundConstructor; 19 | import org.kohsuke.stapler.QueryParameter; 20 | import org.kohsuke.stapler.verb.POST; 21 | import org.springframework.lang.NonNull; 22 | 23 | import java.util.regex.Pattern; 24 | import java.util.regex.PatternSyntaxException; 25 | 26 | public class LogFlowInputAdvanced extends LogFlowInput { 27 | 28 | private static final int DEFAULT_CONTENT_LENGTH = 30; 29 | 30 | private final String startMark; 31 | 32 | // to find specified mark in content of mark private final String namedMark; 33 | private final String endMark; 34 | 35 | private final Boolean deleteMark; 36 | private int maxContentLength = DEFAULT_CONTENT_LENGTH; 37 | 38 | private int numberOfLineToDisplay = 0; 39 | 40 | @DataBoundConstructor 41 | public LogFlowInputAdvanced(String startMark, String endMark, Boolean deleteMark, int maxContentLength, int numberOfLineToDisplay) { 42 | this.startMark = startMark; 43 | this.endMark = endMark; 44 | this.deleteMark = deleteMark; 45 | this.maxContentLength = maxContentLength; 46 | this.numberOfLineToDisplay = numberOfLineToDisplay; 47 | } 48 | 49 | public String getStartMark() { 50 | return startMark; 51 | } 52 | 53 | public String getEndMark() { 54 | return endMark; 55 | } 56 | 57 | public Boolean getDeleteMark() { 58 | return deleteMark; 59 | } 60 | 61 | public int getMaxContentLength() { 62 | return maxContentLength; 63 | } 64 | 65 | public int getNumberOfLineToDisplay() { 66 | return numberOfLineToDisplay; 67 | } 68 | 69 | 70 | @Extension @Symbol("advancedInput") 71 | public static final class DescriptorImpl extends LogFlowInputDescriptor { 72 | @SuppressWarnings("lgtm[jenkins/no-permission-check]") 73 | @POST 74 | public FormValidation doCheckStartMark(@QueryParameter String startMark) { 75 | if (startMark.isEmpty()) { 76 | return FormValidation.error("Regex is empty"); 77 | } 78 | try { 79 | Pattern.compile(startMark); 80 | } catch (PatternSyntaxException exception) { 81 | return FormValidation.error("Regex is invalid"); 82 | } 83 | return FormValidation.ok(); 84 | } 85 | 86 | @SuppressWarnings("lgtm[jenkins/no-permission-check]") 87 | @POST 88 | public FormValidation doCheckEndMark(@QueryParameter String endMark) { 89 | if (endMark.isEmpty()) { 90 | return FormValidation.error("Regex is empty"); 91 | } 92 | try { 93 | Pattern.compile(endMark); 94 | } catch (PatternSyntaxException exception) { 95 | return FormValidation.error("Regex is invalid"); 96 | } 97 | return FormValidation.ok(); 98 | } 99 | 100 | @SuppressWarnings("lgtm[jenkins/no-permission-check]") 101 | @POST 102 | public FormValidation doCheckMaxContentLength(@QueryParameter String maxContentLength) { 103 | try { 104 | int inputNumber = Integer.parseInt(maxContentLength); 105 | if (inputNumber < 1) { 106 | return FormValidation.error("Max content length should be greater than 0"); 107 | } 108 | return FormValidation.ok(); 109 | 110 | } catch (NumberFormatException e) { 111 | return FormValidation.error("Couldn't parse number input"); 112 | } catch (Exception e) { 113 | return FormValidation.error("Something went wrong"); 114 | } 115 | 116 | } 117 | 118 | @SuppressWarnings("lgtm[jenkins/no-permission-check]") 119 | @POST 120 | public FormValidation doCheckNumberOfLineToDisplay(@QueryParameter String numberOfLineToDisplay) { 121 | try { 122 | int inputNumber = Integer.parseInt(numberOfLineToDisplay); 123 | if (inputNumber < 0) { 124 | return FormValidation.error("Number of line to display should be 0 or greater"); 125 | } 126 | return FormValidation.ok(); 127 | } catch (NumberFormatException e) { 128 | return FormValidation.error("Couldn't parse number input"); 129 | } catch (Exception e) { 130 | return FormValidation.error("Something went wrong"); 131 | } 132 | } 133 | 134 | @Override 135 | @NonNull 136 | public String getDisplayName() { 137 | return "Advanced Regex Format"; 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowHistoryDiffAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.actions; 14 | 15 | import com.fasterxml.jackson.core.type.TypeReference; 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | import com.github.difflib.text.DiffRow; 18 | import com.github.difflib.text.DiffRowGenerator; 19 | import hudson.model.Action; 20 | import hudson.model.Run; 21 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 22 | import io.jenkins.plugins.LogFlowVisualizer.LogFlowRecorder; 23 | import jenkins.tasks.SimpleBuildStep; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.ArrayList; 28 | import java.util.Collection; 29 | import java.util.List; 30 | import java.util.Objects; 31 | import java.util.stream.Collectors; 32 | 33 | 34 | public class LogFlowHistoryDiffAction implements SimpleBuildStep.LastBuildAction { 35 | private final Run run; 36 | private final File cacheFile; 37 | 38 | private Boolean compareAgainstLastStableBuild = false; 39 | 40 | public LogFlowHistoryDiffAction(Run run, File cacheFile, Boolean compareAgainstLastStableBuild) { 41 | this.run = run; 42 | this.cacheFile = cacheFile; 43 | this.compareAgainstLastStableBuild = compareAgainstLastStableBuild; 44 | } 45 | 46 | public Run getRun() { 47 | return run; 48 | } 49 | 50 | public File getCacheFile() { 51 | return cacheFile; 52 | } 53 | 54 | public Boolean getCompareAgainstLastStableBuild() { 55 | return compareAgainstLastStableBuild; 56 | } 57 | 58 | public int getCurrentBuildNumber() { 59 | return this.run.getNumber(); 60 | } 61 | 62 | public int getCompareBuildNumber() { 63 | if (compareAgainstLastStableBuild) { 64 | Run previousCompletedBuild = this.run.getPreviousCompletedBuild(); 65 | if (previousCompletedBuild != null) { 66 | return previousCompletedBuild.getNumber(); 67 | } 68 | } 69 | Run previousBuild = this.run.getPreviousBuild(); 70 | if (Objects.isNull(previousBuild)) { 71 | return this.getCurrentBuildNumber(); 72 | } 73 | return previousBuild.getNumber(); 74 | } 75 | 76 | 77 | public List getDiffLines() { 78 | File comparingFile; // build File can be changed here 79 | if (compareAgainstLastStableBuild) { 80 | comparingFile = this.getLastStableBuildFile(); 81 | } else { 82 | comparingFile = this.getPreviousBuildFile(); 83 | } 84 | if(comparingFile == null){ 85 | return new ArrayList<>(); 86 | } 87 | 88 | 89 | List currentBuildLines = getAllCacheFromFile(); 90 | 91 | List currentBuildLinesToDisplay = currentBuildLines 92 | .stream() 93 | .filter(LineOutput::getDisplay) 94 | .collect(Collectors.toList()); 95 | List previousBuildLines = getAllCacheFromNamedFile(comparingFile); 96 | List previousBuildLinesToDisplay = previousBuildLines 97 | .stream() 98 | .filter(LineOutput::getDisplay) 99 | .collect(Collectors.toList()); 100 | 101 | List current = extractStringFromLogFlowOutput(currentBuildLinesToDisplay); 102 | List previous = extractStringFromLogFlowOutput(previousBuildLinesToDisplay); 103 | 104 | DiffRowGenerator generator = DiffRowGenerator.create() 105 | .showInlineDiffs(true) 106 | .inlineDiffByWord(true) 107 | .oldTag(f -> "~") 108 | .newTag(f -> "**") 109 | .build(); 110 | 111 | return generator.generateDiffRows(previous, current); 112 | } 113 | 114 | private List extractStringFromLogFlowOutput(List inputList) { 115 | List result = new ArrayList<>(); 116 | for (LineOutput input : 117 | inputList) { 118 | result.add(input.getLine()); 119 | } 120 | return result; 121 | } 122 | 123 | public List getAllCacheFromFile() { 124 | return this.getAllCacheFromNamedFile(cacheFile); 125 | } 126 | 127 | private File getPreviousBuildFile() { 128 | Run previousBuild = this.getRun().getPreviousBuild(); 129 | if (Objects.isNull(previousBuild)) { 130 | return null; 131 | } 132 | File buildFolder = previousBuild.getRootDir(); 133 | return new File(buildFolder + File.separator + LogFlowRecorder.cacheName); 134 | } 135 | 136 | private File getLastStableBuildFile() { 137 | Run previousLastStableBuild = this.getRun().getPreviousCompletedBuild(); 138 | if (Objects.isNull(previousLastStableBuild)) { 139 | return null; 140 | } 141 | File buildFolder = previousLastStableBuild.getRootDir(); 142 | return new File(buildFolder + File.separator + LogFlowRecorder.cacheName); 143 | } 144 | 145 | private List getAllCacheFromNamedFile(File file) { 146 | ObjectMapper objectMapper = new ObjectMapper(); 147 | try { 148 | return objectMapper.readValue(file, new TypeReference<>() { 149 | }); 150 | } catch (IOException e) { 151 | throw new RuntimeException(e); 152 | } 153 | 154 | } 155 | 156 | 157 | @Override 158 | public Collection getProjectActions() { 159 | return new ArrayList<>(); 160 | } 161 | 162 | @Override 163 | public String getIconFileName() { 164 | return "symbol-changes"; 165 | } 166 | 167 | @Override 168 | public String getDisplayName() { 169 | return "Log Flow Visualizer Diff"; 170 | } 171 | 172 | @Override 173 | public String getUrlName() { 174 | return "historyDiff"; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/LogFlowFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer; 14 | 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInput; 17 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInputAdvanced; 18 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInputSimple; 19 | import io.jenkins.plugins.LogFlowVisualizer.model.LineType; 20 | import io.jenkins.plugins.LogFlowVisualizer.model.LineWithOffset; 21 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | public class LogFlowFilter { 29 | 30 | public static List filter(List lines, List configs) { 31 | List result = new ArrayList<>(); 32 | 33 | if (configs.isEmpty()) { 34 | return result; 35 | } 36 | 37 | 38 | int lineIndex = 0; 39 | 40 | int activeRegexCount = 0; 41 | 42 | //advanced setup 43 | boolean advancedRegexLock = false; 44 | LogFlowInputAdvanced activeConfig = null; 45 | 46 | 47 | //going through every line 48 | for (LineWithOffset lineWithOffset : 49 | lines) { 50 | String line = lineWithOffset.getLine(); 51 | 52 | //check if advanced regex is currently active 53 | if (advancedRegexLock) { 54 | if (line.matches(activeConfig.getEndMark())) { 55 | //end mark found 56 | boolean display = activeConfig.getNumberOfLineToDisplay() == activeRegexCount + 1; // line 0 is start mark 57 | if (activeConfig.getDeleteMark()) { 58 | String lineWithoutRegex = LogFlowFilter.removeRegexMark(line, activeConfig.getStartMark()); 59 | result.add(new LineOutput(activeConfig.getEndMark(), lineWithoutRegex, lineIndex, activeConfig.getDeleteMark(), LineType.END_MARK, lineWithOffset.getOffset(), display)); 60 | } else { 61 | result.add(new LineOutput(activeConfig.getEndMark(), line, lineIndex, activeConfig.getDeleteMark(), LineType.END_MARK, lineWithOffset.getOffset(), display)); 62 | } 63 | 64 | advancedRegexLock = false; 65 | 66 | activeRegexCount = 0; 67 | activeConfig = null; 68 | } else { 69 | // filling content of an advanced regex 70 | 71 | boolean display = activeConfig.getNumberOfLineToDisplay() == activeRegexCount + 1; // line 0 is start mark 72 | if (activeRegexCount >= activeConfig.getMaxContentLength()) { 73 | //end regex because of reaching the max limit 74 | result.add(new LineOutput(activeConfig.getStartMark(), line, lineIndex, activeConfig.getDeleteMark(), LineType.LIMIT_REACHED_LINE, lineWithOffset.getOffset(), display)); 75 | 76 | 77 | advancedRegexLock = false; 78 | 79 | activeConfig = null; 80 | 81 | activeRegexCount = 0; 82 | } else { 83 | result.add(new LineOutput(activeConfig.getStartMark(), line, lineIndex, activeConfig.getDeleteMark(), LineType.CONTENT_LINE, lineWithOffset.getOffset(), display)); 84 | } 85 | 86 | } 87 | activeRegexCount++; 88 | lineIndex++; 89 | continue; //skipping to next line 90 | } 91 | 92 | //matching first config, first come, first served principal 93 | for (LogFlowInput config : 94 | configs) { 95 | 96 | 97 | //determining the type of 98 | if (config instanceof LogFlowInputSimple) { 99 | 100 | // SIMPLE 101 | LogFlowInputSimple simpleConfig = (LogFlowInputSimple) config; 102 | 103 | if (simpleConfig.getRegex().isEmpty()) { // empty regex would match all lines 104 | continue; 105 | } 106 | 107 | if (line.matches(simpleConfig.getRegex())) { 108 | //check to include mark or not 109 | if (simpleConfig.getDeleteMark()) { 110 | String lineWithoutRegex = LogFlowFilter.removeRegexMark(line, simpleConfig.getRegex()); 111 | result.add(new LineOutput(simpleConfig.getRegex(), lineWithoutRegex, lineIndex, simpleConfig.getDeleteMark(), LineType.ONE_LINE, lineWithOffset.getOffset(), true)); 112 | } else { 113 | result.add(new LineOutput(simpleConfig.getRegex(), line, lineIndex, simpleConfig.getDeleteMark(), LineType.ONE_LINE, lineWithOffset.getOffset(), true)); 114 | } 115 | break; 116 | } 117 | 118 | } else if (config instanceof LogFlowInputAdvanced) { 119 | 120 | // ADVANCED 121 | 122 | LogFlowInputAdvanced advancedConfig = (LogFlowInputAdvanced) config; 123 | 124 | if (advancedConfig.getStartMark().isEmpty() || advancedConfig.getEndMark().isEmpty()) { // empty regex would match all lines 125 | continue; 126 | } 127 | 128 | if (line.matches(advancedConfig.getStartMark())) { 129 | boolean display = advancedConfig.getNumberOfLineToDisplay() == 0; 130 | if (advancedConfig.getDeleteMark()) { 131 | String lineWithoutRegex = LogFlowFilter.removeRegexMark(line, advancedConfig.getStartMark()); 132 | result.add(new LineOutput(advancedConfig.getStartMark(), lineWithoutRegex, lineIndex, advancedConfig.getDeleteMark(), LineType.START_MARK, lineWithOffset.getOffset(), display)); 133 | } else { 134 | result.add(new LineOutput(advancedConfig.getStartMark(), line, lineIndex, advancedConfig.getDeleteMark(), LineType.START_MARK, lineWithOffset.getOffset(), display)); 135 | } 136 | activeConfig = advancedConfig; 137 | advancedRegexLock = true; 138 | break; 139 | } 140 | 141 | } else { 142 | //ERROR 143 | System.err.println("Unknown instance of config"); 144 | return new ArrayList<>(); 145 | } 146 | 147 | } 148 | 149 | lineIndex++; 150 | } 151 | return result; 152 | } 153 | 154 | public static void saveToJSON(List list, File file) { 155 | 156 | ObjectMapper objectMapper = new ObjectMapper(); 157 | try { 158 | objectMapper.writeValue(file, list); 159 | } catch (IOException e) { 160 | throw new RuntimeException(e); 161 | } 162 | } 163 | 164 | 165 | public static String removeRegexMark(String line, String regex) { 166 | return line.replaceAll(regex, ""); 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/actions/LogFlowProjectAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer.actions; 14 | 15 | import com.fasterxml.jackson.core.type.TypeReference; 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | import hudson.model.Action; 18 | import hudson.model.Run; 19 | import io.jenkins.plugins.LogFlowVisualizer.model.HistoryType; 20 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 21 | import io.jenkins.plugins.LogFlowVisualizer.model.OutputHistoryMarked; 22 | import io.jenkins.plugins.LogFlowVisualizer.LogFlowRecorder; 23 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInput; 24 | import jenkins.model.Jenkins; 25 | import jenkins.tasks.SimpleBuildStep; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.util.ArrayList; 30 | import java.util.Collection; 31 | import java.util.List; 32 | import java.util.Objects; 33 | 34 | public class LogFlowProjectAction implements SimpleBuildStep.LastBuildAction { 35 | 36 | private final Run run; 37 | 38 | private final List configurations; 39 | 40 | private final File cacheFile; 41 | 42 | private final Boolean compareAgainstLastStableBuild; 43 | 44 | 45 | public LogFlowProjectAction(Run run, List configurations, File cacheFolder, Boolean compareAgainstLastStableBuild) { 46 | this.run = run; 47 | this.configurations = configurations; 48 | this.cacheFile = cacheFolder; 49 | this.compareAgainstLastStableBuild = compareAgainstLastStableBuild; 50 | } 51 | 52 | public File getCacheFile() { 53 | return cacheFile; 54 | } 55 | 56 | public List getAllCacheFromFile() { 57 | return this.getAllCacheFromNamedFile(cacheFile); 58 | } 59 | 60 | 61 | public List getHistoryMarkedLines() { 62 | File comparingFile; // build File can be changed here 63 | if (compareAgainstLastStableBuild) { 64 | comparingFile = this.getLastStableBuildFile(); 65 | } else { 66 | comparingFile = this.getPreviousBuildFile(); 67 | } 68 | 69 | 70 | List currentBuildLines = getAllCacheFromFile(); 71 | List previousBuildLines = getAllCacheFromNamedFile(comparingFile); 72 | 73 | List result = new ArrayList<>(); 74 | 75 | 76 | //comparing 77 | for (int lineIndex = 0; lineIndex < currentBuildLines.size(); lineIndex++) { 78 | LineOutput currentLine = currentBuildLines.get(lineIndex); 79 | LineOutput previousLine = previousBuildLines.get(lineIndex); 80 | 81 | if (currentLine.getDisplay() || previousLine.getDisplay()) { // comparing only lines to display 82 | 83 | // lines are same 84 | if (Objects.equals(currentLine.getLine(), previousLine.getLine())) { 85 | result.add(new OutputHistoryMarked(currentLine.getRegex(), currentLine.getLine(), 86 | currentLine.getIndex(), currentLine.getDeleteMark(), currentLine.getType(), 87 | HistoryType.SAME, currentLine.getLineStartOffset(), currentLine.getDisplay())); 88 | 89 | } else if (Objects.equals(currentLine.getRegex(), previousLine.getRegex())) { //regex is same, line not 90 | result.add(new OutputHistoryMarked(currentLine.getRegex(), currentLine.getLine(), 91 | currentLine.getIndex(), currentLine.getDeleteMark(), currentLine.getType(), 92 | HistoryType.JUST_SAME_REGEX, currentLine.getLineStartOffset(), currentLine.getDisplay())); 93 | 94 | } else { // different lines, different regex 95 | result.add(new OutputHistoryMarked(currentLine.getRegex(), currentLine.getLine(), 96 | currentLine.getIndex(), currentLine.getDeleteMark(), currentLine.getType(), 97 | HistoryType.DIFFERENT_CURRENT, currentLine.getLineStartOffset(), currentLine.getDisplay())); 98 | result.add(new OutputHistoryMarked(previousLine.getRegex(), previousLine.getLine(), 99 | previousLine.getIndex(), previousLine.getDeleteMark(), previousLine.getType(), 100 | HistoryType.DIFFERENT_PREVIOUS, previousLine.getLineStartOffset(), previousLine.getDisplay())); 101 | } 102 | 103 | } 104 | 105 | } 106 | 107 | return result; 108 | 109 | } 110 | 111 | 112 | // for shortened summary on build and project page 113 | public List getOnlyMarkedLinesToDisplay() { 114 | List list = this.getAllCacheFromFile(); 115 | List result = new ArrayList<>(); 116 | for (LineOutput line : 117 | list) { 118 | if (line.getDisplay()) { 119 | result.add(line); 120 | } 121 | } 122 | return result; 123 | } 124 | 125 | private File getPreviousBuildFile() { 126 | Run previousBuild = this.getRun().getPreviousBuild(); 127 | if (Objects.isNull(previousBuild)) { 128 | return null; 129 | } 130 | int buildNumber = previousBuild.getNumber(); 131 | File buildFolder = getBuildFolderFromBuildNumber(buildNumber); 132 | return new File(buildFolder + File.separator + LogFlowRecorder.cacheName); 133 | } 134 | 135 | private File getLastStableBuildFile() { 136 | Run previousLastStableBuild = this.getRun().getPreviousCompletedBuild(); 137 | if (Objects.isNull(previousLastStableBuild)) { 138 | return null; 139 | } 140 | int buildNumber = previousLastStableBuild.getNumber(); 141 | File buildFolder = getBuildFolderFromBuildNumber(buildNumber); 142 | return new File(buildFolder + File.separator + LogFlowRecorder.cacheName); 143 | } 144 | 145 | public File getProjectDirFile() { 146 | return run.getRootDir(); 147 | } 148 | 149 | public File getBuildFolderFromBuildNumber(int buildNumber) { 150 | return new File(this.getProjectDirFile() + File.separator + "builds" + File.separator + buildNumber); 151 | } 152 | 153 | private List getAllCacheFromNamedFile(File file) { 154 | ObjectMapper objectMapper = new ObjectMapper(); 155 | try { 156 | return objectMapper.readValue(file, new TypeReference<>() { 157 | }); 158 | } catch (IOException e) { 159 | throw new RuntimeException(e); 160 | } 161 | 162 | } 163 | 164 | public String getJenkinsRootUrl(){ 165 | Jenkins instance = Jenkins.getInstanceOrNull(); 166 | if(instance == null){ 167 | return ""; 168 | } 169 | return instance.getRootUrlFromRequest(); 170 | } 171 | 172 | public Run getRun() { 173 | return run; 174 | } 175 | 176 | public List getConfigurations() { 177 | return configurations; 178 | } 179 | 180 | @Override 181 | public String getIconFileName() { 182 | return null; 183 | } 184 | 185 | /** 186 | * used for displaying action in the sidebar 187 | * 188 | * @return null because we don't use it 189 | */ 190 | @Override 191 | public String getDisplayName() { 192 | return null; 193 | } 194 | 195 | @Override 196 | public String getUrlName() { 197 | return "LFV"; 198 | } 199 | 200 | @Override 201 | public Collection getProjectActions() { 202 | ArrayList list = new ArrayList<>(); 203 | list.add(new LogFlowProjectAction(this.getRun(), this.getConfigurations(), this.getCacheFile(), compareAgainstLastStableBuild)); 204 | return list; 205 | } 206 | 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/LogFlowVisualizer/LogFlowRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | * 8 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | */ 12 | 13 | package io.jenkins.plugins.LogFlowVisualizer; 14 | 15 | import edu.umd.cs.findbugs.annotations.NonNull; 16 | import hudson.*; 17 | import hudson.console.ConsoleNote; 18 | import hudson.model.*; 19 | import hudson.tasks.BuildStepDescriptor; 20 | import hudson.tasks.Publisher; 21 | import hudson.tasks.Recorder; 22 | import io.jenkins.plugins.LogFlowVisualizer.actions.LogFlowHTMLAction; 23 | import io.jenkins.plugins.LogFlowVisualizer.actions.LogFlowHistoryDiffAction; 24 | import io.jenkins.plugins.LogFlowVisualizer.actions.LogFlowOffsetAction; 25 | import io.jenkins.plugins.LogFlowVisualizer.actions.LogFlowProjectAction; 26 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInput; 27 | import io.jenkins.plugins.LogFlowVisualizer.input.LogFlowInputDescriptor; 28 | import io.jenkins.plugins.LogFlowVisualizer.model.LineWithOffset; 29 | import io.jenkins.plugins.LogFlowVisualizer.model.LineOutput; 30 | import jenkins.model.Jenkins; 31 | import jenkins.tasks.SimpleBuildStep; 32 | import org.jenkinsci.Symbol; 33 | import org.kohsuke.stapler.DataBoundConstructor; 34 | import org.kohsuke.stapler.DataBoundSetter; 35 | 36 | import java.awt.image.BufferedImage; 37 | import java.io.File; 38 | import java.io.IOException; 39 | import java.io.RandomAccessFile; 40 | import java.util.ArrayList; 41 | import java.util.List; 42 | import java.util.Objects; 43 | 44 | 45 | 46 | 47 | /** 48 | * responsible for performing all actions of the plugin 49 | */ 50 | @Extension 51 | public class LogFlowRecorder extends Recorder implements SimpleBuildStep { 52 | 53 | public static final String cacheName = "LogFlowVisualizerCache.json"; 54 | public static final String cachePictureName = "LogFlowVisualizerResult.png"; 55 | private List configurations; 56 | private Boolean generatePicture = false; 57 | private Boolean compareAgainstLastStableBuild = false; 58 | 59 | int DEFAULT_IMAGE_WIDTH = 1200; 60 | int DEFAULT_IMAGE_HEIGHT = 800; 61 | 62 | 63 | public LogFlowRecorder() { 64 | } 65 | 66 | @DataBoundConstructor 67 | public LogFlowRecorder(List configurations, Boolean generatePicture, Boolean compareAgainstLastStableBuild) { 68 | this.configurations = configurations; 69 | this.generatePicture = generatePicture; 70 | this.compareAgainstLastStableBuild = compareAgainstLastStableBuild; 71 | } 72 | 73 | public Boolean getCompareAgainstLastStableBuild() { 74 | return compareAgainstLastStableBuild; 75 | } 76 | 77 | @DataBoundSetter 78 | public void setCompareAgainstLastStableBuild(Boolean compareAgainstLastStableBuild) { 79 | this.compareAgainstLastStableBuild = compareAgainstLastStableBuild; 80 | } 81 | 82 | public Boolean getGeneratePicture() { 83 | return generatePicture; 84 | } 85 | 86 | @DataBoundSetter 87 | public void setGeneratePicture(Boolean generatePicture) { 88 | this.generatePicture = generatePicture; 89 | } 90 | 91 | 92 | public List getConfigurations() { 93 | return configurations; 94 | } 95 | 96 | 97 | public void setConfigurations(List configurations) { 98 | this.configurations = configurations; 99 | } 100 | 101 | public DescriptorExtensionList getFormatDescriptors() { 102 | return Jenkins.get().getDescriptorList(LogFlowInput.class); 103 | } 104 | 105 | 106 | /** 107 | * 108 | * @param run a build this is running as a part of 109 | * @param workspace a workspace to use for any file operations 110 | * @param env environment variables applicable to this step 111 | * @param launcher a way to start processes 112 | * @param listener a place to send output 113 | * @throws IOException 114 | */ 115 | @Override 116 | public void perform(@NonNull Run run, @NonNull FilePath workspace, @NonNull EnvVars env, @NonNull Launcher launcher, @NonNull TaskListener listener) throws IOException { 117 | 118 | if (Objects.isNull(configurations) || configurations.isEmpty()) { 119 | listener.getLogger().println("LFV: configurations are empty"); 120 | return ; 121 | } 122 | 123 | //here is the performance part, e.g. drawing, printing itself 124 | File rootDir = run.getRootDir(); 125 | File currentBuildFolder = new File(rootDir.getPath()); 126 | File defaultLogs = new File(currentBuildFolder + File.separator + "log"); 127 | 128 | 129 | //creates necessary directories 130 | boolean mkdirsResult = currentBuildFolder.mkdirs(); 131 | if (mkdirsResult) { 132 | listener.getLogger().println("LFV: Created new directories"); 133 | } 134 | 135 | 136 | RandomAccessFile raf = new RandomAccessFile(defaultLogs, "r"); 137 | 138 | //reading log file 139 | 140 | List logLines = new ArrayList<>(); 141 | long currentOffset = raf.getFilePointer(); 142 | String line = raf.readLine(); 143 | while (line != null) { 144 | line = ConsoleNote.removeNotes(line); 145 | logLines.add(new LineWithOffset(line, currentOffset)); 146 | currentOffset = raf.getFilePointer(); 147 | line = raf.readLine(); 148 | } 149 | raf.close(); 150 | 151 | //getting filtered lines 152 | List filterOutput = LogFlowFilter.filter(logLines, getConfigurations()); 153 | 154 | // writing in JSON format into output.json 155 | File jsonCacheFile = new File(currentBuildFolder.getPath() + File.separator + cacheName); 156 | 157 | boolean createFileResult = jsonCacheFile.createNewFile(); 158 | if (!createFileResult) { 159 | listener.getLogger().println("LFV: " + cacheName + " was not created"); 160 | return; 161 | } 162 | 163 | 164 | LogFlowFilter.saveToJSON(filterOutput, jsonCacheFile); 165 | 166 | 167 | 168 | //creating picture 169 | if (generatePicture) { 170 | if (createPicture(listener, filterOutput, currentBuildFolder)) return; 171 | } 172 | 173 | 174 | // adding actions to build in Jenkins 175 | LogFlowProjectAction action = new LogFlowProjectAction(run, this.getConfigurations(), jsonCacheFile, compareAgainstLastStableBuild); 176 | run.addAction(action); 177 | run.addAction(new LogFlowHTMLAction(run, jsonCacheFile)); 178 | run.addAction(new LogFlowOffsetAction(run)); 179 | run.addAction(new LogFlowHistoryDiffAction(run, jsonCacheFile, compareAgainstLastStableBuild)); 180 | 181 | } 182 | 183 | private boolean createPicture(TaskListener listener, List filterOutput, File currentBuildFolder) throws IOException { 184 | int width = DEFAULT_IMAGE_WIDTH; 185 | int height = DEFAULT_IMAGE_HEIGHT; 186 | LogFlowPictureMaker pm = new LogFlowPictureMaker(width, height); 187 | BufferedImage image = pm.createPicture(filterOutput); 188 | File picturePath = new File(currentBuildFolder + File.separator + "archive" + File.separator + cachePictureName); 189 | boolean pictureMkdirResult = picturePath.mkdirs(); 190 | if (!pictureMkdirResult) { 191 | listener.getLogger().println("LFV: cache directories for picture were not successfully created"); 192 | return true; 193 | } 194 | javax.imageio.ImageIO.write(image, "png", picturePath); 195 | return false; 196 | } 197 | 198 | /** 199 | * mainly used for configuration and input checks 200 | */ 201 | @Symbol("logFlowVisualizer") 202 | @Extension 203 | public static final class DescriptorImpl extends BuildStepDescriptor { 204 | 205 | @Override 206 | @NonNull 207 | public String getDisplayName() { 208 | return "Log Flow Visualizer"; 209 | } 210 | 211 | @Override 212 | public boolean isApplicable(Class jobType) { 213 | return true; 214 | } 215 | } 216 | 217 | 218 | } --------------------------------------------------------------------------------