---
# System prepended metadata

title: 在Jenkins內如何將具體編譯.Net程式碼錯誤導入通知系統
tags: [Jenkins, PowerShell, DotNet, blog]

---

---
title: 在Jenkins內如何將具體編譯.Net程式碼錯誤導入通知系統
tags: [Jenkins,PowerShell,DotNet,blog]
---
{%hackmd @hackmd/blog-article-base-layout %}

<style>
.blog-date {
    margin-top: 60px;
}

.blog-date.no-margin {
    margin-top: 20px;
}

body .ui-toc, #ui-toc-affix {
    display: none !important;
}

body > #doc.markdown-body {
    padding-top: 20px;
}

#doc.markdown-body h2 {
    margin-top: 5px;
}
</style>

:arrow_left: [回到部落格首頁](/@siugar/blog-tw) :arrow_right: <a target="_blank" href="/@siugar/ExtractBuildError">本文單篇連結</a>

# 在Jenkins內如何將具體編譯.Net程式碼錯誤導入通知系統

## 前言
因為專案的運營人員使用Jenkins製作正式區版本，但因為我們開發人員沒有正式區的Jenkins權限，所以如果發生編譯錯誤，只知道發生錯誤，但無法得知具體編譯錯誤發生甚麼錯誤，在哪個檔案的哪一行。

