Get Sonar-Scanner to log coverage

In Gitlab you can show the code coverage in merge requests, which looks like this:

To do that you need to write a regex which then extracts the number from the log. However, Sonarqube doesn’t log it, so I can’t use this feature, though it would be very handy.

I use Sonarqube to combine two coverage reports, otherwise I could have just taken the coverage directly from the original test run.

Is there a way to get the coverage in % in the logs?

I’m using SonarQube Server Enterprise Edition v2025.1.1.

Thank you!

Hey @conrad-hd

SonarQube processes the coverage files during analysis, but doesn’t compute coverage until the background task processes server-side. So what you’re asking for, unfortunately isn’t possible. :frowning:

Thank you for your answer. We set the flag (sonar.qualitygate.wait=true) to wait for the quality gate , so at this point there should be the total coverage available, as it’s needed for the QualityGate. Is it possible to log the QualityGate metrics (like “86.78% is more than 80%: Passed”)? Or to easily get the coverage through an API call?

Hey @conrad-hd

There’s a couple APIs you could call – the one that comes to mind (and makes sure you can reference a specific analysis ID) is GET api/qualitygates/project_status. You can get the analysisID by parsing the .scannerwork/report-task.txt file.

This will make sure you can see all the measures in your Quality Gate. If you want to query a measure outside your Quality Gate, GET api/measures/component is another option. And, this will always return the latest measure. It’s possible that many analyses processing at once could lead to the wrong measure being returned.

Thank you for your suggestions.

I now used the GET api/measures/component endpoint as the risk of multiple analyses on the same branch isn’t that high, also it wouldn’t change the result.

Here’s my Node.JS script for anyone interested:

import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';

// Read sonar-project.properties and extract sonar.projectKey
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const propsPath = path.resolve(__dirname, '../sonar-project.properties');
const propsContent = fs.readFileSync(propsPath, 'utf-8');
const projectKeyMatch = propsContent.match(/^sonar\.projectKey\s*=\s*(.+)$/m);
if (!projectKeyMatch) {
  console.error('sonar.projectKey not found in sonar-project.properties');
  process.exit(1);
}
const projectKey = projectKeyMatch[1].trim();

// Get current git branch
let branch = process.env.CI_COMMIT_BRANCH;
if (!branch) {
  try {
    branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
  } catch (e) {
    console.error('Failed to get current git branch');
    process.exit(1);
  }
}

// Get token from env
const token = process.env.SONARQUBE_TOKEN;
if (!token) {
  console.error('SONARQUBE_TOKEN env variable not set');
  process.exit(1);
}

// Get SonarQube host from env
const host = process.env.SONARQUBE_HOST;
if (!host) {
  console.error('SONARQUBE_HOST env variable not set');
  process.exit(1);
}

// Fetch coverage
(async () => {
  try {
    const url = new URL('/api/measures/component', host);
    url.searchParams.set('component', projectKey);
    url.searchParams.set('metricKeys', 'coverage');
    url.searchParams.set('branch', branch);

    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${token}`
      }
    });

    if (!res.ok) {
      const errText = await res.text();
      throw new Error(`HTTP ${res.status}: ${errText}`);
    }

    const data = await res.json();
    const coverage = data?.component?.measures?.find(m => m.metric === 'coverage')?.value;
    if (coverage !== undefined) {
      console.log(`Coverage received from SonarQube: ${coverage}%`);
    } else {
      console.error('Coverage metric not found in response');
      process.exit(1);
    }
  } catch (err) {
    console.error('Failed to fetch SonarQube coverage:', err.message);
    process.exit(1);
  }
})();

Extract the coverage from the log (.gitlab-ci.yml):

  coverage: '/Coverage received from SonarQube: ([\d.]+)%/'

It works, however it would be nice to have an official intergration for Gitlab, which would be easier to use and could also display the Code Quality directly in Gitlab.