![image](https://hackmd.io/_uploads/S1X-Nbn91x.png)

因為我之前有導入Jenkins運用MsBuild編譯C++服務器程式上版的經驗，所以後來我主動提出來協助解決這一塊。
最後完成結果如下，可以清楚知道是哪一個檔案的哪一行發生甚麼錯誤

![image](https://hackmd.io/_uploads/rJyEE-3qke.png)

## 本文

# 流程圖：取得編譯錯誤資訊

![image](https://hackmd.io/_uploads/Bkawrcchye.png)


```javascript
node('windows-node') {
    ws('D:\\Dev_git\\your_project_path') {
        try {
            stage('Git') {
                deleteDir()
                checkout([$class: 'GitSCM',                 
                    branches: [[name: 'origin/feature/your_project_git_path']],
                    doGenerateSubmoduleConfigurations: false, 
                    extensions: [], 
                    submoduleCfg: [], 
                    userRemoteConfigs: [[credentialsId: 'a1a2...', url: 'your_project_git_url.git']]
                ])  
            }        
            stage('BuildProject') {            
                script {
                        // 執行 dotnet publish 並將輸出寫入日誌文件
                        def status = powershell returnStatus: true, script: """
                        chcp 65001
                        \$projectPath = "D:\\Dev_git\\project_path\\your_project.csproj"
                        \$logFile = "build_output.log"
                        \$errorLogFile = "error.log"
                        \$tempErrorLogFile = "temp_error.log"

                        # 記錄當前目錄
                        \$currentDir = Get-Location

                        # 使用 dotnet build 並將輸出同時寫入控制台和日誌文件
                        \$buildCommand = "dotnet build `"\$projectPath`" --configuration Release -v m"
                        Invoke-Expression "\$buildCommand 2>&1 | Tee-Object -FilePath \$logFile"

                        # 檢查構建結果
                        \$buildOutput = Get-Content -Path \$logFile -Raw
                        if (\$buildOutput -match "error") {
                            # 如果構建失敗，將包含 "error" 的行寫入臨時錯誤日誌文件
                            Select-String -Pattern "error" -Path \$logFile | ForEach-Object { \$_.Line } | Set-Content -Path \$tempErrorLogFile -Encoding utf8
                            # 使用 PowerShell 進行排序和去重操作，確保使用 UTF-8 編碼
                            Get-Content -Path \$tempErrorLogFile -Encoding utf8 | Sort-Object -Unique | Set-Content -Path \$errorLogFile -Encoding utf8
                            # 刪除臨時錯誤日誌文件
                            Remove-Item -Path \$tempErrorLogFile
                        } else {
                            # 如果構建成功，刪除錯誤日誌文件
                            if (Test-Path -Path \$errorLogFile) {
                                Remove-Item -Path \$errorLogFile
                            }
                        }
                        """

                        if (status != 0) {
                            def logContent = readFile('error.log')
                            echo "Build failed with status ${status}. Log content:\n${logContent}"
                            error "Build failed with status ${status}"
                        }
                   }
            }
            stage('PublishProject') {
                bat returnStdout: true, script: '''c:
                chcp 65001
                dotnet publish  "D:\\Dev_git\\project_path\\your_project.csproj" -o D:\\Deployment\\output_path -c Release -r win-x64 --no-self-contained '''
            }        
        
        } catch (Exception e) {
            currentBuild.result = 'FAILURE'
            throw e
        } finally {
            stage('Post') {
                script {                    
                    // 發送 Office 365 Connector 通知
                    if (currentBuild.result == 'FAILURE') {
                        def errorMsg = "Unknow"
                        if (fileExists('error.log')) {
                            errorMsg = readFile(file: "error.log")
                            echo errorMsg
                        }
                        sendNotification(currentBuild.result, errorMsg)
                    }
                }
            }
        }
   }
}

def sendNotification(buildStatus, logContent) {
    def webhookUrl = 'https://your_webhook_url'
    def buildNumber = currentBuild.number
    def startedBy = currentBuild.getBuildCauses()[0].userName ?: 'Unknown'
    def failingSince = getFirstFailingBuildNumber(currentBuild)
    def repeatedFailure = currentBuild.previousBuild?.result == 'FAILURE' ? 'Yes' : 'No'

    def facts = [
        [name: 'Failing since', template: "build #${failingSince}"],
        [name: 'Remarks', template: "Started by user ${startedBy}"]
    ]

    office365ConnectorSend webhookUrl: webhookUrl, color:"#FF0000", message: "Latest status of build #${buildNumber}<br> Build failed. See details below.<br> ${logContent}", status: "Failed", factDefinitions: facts      
}

```

## 重點說明

### 1. Pipleline Script vs Groovy

#### Groovy Script
- Groovy Script 是一種通用的 Groovy 程式碼，可以用於各種自動化任務，不限於 Jenkins Pipeline。
- 它可以獨立執行，也可以嵌入到 Java 應用程式中。
- 在 Jenkins 中，您可以在「Script Console」（腳本控制台）中執行任意 Groovy Script，用於管理 Jenkins 實例、操作節點、檢索資訊等

```Groovy
Jenkins.instance.nodes.each {
    println it.displayName
}
```

#### Pipeline Script
- 專門用於定義 Jenkins Pipeline 的執行流程
- 強調結構化和可讀性，通過 stages、steps 等關鍵字來組織程式碼
- 本身也是Groovy script, 只是為了jenkins pipeline有額外的定義以及方法

```
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean install'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                sh 'deploy.sh'
            }
        }
    }
}
```

### 2. Tee-Object
這是PowerShell的指令，用於同時輸出寫入變數或檔案，並且保持輸出到Console，類似Linux的tee。

指令:

```
command | Tee-Object -FilePath <檔案路徑>
```

範例:
```
$projectPath = "your_project.csproj"
$logFile = "$PSScriptRoot\build_output.log"

# 使用 dotnet build 並將輸出同時寫入控制台和日誌文件
$buildCommand = "dotnet build `"$projectPath`" --configuration Release -v m"
Invoke-Expression "$buildCommand 2>&1 | Tee-Object -FilePath $logFile"
```

## 結論

這段介紹的核心重點在於怎麼取出編譯失敗的訊息，其他的部分包含Jenkins與Pipleline script，只要有接觸過都可以編寫出來。而至於怎麼編寫出那一段power shell script有部分是靠AI的輔助，因為一開始我是用batch script編寫，但其中有一個需求是需要可以將訊息寫入檔案的同時，standard output仍然要可以正常運作，否則在jenkins或是平時debug看訊息會變成沒有資訊可以依靠，後來AI給的方案中Power shell script的Tee-Object是比較單純的解法，所以後來都在AI的依靠下完成這段簡單但又不單純的錯誤訊息擷取。

